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_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13682    init_test(cx, |_| {});
13683
13684    cx.update(|cx| {
13685        cx.update_global::<SettingsStore, _>(|settings, cx| {
13686            settings.update_user_settings(cx, |settings| {
13687                settings.editor.auto_signature_help = Some(false);
13688                settings.editor.show_signature_help_after_edits = Some(false);
13689            });
13690        });
13691    });
13692
13693    let mut cx = EditorLspTestContext::new_rust(
13694        lsp::ServerCapabilities {
13695            signature_help_provider: Some(lsp::SignatureHelpOptions {
13696                ..Default::default()
13697            }),
13698            ..Default::default()
13699        },
13700        cx,
13701    )
13702    .await;
13703
13704    let language = Language::new(
13705        LanguageConfig {
13706            name: "Rust".into(),
13707            brackets: BracketPairConfig {
13708                pairs: vec![
13709                    BracketPair {
13710                        start: "{".to_string(),
13711                        end: "}".to_string(),
13712                        close: true,
13713                        surround: true,
13714                        newline: true,
13715                    },
13716                    BracketPair {
13717                        start: "(".to_string(),
13718                        end: ")".to_string(),
13719                        close: true,
13720                        surround: true,
13721                        newline: true,
13722                    },
13723                    BracketPair {
13724                        start: "/*".to_string(),
13725                        end: " */".to_string(),
13726                        close: true,
13727                        surround: true,
13728                        newline: true,
13729                    },
13730                    BracketPair {
13731                        start: "[".to_string(),
13732                        end: "]".to_string(),
13733                        close: false,
13734                        surround: false,
13735                        newline: true,
13736                    },
13737                    BracketPair {
13738                        start: "\"".to_string(),
13739                        end: "\"".to_string(),
13740                        close: true,
13741                        surround: true,
13742                        newline: false,
13743                    },
13744                    BracketPair {
13745                        start: "<".to_string(),
13746                        end: ">".to_string(),
13747                        close: false,
13748                        surround: true,
13749                        newline: true,
13750                    },
13751                ],
13752                ..Default::default()
13753            },
13754            autoclose_before: "})]".to_string(),
13755            ..Default::default()
13756        },
13757        Some(tree_sitter_rust::LANGUAGE.into()),
13758    );
13759    let language = Arc::new(language);
13760
13761    cx.language_registry().add(language.clone());
13762    cx.update_buffer(|buffer, cx| {
13763        buffer.set_language(Some(language), cx);
13764    });
13765
13766    // Ensure that signature_help is not called when no signature help is enabled.
13767    cx.set_state(
13768        &r#"
13769            fn main() {
13770                sampleˇ
13771            }
13772        "#
13773        .unindent(),
13774    );
13775    cx.update_editor(|editor, window, cx| {
13776        editor.handle_input("(", window, cx);
13777    });
13778    cx.assert_editor_state(
13779        &"
13780            fn main() {
13781                sample(ˇ)
13782            }
13783        "
13784        .unindent(),
13785    );
13786    cx.editor(|editor, _, _| {
13787        assert!(editor.signature_help_state.task().is_none());
13788    });
13789
13790    let mocked_response = lsp::SignatureHelp {
13791        signatures: vec![lsp::SignatureInformation {
13792            label: "fn sample(param1: u8, param2: u8)".to_string(),
13793            documentation: None,
13794            parameters: Some(vec![
13795                lsp::ParameterInformation {
13796                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13797                    documentation: None,
13798                },
13799                lsp::ParameterInformation {
13800                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13801                    documentation: None,
13802                },
13803            ]),
13804            active_parameter: None,
13805        }],
13806        active_signature: Some(0),
13807        active_parameter: Some(0),
13808    };
13809
13810    // Ensure that signature_help is called when enabled afte edits
13811    cx.update(|_, cx| {
13812        cx.update_global::<SettingsStore, _>(|settings, cx| {
13813            settings.update_user_settings(cx, |settings| {
13814                settings.editor.auto_signature_help = Some(false);
13815                settings.editor.show_signature_help_after_edits = Some(true);
13816            });
13817        });
13818    });
13819    cx.set_state(
13820        &r#"
13821            fn main() {
13822                sampleˇ
13823            }
13824        "#
13825        .unindent(),
13826    );
13827    cx.update_editor(|editor, window, cx| {
13828        editor.handle_input("(", window, cx);
13829    });
13830    cx.assert_editor_state(
13831        &"
13832            fn main() {
13833                sample(ˇ)
13834            }
13835        "
13836        .unindent(),
13837    );
13838    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13839    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13840        .await;
13841    cx.update_editor(|editor, _, _| {
13842        let signature_help_state = editor.signature_help_state.popover().cloned();
13843        assert!(signature_help_state.is_some());
13844        let signature = signature_help_state.unwrap();
13845        assert_eq!(
13846            signature.signatures[signature.current_signature].label,
13847            "fn sample(param1: u8, param2: u8)"
13848        );
13849        editor.signature_help_state = SignatureHelpState::default();
13850    });
13851
13852    // Ensure that signature_help is called when auto signature help override is enabled
13853    cx.update(|_, cx| {
13854        cx.update_global::<SettingsStore, _>(|settings, cx| {
13855            settings.update_user_settings(cx, |settings| {
13856                settings.editor.auto_signature_help = Some(true);
13857                settings.editor.show_signature_help_after_edits = Some(false);
13858            });
13859        });
13860    });
13861    cx.set_state(
13862        &r#"
13863            fn main() {
13864                sampleˇ
13865            }
13866        "#
13867        .unindent(),
13868    );
13869    cx.update_editor(|editor, window, cx| {
13870        editor.handle_input("(", window, cx);
13871    });
13872    cx.assert_editor_state(
13873        &"
13874            fn main() {
13875                sample(ˇ)
13876            }
13877        "
13878        .unindent(),
13879    );
13880    handle_signature_help_request(&mut cx, mocked_response).await;
13881    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13882        .await;
13883    cx.editor(|editor, _, _| {
13884        let signature_help_state = editor.signature_help_state.popover().cloned();
13885        assert!(signature_help_state.is_some());
13886        let signature = signature_help_state.unwrap();
13887        assert_eq!(
13888            signature.signatures[signature.current_signature].label,
13889            "fn sample(param1: u8, param2: u8)"
13890        );
13891    });
13892}
13893
13894#[gpui::test]
13895async fn test_signature_help(cx: &mut TestAppContext) {
13896    init_test(cx, |_| {});
13897    cx.update(|cx| {
13898        cx.update_global::<SettingsStore, _>(|settings, cx| {
13899            settings.update_user_settings(cx, |settings| {
13900                settings.editor.auto_signature_help = Some(true);
13901            });
13902        });
13903    });
13904
13905    let mut cx = EditorLspTestContext::new_rust(
13906        lsp::ServerCapabilities {
13907            signature_help_provider: Some(lsp::SignatureHelpOptions {
13908                ..Default::default()
13909            }),
13910            ..Default::default()
13911        },
13912        cx,
13913    )
13914    .await;
13915
13916    // A test that directly calls `show_signature_help`
13917    cx.update_editor(|editor, window, cx| {
13918        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13919    });
13920
13921    let mocked_response = lsp::SignatureHelp {
13922        signatures: vec![lsp::SignatureInformation {
13923            label: "fn sample(param1: u8, param2: u8)".to_string(),
13924            documentation: None,
13925            parameters: Some(vec![
13926                lsp::ParameterInformation {
13927                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13928                    documentation: None,
13929                },
13930                lsp::ParameterInformation {
13931                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13932                    documentation: None,
13933                },
13934            ]),
13935            active_parameter: None,
13936        }],
13937        active_signature: Some(0),
13938        active_parameter: Some(0),
13939    };
13940    handle_signature_help_request(&mut cx, mocked_response).await;
13941
13942    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13943        .await;
13944
13945    cx.editor(|editor, _, _| {
13946        let signature_help_state = editor.signature_help_state.popover().cloned();
13947        assert!(signature_help_state.is_some());
13948        let signature = signature_help_state.unwrap();
13949        assert_eq!(
13950            signature.signatures[signature.current_signature].label,
13951            "fn sample(param1: u8, param2: u8)"
13952        );
13953    });
13954
13955    // When exiting outside from inside the brackets, `signature_help` is closed.
13956    cx.set_state(indoc! {"
13957        fn main() {
13958            sample(ˇ);
13959        }
13960
13961        fn sample(param1: u8, param2: u8) {}
13962    "});
13963
13964    cx.update_editor(|editor, window, cx| {
13965        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13966            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13967        });
13968    });
13969
13970    let mocked_response = lsp::SignatureHelp {
13971        signatures: Vec::new(),
13972        active_signature: None,
13973        active_parameter: None,
13974    };
13975    handle_signature_help_request(&mut cx, mocked_response).await;
13976
13977    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13978        .await;
13979
13980    cx.editor(|editor, _, _| {
13981        assert!(!editor.signature_help_state.is_shown());
13982    });
13983
13984    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13985    cx.set_state(indoc! {"
13986        fn main() {
13987            sample(ˇ);
13988        }
13989
13990        fn sample(param1: u8, param2: u8) {}
13991    "});
13992
13993    let mocked_response = lsp::SignatureHelp {
13994        signatures: vec![lsp::SignatureInformation {
13995            label: "fn sample(param1: u8, param2: u8)".to_string(),
13996            documentation: None,
13997            parameters: Some(vec![
13998                lsp::ParameterInformation {
13999                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14000                    documentation: None,
14001                },
14002                lsp::ParameterInformation {
14003                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14004                    documentation: None,
14005                },
14006            ]),
14007            active_parameter: None,
14008        }],
14009        active_signature: Some(0),
14010        active_parameter: Some(0),
14011    };
14012    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14013    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14014        .await;
14015    cx.editor(|editor, _, _| {
14016        assert!(editor.signature_help_state.is_shown());
14017    });
14018
14019    // Restore the popover with more parameter input
14020    cx.set_state(indoc! {"
14021        fn main() {
14022            sample(param1, param2ˇ);
14023        }
14024
14025        fn sample(param1: u8, param2: u8) {}
14026    "});
14027
14028    let mocked_response = lsp::SignatureHelp {
14029        signatures: vec![lsp::SignatureInformation {
14030            label: "fn sample(param1: u8, param2: u8)".to_string(),
14031            documentation: None,
14032            parameters: Some(vec![
14033                lsp::ParameterInformation {
14034                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14035                    documentation: None,
14036                },
14037                lsp::ParameterInformation {
14038                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14039                    documentation: None,
14040                },
14041            ]),
14042            active_parameter: None,
14043        }],
14044        active_signature: Some(0),
14045        active_parameter: Some(1),
14046    };
14047    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14048    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14049        .await;
14050
14051    // When selecting a range, the popover is gone.
14052    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
14053    cx.update_editor(|editor, window, cx| {
14054        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14055            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
14056        })
14057    });
14058    cx.assert_editor_state(indoc! {"
14059        fn main() {
14060            sample(param1, «ˇparam2»);
14061        }
14062
14063        fn sample(param1: u8, param2: u8) {}
14064    "});
14065    cx.editor(|editor, _, _| {
14066        assert!(!editor.signature_help_state.is_shown());
14067    });
14068
14069    // When unselecting again, the popover is back if within the brackets.
14070    cx.update_editor(|editor, window, cx| {
14071        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14072            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14073        })
14074    });
14075    cx.assert_editor_state(indoc! {"
14076        fn main() {
14077            sample(param1, ˇparam2);
14078        }
14079
14080        fn sample(param1: u8, param2: u8) {}
14081    "});
14082    handle_signature_help_request(&mut cx, mocked_response).await;
14083    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14084        .await;
14085    cx.editor(|editor, _, _| {
14086        assert!(editor.signature_help_state.is_shown());
14087    });
14088
14089    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
14090    cx.update_editor(|editor, window, cx| {
14091        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14092            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
14093            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14094        })
14095    });
14096    cx.assert_editor_state(indoc! {"
14097        fn main() {
14098            sample(param1, ˇparam2);
14099        }
14100
14101        fn sample(param1: u8, param2: u8) {}
14102    "});
14103
14104    let mocked_response = lsp::SignatureHelp {
14105        signatures: vec![lsp::SignatureInformation {
14106            label: "fn sample(param1: u8, param2: u8)".to_string(),
14107            documentation: None,
14108            parameters: Some(vec![
14109                lsp::ParameterInformation {
14110                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14111                    documentation: None,
14112                },
14113                lsp::ParameterInformation {
14114                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14115                    documentation: None,
14116                },
14117            ]),
14118            active_parameter: None,
14119        }],
14120        active_signature: Some(0),
14121        active_parameter: Some(1),
14122    };
14123    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14124    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14125        .await;
14126    cx.update_editor(|editor, _, cx| {
14127        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
14128    });
14129    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
14130        .await;
14131    cx.update_editor(|editor, window, cx| {
14132        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14133            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
14134        })
14135    });
14136    cx.assert_editor_state(indoc! {"
14137        fn main() {
14138            sample(param1, «ˇparam2»);
14139        }
14140
14141        fn sample(param1: u8, param2: u8) {}
14142    "});
14143    cx.update_editor(|editor, window, cx| {
14144        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14145            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
14146        })
14147    });
14148    cx.assert_editor_state(indoc! {"
14149        fn main() {
14150            sample(param1, ˇparam2);
14151        }
14152
14153        fn sample(param1: u8, param2: u8) {}
14154    "});
14155    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
14156        .await;
14157}
14158
14159#[gpui::test]
14160async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
14161    init_test(cx, |_| {});
14162
14163    let mut cx = EditorLspTestContext::new_rust(
14164        lsp::ServerCapabilities {
14165            signature_help_provider: Some(lsp::SignatureHelpOptions {
14166                ..Default::default()
14167            }),
14168            ..Default::default()
14169        },
14170        cx,
14171    )
14172    .await;
14173
14174    cx.set_state(indoc! {"
14175        fn main() {
14176            overloadedˇ
14177        }
14178    "});
14179
14180    cx.update_editor(|editor, window, cx| {
14181        editor.handle_input("(", window, cx);
14182        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14183    });
14184
14185    // Mock response with 3 signatures
14186    let mocked_response = lsp::SignatureHelp {
14187        signatures: vec![
14188            lsp::SignatureInformation {
14189                label: "fn overloaded(x: i32)".to_string(),
14190                documentation: None,
14191                parameters: Some(vec![lsp::ParameterInformation {
14192                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14193                    documentation: None,
14194                }]),
14195                active_parameter: None,
14196            },
14197            lsp::SignatureInformation {
14198                label: "fn overloaded(x: i32, y: i32)".to_string(),
14199                documentation: None,
14200                parameters: Some(vec![
14201                    lsp::ParameterInformation {
14202                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14203                        documentation: None,
14204                    },
14205                    lsp::ParameterInformation {
14206                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14207                        documentation: None,
14208                    },
14209                ]),
14210                active_parameter: None,
14211            },
14212            lsp::SignatureInformation {
14213                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
14214                documentation: None,
14215                parameters: Some(vec![
14216                    lsp::ParameterInformation {
14217                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
14218                        documentation: None,
14219                    },
14220                    lsp::ParameterInformation {
14221                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14222                        documentation: None,
14223                    },
14224                    lsp::ParameterInformation {
14225                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
14226                        documentation: None,
14227                    },
14228                ]),
14229                active_parameter: None,
14230            },
14231        ],
14232        active_signature: Some(1),
14233        active_parameter: Some(0),
14234    };
14235    handle_signature_help_request(&mut cx, mocked_response).await;
14236
14237    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14238        .await;
14239
14240    // Verify we have multiple signatures and the right one is selected
14241    cx.editor(|editor, _, _| {
14242        let popover = editor.signature_help_state.popover().cloned().unwrap();
14243        assert_eq!(popover.signatures.len(), 3);
14244        // active_signature was 1, so that should be the current
14245        assert_eq!(popover.current_signature, 1);
14246        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
14247        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
14248        assert_eq!(
14249            popover.signatures[2].label,
14250            "fn overloaded(x: i32, y: i32, z: i32)"
14251        );
14252    });
14253
14254    // Test navigation functionality
14255    cx.update_editor(|editor, window, cx| {
14256        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14257    });
14258
14259    cx.editor(|editor, _, _| {
14260        let popover = editor.signature_help_state.popover().cloned().unwrap();
14261        assert_eq!(popover.current_signature, 2);
14262    });
14263
14264    // Test wrap around
14265    cx.update_editor(|editor, window, cx| {
14266        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14267    });
14268
14269    cx.editor(|editor, _, _| {
14270        let popover = editor.signature_help_state.popover().cloned().unwrap();
14271        assert_eq!(popover.current_signature, 0);
14272    });
14273
14274    // Test previous navigation
14275    cx.update_editor(|editor, window, cx| {
14276        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
14277    });
14278
14279    cx.editor(|editor, _, _| {
14280        let popover = editor.signature_help_state.popover().cloned().unwrap();
14281        assert_eq!(popover.current_signature, 2);
14282    });
14283}
14284
14285#[gpui::test]
14286async fn test_completion_mode(cx: &mut TestAppContext) {
14287    init_test(cx, |_| {});
14288    let mut cx = EditorLspTestContext::new_rust(
14289        lsp::ServerCapabilities {
14290            completion_provider: Some(lsp::CompletionOptions {
14291                resolve_provider: Some(true),
14292                ..Default::default()
14293            }),
14294            ..Default::default()
14295        },
14296        cx,
14297    )
14298    .await;
14299
14300    struct Run {
14301        run_description: &'static str,
14302        initial_state: String,
14303        buffer_marked_text: String,
14304        completion_label: &'static str,
14305        completion_text: &'static str,
14306        expected_with_insert_mode: String,
14307        expected_with_replace_mode: String,
14308        expected_with_replace_subsequence_mode: String,
14309        expected_with_replace_suffix_mode: String,
14310    }
14311
14312    let runs = [
14313        Run {
14314            run_description: "Start of word matches completion text",
14315            initial_state: "before ediˇ after".into(),
14316            buffer_marked_text: "before <edi|> after".into(),
14317            completion_label: "editor",
14318            completion_text: "editor",
14319            expected_with_insert_mode: "before editorˇ after".into(),
14320            expected_with_replace_mode: "before editorˇ after".into(),
14321            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14322            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14323        },
14324        Run {
14325            run_description: "Accept same text at the middle of the word",
14326            initial_state: "before ediˇtor after".into(),
14327            buffer_marked_text: "before <edi|tor> after".into(),
14328            completion_label: "editor",
14329            completion_text: "editor",
14330            expected_with_insert_mode: "before editorˇtor after".into(),
14331            expected_with_replace_mode: "before editorˇ after".into(),
14332            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14333            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14334        },
14335        Run {
14336            run_description: "End of word matches completion text -- cursor at end",
14337            initial_state: "before torˇ after".into(),
14338            buffer_marked_text: "before <tor|> after".into(),
14339            completion_label: "editor",
14340            completion_text: "editor",
14341            expected_with_insert_mode: "before editorˇ after".into(),
14342            expected_with_replace_mode: "before editorˇ after".into(),
14343            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14344            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14345        },
14346        Run {
14347            run_description: "End of word matches completion text -- cursor at start",
14348            initial_state: "before ˇtor after".into(),
14349            buffer_marked_text: "before <|tor> after".into(),
14350            completion_label: "editor",
14351            completion_text: "editor",
14352            expected_with_insert_mode: "before editorˇtor after".into(),
14353            expected_with_replace_mode: "before editorˇ after".into(),
14354            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14355            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14356        },
14357        Run {
14358            run_description: "Prepend text containing whitespace",
14359            initial_state: "pˇfield: bool".into(),
14360            buffer_marked_text: "<p|field>: bool".into(),
14361            completion_label: "pub ",
14362            completion_text: "pub ",
14363            expected_with_insert_mode: "pub ˇfield: bool".into(),
14364            expected_with_replace_mode: "pub ˇ: bool".into(),
14365            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
14366            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
14367        },
14368        Run {
14369            run_description: "Add element to start of list",
14370            initial_state: "[element_ˇelement_2]".into(),
14371            buffer_marked_text: "[<element_|element_2>]".into(),
14372            completion_label: "element_1",
14373            completion_text: "element_1",
14374            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
14375            expected_with_replace_mode: "[element_1ˇ]".into(),
14376            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
14377            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
14378        },
14379        Run {
14380            run_description: "Add element to start of list -- first and second elements are equal",
14381            initial_state: "[elˇelement]".into(),
14382            buffer_marked_text: "[<el|element>]".into(),
14383            completion_label: "element",
14384            completion_text: "element",
14385            expected_with_insert_mode: "[elementˇelement]".into(),
14386            expected_with_replace_mode: "[elementˇ]".into(),
14387            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
14388            expected_with_replace_suffix_mode: "[elementˇ]".into(),
14389        },
14390        Run {
14391            run_description: "Ends with matching suffix",
14392            initial_state: "SubˇError".into(),
14393            buffer_marked_text: "<Sub|Error>".into(),
14394            completion_label: "SubscriptionError",
14395            completion_text: "SubscriptionError",
14396            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
14397            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14398            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14399            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
14400        },
14401        Run {
14402            run_description: "Suffix is a subsequence -- contiguous",
14403            initial_state: "SubˇErr".into(),
14404            buffer_marked_text: "<Sub|Err>".into(),
14405            completion_label: "SubscriptionError",
14406            completion_text: "SubscriptionError",
14407            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
14408            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14409            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14410            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
14411        },
14412        Run {
14413            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
14414            initial_state: "Suˇscrirr".into(),
14415            buffer_marked_text: "<Su|scrirr>".into(),
14416            completion_label: "SubscriptionError",
14417            completion_text: "SubscriptionError",
14418            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
14419            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14420            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14421            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
14422        },
14423        Run {
14424            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
14425            initial_state: "foo(indˇix)".into(),
14426            buffer_marked_text: "foo(<ind|ix>)".into(),
14427            completion_label: "node_index",
14428            completion_text: "node_index",
14429            expected_with_insert_mode: "foo(node_indexˇix)".into(),
14430            expected_with_replace_mode: "foo(node_indexˇ)".into(),
14431            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
14432            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
14433        },
14434        Run {
14435            run_description: "Replace range ends before cursor - should extend to cursor",
14436            initial_state: "before editˇo after".into(),
14437            buffer_marked_text: "before <{ed}>it|o after".into(),
14438            completion_label: "editor",
14439            completion_text: "editor",
14440            expected_with_insert_mode: "before editorˇo after".into(),
14441            expected_with_replace_mode: "before editorˇo after".into(),
14442            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14443            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14444        },
14445        Run {
14446            run_description: "Uses label for suffix matching",
14447            initial_state: "before ediˇtor after".into(),
14448            buffer_marked_text: "before <edi|tor> after".into(),
14449            completion_label: "editor",
14450            completion_text: "editor()",
14451            expected_with_insert_mode: "before editor()ˇtor after".into(),
14452            expected_with_replace_mode: "before editor()ˇ after".into(),
14453            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14454            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14455        },
14456        Run {
14457            run_description: "Case insensitive subsequence and suffix matching",
14458            initial_state: "before EDiˇtoR after".into(),
14459            buffer_marked_text: "before <EDi|toR> after".into(),
14460            completion_label: "editor",
14461            completion_text: "editor",
14462            expected_with_insert_mode: "before editorˇtoR after".into(),
14463            expected_with_replace_mode: "before editorˇ after".into(),
14464            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14465            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14466        },
14467    ];
14468
14469    for run in runs {
14470        let run_variations = [
14471            (LspInsertMode::Insert, run.expected_with_insert_mode),
14472            (LspInsertMode::Replace, run.expected_with_replace_mode),
14473            (
14474                LspInsertMode::ReplaceSubsequence,
14475                run.expected_with_replace_subsequence_mode,
14476            ),
14477            (
14478                LspInsertMode::ReplaceSuffix,
14479                run.expected_with_replace_suffix_mode,
14480            ),
14481        ];
14482
14483        for (lsp_insert_mode, expected_text) in run_variations {
14484            eprintln!(
14485                "run = {:?}, mode = {lsp_insert_mode:.?}",
14486                run.run_description,
14487            );
14488
14489            update_test_language_settings(&mut cx, |settings| {
14490                settings.defaults.completions = Some(CompletionSettingsContent {
14491                    lsp_insert_mode: Some(lsp_insert_mode),
14492                    words: Some(WordsCompletionMode::Disabled),
14493                    words_min_length: Some(0),
14494                    ..Default::default()
14495                });
14496            });
14497
14498            cx.set_state(&run.initial_state);
14499
14500            // Set up resolve handler before showing completions, since resolve may be
14501            // triggered when menu becomes visible (for documentation), not just on confirm.
14502            cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
14503                move |_, _, _| async move {
14504                    Ok(lsp::CompletionItem {
14505                        additional_text_edits: None,
14506                        ..Default::default()
14507                    })
14508                },
14509            );
14510
14511            cx.update_editor(|editor, window, cx| {
14512                editor.show_completions(&ShowCompletions, window, cx);
14513            });
14514
14515            let counter = Arc::new(AtomicUsize::new(0));
14516            handle_completion_request_with_insert_and_replace(
14517                &mut cx,
14518                &run.buffer_marked_text,
14519                vec![(run.completion_label, run.completion_text)],
14520                counter.clone(),
14521            )
14522            .await;
14523            cx.condition(|editor, _| editor.context_menu_visible())
14524                .await;
14525            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14526
14527            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14528                editor
14529                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14530                    .unwrap()
14531            });
14532            cx.assert_editor_state(&expected_text);
14533            apply_additional_edits.await.unwrap();
14534        }
14535    }
14536}
14537
14538#[gpui::test]
14539async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14540    init_test(cx, |_| {});
14541    let mut cx = EditorLspTestContext::new_rust(
14542        lsp::ServerCapabilities {
14543            completion_provider: Some(lsp::CompletionOptions {
14544                resolve_provider: Some(true),
14545                ..Default::default()
14546            }),
14547            ..Default::default()
14548        },
14549        cx,
14550    )
14551    .await;
14552
14553    let initial_state = "SubˇError";
14554    let buffer_marked_text = "<Sub|Error>";
14555    let completion_text = "SubscriptionError";
14556    let expected_with_insert_mode = "SubscriptionErrorˇError";
14557    let expected_with_replace_mode = "SubscriptionErrorˇ";
14558
14559    update_test_language_settings(&mut cx, |settings| {
14560        settings.defaults.completions = Some(CompletionSettingsContent {
14561            words: Some(WordsCompletionMode::Disabled),
14562            words_min_length: Some(0),
14563            // set the opposite here to ensure that the action is overriding the default behavior
14564            lsp_insert_mode: Some(LspInsertMode::Insert),
14565            ..Default::default()
14566        });
14567    });
14568
14569    cx.set_state(initial_state);
14570    cx.update_editor(|editor, window, cx| {
14571        editor.show_completions(&ShowCompletions, window, cx);
14572    });
14573
14574    let counter = Arc::new(AtomicUsize::new(0));
14575    handle_completion_request_with_insert_and_replace(
14576        &mut cx,
14577        buffer_marked_text,
14578        vec![(completion_text, completion_text)],
14579        counter.clone(),
14580    )
14581    .await;
14582    cx.condition(|editor, _| editor.context_menu_visible())
14583        .await;
14584    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14585
14586    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14587        editor
14588            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14589            .unwrap()
14590    });
14591    cx.assert_editor_state(expected_with_replace_mode);
14592    handle_resolve_completion_request(&mut cx, None).await;
14593    apply_additional_edits.await.unwrap();
14594
14595    update_test_language_settings(&mut cx, |settings| {
14596        settings.defaults.completions = Some(CompletionSettingsContent {
14597            words: Some(WordsCompletionMode::Disabled),
14598            words_min_length: Some(0),
14599            // set the opposite here to ensure that the action is overriding the default behavior
14600            lsp_insert_mode: Some(LspInsertMode::Replace),
14601            ..Default::default()
14602        });
14603    });
14604
14605    cx.set_state(initial_state);
14606    cx.update_editor(|editor, window, cx| {
14607        editor.show_completions(&ShowCompletions, window, cx);
14608    });
14609    handle_completion_request_with_insert_and_replace(
14610        &mut cx,
14611        buffer_marked_text,
14612        vec![(completion_text, completion_text)],
14613        counter.clone(),
14614    )
14615    .await;
14616    cx.condition(|editor, _| editor.context_menu_visible())
14617        .await;
14618    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14619
14620    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14621        editor
14622            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14623            .unwrap()
14624    });
14625    cx.assert_editor_state(expected_with_insert_mode);
14626    handle_resolve_completion_request(&mut cx, None).await;
14627    apply_additional_edits.await.unwrap();
14628}
14629
14630#[gpui::test]
14631async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14632    init_test(cx, |_| {});
14633    let mut cx = EditorLspTestContext::new_rust(
14634        lsp::ServerCapabilities {
14635            completion_provider: Some(lsp::CompletionOptions {
14636                resolve_provider: Some(true),
14637                ..Default::default()
14638            }),
14639            ..Default::default()
14640        },
14641        cx,
14642    )
14643    .await;
14644
14645    // scenario: surrounding text matches completion text
14646    let completion_text = "to_offset";
14647    let initial_state = indoc! {"
14648        1. buf.to_offˇsuffix
14649        2. buf.to_offˇsuf
14650        3. buf.to_offˇfix
14651        4. buf.to_offˇ
14652        5. into_offˇensive
14653        6. ˇsuffix
14654        7. let ˇ //
14655        8. aaˇzz
14656        9. buf.to_off«zzzzzˇ»suffix
14657        10. buf.«ˇzzzzz»suffix
14658        11. to_off«ˇzzzzz»
14659
14660        buf.to_offˇsuffix  // newest cursor
14661    "};
14662    let completion_marked_buffer = indoc! {"
14663        1. buf.to_offsuffix
14664        2. buf.to_offsuf
14665        3. buf.to_offfix
14666        4. buf.to_off
14667        5. into_offensive
14668        6. suffix
14669        7. let  //
14670        8. aazz
14671        9. buf.to_offzzzzzsuffix
14672        10. buf.zzzzzsuffix
14673        11. to_offzzzzz
14674
14675        buf.<to_off|suffix>  // newest cursor
14676    "};
14677    let expected = indoc! {"
14678        1. buf.to_offsetˇ
14679        2. buf.to_offsetˇsuf
14680        3. buf.to_offsetˇfix
14681        4. buf.to_offsetˇ
14682        5. into_offsetˇensive
14683        6. to_offsetˇsuffix
14684        7. let to_offsetˇ //
14685        8. aato_offsetˇzz
14686        9. buf.to_offsetˇ
14687        10. buf.to_offsetˇsuffix
14688        11. to_offsetˇ
14689
14690        buf.to_offsetˇ  // newest cursor
14691    "};
14692    cx.set_state(initial_state);
14693    cx.update_editor(|editor, window, cx| {
14694        editor.show_completions(&ShowCompletions, window, cx);
14695    });
14696    handle_completion_request_with_insert_and_replace(
14697        &mut cx,
14698        completion_marked_buffer,
14699        vec![(completion_text, completion_text)],
14700        Arc::new(AtomicUsize::new(0)),
14701    )
14702    .await;
14703    cx.condition(|editor, _| editor.context_menu_visible())
14704        .await;
14705    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14706        editor
14707            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14708            .unwrap()
14709    });
14710    cx.assert_editor_state(expected);
14711    handle_resolve_completion_request(&mut cx, None).await;
14712    apply_additional_edits.await.unwrap();
14713
14714    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14715    let completion_text = "foo_and_bar";
14716    let initial_state = indoc! {"
14717        1. ooanbˇ
14718        2. zooanbˇ
14719        3. ooanbˇz
14720        4. zooanbˇz
14721        5. ooanˇ
14722        6. oanbˇ
14723
14724        ooanbˇ
14725    "};
14726    let completion_marked_buffer = indoc! {"
14727        1. ooanb
14728        2. zooanb
14729        3. ooanbz
14730        4. zooanbz
14731        5. ooan
14732        6. oanb
14733
14734        <ooanb|>
14735    "};
14736    let expected = indoc! {"
14737        1. foo_and_barˇ
14738        2. zfoo_and_barˇ
14739        3. foo_and_barˇz
14740        4. zfoo_and_barˇz
14741        5. ooanfoo_and_barˇ
14742        6. oanbfoo_and_barˇ
14743
14744        foo_and_barˇ
14745    "};
14746    cx.set_state(initial_state);
14747    cx.update_editor(|editor, window, cx| {
14748        editor.show_completions(&ShowCompletions, window, cx);
14749    });
14750    handle_completion_request_with_insert_and_replace(
14751        &mut cx,
14752        completion_marked_buffer,
14753        vec![(completion_text, completion_text)],
14754        Arc::new(AtomicUsize::new(0)),
14755    )
14756    .await;
14757    cx.condition(|editor, _| editor.context_menu_visible())
14758        .await;
14759    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14760        editor
14761            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14762            .unwrap()
14763    });
14764    cx.assert_editor_state(expected);
14765    handle_resolve_completion_request(&mut cx, None).await;
14766    apply_additional_edits.await.unwrap();
14767
14768    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14769    // (expects the same as if it was inserted at the end)
14770    let completion_text = "foo_and_bar";
14771    let initial_state = indoc! {"
14772        1. ooˇanb
14773        2. zooˇanb
14774        3. ooˇanbz
14775        4. zooˇanbz
14776
14777        ooˇanb
14778    "};
14779    let completion_marked_buffer = indoc! {"
14780        1. ooanb
14781        2. zooanb
14782        3. ooanbz
14783        4. zooanbz
14784
14785        <oo|anb>
14786    "};
14787    let expected = indoc! {"
14788        1. foo_and_barˇ
14789        2. zfoo_and_barˇ
14790        3. foo_and_barˇz
14791        4. zfoo_and_barˇz
14792
14793        foo_and_barˇ
14794    "};
14795    cx.set_state(initial_state);
14796    cx.update_editor(|editor, window, cx| {
14797        editor.show_completions(&ShowCompletions, window, cx);
14798    });
14799    handle_completion_request_with_insert_and_replace(
14800        &mut cx,
14801        completion_marked_buffer,
14802        vec![(completion_text, completion_text)],
14803        Arc::new(AtomicUsize::new(0)),
14804    )
14805    .await;
14806    cx.condition(|editor, _| editor.context_menu_visible())
14807        .await;
14808    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14809        editor
14810            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14811            .unwrap()
14812    });
14813    cx.assert_editor_state(expected);
14814    handle_resolve_completion_request(&mut cx, None).await;
14815    apply_additional_edits.await.unwrap();
14816}
14817
14818// This used to crash
14819#[gpui::test]
14820async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14821    init_test(cx, |_| {});
14822
14823    let buffer_text = indoc! {"
14824        fn main() {
14825            10.satu;
14826
14827            //
14828            // separate cursors so they open in different excerpts (manually reproducible)
14829            //
14830
14831            10.satu20;
14832        }
14833    "};
14834    let multibuffer_text_with_selections = indoc! {"
14835        fn main() {
14836            10.satuˇ;
14837
14838            //
14839
14840            //
14841
14842            10.satuˇ20;
14843        }
14844    "};
14845    let expected_multibuffer = indoc! {"
14846        fn main() {
14847            10.saturating_sub()ˇ;
14848
14849            //
14850
14851            //
14852
14853            10.saturating_sub()ˇ;
14854        }
14855    "};
14856
14857    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14858    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14859
14860    let fs = FakeFs::new(cx.executor());
14861    fs.insert_tree(
14862        path!("/a"),
14863        json!({
14864            "main.rs": buffer_text,
14865        }),
14866    )
14867    .await;
14868
14869    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14870    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14871    language_registry.add(rust_lang());
14872    let mut fake_servers = language_registry.register_fake_lsp(
14873        "Rust",
14874        FakeLspAdapter {
14875            capabilities: lsp::ServerCapabilities {
14876                completion_provider: Some(lsp::CompletionOptions {
14877                    resolve_provider: None,
14878                    ..lsp::CompletionOptions::default()
14879                }),
14880                ..lsp::ServerCapabilities::default()
14881            },
14882            ..FakeLspAdapter::default()
14883        },
14884    );
14885    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14886    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14887    let buffer = project
14888        .update(cx, |project, cx| {
14889            project.open_local_buffer(path!("/a/main.rs"), cx)
14890        })
14891        .await
14892        .unwrap();
14893
14894    let multi_buffer = cx.new(|cx| {
14895        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14896        multi_buffer.push_excerpts(
14897            buffer.clone(),
14898            [ExcerptRange::new(0..first_excerpt_end)],
14899            cx,
14900        );
14901        multi_buffer.push_excerpts(
14902            buffer.clone(),
14903            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14904            cx,
14905        );
14906        multi_buffer
14907    });
14908
14909    let editor = workspace
14910        .update(cx, |_, window, cx| {
14911            cx.new(|cx| {
14912                Editor::new(
14913                    EditorMode::Full {
14914                        scale_ui_elements_with_buffer_font_size: false,
14915                        show_active_line_background: false,
14916                        sizing_behavior: SizingBehavior::Default,
14917                    },
14918                    multi_buffer.clone(),
14919                    Some(project.clone()),
14920                    window,
14921                    cx,
14922                )
14923            })
14924        })
14925        .unwrap();
14926
14927    let pane = workspace
14928        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14929        .unwrap();
14930    pane.update_in(cx, |pane, window, cx| {
14931        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14932    });
14933
14934    let fake_server = fake_servers.next().await.unwrap();
14935    cx.run_until_parked();
14936
14937    editor.update_in(cx, |editor, window, cx| {
14938        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14939            s.select_ranges([
14940                Point::new(1, 11)..Point::new(1, 11),
14941                Point::new(7, 11)..Point::new(7, 11),
14942            ])
14943        });
14944
14945        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14946    });
14947
14948    editor.update_in(cx, |editor, window, cx| {
14949        editor.show_completions(&ShowCompletions, window, cx);
14950    });
14951
14952    fake_server
14953        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14954            let completion_item = lsp::CompletionItem {
14955                label: "saturating_sub()".into(),
14956                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14957                    lsp::InsertReplaceEdit {
14958                        new_text: "saturating_sub()".to_owned(),
14959                        insert: lsp::Range::new(
14960                            lsp::Position::new(7, 7),
14961                            lsp::Position::new(7, 11),
14962                        ),
14963                        replace: lsp::Range::new(
14964                            lsp::Position::new(7, 7),
14965                            lsp::Position::new(7, 13),
14966                        ),
14967                    },
14968                )),
14969                ..lsp::CompletionItem::default()
14970            };
14971
14972            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14973        })
14974        .next()
14975        .await
14976        .unwrap();
14977
14978    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14979        .await;
14980
14981    editor
14982        .update_in(cx, |editor, window, cx| {
14983            editor
14984                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14985                .unwrap()
14986        })
14987        .await
14988        .unwrap();
14989
14990    editor.update(cx, |editor, cx| {
14991        assert_text_with_selections(editor, expected_multibuffer, cx);
14992    })
14993}
14994
14995#[gpui::test]
14996async fn test_completion(cx: &mut TestAppContext) {
14997    init_test(cx, |_| {});
14998
14999    let mut cx = EditorLspTestContext::new_rust(
15000        lsp::ServerCapabilities {
15001            completion_provider: Some(lsp::CompletionOptions {
15002                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15003                resolve_provider: Some(true),
15004                ..Default::default()
15005            }),
15006            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15007            ..Default::default()
15008        },
15009        cx,
15010    )
15011    .await;
15012    let counter = Arc::new(AtomicUsize::new(0));
15013
15014    cx.set_state(indoc! {"
15015        oneˇ
15016        two
15017        three
15018    "});
15019    cx.simulate_keystroke(".");
15020    handle_completion_request(
15021        indoc! {"
15022            one.|<>
15023            two
15024            three
15025        "},
15026        vec!["first_completion", "second_completion"],
15027        true,
15028        counter.clone(),
15029        &mut cx,
15030    )
15031    .await;
15032    cx.condition(|editor, _| editor.context_menu_visible())
15033        .await;
15034    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15035
15036    let _handler = handle_signature_help_request(
15037        &mut cx,
15038        lsp::SignatureHelp {
15039            signatures: vec![lsp::SignatureInformation {
15040                label: "test signature".to_string(),
15041                documentation: None,
15042                parameters: Some(vec![lsp::ParameterInformation {
15043                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
15044                    documentation: None,
15045                }]),
15046                active_parameter: None,
15047            }],
15048            active_signature: None,
15049            active_parameter: None,
15050        },
15051    );
15052    cx.update_editor(|editor, window, cx| {
15053        assert!(
15054            !editor.signature_help_state.is_shown(),
15055            "No signature help was called for"
15056        );
15057        editor.show_signature_help(&ShowSignatureHelp, window, cx);
15058    });
15059    cx.run_until_parked();
15060    cx.update_editor(|editor, _, _| {
15061        assert!(
15062            !editor.signature_help_state.is_shown(),
15063            "No signature help should be shown when completions menu is open"
15064        );
15065    });
15066
15067    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15068        editor.context_menu_next(&Default::default(), window, cx);
15069        editor
15070            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15071            .unwrap()
15072    });
15073    cx.assert_editor_state(indoc! {"
15074        one.second_completionˇ
15075        two
15076        three
15077    "});
15078
15079    handle_resolve_completion_request(
15080        &mut cx,
15081        Some(vec![
15082            (
15083                //This overlaps with the primary completion edit which is
15084                //misbehavior from the LSP spec, test that we filter it out
15085                indoc! {"
15086                    one.second_ˇcompletion
15087                    two
15088                    threeˇ
15089                "},
15090                "overlapping additional edit",
15091            ),
15092            (
15093                indoc! {"
15094                    one.second_completion
15095                    two
15096                    threeˇ
15097                "},
15098                "\nadditional edit",
15099            ),
15100        ]),
15101    )
15102    .await;
15103    apply_additional_edits.await.unwrap();
15104    cx.assert_editor_state(indoc! {"
15105        one.second_completionˇ
15106        two
15107        three
15108        additional edit
15109    "});
15110
15111    cx.set_state(indoc! {"
15112        one.second_completion
15113        twoˇ
15114        threeˇ
15115        additional edit
15116    "});
15117    cx.simulate_keystroke(" ");
15118    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15119    cx.simulate_keystroke("s");
15120    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15121
15122    cx.assert_editor_state(indoc! {"
15123        one.second_completion
15124        two sˇ
15125        three sˇ
15126        additional edit
15127    "});
15128    handle_completion_request(
15129        indoc! {"
15130            one.second_completion
15131            two s
15132            three <s|>
15133            additional edit
15134        "},
15135        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15136        true,
15137        counter.clone(),
15138        &mut cx,
15139    )
15140    .await;
15141    cx.condition(|editor, _| editor.context_menu_visible())
15142        .await;
15143    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15144
15145    cx.simulate_keystroke("i");
15146
15147    handle_completion_request(
15148        indoc! {"
15149            one.second_completion
15150            two si
15151            three <si|>
15152            additional edit
15153        "},
15154        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
15155        true,
15156        counter.clone(),
15157        &mut cx,
15158    )
15159    .await;
15160    cx.condition(|editor, _| editor.context_menu_visible())
15161        .await;
15162    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15163
15164    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15165        editor
15166            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15167            .unwrap()
15168    });
15169    cx.assert_editor_state(indoc! {"
15170        one.second_completion
15171        two sixth_completionˇ
15172        three sixth_completionˇ
15173        additional edit
15174    "});
15175
15176    apply_additional_edits.await.unwrap();
15177
15178    update_test_language_settings(&mut cx, |settings| {
15179        settings.defaults.show_completions_on_input = Some(false);
15180    });
15181    cx.set_state("editorˇ");
15182    cx.simulate_keystroke(".");
15183    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15184    cx.simulate_keystrokes("c l o");
15185    cx.assert_editor_state("editor.cloˇ");
15186    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
15187    cx.update_editor(|editor, window, cx| {
15188        editor.show_completions(&ShowCompletions, window, cx);
15189    });
15190    handle_completion_request(
15191        "editor.<clo|>",
15192        vec!["close", "clobber"],
15193        true,
15194        counter.clone(),
15195        &mut cx,
15196    )
15197    .await;
15198    cx.condition(|editor, _| editor.context_menu_visible())
15199        .await;
15200    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
15201
15202    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15203        editor
15204            .confirm_completion(&ConfirmCompletion::default(), window, cx)
15205            .unwrap()
15206    });
15207    cx.assert_editor_state("editor.clobberˇ");
15208    handle_resolve_completion_request(&mut cx, None).await;
15209    apply_additional_edits.await.unwrap();
15210}
15211
15212#[gpui::test]
15213async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
15214    init_test(cx, |_| {});
15215
15216    let fs = FakeFs::new(cx.executor());
15217    fs.insert_tree(
15218        path!("/a"),
15219        json!({
15220            "main.rs": "",
15221        }),
15222    )
15223    .await;
15224
15225    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15226    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15227    language_registry.add(rust_lang());
15228    let command_calls = Arc::new(AtomicUsize::new(0));
15229    let registered_command = "_the/command";
15230
15231    let closure_command_calls = command_calls.clone();
15232    let mut fake_servers = language_registry.register_fake_lsp(
15233        "Rust",
15234        FakeLspAdapter {
15235            capabilities: lsp::ServerCapabilities {
15236                completion_provider: Some(lsp::CompletionOptions {
15237                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15238                    ..lsp::CompletionOptions::default()
15239                }),
15240                execute_command_provider: Some(lsp::ExecuteCommandOptions {
15241                    commands: vec![registered_command.to_owned()],
15242                    ..lsp::ExecuteCommandOptions::default()
15243                }),
15244                ..lsp::ServerCapabilities::default()
15245            },
15246            initializer: Some(Box::new(move |fake_server| {
15247                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15248                    move |params, _| async move {
15249                        Ok(Some(lsp::CompletionResponse::Array(vec![
15250                            lsp::CompletionItem {
15251                                label: "registered_command".to_owned(),
15252                                text_edit: gen_text_edit(&params, ""),
15253                                command: Some(lsp::Command {
15254                                    title: registered_command.to_owned(),
15255                                    command: "_the/command".to_owned(),
15256                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
15257                                }),
15258                                ..lsp::CompletionItem::default()
15259                            },
15260                            lsp::CompletionItem {
15261                                label: "unregistered_command".to_owned(),
15262                                text_edit: gen_text_edit(&params, ""),
15263                                command: Some(lsp::Command {
15264                                    title: "????????????".to_owned(),
15265                                    command: "????????????".to_owned(),
15266                                    arguments: Some(vec![serde_json::Value::Null]),
15267                                }),
15268                                ..lsp::CompletionItem::default()
15269                            },
15270                        ])))
15271                    },
15272                );
15273                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
15274                    let command_calls = closure_command_calls.clone();
15275                    move |params, _| {
15276                        assert_eq!(params.command, registered_command);
15277                        let command_calls = command_calls.clone();
15278                        async move {
15279                            command_calls.fetch_add(1, atomic::Ordering::Release);
15280                            Ok(Some(json!(null)))
15281                        }
15282                    }
15283                });
15284            })),
15285            ..FakeLspAdapter::default()
15286        },
15287    );
15288    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15289    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15290    let editor = workspace
15291        .update(cx, |workspace, window, cx| {
15292            workspace.open_abs_path(
15293                PathBuf::from(path!("/a/main.rs")),
15294                OpenOptions::default(),
15295                window,
15296                cx,
15297            )
15298        })
15299        .unwrap()
15300        .await
15301        .unwrap()
15302        .downcast::<Editor>()
15303        .unwrap();
15304    let _fake_server = fake_servers.next().await.unwrap();
15305    cx.run_until_parked();
15306
15307    editor.update_in(cx, |editor, window, cx| {
15308        cx.focus_self(window);
15309        editor.move_to_end(&MoveToEnd, window, cx);
15310        editor.handle_input(".", window, cx);
15311    });
15312    cx.run_until_parked();
15313    editor.update(cx, |editor, _| {
15314        assert!(editor.context_menu_visible());
15315        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15316        {
15317            let completion_labels = menu
15318                .completions
15319                .borrow()
15320                .iter()
15321                .map(|c| c.label.text.clone())
15322                .collect::<Vec<_>>();
15323            assert_eq!(
15324                completion_labels,
15325                &["registered_command", "unregistered_command",],
15326            );
15327        } else {
15328            panic!("expected completion menu to be open");
15329        }
15330    });
15331
15332    editor
15333        .update_in(cx, |editor, window, cx| {
15334            editor
15335                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15336                .unwrap()
15337        })
15338        .await
15339        .unwrap();
15340    cx.run_until_parked();
15341    assert_eq!(
15342        command_calls.load(atomic::Ordering::Acquire),
15343        1,
15344        "For completion with a registered command, Zed should send a command execution request",
15345    );
15346
15347    editor.update_in(cx, |editor, window, cx| {
15348        cx.focus_self(window);
15349        editor.handle_input(".", window, cx);
15350    });
15351    cx.run_until_parked();
15352    editor.update(cx, |editor, _| {
15353        assert!(editor.context_menu_visible());
15354        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15355        {
15356            let completion_labels = menu
15357                .completions
15358                .borrow()
15359                .iter()
15360                .map(|c| c.label.text.clone())
15361                .collect::<Vec<_>>();
15362            assert_eq!(
15363                completion_labels,
15364                &["registered_command", "unregistered_command",],
15365            );
15366        } else {
15367            panic!("expected completion menu to be open");
15368        }
15369    });
15370    editor
15371        .update_in(cx, |editor, window, cx| {
15372            editor.context_menu_next(&Default::default(), window, cx);
15373            editor
15374                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15375                .unwrap()
15376        })
15377        .await
15378        .unwrap();
15379    cx.run_until_parked();
15380    assert_eq!(
15381        command_calls.load(atomic::Ordering::Acquire),
15382        1,
15383        "For completion with an unregistered command, Zed should not send a command execution request",
15384    );
15385}
15386
15387#[gpui::test]
15388async fn test_completion_reuse(cx: &mut TestAppContext) {
15389    init_test(cx, |_| {});
15390
15391    let mut cx = EditorLspTestContext::new_rust(
15392        lsp::ServerCapabilities {
15393            completion_provider: Some(lsp::CompletionOptions {
15394                trigger_characters: Some(vec![".".to_string()]),
15395                ..Default::default()
15396            }),
15397            ..Default::default()
15398        },
15399        cx,
15400    )
15401    .await;
15402
15403    let counter = Arc::new(AtomicUsize::new(0));
15404    cx.set_state("objˇ");
15405    cx.simulate_keystroke(".");
15406
15407    // Initial completion request returns complete results
15408    let is_incomplete = false;
15409    handle_completion_request(
15410        "obj.|<>",
15411        vec!["a", "ab", "abc"],
15412        is_incomplete,
15413        counter.clone(),
15414        &mut cx,
15415    )
15416    .await;
15417    cx.run_until_parked();
15418    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15419    cx.assert_editor_state("obj.ˇ");
15420    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15421
15422    // Type "a" - filters existing completions
15423    cx.simulate_keystroke("a");
15424    cx.run_until_parked();
15425    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15426    cx.assert_editor_state("obj.aˇ");
15427    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15428
15429    // Type "b" - filters existing completions
15430    cx.simulate_keystroke("b");
15431    cx.run_until_parked();
15432    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15433    cx.assert_editor_state("obj.abˇ");
15434    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15435
15436    // Type "c" - filters existing completions
15437    cx.simulate_keystroke("c");
15438    cx.run_until_parked();
15439    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15440    cx.assert_editor_state("obj.abcˇ");
15441    check_displayed_completions(vec!["abc"], &mut cx);
15442
15443    // Backspace to delete "c" - filters existing completions
15444    cx.update_editor(|editor, window, cx| {
15445        editor.backspace(&Backspace, window, cx);
15446    });
15447    cx.run_until_parked();
15448    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15449    cx.assert_editor_state("obj.abˇ");
15450    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15451
15452    // Moving cursor to the left dismisses menu.
15453    cx.update_editor(|editor, window, cx| {
15454        editor.move_left(&MoveLeft, window, cx);
15455    });
15456    cx.run_until_parked();
15457    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15458    cx.assert_editor_state("obj.aˇb");
15459    cx.update_editor(|editor, _, _| {
15460        assert_eq!(editor.context_menu_visible(), false);
15461    });
15462
15463    // Type "b" - new request
15464    cx.simulate_keystroke("b");
15465    let is_incomplete = false;
15466    handle_completion_request(
15467        "obj.<ab|>a",
15468        vec!["ab", "abc"],
15469        is_incomplete,
15470        counter.clone(),
15471        &mut cx,
15472    )
15473    .await;
15474    cx.run_until_parked();
15475    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15476    cx.assert_editor_state("obj.abˇb");
15477    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15478
15479    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15480    cx.update_editor(|editor, window, cx| {
15481        editor.backspace(&Backspace, window, cx);
15482    });
15483    let is_incomplete = false;
15484    handle_completion_request(
15485        "obj.<a|>b",
15486        vec!["a", "ab", "abc"],
15487        is_incomplete,
15488        counter.clone(),
15489        &mut cx,
15490    )
15491    .await;
15492    cx.run_until_parked();
15493    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15494    cx.assert_editor_state("obj.aˇb");
15495    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15496
15497    // Backspace to delete "a" - dismisses menu.
15498    cx.update_editor(|editor, window, cx| {
15499        editor.backspace(&Backspace, window, cx);
15500    });
15501    cx.run_until_parked();
15502    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15503    cx.assert_editor_state("obj.ˇb");
15504    cx.update_editor(|editor, _, _| {
15505        assert_eq!(editor.context_menu_visible(), false);
15506    });
15507}
15508
15509#[gpui::test]
15510async fn test_word_completion(cx: &mut TestAppContext) {
15511    let lsp_fetch_timeout_ms = 10;
15512    init_test(cx, |language_settings| {
15513        language_settings.defaults.completions = Some(CompletionSettingsContent {
15514            words_min_length: Some(0),
15515            lsp_fetch_timeout_ms: Some(10),
15516            lsp_insert_mode: Some(LspInsertMode::Insert),
15517            ..Default::default()
15518        });
15519    });
15520
15521    let mut cx = EditorLspTestContext::new_rust(
15522        lsp::ServerCapabilities {
15523            completion_provider: Some(lsp::CompletionOptions {
15524                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15525                ..lsp::CompletionOptions::default()
15526            }),
15527            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15528            ..lsp::ServerCapabilities::default()
15529        },
15530        cx,
15531    )
15532    .await;
15533
15534    let throttle_completions = Arc::new(AtomicBool::new(false));
15535
15536    let lsp_throttle_completions = throttle_completions.clone();
15537    let _completion_requests_handler =
15538        cx.lsp
15539            .server
15540            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15541                let lsp_throttle_completions = lsp_throttle_completions.clone();
15542                let cx = cx.clone();
15543                async move {
15544                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15545                        cx.background_executor()
15546                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15547                            .await;
15548                    }
15549                    Ok(Some(lsp::CompletionResponse::Array(vec![
15550                        lsp::CompletionItem {
15551                            label: "first".into(),
15552                            ..lsp::CompletionItem::default()
15553                        },
15554                        lsp::CompletionItem {
15555                            label: "last".into(),
15556                            ..lsp::CompletionItem::default()
15557                        },
15558                    ])))
15559                }
15560            });
15561
15562    cx.set_state(indoc! {"
15563        oneˇ
15564        two
15565        three
15566    "});
15567    cx.simulate_keystroke(".");
15568    cx.executor().run_until_parked();
15569    cx.condition(|editor, _| editor.context_menu_visible())
15570        .await;
15571    cx.update_editor(|editor, window, cx| {
15572        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15573        {
15574            assert_eq!(
15575                completion_menu_entries(menu),
15576                &["first", "last"],
15577                "When LSP server is fast to reply, no fallback word completions are used"
15578            );
15579        } else {
15580            panic!("expected completion menu to be open");
15581        }
15582        editor.cancel(&Cancel, window, cx);
15583    });
15584    cx.executor().run_until_parked();
15585    cx.condition(|editor, _| !editor.context_menu_visible())
15586        .await;
15587
15588    throttle_completions.store(true, atomic::Ordering::Release);
15589    cx.simulate_keystroke(".");
15590    cx.executor()
15591        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15592    cx.executor().run_until_parked();
15593    cx.condition(|editor, _| editor.context_menu_visible())
15594        .await;
15595    cx.update_editor(|editor, _, _| {
15596        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15597        {
15598            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15599                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15600        } else {
15601            panic!("expected completion menu to be open");
15602        }
15603    });
15604}
15605
15606#[gpui::test]
15607async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15608    init_test(cx, |language_settings| {
15609        language_settings.defaults.completions = Some(CompletionSettingsContent {
15610            words: Some(WordsCompletionMode::Enabled),
15611            words_min_length: Some(0),
15612            lsp_insert_mode: Some(LspInsertMode::Insert),
15613            ..Default::default()
15614        });
15615    });
15616
15617    let mut cx = EditorLspTestContext::new_rust(
15618        lsp::ServerCapabilities {
15619            completion_provider: Some(lsp::CompletionOptions {
15620                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15621                ..lsp::CompletionOptions::default()
15622            }),
15623            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15624            ..lsp::ServerCapabilities::default()
15625        },
15626        cx,
15627    )
15628    .await;
15629
15630    let _completion_requests_handler =
15631        cx.lsp
15632            .server
15633            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15634                Ok(Some(lsp::CompletionResponse::Array(vec![
15635                    lsp::CompletionItem {
15636                        label: "first".into(),
15637                        ..lsp::CompletionItem::default()
15638                    },
15639                    lsp::CompletionItem {
15640                        label: "last".into(),
15641                        ..lsp::CompletionItem::default()
15642                    },
15643                ])))
15644            });
15645
15646    cx.set_state(indoc! {"ˇ
15647        first
15648        last
15649        second
15650    "});
15651    cx.simulate_keystroke(".");
15652    cx.executor().run_until_parked();
15653    cx.condition(|editor, _| editor.context_menu_visible())
15654        .await;
15655    cx.update_editor(|editor, _, _| {
15656        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15657        {
15658            assert_eq!(
15659                completion_menu_entries(menu),
15660                &["first", "last", "second"],
15661                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15662            );
15663        } else {
15664            panic!("expected completion menu to be open");
15665        }
15666    });
15667}
15668
15669#[gpui::test]
15670async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15671    init_test(cx, |language_settings| {
15672        language_settings.defaults.completions = Some(CompletionSettingsContent {
15673            words: Some(WordsCompletionMode::Disabled),
15674            words_min_length: Some(0),
15675            lsp_insert_mode: Some(LspInsertMode::Insert),
15676            ..Default::default()
15677        });
15678    });
15679
15680    let mut cx = EditorLspTestContext::new_rust(
15681        lsp::ServerCapabilities {
15682            completion_provider: Some(lsp::CompletionOptions {
15683                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15684                ..lsp::CompletionOptions::default()
15685            }),
15686            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15687            ..lsp::ServerCapabilities::default()
15688        },
15689        cx,
15690    )
15691    .await;
15692
15693    let _completion_requests_handler =
15694        cx.lsp
15695            .server
15696            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15697                panic!("LSP completions should not be queried when dealing with word completions")
15698            });
15699
15700    cx.set_state(indoc! {"ˇ
15701        first
15702        last
15703        second
15704    "});
15705    cx.update_editor(|editor, window, cx| {
15706        editor.show_word_completions(&ShowWordCompletions, window, cx);
15707    });
15708    cx.executor().run_until_parked();
15709    cx.condition(|editor, _| editor.context_menu_visible())
15710        .await;
15711    cx.update_editor(|editor, _, _| {
15712        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15713        {
15714            assert_eq!(
15715                completion_menu_entries(menu),
15716                &["first", "last", "second"],
15717                "`ShowWordCompletions` action should show word completions"
15718            );
15719        } else {
15720            panic!("expected completion menu to be open");
15721        }
15722    });
15723
15724    cx.simulate_keystroke("l");
15725    cx.executor().run_until_parked();
15726    cx.condition(|editor, _| editor.context_menu_visible())
15727        .await;
15728    cx.update_editor(|editor, _, _| {
15729        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15730        {
15731            assert_eq!(
15732                completion_menu_entries(menu),
15733                &["last"],
15734                "After showing word completions, further editing should filter them and not query the LSP"
15735            );
15736        } else {
15737            panic!("expected completion menu to be open");
15738        }
15739    });
15740}
15741
15742#[gpui::test]
15743async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15744    init_test(cx, |language_settings| {
15745        language_settings.defaults.completions = Some(CompletionSettingsContent {
15746            words_min_length: Some(0),
15747            lsp: Some(false),
15748            lsp_insert_mode: Some(LspInsertMode::Insert),
15749            ..Default::default()
15750        });
15751    });
15752
15753    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15754
15755    cx.set_state(indoc! {"ˇ
15756        0_usize
15757        let
15758        33
15759        4.5f32
15760    "});
15761    cx.update_editor(|editor, window, cx| {
15762        editor.show_completions(&ShowCompletions, window, cx);
15763    });
15764    cx.executor().run_until_parked();
15765    cx.condition(|editor, _| editor.context_menu_visible())
15766        .await;
15767    cx.update_editor(|editor, window, cx| {
15768        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15769        {
15770            assert_eq!(
15771                completion_menu_entries(menu),
15772                &["let"],
15773                "With no digits in the completion query, no digits should be in the word completions"
15774            );
15775        } else {
15776            panic!("expected completion menu to be open");
15777        }
15778        editor.cancel(&Cancel, window, cx);
15779    });
15780
15781    cx.set_state(indoc! {"15782        0_usize
15783        let
15784        3
15785        33.35f32
15786    "});
15787    cx.update_editor(|editor, window, cx| {
15788        editor.show_completions(&ShowCompletions, window, cx);
15789    });
15790    cx.executor().run_until_parked();
15791    cx.condition(|editor, _| editor.context_menu_visible())
15792        .await;
15793    cx.update_editor(|editor, _, _| {
15794        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15795        {
15796            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15797                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15798        } else {
15799            panic!("expected completion menu to be open");
15800        }
15801    });
15802}
15803
15804#[gpui::test]
15805async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15806    init_test(cx, |language_settings| {
15807        language_settings.defaults.completions = Some(CompletionSettingsContent {
15808            words: Some(WordsCompletionMode::Enabled),
15809            words_min_length: Some(3),
15810            lsp_insert_mode: Some(LspInsertMode::Insert),
15811            ..Default::default()
15812        });
15813    });
15814
15815    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15816    cx.set_state(indoc! {"ˇ
15817        wow
15818        wowen
15819        wowser
15820    "});
15821    cx.simulate_keystroke("w");
15822    cx.executor().run_until_parked();
15823    cx.update_editor(|editor, _, _| {
15824        if editor.context_menu.borrow_mut().is_some() {
15825            panic!(
15826                "expected completion menu to be hidden, as words completion threshold is not met"
15827            );
15828        }
15829    });
15830
15831    cx.update_editor(|editor, window, cx| {
15832        editor.show_word_completions(&ShowWordCompletions, window, cx);
15833    });
15834    cx.executor().run_until_parked();
15835    cx.update_editor(|editor, window, cx| {
15836        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15837        {
15838            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");
15839        } else {
15840            panic!("expected completion menu to be open after the word completions are called with an action");
15841        }
15842
15843        editor.cancel(&Cancel, window, cx);
15844    });
15845    cx.update_editor(|editor, _, _| {
15846        if editor.context_menu.borrow_mut().is_some() {
15847            panic!("expected completion menu to be hidden after canceling");
15848        }
15849    });
15850
15851    cx.simulate_keystroke("o");
15852    cx.executor().run_until_parked();
15853    cx.update_editor(|editor, _, _| {
15854        if editor.context_menu.borrow_mut().is_some() {
15855            panic!(
15856                "expected completion menu to be hidden, as words completion threshold is not met still"
15857            );
15858        }
15859    });
15860
15861    cx.simulate_keystroke("w");
15862    cx.executor().run_until_parked();
15863    cx.update_editor(|editor, _, _| {
15864        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15865        {
15866            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15867        } else {
15868            panic!("expected completion menu to be open after the word completions threshold is met");
15869        }
15870    });
15871}
15872
15873#[gpui::test]
15874async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15875    init_test(cx, |language_settings| {
15876        language_settings.defaults.completions = Some(CompletionSettingsContent {
15877            words: Some(WordsCompletionMode::Enabled),
15878            words_min_length: Some(0),
15879            lsp_insert_mode: Some(LspInsertMode::Insert),
15880            ..Default::default()
15881        });
15882    });
15883
15884    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15885    cx.update_editor(|editor, _, _| {
15886        editor.disable_word_completions();
15887    });
15888    cx.set_state(indoc! {"ˇ
15889        wow
15890        wowen
15891        wowser
15892    "});
15893    cx.simulate_keystroke("w");
15894    cx.executor().run_until_parked();
15895    cx.update_editor(|editor, _, _| {
15896        if editor.context_menu.borrow_mut().is_some() {
15897            panic!(
15898                "expected completion menu to be hidden, as words completion are disabled for this editor"
15899            );
15900        }
15901    });
15902
15903    cx.update_editor(|editor, window, cx| {
15904        editor.show_word_completions(&ShowWordCompletions, window, cx);
15905    });
15906    cx.executor().run_until_parked();
15907    cx.update_editor(|editor, _, _| {
15908        if editor.context_menu.borrow_mut().is_some() {
15909            panic!(
15910                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15911            );
15912        }
15913    });
15914}
15915
15916#[gpui::test]
15917async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15918    init_test(cx, |language_settings| {
15919        language_settings.defaults.completions = Some(CompletionSettingsContent {
15920            words: Some(WordsCompletionMode::Disabled),
15921            words_min_length: Some(0),
15922            lsp_insert_mode: Some(LspInsertMode::Insert),
15923            ..Default::default()
15924        });
15925    });
15926
15927    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15928    cx.update_editor(|editor, _, _| {
15929        editor.set_completion_provider(None);
15930    });
15931    cx.set_state(indoc! {"ˇ
15932        wow
15933        wowen
15934        wowser
15935    "});
15936    cx.simulate_keystroke("w");
15937    cx.executor().run_until_parked();
15938    cx.update_editor(|editor, _, _| {
15939        if editor.context_menu.borrow_mut().is_some() {
15940            panic!("expected completion menu to be hidden, as disabled in settings");
15941        }
15942    });
15943}
15944
15945fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15946    let position = || lsp::Position {
15947        line: params.text_document_position.position.line,
15948        character: params.text_document_position.position.character,
15949    };
15950    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15951        range: lsp::Range {
15952            start: position(),
15953            end: position(),
15954        },
15955        new_text: text.to_string(),
15956    }))
15957}
15958
15959#[gpui::test]
15960async fn test_multiline_completion(cx: &mut TestAppContext) {
15961    init_test(cx, |_| {});
15962
15963    let fs = FakeFs::new(cx.executor());
15964    fs.insert_tree(
15965        path!("/a"),
15966        json!({
15967            "main.ts": "a",
15968        }),
15969    )
15970    .await;
15971
15972    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15973    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15974    let typescript_language = Arc::new(Language::new(
15975        LanguageConfig {
15976            name: "TypeScript".into(),
15977            matcher: LanguageMatcher {
15978                path_suffixes: vec!["ts".to_string()],
15979                ..LanguageMatcher::default()
15980            },
15981            line_comments: vec!["// ".into()],
15982            ..LanguageConfig::default()
15983        },
15984        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15985    ));
15986    language_registry.add(typescript_language.clone());
15987    let mut fake_servers = language_registry.register_fake_lsp(
15988        "TypeScript",
15989        FakeLspAdapter {
15990            capabilities: lsp::ServerCapabilities {
15991                completion_provider: Some(lsp::CompletionOptions {
15992                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15993                    ..lsp::CompletionOptions::default()
15994                }),
15995                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15996                ..lsp::ServerCapabilities::default()
15997            },
15998            // Emulate vtsls label generation
15999            label_for_completion: Some(Box::new(|item, _| {
16000                let text = if let Some(description) = item
16001                    .label_details
16002                    .as_ref()
16003                    .and_then(|label_details| label_details.description.as_ref())
16004                {
16005                    format!("{} {}", item.label, description)
16006                } else if let Some(detail) = &item.detail {
16007                    format!("{} {}", item.label, detail)
16008                } else {
16009                    item.label.clone()
16010                };
16011                Some(language::CodeLabel::plain(text, None))
16012            })),
16013            ..FakeLspAdapter::default()
16014        },
16015    );
16016    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16017    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16018    let worktree_id = workspace
16019        .update(cx, |workspace, _window, cx| {
16020            workspace.project().update(cx, |project, cx| {
16021                project.worktrees(cx).next().unwrap().read(cx).id()
16022            })
16023        })
16024        .unwrap();
16025    let _buffer = project
16026        .update(cx, |project, cx| {
16027            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
16028        })
16029        .await
16030        .unwrap();
16031    let editor = workspace
16032        .update(cx, |workspace, window, cx| {
16033            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
16034        })
16035        .unwrap()
16036        .await
16037        .unwrap()
16038        .downcast::<Editor>()
16039        .unwrap();
16040    let fake_server = fake_servers.next().await.unwrap();
16041    cx.run_until_parked();
16042
16043    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
16044    let multiline_label_2 = "a\nb\nc\n";
16045    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
16046    let multiline_description = "d\ne\nf\n";
16047    let multiline_detail_2 = "g\nh\ni\n";
16048
16049    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
16050        move |params, _| async move {
16051            Ok(Some(lsp::CompletionResponse::Array(vec![
16052                lsp::CompletionItem {
16053                    label: multiline_label.to_string(),
16054                    text_edit: gen_text_edit(&params, "new_text_1"),
16055                    ..lsp::CompletionItem::default()
16056                },
16057                lsp::CompletionItem {
16058                    label: "single line label 1".to_string(),
16059                    detail: Some(multiline_detail.to_string()),
16060                    text_edit: gen_text_edit(&params, "new_text_2"),
16061                    ..lsp::CompletionItem::default()
16062                },
16063                lsp::CompletionItem {
16064                    label: "single line label 2".to_string(),
16065                    label_details: Some(lsp::CompletionItemLabelDetails {
16066                        description: Some(multiline_description.to_string()),
16067                        detail: None,
16068                    }),
16069                    text_edit: gen_text_edit(&params, "new_text_2"),
16070                    ..lsp::CompletionItem::default()
16071                },
16072                lsp::CompletionItem {
16073                    label: multiline_label_2.to_string(),
16074                    detail: Some(multiline_detail_2.to_string()),
16075                    text_edit: gen_text_edit(&params, "new_text_3"),
16076                    ..lsp::CompletionItem::default()
16077                },
16078                lsp::CompletionItem {
16079                    label: "Label with many     spaces and \t but without newlines".to_string(),
16080                    detail: Some(
16081                        "Details with many     spaces and \t but without newlines".to_string(),
16082                    ),
16083                    text_edit: gen_text_edit(&params, "new_text_4"),
16084                    ..lsp::CompletionItem::default()
16085                },
16086            ])))
16087        },
16088    );
16089
16090    editor.update_in(cx, |editor, window, cx| {
16091        cx.focus_self(window);
16092        editor.move_to_end(&MoveToEnd, window, cx);
16093        editor.handle_input(".", window, cx);
16094    });
16095    cx.run_until_parked();
16096    completion_handle.next().await.unwrap();
16097
16098    editor.update(cx, |editor, _| {
16099        assert!(editor.context_menu_visible());
16100        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16101        {
16102            let completion_labels = menu
16103                .completions
16104                .borrow()
16105                .iter()
16106                .map(|c| c.label.text.clone())
16107                .collect::<Vec<_>>();
16108            assert_eq!(
16109                completion_labels,
16110                &[
16111                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
16112                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
16113                    "single line label 2 d e f ",
16114                    "a b c g h i ",
16115                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
16116                ],
16117                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
16118            );
16119
16120            for completion in menu
16121                .completions
16122                .borrow()
16123                .iter() {
16124                    assert_eq!(
16125                        completion.label.filter_range,
16126                        0..completion.label.text.len(),
16127                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
16128                    );
16129                }
16130        } else {
16131            panic!("expected completion menu to be open");
16132        }
16133    });
16134}
16135
16136#[gpui::test]
16137async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
16138    init_test(cx, |_| {});
16139    let mut cx = EditorLspTestContext::new_rust(
16140        lsp::ServerCapabilities {
16141            completion_provider: Some(lsp::CompletionOptions {
16142                trigger_characters: Some(vec![".".to_string()]),
16143                ..Default::default()
16144            }),
16145            ..Default::default()
16146        },
16147        cx,
16148    )
16149    .await;
16150    cx.lsp
16151        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16152            Ok(Some(lsp::CompletionResponse::Array(vec![
16153                lsp::CompletionItem {
16154                    label: "first".into(),
16155                    ..Default::default()
16156                },
16157                lsp::CompletionItem {
16158                    label: "last".into(),
16159                    ..Default::default()
16160                },
16161            ])))
16162        });
16163    cx.set_state("variableˇ");
16164    cx.simulate_keystroke(".");
16165    cx.executor().run_until_parked();
16166
16167    cx.update_editor(|editor, _, _| {
16168        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16169        {
16170            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
16171        } else {
16172            panic!("expected completion menu to be open");
16173        }
16174    });
16175
16176    cx.update_editor(|editor, window, cx| {
16177        editor.move_page_down(&MovePageDown::default(), window, cx);
16178        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16179        {
16180            assert!(
16181                menu.selected_item == 1,
16182                "expected PageDown to select the last item from the context menu"
16183            );
16184        } else {
16185            panic!("expected completion menu to stay open after PageDown");
16186        }
16187    });
16188
16189    cx.update_editor(|editor, window, cx| {
16190        editor.move_page_up(&MovePageUp::default(), window, cx);
16191        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16192        {
16193            assert!(
16194                menu.selected_item == 0,
16195                "expected PageUp to select the first item from the context menu"
16196            );
16197        } else {
16198            panic!("expected completion menu to stay open after PageUp");
16199        }
16200    });
16201}
16202
16203#[gpui::test]
16204async fn test_as_is_completions(cx: &mut TestAppContext) {
16205    init_test(cx, |_| {});
16206    let mut cx = EditorLspTestContext::new_rust(
16207        lsp::ServerCapabilities {
16208            completion_provider: Some(lsp::CompletionOptions {
16209                ..Default::default()
16210            }),
16211            ..Default::default()
16212        },
16213        cx,
16214    )
16215    .await;
16216    cx.lsp
16217        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16218            Ok(Some(lsp::CompletionResponse::Array(vec![
16219                lsp::CompletionItem {
16220                    label: "unsafe".into(),
16221                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16222                        range: lsp::Range {
16223                            start: lsp::Position {
16224                                line: 1,
16225                                character: 2,
16226                            },
16227                            end: lsp::Position {
16228                                line: 1,
16229                                character: 3,
16230                            },
16231                        },
16232                        new_text: "unsafe".to_string(),
16233                    })),
16234                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
16235                    ..Default::default()
16236                },
16237            ])))
16238        });
16239    cx.set_state("fn a() {}\n");
16240    cx.executor().run_until_parked();
16241    cx.update_editor(|editor, window, cx| {
16242        editor.trigger_completion_on_input("n", true, window, cx)
16243    });
16244    cx.executor().run_until_parked();
16245
16246    cx.update_editor(|editor, window, cx| {
16247        editor.confirm_completion(&Default::default(), window, cx)
16248    });
16249    cx.executor().run_until_parked();
16250    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
16251}
16252
16253#[gpui::test]
16254async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
16255    init_test(cx, |_| {});
16256    let language =
16257        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
16258    let mut cx = EditorLspTestContext::new(
16259        language,
16260        lsp::ServerCapabilities {
16261            completion_provider: Some(lsp::CompletionOptions {
16262                ..lsp::CompletionOptions::default()
16263            }),
16264            ..lsp::ServerCapabilities::default()
16265        },
16266        cx,
16267    )
16268    .await;
16269
16270    cx.set_state(
16271        "#ifndef BAR_H
16272#define BAR_H
16273
16274#include <stdbool.h>
16275
16276int fn_branch(bool do_branch1, bool do_branch2);
16277
16278#endif // BAR_H
16279ˇ",
16280    );
16281    cx.executor().run_until_parked();
16282    cx.update_editor(|editor, window, cx| {
16283        editor.handle_input("#", window, cx);
16284    });
16285    cx.executor().run_until_parked();
16286    cx.update_editor(|editor, window, cx| {
16287        editor.handle_input("i", window, cx);
16288    });
16289    cx.executor().run_until_parked();
16290    cx.update_editor(|editor, window, cx| {
16291        editor.handle_input("n", window, cx);
16292    });
16293    cx.executor().run_until_parked();
16294    cx.assert_editor_state(
16295        "#ifndef BAR_H
16296#define BAR_H
16297
16298#include <stdbool.h>
16299
16300int fn_branch(bool do_branch1, bool do_branch2);
16301
16302#endif // BAR_H
16303#inˇ",
16304    );
16305
16306    cx.lsp
16307        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16308            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16309                is_incomplete: false,
16310                item_defaults: None,
16311                items: vec![lsp::CompletionItem {
16312                    kind: Some(lsp::CompletionItemKind::SNIPPET),
16313                    label_details: Some(lsp::CompletionItemLabelDetails {
16314                        detail: Some("header".to_string()),
16315                        description: None,
16316                    }),
16317                    label: " include".to_string(),
16318                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16319                        range: lsp::Range {
16320                            start: lsp::Position {
16321                                line: 8,
16322                                character: 1,
16323                            },
16324                            end: lsp::Position {
16325                                line: 8,
16326                                character: 1,
16327                            },
16328                        },
16329                        new_text: "include \"$0\"".to_string(),
16330                    })),
16331                    sort_text: Some("40b67681include".to_string()),
16332                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16333                    filter_text: Some("include".to_string()),
16334                    insert_text: Some("include \"$0\"".to_string()),
16335                    ..lsp::CompletionItem::default()
16336                }],
16337            })))
16338        });
16339    cx.update_editor(|editor, window, cx| {
16340        editor.show_completions(&ShowCompletions, window, cx);
16341    });
16342    cx.executor().run_until_parked();
16343    cx.update_editor(|editor, window, cx| {
16344        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16345    });
16346    cx.executor().run_until_parked();
16347    cx.assert_editor_state(
16348        "#ifndef BAR_H
16349#define BAR_H
16350
16351#include <stdbool.h>
16352
16353int fn_branch(bool do_branch1, bool do_branch2);
16354
16355#endif // BAR_H
16356#include \"ˇ\"",
16357    );
16358
16359    cx.lsp
16360        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16361            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16362                is_incomplete: true,
16363                item_defaults: None,
16364                items: vec![lsp::CompletionItem {
16365                    kind: Some(lsp::CompletionItemKind::FILE),
16366                    label: "AGL/".to_string(),
16367                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16368                        range: lsp::Range {
16369                            start: lsp::Position {
16370                                line: 8,
16371                                character: 10,
16372                            },
16373                            end: lsp::Position {
16374                                line: 8,
16375                                character: 11,
16376                            },
16377                        },
16378                        new_text: "AGL/".to_string(),
16379                    })),
16380                    sort_text: Some("40b67681AGL/".to_string()),
16381                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16382                    filter_text: Some("AGL/".to_string()),
16383                    insert_text: Some("AGL/".to_string()),
16384                    ..lsp::CompletionItem::default()
16385                }],
16386            })))
16387        });
16388    cx.update_editor(|editor, window, cx| {
16389        editor.show_completions(&ShowCompletions, window, cx);
16390    });
16391    cx.executor().run_until_parked();
16392    cx.update_editor(|editor, window, cx| {
16393        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16394    });
16395    cx.executor().run_until_parked();
16396    cx.assert_editor_state(
16397        r##"#ifndef BAR_H
16398#define BAR_H
16399
16400#include <stdbool.h>
16401
16402int fn_branch(bool do_branch1, bool do_branch2);
16403
16404#endif // BAR_H
16405#include "AGL/ˇ"##,
16406    );
16407
16408    cx.update_editor(|editor, window, cx| {
16409        editor.handle_input("\"", window, cx);
16410    });
16411    cx.executor().run_until_parked();
16412    cx.assert_editor_state(
16413        r##"#ifndef BAR_H
16414#define BAR_H
16415
16416#include <stdbool.h>
16417
16418int fn_branch(bool do_branch1, bool do_branch2);
16419
16420#endif // BAR_H
16421#include "AGL/"ˇ"##,
16422    );
16423}
16424
16425#[gpui::test]
16426async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
16427    init_test(cx, |_| {});
16428
16429    let mut cx = EditorLspTestContext::new_rust(
16430        lsp::ServerCapabilities {
16431            completion_provider: Some(lsp::CompletionOptions {
16432                trigger_characters: Some(vec![".".to_string()]),
16433                resolve_provider: Some(true),
16434                ..Default::default()
16435            }),
16436            ..Default::default()
16437        },
16438        cx,
16439    )
16440    .await;
16441
16442    cx.set_state("fn main() { let a = 2ˇ; }");
16443    cx.simulate_keystroke(".");
16444    let completion_item = lsp::CompletionItem {
16445        label: "Some".into(),
16446        kind: Some(lsp::CompletionItemKind::SNIPPET),
16447        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16448        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16449            kind: lsp::MarkupKind::Markdown,
16450            value: "```rust\nSome(2)\n```".to_string(),
16451        })),
16452        deprecated: Some(false),
16453        sort_text: Some("Some".to_string()),
16454        filter_text: Some("Some".to_string()),
16455        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16456        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16457            range: lsp::Range {
16458                start: lsp::Position {
16459                    line: 0,
16460                    character: 22,
16461                },
16462                end: lsp::Position {
16463                    line: 0,
16464                    character: 22,
16465                },
16466            },
16467            new_text: "Some(2)".to_string(),
16468        })),
16469        additional_text_edits: Some(vec![lsp::TextEdit {
16470            range: lsp::Range {
16471                start: lsp::Position {
16472                    line: 0,
16473                    character: 20,
16474                },
16475                end: lsp::Position {
16476                    line: 0,
16477                    character: 22,
16478                },
16479            },
16480            new_text: "".to_string(),
16481        }]),
16482        ..Default::default()
16483    };
16484
16485    let closure_completion_item = completion_item.clone();
16486    let counter = Arc::new(AtomicUsize::new(0));
16487    let counter_clone = counter.clone();
16488    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16489        let task_completion_item = closure_completion_item.clone();
16490        counter_clone.fetch_add(1, atomic::Ordering::Release);
16491        async move {
16492            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16493                is_incomplete: true,
16494                item_defaults: None,
16495                items: vec![task_completion_item],
16496            })))
16497        }
16498    });
16499
16500    cx.condition(|editor, _| editor.context_menu_visible())
16501        .await;
16502    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16503    assert!(request.next().await.is_some());
16504    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16505
16506    cx.simulate_keystrokes("S o m");
16507    cx.condition(|editor, _| editor.context_menu_visible())
16508        .await;
16509    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16510    assert!(request.next().await.is_some());
16511    assert!(request.next().await.is_some());
16512    assert!(request.next().await.is_some());
16513    request.close();
16514    assert!(request.next().await.is_none());
16515    assert_eq!(
16516        counter.load(atomic::Ordering::Acquire),
16517        4,
16518        "With the completions menu open, only one LSP request should happen per input"
16519    );
16520}
16521
16522#[gpui::test]
16523async fn test_toggle_comment(cx: &mut TestAppContext) {
16524    init_test(cx, |_| {});
16525    let mut cx = EditorTestContext::new(cx).await;
16526    let language = Arc::new(Language::new(
16527        LanguageConfig {
16528            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16529            ..Default::default()
16530        },
16531        Some(tree_sitter_rust::LANGUAGE.into()),
16532    ));
16533    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16534
16535    // If multiple selections intersect a line, the line is only toggled once.
16536    cx.set_state(indoc! {"
16537        fn a() {
16538            «//b();
16539            ˇ»// «c();
16540            //ˇ»  d();
16541        }
16542    "});
16543
16544    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16545
16546    cx.assert_editor_state(indoc! {"
16547        fn a() {
16548            «b();
16549            ˇ»«c();
16550            ˇ» d();
16551        }
16552    "});
16553
16554    // The comment prefix is inserted at the same column for every line in a
16555    // selection.
16556    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16557
16558    cx.assert_editor_state(indoc! {"
16559        fn a() {
16560            // «b();
16561            ˇ»// «c();
16562            ˇ» // d();
16563        }
16564    "});
16565
16566    // If a selection ends at the beginning of a line, that line is not toggled.
16567    cx.set_selections_state(indoc! {"
16568        fn a() {
16569            // b();
16570            «// c();
16571        ˇ»     // d();
16572        }
16573    "});
16574
16575    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16576
16577    cx.assert_editor_state(indoc! {"
16578        fn a() {
16579            // b();
16580            «c();
16581        ˇ»     // d();
16582        }
16583    "});
16584
16585    // If a selection span a single line and is empty, the line is toggled.
16586    cx.set_state(indoc! {"
16587        fn a() {
16588            a();
16589            b();
16590        ˇ
16591        }
16592    "});
16593
16594    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16595
16596    cx.assert_editor_state(indoc! {"
16597        fn a() {
16598            a();
16599            b();
16600        //•ˇ
16601        }
16602    "});
16603
16604    // If a selection span multiple lines, empty lines are not toggled.
16605    cx.set_state(indoc! {"
16606        fn a() {
16607            «a();
16608
16609            c();ˇ»
16610        }
16611    "});
16612
16613    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16614
16615    cx.assert_editor_state(indoc! {"
16616        fn a() {
16617            // «a();
16618
16619            // c();ˇ»
16620        }
16621    "});
16622
16623    // If a selection includes multiple comment prefixes, all lines are uncommented.
16624    cx.set_state(indoc! {"
16625        fn a() {
16626            «// a();
16627            /// b();
16628            //! c();ˇ»
16629        }
16630    "});
16631
16632    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16633
16634    cx.assert_editor_state(indoc! {"
16635        fn a() {
16636            «a();
16637            b();
16638            c();ˇ»
16639        }
16640    "});
16641}
16642
16643#[gpui::test]
16644async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16645    init_test(cx, |_| {});
16646    let mut cx = EditorTestContext::new(cx).await;
16647    let language = Arc::new(Language::new(
16648        LanguageConfig {
16649            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16650            ..Default::default()
16651        },
16652        Some(tree_sitter_rust::LANGUAGE.into()),
16653    ));
16654    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16655
16656    let toggle_comments = &ToggleComments {
16657        advance_downwards: false,
16658        ignore_indent: true,
16659    };
16660
16661    // If multiple selections intersect a line, the line is only toggled once.
16662    cx.set_state(indoc! {"
16663        fn a() {
16664        //    «b();
16665        //    c();
16666        //    ˇ» d();
16667        }
16668    "});
16669
16670    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16671
16672    cx.assert_editor_state(indoc! {"
16673        fn a() {
16674            «b();
16675            c();
16676            ˇ» d();
16677        }
16678    "});
16679
16680    // The comment prefix is inserted at the beginning of each line
16681    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16682
16683    cx.assert_editor_state(indoc! {"
16684        fn a() {
16685        //    «b();
16686        //    c();
16687        //    ˇ» d();
16688        }
16689    "});
16690
16691    // If a selection ends at the beginning of a line, that line is not toggled.
16692    cx.set_selections_state(indoc! {"
16693        fn a() {
16694        //    b();
16695        //    «c();
16696        ˇ»//     d();
16697        }
16698    "});
16699
16700    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16701
16702    cx.assert_editor_state(indoc! {"
16703        fn a() {
16704        //    b();
16705            «c();
16706        ˇ»//     d();
16707        }
16708    "});
16709
16710    // If a selection span a single line and is empty, the line is toggled.
16711    cx.set_state(indoc! {"
16712        fn a() {
16713            a();
16714            b();
16715        ˇ
16716        }
16717    "});
16718
16719    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16720
16721    cx.assert_editor_state(indoc! {"
16722        fn a() {
16723            a();
16724            b();
16725        //ˇ
16726        }
16727    "});
16728
16729    // If a selection span multiple lines, empty lines are not toggled.
16730    cx.set_state(indoc! {"
16731        fn a() {
16732            «a();
16733
16734            c();ˇ»
16735        }
16736    "});
16737
16738    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16739
16740    cx.assert_editor_state(indoc! {"
16741        fn a() {
16742        //    «a();
16743
16744        //    c();ˇ»
16745        }
16746    "});
16747
16748    // If a selection includes multiple comment prefixes, all lines are uncommented.
16749    cx.set_state(indoc! {"
16750        fn a() {
16751        //    «a();
16752        ///    b();
16753        //!    c();ˇ»
16754        }
16755    "});
16756
16757    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16758
16759    cx.assert_editor_state(indoc! {"
16760        fn a() {
16761            «a();
16762            b();
16763            c();ˇ»
16764        }
16765    "});
16766}
16767
16768#[gpui::test]
16769async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16770    init_test(cx, |_| {});
16771
16772    let language = Arc::new(Language::new(
16773        LanguageConfig {
16774            line_comments: vec!["// ".into()],
16775            ..Default::default()
16776        },
16777        Some(tree_sitter_rust::LANGUAGE.into()),
16778    ));
16779
16780    let mut cx = EditorTestContext::new(cx).await;
16781
16782    cx.language_registry().add(language.clone());
16783    cx.update_buffer(|buffer, cx| {
16784        buffer.set_language(Some(language), cx);
16785    });
16786
16787    let toggle_comments = &ToggleComments {
16788        advance_downwards: true,
16789        ignore_indent: false,
16790    };
16791
16792    // Single cursor on one line -> advance
16793    // Cursor moves horizontally 3 characters as well on non-blank line
16794    cx.set_state(indoc!(
16795        "fn a() {
16796             ˇdog();
16797             cat();
16798        }"
16799    ));
16800    cx.update_editor(|editor, window, cx| {
16801        editor.toggle_comments(toggle_comments, window, cx);
16802    });
16803    cx.assert_editor_state(indoc!(
16804        "fn a() {
16805             // dog();
16806             catˇ();
16807        }"
16808    ));
16809
16810    // Single selection on one line -> don't advance
16811    cx.set_state(indoc!(
16812        "fn a() {
16813             «dog()ˇ»;
16814             cat();
16815        }"
16816    ));
16817    cx.update_editor(|editor, window, cx| {
16818        editor.toggle_comments(toggle_comments, window, cx);
16819    });
16820    cx.assert_editor_state(indoc!(
16821        "fn a() {
16822             // «dog()ˇ»;
16823             cat();
16824        }"
16825    ));
16826
16827    // Multiple cursors on one line -> advance
16828    cx.set_state(indoc!(
16829        "fn a() {
16830             ˇdˇog();
16831             cat();
16832        }"
16833    ));
16834    cx.update_editor(|editor, window, cx| {
16835        editor.toggle_comments(toggle_comments, window, cx);
16836    });
16837    cx.assert_editor_state(indoc!(
16838        "fn a() {
16839             // dog();
16840             catˇ(ˇ);
16841        }"
16842    ));
16843
16844    // Multiple cursors on one line, with selection -> don't advance
16845    cx.set_state(indoc!(
16846        "fn a() {
16847             ˇdˇog«()ˇ»;
16848             cat();
16849        }"
16850    ));
16851    cx.update_editor(|editor, window, cx| {
16852        editor.toggle_comments(toggle_comments, window, cx);
16853    });
16854    cx.assert_editor_state(indoc!(
16855        "fn a() {
16856             // ˇdˇog«()ˇ»;
16857             cat();
16858        }"
16859    ));
16860
16861    // Single cursor on one line -> advance
16862    // Cursor moves to column 0 on blank line
16863    cx.set_state(indoc!(
16864        "fn a() {
16865             ˇdog();
16866
16867             cat();
16868        }"
16869    ));
16870    cx.update_editor(|editor, window, cx| {
16871        editor.toggle_comments(toggle_comments, window, cx);
16872    });
16873    cx.assert_editor_state(indoc!(
16874        "fn a() {
16875             // dog();
16876        ˇ
16877             cat();
16878        }"
16879    ));
16880
16881    // Single cursor on one line -> advance
16882    // Cursor starts and ends at column 0
16883    cx.set_state(indoc!(
16884        "fn a() {
16885         ˇ    dog();
16886             cat();
16887        }"
16888    ));
16889    cx.update_editor(|editor, window, cx| {
16890        editor.toggle_comments(toggle_comments, window, cx);
16891    });
16892    cx.assert_editor_state(indoc!(
16893        "fn a() {
16894             // dog();
16895         ˇ    cat();
16896        }"
16897    ));
16898}
16899
16900#[gpui::test]
16901async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16902    init_test(cx, |_| {});
16903
16904    let mut cx = EditorTestContext::new(cx).await;
16905
16906    let html_language = Arc::new(
16907        Language::new(
16908            LanguageConfig {
16909                name: "HTML".into(),
16910                block_comment: Some(BlockCommentConfig {
16911                    start: "<!-- ".into(),
16912                    prefix: "".into(),
16913                    end: " -->".into(),
16914                    tab_size: 0,
16915                }),
16916                ..Default::default()
16917            },
16918            Some(tree_sitter_html::LANGUAGE.into()),
16919        )
16920        .with_injection_query(
16921            r#"
16922            (script_element
16923                (raw_text) @injection.content
16924                (#set! injection.language "javascript"))
16925            "#,
16926        )
16927        .unwrap(),
16928    );
16929
16930    let javascript_language = Arc::new(Language::new(
16931        LanguageConfig {
16932            name: "JavaScript".into(),
16933            line_comments: vec!["// ".into()],
16934            ..Default::default()
16935        },
16936        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16937    ));
16938
16939    cx.language_registry().add(html_language.clone());
16940    cx.language_registry().add(javascript_language);
16941    cx.update_buffer(|buffer, cx| {
16942        buffer.set_language(Some(html_language), cx);
16943    });
16944
16945    // Toggle comments for empty selections
16946    cx.set_state(
16947        &r#"
16948            <p>A</p>ˇ
16949            <p>B</p>ˇ
16950            <p>C</p>ˇ
16951        "#
16952        .unindent(),
16953    );
16954    cx.update_editor(|editor, window, cx| {
16955        editor.toggle_comments(&ToggleComments::default(), window, cx)
16956    });
16957    cx.assert_editor_state(
16958        &r#"
16959            <!-- <p>A</p>ˇ -->
16960            <!-- <p>B</p>ˇ -->
16961            <!-- <p>C</p>ˇ -->
16962        "#
16963        .unindent(),
16964    );
16965    cx.update_editor(|editor, window, cx| {
16966        editor.toggle_comments(&ToggleComments::default(), window, cx)
16967    });
16968    cx.assert_editor_state(
16969        &r#"
16970            <p>A</p>ˇ
16971            <p>B</p>ˇ
16972            <p>C</p>ˇ
16973        "#
16974        .unindent(),
16975    );
16976
16977    // Toggle comments for mixture of empty and non-empty selections, where
16978    // multiple selections occupy a given line.
16979    cx.set_state(
16980        &r#"
16981            <p>A«</p>
16982            <p>ˇ»B</p>ˇ
16983            <p>C«</p>
16984            <p>ˇ»D</p>ˇ
16985        "#
16986        .unindent(),
16987    );
16988
16989    cx.update_editor(|editor, window, cx| {
16990        editor.toggle_comments(&ToggleComments::default(), window, cx)
16991    });
16992    cx.assert_editor_state(
16993        &r#"
16994            <!-- <p>A«</p>
16995            <p>ˇ»B</p>ˇ -->
16996            <!-- <p>C«</p>
16997            <p>ˇ»D</p>ˇ -->
16998        "#
16999        .unindent(),
17000    );
17001    cx.update_editor(|editor, window, cx| {
17002        editor.toggle_comments(&ToggleComments::default(), window, cx)
17003    });
17004    cx.assert_editor_state(
17005        &r#"
17006            <p>A«</p>
17007            <p>ˇ»B</p>ˇ
17008            <p>C«</p>
17009            <p>ˇ»D</p>ˇ
17010        "#
17011        .unindent(),
17012    );
17013
17014    // Toggle comments when different languages are active for different
17015    // selections.
17016    cx.set_state(
17017        &r#"
17018            ˇ<script>
17019                ˇvar x = new Y();
17020            ˇ</script>
17021        "#
17022        .unindent(),
17023    );
17024    cx.executor().run_until_parked();
17025    cx.update_editor(|editor, window, cx| {
17026        editor.toggle_comments(&ToggleComments::default(), window, cx)
17027    });
17028    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
17029    // Uncommenting and commenting from this position brings in even more wrong artifacts.
17030    cx.assert_editor_state(
17031        &r#"
17032            <!-- ˇ<script> -->
17033                // ˇvar x = new Y();
17034            <!-- ˇ</script> -->
17035        "#
17036        .unindent(),
17037    );
17038}
17039
17040#[gpui::test]
17041fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
17042    init_test(cx, |_| {});
17043
17044    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17045    let multibuffer = cx.new(|cx| {
17046        let mut multibuffer = MultiBuffer::new(ReadWrite);
17047        multibuffer.push_excerpts(
17048            buffer.clone(),
17049            [
17050                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
17051                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
17052            ],
17053            cx,
17054        );
17055        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
17056        multibuffer
17057    });
17058
17059    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
17060    editor.update_in(cx, |editor, window, cx| {
17061        assert_eq!(editor.text(cx), "aaaa\nbbbb");
17062        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17063            s.select_ranges([
17064                Point::new(0, 0)..Point::new(0, 0),
17065                Point::new(1, 0)..Point::new(1, 0),
17066            ])
17067        });
17068
17069        editor.handle_input("X", window, cx);
17070        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
17071        assert_eq!(
17072            editor.selections.ranges(&editor.display_snapshot(cx)),
17073            [
17074                Point::new(0, 1)..Point::new(0, 1),
17075                Point::new(1, 1)..Point::new(1, 1),
17076            ]
17077        );
17078
17079        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
17080        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17081            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
17082        });
17083        editor.backspace(&Default::default(), window, cx);
17084        assert_eq!(editor.text(cx), "Xa\nbbb");
17085        assert_eq!(
17086            editor.selections.ranges(&editor.display_snapshot(cx)),
17087            [Point::new(1, 0)..Point::new(1, 0)]
17088        );
17089
17090        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17091            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
17092        });
17093        editor.backspace(&Default::default(), window, cx);
17094        assert_eq!(editor.text(cx), "X\nbb");
17095        assert_eq!(
17096            editor.selections.ranges(&editor.display_snapshot(cx)),
17097            [Point::new(0, 1)..Point::new(0, 1)]
17098        );
17099    });
17100}
17101
17102#[gpui::test]
17103fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
17104    init_test(cx, |_| {});
17105
17106    let markers = vec![('[', ']').into(), ('(', ')').into()];
17107    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
17108        indoc! {"
17109            [aaaa
17110            (bbbb]
17111            cccc)",
17112        },
17113        markers.clone(),
17114    );
17115    let excerpt_ranges = markers.into_iter().map(|marker| {
17116        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
17117        ExcerptRange::new(context)
17118    });
17119    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
17120    let multibuffer = cx.new(|cx| {
17121        let mut multibuffer = MultiBuffer::new(ReadWrite);
17122        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
17123        multibuffer
17124    });
17125
17126    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
17127    editor.update_in(cx, |editor, window, cx| {
17128        let (expected_text, selection_ranges) = marked_text_ranges(
17129            indoc! {"
17130                aaaa
17131                bˇbbb
17132                bˇbbˇb
17133                cccc"
17134            },
17135            true,
17136        );
17137        assert_eq!(editor.text(cx), expected_text);
17138        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17139            s.select_ranges(
17140                selection_ranges
17141                    .iter()
17142                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
17143            )
17144        });
17145
17146        editor.handle_input("X", window, cx);
17147
17148        let (expected_text, expected_selections) = marked_text_ranges(
17149            indoc! {"
17150                aaaa
17151                bXˇbbXb
17152                bXˇbbXˇb
17153                cccc"
17154            },
17155            false,
17156        );
17157        assert_eq!(editor.text(cx), expected_text);
17158        assert_eq!(
17159            editor.selections.ranges(&editor.display_snapshot(cx)),
17160            expected_selections
17161                .iter()
17162                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17163                .collect::<Vec<_>>()
17164        );
17165
17166        editor.newline(&Newline, window, cx);
17167        let (expected_text, expected_selections) = marked_text_ranges(
17168            indoc! {"
17169                aaaa
17170                bX
17171                ˇbbX
17172                b
17173                bX
17174                ˇbbX
17175                ˇb
17176                cccc"
17177            },
17178            false,
17179        );
17180        assert_eq!(editor.text(cx), expected_text);
17181        assert_eq!(
17182            editor.selections.ranges(&editor.display_snapshot(cx)),
17183            expected_selections
17184                .iter()
17185                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
17186                .collect::<Vec<_>>()
17187        );
17188    });
17189}
17190
17191#[gpui::test]
17192fn test_refresh_selections(cx: &mut TestAppContext) {
17193    init_test(cx, |_| {});
17194
17195    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17196    let mut excerpt1_id = None;
17197    let multibuffer = cx.new(|cx| {
17198        let mut multibuffer = MultiBuffer::new(ReadWrite);
17199        excerpt1_id = multibuffer
17200            .push_excerpts(
17201                buffer.clone(),
17202                [
17203                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17204                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17205                ],
17206                cx,
17207            )
17208            .into_iter()
17209            .next();
17210        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17211        multibuffer
17212    });
17213
17214    let editor = cx.add_window(|window, cx| {
17215        let mut editor = build_editor(multibuffer.clone(), window, cx);
17216        let snapshot = editor.snapshot(window, cx);
17217        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17218            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
17219        });
17220        editor.begin_selection(
17221            Point::new(2, 1).to_display_point(&snapshot),
17222            true,
17223            1,
17224            window,
17225            cx,
17226        );
17227        assert_eq!(
17228            editor.selections.ranges(&editor.display_snapshot(cx)),
17229            [
17230                Point::new(1, 3)..Point::new(1, 3),
17231                Point::new(2, 1)..Point::new(2, 1),
17232            ]
17233        );
17234        editor
17235    });
17236
17237    // Refreshing selections is a no-op when excerpts haven't changed.
17238    _ = editor.update(cx, |editor, window, cx| {
17239        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17240        assert_eq!(
17241            editor.selections.ranges(&editor.display_snapshot(cx)),
17242            [
17243                Point::new(1, 3)..Point::new(1, 3),
17244                Point::new(2, 1)..Point::new(2, 1),
17245            ]
17246        );
17247    });
17248
17249    multibuffer.update(cx, |multibuffer, cx| {
17250        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17251    });
17252    _ = editor.update(cx, |editor, window, cx| {
17253        // Removing an excerpt causes the first selection to become degenerate.
17254        assert_eq!(
17255            editor.selections.ranges(&editor.display_snapshot(cx)),
17256            [
17257                Point::new(0, 0)..Point::new(0, 0),
17258                Point::new(0, 1)..Point::new(0, 1)
17259            ]
17260        );
17261
17262        // Refreshing selections will relocate the first selection to the original buffer
17263        // location.
17264        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17265        assert_eq!(
17266            editor.selections.ranges(&editor.display_snapshot(cx)),
17267            [
17268                Point::new(0, 1)..Point::new(0, 1),
17269                Point::new(0, 3)..Point::new(0, 3)
17270            ]
17271        );
17272        assert!(editor.selections.pending_anchor().is_some());
17273    });
17274}
17275
17276#[gpui::test]
17277fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
17278    init_test(cx, |_| {});
17279
17280    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17281    let mut excerpt1_id = None;
17282    let multibuffer = cx.new(|cx| {
17283        let mut multibuffer = MultiBuffer::new(ReadWrite);
17284        excerpt1_id = multibuffer
17285            .push_excerpts(
17286                buffer.clone(),
17287                [
17288                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17289                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17290                ],
17291                cx,
17292            )
17293            .into_iter()
17294            .next();
17295        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17296        multibuffer
17297    });
17298
17299    let editor = cx.add_window(|window, cx| {
17300        let mut editor = build_editor(multibuffer.clone(), window, cx);
17301        let snapshot = editor.snapshot(window, cx);
17302        editor.begin_selection(
17303            Point::new(1, 3).to_display_point(&snapshot),
17304            false,
17305            1,
17306            window,
17307            cx,
17308        );
17309        assert_eq!(
17310            editor.selections.ranges(&editor.display_snapshot(cx)),
17311            [Point::new(1, 3)..Point::new(1, 3)]
17312        );
17313        editor
17314    });
17315
17316    multibuffer.update(cx, |multibuffer, cx| {
17317        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17318    });
17319    _ = editor.update(cx, |editor, window, cx| {
17320        assert_eq!(
17321            editor.selections.ranges(&editor.display_snapshot(cx)),
17322            [Point::new(0, 0)..Point::new(0, 0)]
17323        );
17324
17325        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
17326        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17327        assert_eq!(
17328            editor.selections.ranges(&editor.display_snapshot(cx)),
17329            [Point::new(0, 3)..Point::new(0, 3)]
17330        );
17331        assert!(editor.selections.pending_anchor().is_some());
17332    });
17333}
17334
17335#[gpui::test]
17336async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
17337    init_test(cx, |_| {});
17338
17339    let language = Arc::new(
17340        Language::new(
17341            LanguageConfig {
17342                brackets: BracketPairConfig {
17343                    pairs: vec![
17344                        BracketPair {
17345                            start: "{".to_string(),
17346                            end: "}".to_string(),
17347                            close: true,
17348                            surround: true,
17349                            newline: true,
17350                        },
17351                        BracketPair {
17352                            start: "/* ".to_string(),
17353                            end: " */".to_string(),
17354                            close: true,
17355                            surround: true,
17356                            newline: true,
17357                        },
17358                    ],
17359                    ..Default::default()
17360                },
17361                ..Default::default()
17362            },
17363            Some(tree_sitter_rust::LANGUAGE.into()),
17364        )
17365        .with_indents_query("")
17366        .unwrap(),
17367    );
17368
17369    let text = concat!(
17370        "{   }\n",     //
17371        "  x\n",       //
17372        "  /*   */\n", //
17373        "x\n",         //
17374        "{{} }\n",     //
17375    );
17376
17377    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17378    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17379    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17380    editor
17381        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
17382        .await;
17383
17384    editor.update_in(cx, |editor, window, cx| {
17385        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17386            s.select_display_ranges([
17387                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
17388                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
17389                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
17390            ])
17391        });
17392        editor.newline(&Newline, window, cx);
17393
17394        assert_eq!(
17395            editor.buffer().read(cx).read(cx).text(),
17396            concat!(
17397                "{ \n",    // Suppress rustfmt
17398                "\n",      //
17399                "}\n",     //
17400                "  x\n",   //
17401                "  /* \n", //
17402                "  \n",    //
17403                "  */\n",  //
17404                "x\n",     //
17405                "{{} \n",  //
17406                "}\n",     //
17407            )
17408        );
17409    });
17410}
17411
17412#[gpui::test]
17413fn test_highlighted_ranges(cx: &mut TestAppContext) {
17414    init_test(cx, |_| {});
17415
17416    let editor = cx.add_window(|window, cx| {
17417        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
17418        build_editor(buffer, window, cx)
17419    });
17420
17421    _ = editor.update(cx, |editor, window, cx| {
17422        struct Type1;
17423        struct Type2;
17424
17425        let buffer = editor.buffer.read(cx).snapshot(cx);
17426
17427        let anchor_range =
17428            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
17429
17430        editor.highlight_background::<Type1>(
17431            &[
17432                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
17433                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
17434                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
17435                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
17436            ],
17437            |_, _| Hsla::red(),
17438            cx,
17439        );
17440        editor.highlight_background::<Type2>(
17441            &[
17442                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
17443                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
17444                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
17445                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
17446            ],
17447            |_, _| Hsla::green(),
17448            cx,
17449        );
17450
17451        let snapshot = editor.snapshot(window, cx);
17452        let highlighted_ranges = editor.sorted_background_highlights_in_range(
17453            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
17454            &snapshot,
17455            cx.theme(),
17456        );
17457        assert_eq!(
17458            highlighted_ranges,
17459            &[
17460                (
17461                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17462                    Hsla::green(),
17463                ),
17464                (
17465                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17466                    Hsla::red(),
17467                ),
17468                (
17469                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17470                    Hsla::green(),
17471                ),
17472                (
17473                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17474                    Hsla::red(),
17475                ),
17476            ]
17477        );
17478        assert_eq!(
17479            editor.sorted_background_highlights_in_range(
17480                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17481                &snapshot,
17482                cx.theme(),
17483            ),
17484            &[(
17485                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17486                Hsla::red(),
17487            )]
17488        );
17489    });
17490}
17491
17492#[gpui::test]
17493async fn test_following(cx: &mut TestAppContext) {
17494    init_test(cx, |_| {});
17495
17496    let fs = FakeFs::new(cx.executor());
17497    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17498
17499    let buffer = project.update(cx, |project, cx| {
17500        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17501        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17502    });
17503    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17504    let follower = cx.update(|cx| {
17505        cx.open_window(
17506            WindowOptions {
17507                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17508                    gpui::Point::new(px(0.), px(0.)),
17509                    gpui::Point::new(px(10.), px(80.)),
17510                ))),
17511                ..Default::default()
17512            },
17513            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17514        )
17515        .unwrap()
17516    });
17517
17518    let is_still_following = Rc::new(RefCell::new(true));
17519    let follower_edit_event_count = Rc::new(RefCell::new(0));
17520    let pending_update = Rc::new(RefCell::new(None));
17521    let leader_entity = leader.root(cx).unwrap();
17522    let follower_entity = follower.root(cx).unwrap();
17523    _ = follower.update(cx, {
17524        let update = pending_update.clone();
17525        let is_still_following = is_still_following.clone();
17526        let follower_edit_event_count = follower_edit_event_count.clone();
17527        |_, window, cx| {
17528            cx.subscribe_in(
17529                &leader_entity,
17530                window,
17531                move |_, leader, event, window, cx| {
17532                    leader.read(cx).add_event_to_update_proto(
17533                        event,
17534                        &mut update.borrow_mut(),
17535                        window,
17536                        cx,
17537                    );
17538                },
17539            )
17540            .detach();
17541
17542            cx.subscribe_in(
17543                &follower_entity,
17544                window,
17545                move |_, _, event: &EditorEvent, _window, _cx| {
17546                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17547                        *is_still_following.borrow_mut() = false;
17548                    }
17549
17550                    if let EditorEvent::BufferEdited = event {
17551                        *follower_edit_event_count.borrow_mut() += 1;
17552                    }
17553                },
17554            )
17555            .detach();
17556        }
17557    });
17558
17559    // Update the selections only
17560    _ = leader.update(cx, |leader, window, cx| {
17561        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17562            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17563        });
17564    });
17565    follower
17566        .update(cx, |follower, window, cx| {
17567            follower.apply_update_proto(
17568                &project,
17569                pending_update.borrow_mut().take().unwrap(),
17570                window,
17571                cx,
17572            )
17573        })
17574        .unwrap()
17575        .await
17576        .unwrap();
17577    _ = follower.update(cx, |follower, _, cx| {
17578        assert_eq!(
17579            follower.selections.ranges(&follower.display_snapshot(cx)),
17580            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17581        );
17582    });
17583    assert!(*is_still_following.borrow());
17584    assert_eq!(*follower_edit_event_count.borrow(), 0);
17585
17586    // Update the scroll position only
17587    _ = leader.update(cx, |leader, window, cx| {
17588        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17589    });
17590    follower
17591        .update(cx, |follower, window, cx| {
17592            follower.apply_update_proto(
17593                &project,
17594                pending_update.borrow_mut().take().unwrap(),
17595                window,
17596                cx,
17597            )
17598        })
17599        .unwrap()
17600        .await
17601        .unwrap();
17602    assert_eq!(
17603        follower
17604            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17605            .unwrap(),
17606        gpui::Point::new(1.5, 3.5)
17607    );
17608    assert!(*is_still_following.borrow());
17609    assert_eq!(*follower_edit_event_count.borrow(), 0);
17610
17611    // Update the selections and scroll position. The follower's scroll position is updated
17612    // via autoscroll, not via the leader's exact scroll position.
17613    _ = leader.update(cx, |leader, window, cx| {
17614        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17615            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17616        });
17617        leader.request_autoscroll(Autoscroll::newest(), cx);
17618        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17619    });
17620    follower
17621        .update(cx, |follower, window, cx| {
17622            follower.apply_update_proto(
17623                &project,
17624                pending_update.borrow_mut().take().unwrap(),
17625                window,
17626                cx,
17627            )
17628        })
17629        .unwrap()
17630        .await
17631        .unwrap();
17632    _ = follower.update(cx, |follower, _, cx| {
17633        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17634        assert_eq!(
17635            follower.selections.ranges(&follower.display_snapshot(cx)),
17636            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17637        );
17638    });
17639    assert!(*is_still_following.borrow());
17640
17641    // Creating a pending selection that precedes another selection
17642    _ = leader.update(cx, |leader, window, cx| {
17643        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17644            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17645        });
17646        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17647    });
17648    follower
17649        .update(cx, |follower, window, cx| {
17650            follower.apply_update_proto(
17651                &project,
17652                pending_update.borrow_mut().take().unwrap(),
17653                window,
17654                cx,
17655            )
17656        })
17657        .unwrap()
17658        .await
17659        .unwrap();
17660    _ = follower.update(cx, |follower, _, cx| {
17661        assert_eq!(
17662            follower.selections.ranges(&follower.display_snapshot(cx)),
17663            vec![
17664                MultiBufferOffset(0)..MultiBufferOffset(0),
17665                MultiBufferOffset(1)..MultiBufferOffset(1)
17666            ]
17667        );
17668    });
17669    assert!(*is_still_following.borrow());
17670
17671    // Extend the pending selection so that it surrounds another selection
17672    _ = leader.update(cx, |leader, window, cx| {
17673        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17674    });
17675    follower
17676        .update(cx, |follower, window, cx| {
17677            follower.apply_update_proto(
17678                &project,
17679                pending_update.borrow_mut().take().unwrap(),
17680                window,
17681                cx,
17682            )
17683        })
17684        .unwrap()
17685        .await
17686        .unwrap();
17687    _ = follower.update(cx, |follower, _, cx| {
17688        assert_eq!(
17689            follower.selections.ranges(&follower.display_snapshot(cx)),
17690            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17691        );
17692    });
17693
17694    // Scrolling locally breaks the follow
17695    _ = follower.update(cx, |follower, window, cx| {
17696        let top_anchor = follower
17697            .buffer()
17698            .read(cx)
17699            .read(cx)
17700            .anchor_after(MultiBufferOffset(0));
17701        follower.set_scroll_anchor(
17702            ScrollAnchor {
17703                anchor: top_anchor,
17704                offset: gpui::Point::new(0.0, 0.5),
17705            },
17706            window,
17707            cx,
17708        );
17709    });
17710    assert!(!(*is_still_following.borrow()));
17711}
17712
17713#[gpui::test]
17714async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17715    init_test(cx, |_| {});
17716
17717    let fs = FakeFs::new(cx.executor());
17718    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17719    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17720    let pane = workspace
17721        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17722        .unwrap();
17723
17724    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17725
17726    let leader = pane.update_in(cx, |_, window, cx| {
17727        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17728        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17729    });
17730
17731    // Start following the editor when it has no excerpts.
17732    let mut state_message =
17733        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17734    let workspace_entity = workspace.root(cx).unwrap();
17735    let follower_1 = cx
17736        .update_window(*workspace.deref(), |_, window, cx| {
17737            Editor::from_state_proto(
17738                workspace_entity,
17739                ViewId {
17740                    creator: CollaboratorId::PeerId(PeerId::default()),
17741                    id: 0,
17742                },
17743                &mut state_message,
17744                window,
17745                cx,
17746            )
17747        })
17748        .unwrap()
17749        .unwrap()
17750        .await
17751        .unwrap();
17752
17753    let update_message = Rc::new(RefCell::new(None));
17754    follower_1.update_in(cx, {
17755        let update = update_message.clone();
17756        |_, window, cx| {
17757            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17758                leader.read(cx).add_event_to_update_proto(
17759                    event,
17760                    &mut update.borrow_mut(),
17761                    window,
17762                    cx,
17763                );
17764            })
17765            .detach();
17766        }
17767    });
17768
17769    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17770        (
17771            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17772            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17773        )
17774    });
17775
17776    // Insert some excerpts.
17777    leader.update(cx, |leader, cx| {
17778        leader.buffer.update(cx, |multibuffer, cx| {
17779            multibuffer.set_excerpts_for_path(
17780                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17781                buffer_1.clone(),
17782                vec![
17783                    Point::row_range(0..3),
17784                    Point::row_range(1..6),
17785                    Point::row_range(12..15),
17786                ],
17787                0,
17788                cx,
17789            );
17790            multibuffer.set_excerpts_for_path(
17791                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17792                buffer_2.clone(),
17793                vec![Point::row_range(0..6), Point::row_range(8..12)],
17794                0,
17795                cx,
17796            );
17797        });
17798    });
17799
17800    // Apply the update of adding the excerpts.
17801    follower_1
17802        .update_in(cx, |follower, window, cx| {
17803            follower.apply_update_proto(
17804                &project,
17805                update_message.borrow().clone().unwrap(),
17806                window,
17807                cx,
17808            )
17809        })
17810        .await
17811        .unwrap();
17812    assert_eq!(
17813        follower_1.update(cx, |editor, cx| editor.text(cx)),
17814        leader.update(cx, |editor, cx| editor.text(cx))
17815    );
17816    update_message.borrow_mut().take();
17817
17818    // Start following separately after it already has excerpts.
17819    let mut state_message =
17820        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17821    let workspace_entity = workspace.root(cx).unwrap();
17822    let follower_2 = cx
17823        .update_window(*workspace.deref(), |_, window, cx| {
17824            Editor::from_state_proto(
17825                workspace_entity,
17826                ViewId {
17827                    creator: CollaboratorId::PeerId(PeerId::default()),
17828                    id: 0,
17829                },
17830                &mut state_message,
17831                window,
17832                cx,
17833            )
17834        })
17835        .unwrap()
17836        .unwrap()
17837        .await
17838        .unwrap();
17839    assert_eq!(
17840        follower_2.update(cx, |editor, cx| editor.text(cx)),
17841        leader.update(cx, |editor, cx| editor.text(cx))
17842    );
17843
17844    // Remove some excerpts.
17845    leader.update(cx, |leader, cx| {
17846        leader.buffer.update(cx, |multibuffer, cx| {
17847            let excerpt_ids = multibuffer.excerpt_ids();
17848            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17849            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17850        });
17851    });
17852
17853    // Apply the update of removing the excerpts.
17854    follower_1
17855        .update_in(cx, |follower, window, cx| {
17856            follower.apply_update_proto(
17857                &project,
17858                update_message.borrow().clone().unwrap(),
17859                window,
17860                cx,
17861            )
17862        })
17863        .await
17864        .unwrap();
17865    follower_2
17866        .update_in(cx, |follower, window, cx| {
17867            follower.apply_update_proto(
17868                &project,
17869                update_message.borrow().clone().unwrap(),
17870                window,
17871                cx,
17872            )
17873        })
17874        .await
17875        .unwrap();
17876    update_message.borrow_mut().take();
17877    assert_eq!(
17878        follower_1.update(cx, |editor, cx| editor.text(cx)),
17879        leader.update(cx, |editor, cx| editor.text(cx))
17880    );
17881}
17882
17883#[gpui::test]
17884async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17885    init_test(cx, |_| {});
17886
17887    let mut cx = EditorTestContext::new(cx).await;
17888    let lsp_store =
17889        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17890
17891    cx.set_state(indoc! {"
17892        ˇfn func(abc def: i32) -> u32 {
17893        }
17894    "});
17895
17896    cx.update(|_, cx| {
17897        lsp_store.update(cx, |lsp_store, cx| {
17898            lsp_store
17899                .update_diagnostics(
17900                    LanguageServerId(0),
17901                    lsp::PublishDiagnosticsParams {
17902                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17903                        version: None,
17904                        diagnostics: vec![
17905                            lsp::Diagnostic {
17906                                range: lsp::Range::new(
17907                                    lsp::Position::new(0, 11),
17908                                    lsp::Position::new(0, 12),
17909                                ),
17910                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17911                                ..Default::default()
17912                            },
17913                            lsp::Diagnostic {
17914                                range: lsp::Range::new(
17915                                    lsp::Position::new(0, 12),
17916                                    lsp::Position::new(0, 15),
17917                                ),
17918                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17919                                ..Default::default()
17920                            },
17921                            lsp::Diagnostic {
17922                                range: lsp::Range::new(
17923                                    lsp::Position::new(0, 25),
17924                                    lsp::Position::new(0, 28),
17925                                ),
17926                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17927                                ..Default::default()
17928                            },
17929                        ],
17930                    },
17931                    None,
17932                    DiagnosticSourceKind::Pushed,
17933                    &[],
17934                    cx,
17935                )
17936                .unwrap()
17937        });
17938    });
17939
17940    executor.run_until_parked();
17941
17942    cx.update_editor(|editor, window, cx| {
17943        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17944    });
17945
17946    cx.assert_editor_state(indoc! {"
17947        fn func(abc def: i32) -> ˇu32 {
17948        }
17949    "});
17950
17951    cx.update_editor(|editor, window, cx| {
17952        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17953    });
17954
17955    cx.assert_editor_state(indoc! {"
17956        fn func(abc ˇdef: i32) -> u32 {
17957        }
17958    "});
17959
17960    cx.update_editor(|editor, window, cx| {
17961        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17962    });
17963
17964    cx.assert_editor_state(indoc! {"
17965        fn func(abcˇ def: i32) -> u32 {
17966        }
17967    "});
17968
17969    cx.update_editor(|editor, window, cx| {
17970        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17971    });
17972
17973    cx.assert_editor_state(indoc! {"
17974        fn func(abc def: i32) -> ˇu32 {
17975        }
17976    "});
17977}
17978
17979#[gpui::test]
17980async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17981    init_test(cx, |_| {});
17982
17983    let mut cx = EditorTestContext::new(cx).await;
17984
17985    let diff_base = r#"
17986        use some::mod;
17987
17988        const A: u32 = 42;
17989
17990        fn main() {
17991            println!("hello");
17992
17993            println!("world");
17994        }
17995        "#
17996    .unindent();
17997
17998    // Edits are modified, removed, modified, added
17999    cx.set_state(
18000        &r#"
18001        use some::modified;
18002
18003        ˇ
18004        fn main() {
18005            println!("hello there");
18006
18007            println!("around the");
18008            println!("world");
18009        }
18010        "#
18011        .unindent(),
18012    );
18013
18014    cx.set_head_text(&diff_base);
18015    executor.run_until_parked();
18016
18017    cx.update_editor(|editor, window, cx| {
18018        //Wrap around the bottom of the buffer
18019        for _ in 0..3 {
18020            editor.go_to_next_hunk(&GoToHunk, window, cx);
18021        }
18022    });
18023
18024    cx.assert_editor_state(
18025        &r#"
18026        ˇuse some::modified;
18027
18028
18029        fn main() {
18030            println!("hello there");
18031
18032            println!("around the");
18033            println!("world");
18034        }
18035        "#
18036        .unindent(),
18037    );
18038
18039    cx.update_editor(|editor, window, cx| {
18040        //Wrap around the top of the buffer
18041        for _ in 0..2 {
18042            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18043        }
18044    });
18045
18046    cx.assert_editor_state(
18047        &r#"
18048        use some::modified;
18049
18050
18051        fn main() {
18052        ˇ    println!("hello there");
18053
18054            println!("around the");
18055            println!("world");
18056        }
18057        "#
18058        .unindent(),
18059    );
18060
18061    cx.update_editor(|editor, window, cx| {
18062        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18063    });
18064
18065    cx.assert_editor_state(
18066        &r#"
18067        use some::modified;
18068
18069        ˇ
18070        fn main() {
18071            println!("hello there");
18072
18073            println!("around the");
18074            println!("world");
18075        }
18076        "#
18077        .unindent(),
18078    );
18079
18080    cx.update_editor(|editor, window, cx| {
18081        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18082    });
18083
18084    cx.assert_editor_state(
18085        &r#"
18086        ˇuse some::modified;
18087
18088
18089        fn main() {
18090            println!("hello there");
18091
18092            println!("around the");
18093            println!("world");
18094        }
18095        "#
18096        .unindent(),
18097    );
18098
18099    cx.update_editor(|editor, window, cx| {
18100        for _ in 0..2 {
18101            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
18102        }
18103    });
18104
18105    cx.assert_editor_state(
18106        &r#"
18107        use some::modified;
18108
18109
18110        fn main() {
18111        ˇ    println!("hello there");
18112
18113            println!("around the");
18114            println!("world");
18115        }
18116        "#
18117        .unindent(),
18118    );
18119
18120    cx.update_editor(|editor, window, cx| {
18121        editor.fold(&Fold, window, cx);
18122    });
18123
18124    cx.update_editor(|editor, window, cx| {
18125        editor.go_to_next_hunk(&GoToHunk, window, cx);
18126    });
18127
18128    cx.assert_editor_state(
18129        &r#"
18130        ˇuse some::modified;
18131
18132
18133        fn main() {
18134            println!("hello there");
18135
18136            println!("around the");
18137            println!("world");
18138        }
18139        "#
18140        .unindent(),
18141    );
18142}
18143
18144#[test]
18145fn test_split_words() {
18146    fn split(text: &str) -> Vec<&str> {
18147        split_words(text).collect()
18148    }
18149
18150    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
18151    assert_eq!(split("hello_world"), &["hello_", "world"]);
18152    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
18153    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
18154    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
18155    assert_eq!(split("helloworld"), &["helloworld"]);
18156
18157    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
18158}
18159
18160#[test]
18161fn test_split_words_for_snippet_prefix() {
18162    fn split(text: &str) -> Vec<&str> {
18163        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
18164    }
18165
18166    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
18167    assert_eq!(split("hello_world"), &["hello_world"]);
18168    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
18169    assert_eq!(split("Hello_World"), &["Hello_World"]);
18170    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
18171    assert_eq!(split("helloworld"), &["helloworld"]);
18172    assert_eq!(
18173        split("this@is!@#$^many   . symbols"),
18174        &[
18175            "symbols",
18176            " symbols",
18177            ". symbols",
18178            " . symbols",
18179            "  . symbols",
18180            "   . symbols",
18181            "many   . symbols",
18182            "^many   . symbols",
18183            "$^many   . symbols",
18184            "#$^many   . symbols",
18185            "@#$^many   . symbols",
18186            "!@#$^many   . symbols",
18187            "is!@#$^many   . symbols",
18188            "@is!@#$^many   . symbols",
18189            "this@is!@#$^many   . symbols",
18190        ],
18191    );
18192    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
18193}
18194
18195#[gpui::test]
18196async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
18197    init_test(cx, |_| {});
18198
18199    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
18200
18201    #[track_caller]
18202    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
18203        let _state_context = cx.set_state(before);
18204        cx.run_until_parked();
18205        cx.update_editor(|editor, window, cx| {
18206            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
18207        });
18208        cx.run_until_parked();
18209        cx.assert_editor_state(after);
18210    }
18211
18212    // Outside bracket jumps to outside of matching bracket
18213    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
18214    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
18215
18216    // Inside bracket jumps to inside of matching bracket
18217    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
18218    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
18219
18220    // When outside a bracket and inside, favor jumping to the inside bracket
18221    assert(
18222        "console.log('foo', [1, 2, 3]ˇ);",
18223        "console.log('foo', ˇ[1, 2, 3]);",
18224        &mut cx,
18225    );
18226    assert(
18227        "console.log(ˇ'foo', [1, 2, 3]);",
18228        "console.log('foo'ˇ, [1, 2, 3]);",
18229        &mut cx,
18230    );
18231
18232    // Bias forward if two options are equally likely
18233    assert(
18234        "let result = curried_fun()ˇ();",
18235        "let result = curried_fun()()ˇ;",
18236        &mut cx,
18237    );
18238
18239    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
18240    assert(
18241        indoc! {"
18242            function test() {
18243                console.log('test')ˇ
18244            }"},
18245        indoc! {"
18246            function test() {
18247                console.logˇ('test')
18248            }"},
18249        &mut cx,
18250    );
18251}
18252
18253#[gpui::test]
18254async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
18255    init_test(cx, |_| {});
18256    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
18257    language_registry.add(markdown_lang());
18258    language_registry.add(rust_lang());
18259    let buffer = cx.new(|cx| {
18260        let mut buffer = language::Buffer::local(
18261            indoc! {"
18262            ```rs
18263            impl Worktree {
18264                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18265                }
18266            }
18267            ```
18268        "},
18269            cx,
18270        );
18271        buffer.set_language_registry(language_registry.clone());
18272        buffer.set_language(Some(markdown_lang()), cx);
18273        buffer
18274    });
18275    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18276    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18277    cx.executor().run_until_parked();
18278    _ = editor.update(cx, |editor, window, cx| {
18279        // Case 1: Test outer enclosing brackets
18280        select_ranges(
18281            editor,
18282            &indoc! {"
18283                ```rs
18284                impl Worktree {
18285                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18286                    }
1828718288                ```
18289            "},
18290            window,
18291            cx,
18292        );
18293        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18294        assert_text_with_selections(
18295            editor,
18296            &indoc! {"
18297                ```rs
18298                impl Worktree ˇ{
18299                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18300                    }
18301                }
18302                ```
18303            "},
18304            cx,
18305        );
18306        // Case 2: Test inner enclosing brackets
18307        select_ranges(
18308            editor,
18309            &indoc! {"
18310                ```rs
18311                impl Worktree {
18312                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1831318314                }
18315                ```
18316            "},
18317            window,
18318            cx,
18319        );
18320        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18321        assert_text_with_selections(
18322            editor,
18323            &indoc! {"
18324                ```rs
18325                impl Worktree {
18326                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
18327                    }
18328                }
18329                ```
18330            "},
18331            cx,
18332        );
18333    });
18334}
18335
18336#[gpui::test]
18337async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
18338    init_test(cx, |_| {});
18339
18340    let fs = FakeFs::new(cx.executor());
18341    fs.insert_tree(
18342        path!("/a"),
18343        json!({
18344            "main.rs": "fn main() { let a = 5; }",
18345            "other.rs": "// Test file",
18346        }),
18347    )
18348    .await;
18349    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18350
18351    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18352    language_registry.add(Arc::new(Language::new(
18353        LanguageConfig {
18354            name: "Rust".into(),
18355            matcher: LanguageMatcher {
18356                path_suffixes: vec!["rs".to_string()],
18357                ..Default::default()
18358            },
18359            brackets: BracketPairConfig {
18360                pairs: vec![BracketPair {
18361                    start: "{".to_string(),
18362                    end: "}".to_string(),
18363                    close: true,
18364                    surround: true,
18365                    newline: true,
18366                }],
18367                disabled_scopes_by_bracket_ix: Vec::new(),
18368            },
18369            ..Default::default()
18370        },
18371        Some(tree_sitter_rust::LANGUAGE.into()),
18372    )));
18373    let mut fake_servers = language_registry.register_fake_lsp(
18374        "Rust",
18375        FakeLspAdapter {
18376            capabilities: lsp::ServerCapabilities {
18377                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18378                    first_trigger_character: "{".to_string(),
18379                    more_trigger_character: None,
18380                }),
18381                ..Default::default()
18382            },
18383            ..Default::default()
18384        },
18385    );
18386
18387    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18388
18389    let cx = &mut VisualTestContext::from_window(*workspace, cx);
18390
18391    let worktree_id = workspace
18392        .update(cx, |workspace, _, cx| {
18393            workspace.project().update(cx, |project, cx| {
18394                project.worktrees(cx).next().unwrap().read(cx).id()
18395            })
18396        })
18397        .unwrap();
18398
18399    let buffer = project
18400        .update(cx, |project, cx| {
18401            project.open_local_buffer(path!("/a/main.rs"), cx)
18402        })
18403        .await
18404        .unwrap();
18405    let editor_handle = workspace
18406        .update(cx, |workspace, window, cx| {
18407            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
18408        })
18409        .unwrap()
18410        .await
18411        .unwrap()
18412        .downcast::<Editor>()
18413        .unwrap();
18414
18415    let fake_server = fake_servers.next().await.unwrap();
18416
18417    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
18418        |params, _| async move {
18419            assert_eq!(
18420                params.text_document_position.text_document.uri,
18421                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
18422            );
18423            assert_eq!(
18424                params.text_document_position.position,
18425                lsp::Position::new(0, 21),
18426            );
18427
18428            Ok(Some(vec![lsp::TextEdit {
18429                new_text: "]".to_string(),
18430                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18431            }]))
18432        },
18433    );
18434
18435    editor_handle.update_in(cx, |editor, window, cx| {
18436        window.focus(&editor.focus_handle(cx), cx);
18437        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18438            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
18439        });
18440        editor.handle_input("{", window, cx);
18441    });
18442
18443    cx.executor().run_until_parked();
18444
18445    buffer.update(cx, |buffer, _| {
18446        assert_eq!(
18447            buffer.text(),
18448            "fn main() { let a = {5}; }",
18449            "No extra braces from on type formatting should appear in the buffer"
18450        )
18451    });
18452}
18453
18454#[gpui::test(iterations = 20, seeds(31))]
18455async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18456    init_test(cx, |_| {});
18457
18458    let mut cx = EditorLspTestContext::new_rust(
18459        lsp::ServerCapabilities {
18460            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18461                first_trigger_character: ".".to_string(),
18462                more_trigger_character: None,
18463            }),
18464            ..Default::default()
18465        },
18466        cx,
18467    )
18468    .await;
18469
18470    cx.update_buffer(|buffer, _| {
18471        // This causes autoindent to be async.
18472        buffer.set_sync_parse_timeout(None)
18473    });
18474
18475    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18476    cx.simulate_keystroke("\n");
18477    cx.run_until_parked();
18478
18479    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18480    let mut request =
18481        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18482            let buffer_cloned = buffer_cloned.clone();
18483            async move {
18484                buffer_cloned.update(&mut cx, |buffer, _| {
18485                    assert_eq!(
18486                        buffer.text(),
18487                        "fn c() {\n    d()\n        .\n}\n",
18488                        "OnTypeFormatting should triggered after autoindent applied"
18489                    )
18490                });
18491
18492                Ok(Some(vec![]))
18493            }
18494        });
18495
18496    cx.simulate_keystroke(".");
18497    cx.run_until_parked();
18498
18499    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18500    assert!(request.next().await.is_some());
18501    request.close();
18502    assert!(request.next().await.is_none());
18503}
18504
18505#[gpui::test]
18506async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18507    init_test(cx, |_| {});
18508
18509    let fs = FakeFs::new(cx.executor());
18510    fs.insert_tree(
18511        path!("/a"),
18512        json!({
18513            "main.rs": "fn main() { let a = 5; }",
18514            "other.rs": "// Test file",
18515        }),
18516    )
18517    .await;
18518
18519    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18520
18521    let server_restarts = Arc::new(AtomicUsize::new(0));
18522    let closure_restarts = Arc::clone(&server_restarts);
18523    let language_server_name = "test language server";
18524    let language_name: LanguageName = "Rust".into();
18525
18526    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18527    language_registry.add(Arc::new(Language::new(
18528        LanguageConfig {
18529            name: language_name.clone(),
18530            matcher: LanguageMatcher {
18531                path_suffixes: vec!["rs".to_string()],
18532                ..Default::default()
18533            },
18534            ..Default::default()
18535        },
18536        Some(tree_sitter_rust::LANGUAGE.into()),
18537    )));
18538    let mut fake_servers = language_registry.register_fake_lsp(
18539        "Rust",
18540        FakeLspAdapter {
18541            name: language_server_name,
18542            initialization_options: Some(json!({
18543                "testOptionValue": true
18544            })),
18545            initializer: Some(Box::new(move |fake_server| {
18546                let task_restarts = Arc::clone(&closure_restarts);
18547                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18548                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18549                    futures::future::ready(Ok(()))
18550                });
18551            })),
18552            ..Default::default()
18553        },
18554    );
18555
18556    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18557    let _buffer = project
18558        .update(cx, |project, cx| {
18559            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18560        })
18561        .await
18562        .unwrap();
18563    let _fake_server = fake_servers.next().await.unwrap();
18564    update_test_language_settings(cx, |language_settings| {
18565        language_settings.languages.0.insert(
18566            language_name.clone().0.to_string(),
18567            LanguageSettingsContent {
18568                tab_size: NonZeroU32::new(8),
18569                ..Default::default()
18570            },
18571        );
18572    });
18573    cx.executor().run_until_parked();
18574    assert_eq!(
18575        server_restarts.load(atomic::Ordering::Acquire),
18576        0,
18577        "Should not restart LSP server on an unrelated change"
18578    );
18579
18580    update_test_project_settings(cx, |project_settings| {
18581        project_settings.lsp.0.insert(
18582            "Some other server name".into(),
18583            LspSettings {
18584                binary: None,
18585                settings: None,
18586                initialization_options: Some(json!({
18587                    "some other init value": false
18588                })),
18589                enable_lsp_tasks: false,
18590                fetch: None,
18591            },
18592        );
18593    });
18594    cx.executor().run_until_parked();
18595    assert_eq!(
18596        server_restarts.load(atomic::Ordering::Acquire),
18597        0,
18598        "Should not restart LSP server on an unrelated LSP settings change"
18599    );
18600
18601    update_test_project_settings(cx, |project_settings| {
18602        project_settings.lsp.0.insert(
18603            language_server_name.into(),
18604            LspSettings {
18605                binary: None,
18606                settings: None,
18607                initialization_options: Some(json!({
18608                    "anotherInitValue": false
18609                })),
18610                enable_lsp_tasks: false,
18611                fetch: None,
18612            },
18613        );
18614    });
18615    cx.executor().run_until_parked();
18616    assert_eq!(
18617        server_restarts.load(atomic::Ordering::Acquire),
18618        1,
18619        "Should restart LSP server on a related LSP settings change"
18620    );
18621
18622    update_test_project_settings(cx, |project_settings| {
18623        project_settings.lsp.0.insert(
18624            language_server_name.into(),
18625            LspSettings {
18626                binary: None,
18627                settings: None,
18628                initialization_options: Some(json!({
18629                    "anotherInitValue": false
18630                })),
18631                enable_lsp_tasks: false,
18632                fetch: None,
18633            },
18634        );
18635    });
18636    cx.executor().run_until_parked();
18637    assert_eq!(
18638        server_restarts.load(atomic::Ordering::Acquire),
18639        1,
18640        "Should not restart LSP server on a related LSP settings change that is the same"
18641    );
18642
18643    update_test_project_settings(cx, |project_settings| {
18644        project_settings.lsp.0.insert(
18645            language_server_name.into(),
18646            LspSettings {
18647                binary: None,
18648                settings: None,
18649                initialization_options: None,
18650                enable_lsp_tasks: false,
18651                fetch: None,
18652            },
18653        );
18654    });
18655    cx.executor().run_until_parked();
18656    assert_eq!(
18657        server_restarts.load(atomic::Ordering::Acquire),
18658        2,
18659        "Should restart LSP server on another related LSP settings change"
18660    );
18661}
18662
18663#[gpui::test]
18664async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18665    init_test(cx, |_| {});
18666
18667    let mut cx = EditorLspTestContext::new_rust(
18668        lsp::ServerCapabilities {
18669            completion_provider: Some(lsp::CompletionOptions {
18670                trigger_characters: Some(vec![".".to_string()]),
18671                resolve_provider: Some(true),
18672                ..Default::default()
18673            }),
18674            ..Default::default()
18675        },
18676        cx,
18677    )
18678    .await;
18679
18680    cx.set_state("fn main() { let a = 2ˇ; }");
18681    cx.simulate_keystroke(".");
18682    let completion_item = lsp::CompletionItem {
18683        label: "some".into(),
18684        kind: Some(lsp::CompletionItemKind::SNIPPET),
18685        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18686        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18687            kind: lsp::MarkupKind::Markdown,
18688            value: "```rust\nSome(2)\n```".to_string(),
18689        })),
18690        deprecated: Some(false),
18691        sort_text: Some("fffffff2".to_string()),
18692        filter_text: Some("some".to_string()),
18693        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18694        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18695            range: lsp::Range {
18696                start: lsp::Position {
18697                    line: 0,
18698                    character: 22,
18699                },
18700                end: lsp::Position {
18701                    line: 0,
18702                    character: 22,
18703                },
18704            },
18705            new_text: "Some(2)".to_string(),
18706        })),
18707        additional_text_edits: Some(vec![lsp::TextEdit {
18708            range: lsp::Range {
18709                start: lsp::Position {
18710                    line: 0,
18711                    character: 20,
18712                },
18713                end: lsp::Position {
18714                    line: 0,
18715                    character: 22,
18716                },
18717            },
18718            new_text: "".to_string(),
18719        }]),
18720        ..Default::default()
18721    };
18722
18723    let closure_completion_item = completion_item.clone();
18724    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18725        let task_completion_item = closure_completion_item.clone();
18726        async move {
18727            Ok(Some(lsp::CompletionResponse::Array(vec![
18728                task_completion_item,
18729            ])))
18730        }
18731    });
18732
18733    request.next().await;
18734
18735    cx.condition(|editor, _| editor.context_menu_visible())
18736        .await;
18737    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18738        editor
18739            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18740            .unwrap()
18741    });
18742    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18743
18744    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18745        let task_completion_item = completion_item.clone();
18746        async move { Ok(task_completion_item) }
18747    })
18748    .next()
18749    .await
18750    .unwrap();
18751    apply_additional_edits.await.unwrap();
18752    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18753}
18754
18755#[gpui::test]
18756async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18757    init_test(cx, |_| {});
18758
18759    let mut cx = EditorLspTestContext::new_rust(
18760        lsp::ServerCapabilities {
18761            completion_provider: Some(lsp::CompletionOptions {
18762                trigger_characters: Some(vec![".".to_string()]),
18763                resolve_provider: Some(true),
18764                ..Default::default()
18765            }),
18766            ..Default::default()
18767        },
18768        cx,
18769    )
18770    .await;
18771
18772    cx.set_state("fn main() { let a = 2ˇ; }");
18773    cx.simulate_keystroke(".");
18774
18775    let item1 = lsp::CompletionItem {
18776        label: "method id()".to_string(),
18777        filter_text: Some("id".to_string()),
18778        detail: None,
18779        documentation: None,
18780        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18781            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18782            new_text: ".id".to_string(),
18783        })),
18784        ..lsp::CompletionItem::default()
18785    };
18786
18787    let item2 = lsp::CompletionItem {
18788        label: "other".to_string(),
18789        filter_text: Some("other".to_string()),
18790        detail: None,
18791        documentation: None,
18792        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18793            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18794            new_text: ".other".to_string(),
18795        })),
18796        ..lsp::CompletionItem::default()
18797    };
18798
18799    let item1 = item1.clone();
18800    cx.set_request_handler::<lsp::request::Completion, _, _>({
18801        let item1 = item1.clone();
18802        move |_, _, _| {
18803            let item1 = item1.clone();
18804            let item2 = item2.clone();
18805            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18806        }
18807    })
18808    .next()
18809    .await;
18810
18811    cx.condition(|editor, _| editor.context_menu_visible())
18812        .await;
18813    cx.update_editor(|editor, _, _| {
18814        let context_menu = editor.context_menu.borrow_mut();
18815        let context_menu = context_menu
18816            .as_ref()
18817            .expect("Should have the context menu deployed");
18818        match context_menu {
18819            CodeContextMenu::Completions(completions_menu) => {
18820                let completions = completions_menu.completions.borrow_mut();
18821                assert_eq!(
18822                    completions
18823                        .iter()
18824                        .map(|completion| &completion.label.text)
18825                        .collect::<Vec<_>>(),
18826                    vec!["method id()", "other"]
18827                )
18828            }
18829            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18830        }
18831    });
18832
18833    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18834        let item1 = item1.clone();
18835        move |_, item_to_resolve, _| {
18836            let item1 = item1.clone();
18837            async move {
18838                if item1 == item_to_resolve {
18839                    Ok(lsp::CompletionItem {
18840                        label: "method id()".to_string(),
18841                        filter_text: Some("id".to_string()),
18842                        detail: Some("Now resolved!".to_string()),
18843                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18844                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18845                            range: lsp::Range::new(
18846                                lsp::Position::new(0, 22),
18847                                lsp::Position::new(0, 22),
18848                            ),
18849                            new_text: ".id".to_string(),
18850                        })),
18851                        ..lsp::CompletionItem::default()
18852                    })
18853                } else {
18854                    Ok(item_to_resolve)
18855                }
18856            }
18857        }
18858    })
18859    .next()
18860    .await
18861    .unwrap();
18862    cx.run_until_parked();
18863
18864    cx.update_editor(|editor, window, cx| {
18865        editor.context_menu_next(&Default::default(), window, cx);
18866    });
18867    cx.run_until_parked();
18868
18869    cx.update_editor(|editor, _, _| {
18870        let context_menu = editor.context_menu.borrow_mut();
18871        let context_menu = context_menu
18872            .as_ref()
18873            .expect("Should have the context menu deployed");
18874        match context_menu {
18875            CodeContextMenu::Completions(completions_menu) => {
18876                let completions = completions_menu.completions.borrow_mut();
18877                assert_eq!(
18878                    completions
18879                        .iter()
18880                        .map(|completion| &completion.label.text)
18881                        .collect::<Vec<_>>(),
18882                    vec!["method id() Now resolved!", "other"],
18883                    "Should update first completion label, but not second as the filter text did not match."
18884                );
18885            }
18886            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18887        }
18888    });
18889}
18890
18891#[gpui::test]
18892async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18893    init_test(cx, |_| {});
18894    let mut cx = EditorLspTestContext::new_rust(
18895        lsp::ServerCapabilities {
18896            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18897            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18898            completion_provider: Some(lsp::CompletionOptions {
18899                resolve_provider: Some(true),
18900                ..Default::default()
18901            }),
18902            ..Default::default()
18903        },
18904        cx,
18905    )
18906    .await;
18907    cx.set_state(indoc! {"
18908        struct TestStruct {
18909            field: i32
18910        }
18911
18912        fn mainˇ() {
18913            let unused_var = 42;
18914            let test_struct = TestStruct { field: 42 };
18915        }
18916    "});
18917    let symbol_range = cx.lsp_range(indoc! {"
18918        struct TestStruct {
18919            field: i32
18920        }
18921
18922        «fn main»() {
18923            let unused_var = 42;
18924            let test_struct = TestStruct { field: 42 };
18925        }
18926    "});
18927    let mut hover_requests =
18928        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18929            Ok(Some(lsp::Hover {
18930                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18931                    kind: lsp::MarkupKind::Markdown,
18932                    value: "Function documentation".to_string(),
18933                }),
18934                range: Some(symbol_range),
18935            }))
18936        });
18937
18938    // Case 1: Test that code action menu hide hover popover
18939    cx.dispatch_action(Hover);
18940    hover_requests.next().await;
18941    cx.condition(|editor, _| editor.hover_state.visible()).await;
18942    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18943        move |_, _, _| async move {
18944            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18945                lsp::CodeAction {
18946                    title: "Remove unused variable".to_string(),
18947                    kind: Some(CodeActionKind::QUICKFIX),
18948                    edit: Some(lsp::WorkspaceEdit {
18949                        changes: Some(
18950                            [(
18951                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18952                                vec![lsp::TextEdit {
18953                                    range: lsp::Range::new(
18954                                        lsp::Position::new(5, 4),
18955                                        lsp::Position::new(5, 27),
18956                                    ),
18957                                    new_text: "".to_string(),
18958                                }],
18959                            )]
18960                            .into_iter()
18961                            .collect(),
18962                        ),
18963                        ..Default::default()
18964                    }),
18965                    ..Default::default()
18966                },
18967            )]))
18968        },
18969    );
18970    cx.update_editor(|editor, window, cx| {
18971        editor.toggle_code_actions(
18972            &ToggleCodeActions {
18973                deployed_from: None,
18974                quick_launch: false,
18975            },
18976            window,
18977            cx,
18978        );
18979    });
18980    code_action_requests.next().await;
18981    cx.run_until_parked();
18982    cx.condition(|editor, _| editor.context_menu_visible())
18983        .await;
18984    cx.update_editor(|editor, _, _| {
18985        assert!(
18986            !editor.hover_state.visible(),
18987            "Hover popover should be hidden when code action menu is shown"
18988        );
18989        // Hide code actions
18990        editor.context_menu.take();
18991    });
18992
18993    // Case 2: Test that code completions hide hover popover
18994    cx.dispatch_action(Hover);
18995    hover_requests.next().await;
18996    cx.condition(|editor, _| editor.hover_state.visible()).await;
18997    let counter = Arc::new(AtomicUsize::new(0));
18998    let mut completion_requests =
18999        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19000            let counter = counter.clone();
19001            async move {
19002                counter.fetch_add(1, atomic::Ordering::Release);
19003                Ok(Some(lsp::CompletionResponse::Array(vec![
19004                    lsp::CompletionItem {
19005                        label: "main".into(),
19006                        kind: Some(lsp::CompletionItemKind::FUNCTION),
19007                        detail: Some("() -> ()".to_string()),
19008                        ..Default::default()
19009                    },
19010                    lsp::CompletionItem {
19011                        label: "TestStruct".into(),
19012                        kind: Some(lsp::CompletionItemKind::STRUCT),
19013                        detail: Some("struct TestStruct".to_string()),
19014                        ..Default::default()
19015                    },
19016                ])))
19017            }
19018        });
19019    cx.update_editor(|editor, window, cx| {
19020        editor.show_completions(&ShowCompletions, window, cx);
19021    });
19022    completion_requests.next().await;
19023    cx.condition(|editor, _| editor.context_menu_visible())
19024        .await;
19025    cx.update_editor(|editor, _, _| {
19026        assert!(
19027            !editor.hover_state.visible(),
19028            "Hover popover should be hidden when completion menu is shown"
19029        );
19030    });
19031}
19032
19033#[gpui::test]
19034async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
19035    init_test(cx, |_| {});
19036
19037    let mut cx = EditorLspTestContext::new_rust(
19038        lsp::ServerCapabilities {
19039            completion_provider: Some(lsp::CompletionOptions {
19040                trigger_characters: Some(vec![".".to_string()]),
19041                resolve_provider: Some(true),
19042                ..Default::default()
19043            }),
19044            ..Default::default()
19045        },
19046        cx,
19047    )
19048    .await;
19049
19050    cx.set_state("fn main() { let a = 2ˇ; }");
19051    cx.simulate_keystroke(".");
19052
19053    let unresolved_item_1 = lsp::CompletionItem {
19054        label: "id".to_string(),
19055        filter_text: Some("id".to_string()),
19056        detail: None,
19057        documentation: None,
19058        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19059            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19060            new_text: ".id".to_string(),
19061        })),
19062        ..lsp::CompletionItem::default()
19063    };
19064    let resolved_item_1 = lsp::CompletionItem {
19065        additional_text_edits: Some(vec![lsp::TextEdit {
19066            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
19067            new_text: "!!".to_string(),
19068        }]),
19069        ..unresolved_item_1.clone()
19070    };
19071    let unresolved_item_2 = lsp::CompletionItem {
19072        label: "other".to_string(),
19073        filter_text: Some("other".to_string()),
19074        detail: None,
19075        documentation: None,
19076        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19077            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19078            new_text: ".other".to_string(),
19079        })),
19080        ..lsp::CompletionItem::default()
19081    };
19082    let resolved_item_2 = lsp::CompletionItem {
19083        additional_text_edits: Some(vec![lsp::TextEdit {
19084            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
19085            new_text: "??".to_string(),
19086        }]),
19087        ..unresolved_item_2.clone()
19088    };
19089
19090    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
19091    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
19092    cx.lsp
19093        .server
19094        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19095            let unresolved_item_1 = unresolved_item_1.clone();
19096            let resolved_item_1 = resolved_item_1.clone();
19097            let unresolved_item_2 = unresolved_item_2.clone();
19098            let resolved_item_2 = resolved_item_2.clone();
19099            let resolve_requests_1 = resolve_requests_1.clone();
19100            let resolve_requests_2 = resolve_requests_2.clone();
19101            move |unresolved_request, _| {
19102                let unresolved_item_1 = unresolved_item_1.clone();
19103                let resolved_item_1 = resolved_item_1.clone();
19104                let unresolved_item_2 = unresolved_item_2.clone();
19105                let resolved_item_2 = resolved_item_2.clone();
19106                let resolve_requests_1 = resolve_requests_1.clone();
19107                let resolve_requests_2 = resolve_requests_2.clone();
19108                async move {
19109                    if unresolved_request == unresolved_item_1 {
19110                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
19111                        Ok(resolved_item_1.clone())
19112                    } else if unresolved_request == unresolved_item_2 {
19113                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
19114                        Ok(resolved_item_2.clone())
19115                    } else {
19116                        panic!("Unexpected completion item {unresolved_request:?}")
19117                    }
19118                }
19119            }
19120        })
19121        .detach();
19122
19123    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19124        let unresolved_item_1 = unresolved_item_1.clone();
19125        let unresolved_item_2 = unresolved_item_2.clone();
19126        async move {
19127            Ok(Some(lsp::CompletionResponse::Array(vec![
19128                unresolved_item_1,
19129                unresolved_item_2,
19130            ])))
19131        }
19132    })
19133    .next()
19134    .await;
19135
19136    cx.condition(|editor, _| editor.context_menu_visible())
19137        .await;
19138    cx.update_editor(|editor, _, _| {
19139        let context_menu = editor.context_menu.borrow_mut();
19140        let context_menu = context_menu
19141            .as_ref()
19142            .expect("Should have the context menu deployed");
19143        match context_menu {
19144            CodeContextMenu::Completions(completions_menu) => {
19145                let completions = completions_menu.completions.borrow_mut();
19146                assert_eq!(
19147                    completions
19148                        .iter()
19149                        .map(|completion| &completion.label.text)
19150                        .collect::<Vec<_>>(),
19151                    vec!["id", "other"]
19152                )
19153            }
19154            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19155        }
19156    });
19157    cx.run_until_parked();
19158
19159    cx.update_editor(|editor, window, cx| {
19160        editor.context_menu_next(&ContextMenuNext, window, cx);
19161    });
19162    cx.run_until_parked();
19163    cx.update_editor(|editor, window, cx| {
19164        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19165    });
19166    cx.run_until_parked();
19167    cx.update_editor(|editor, window, cx| {
19168        editor.context_menu_next(&ContextMenuNext, window, cx);
19169    });
19170    cx.run_until_parked();
19171    cx.update_editor(|editor, window, cx| {
19172        editor
19173            .compose_completion(&ComposeCompletion::default(), window, cx)
19174            .expect("No task returned")
19175    })
19176    .await
19177    .expect("Completion failed");
19178    cx.run_until_parked();
19179
19180    cx.update_editor(|editor, _, cx| {
19181        assert_eq!(
19182            resolve_requests_1.load(atomic::Ordering::Acquire),
19183            1,
19184            "Should always resolve once despite multiple selections"
19185        );
19186        assert_eq!(
19187            resolve_requests_2.load(atomic::Ordering::Acquire),
19188            1,
19189            "Should always resolve once after multiple selections and applying the completion"
19190        );
19191        assert_eq!(
19192            editor.text(cx),
19193            "fn main() { let a = ??.other; }",
19194            "Should use resolved data when applying the completion"
19195        );
19196    });
19197}
19198
19199#[gpui::test]
19200async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
19201    init_test(cx, |_| {});
19202
19203    let item_0 = lsp::CompletionItem {
19204        label: "abs".into(),
19205        insert_text: Some("abs".into()),
19206        data: Some(json!({ "very": "special"})),
19207        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
19208        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
19209            lsp::InsertReplaceEdit {
19210                new_text: "abs".to_string(),
19211                insert: lsp::Range::default(),
19212                replace: lsp::Range::default(),
19213            },
19214        )),
19215        ..lsp::CompletionItem::default()
19216    };
19217    let items = iter::once(item_0.clone())
19218        .chain((11..51).map(|i| lsp::CompletionItem {
19219            label: format!("item_{}", i),
19220            insert_text: Some(format!("item_{}", i)),
19221            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
19222            ..lsp::CompletionItem::default()
19223        }))
19224        .collect::<Vec<_>>();
19225
19226    let default_commit_characters = vec!["?".to_string()];
19227    let default_data = json!({ "default": "data"});
19228    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
19229    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
19230    let default_edit_range = lsp::Range {
19231        start: lsp::Position {
19232            line: 0,
19233            character: 5,
19234        },
19235        end: lsp::Position {
19236            line: 0,
19237            character: 5,
19238        },
19239    };
19240
19241    let mut cx = EditorLspTestContext::new_rust(
19242        lsp::ServerCapabilities {
19243            completion_provider: Some(lsp::CompletionOptions {
19244                trigger_characters: Some(vec![".".to_string()]),
19245                resolve_provider: Some(true),
19246                ..Default::default()
19247            }),
19248            ..Default::default()
19249        },
19250        cx,
19251    )
19252    .await;
19253
19254    cx.set_state("fn main() { let a = 2ˇ; }");
19255    cx.simulate_keystroke(".");
19256
19257    let completion_data = default_data.clone();
19258    let completion_characters = default_commit_characters.clone();
19259    let completion_items = items.clone();
19260    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19261        let default_data = completion_data.clone();
19262        let default_commit_characters = completion_characters.clone();
19263        let items = completion_items.clone();
19264        async move {
19265            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
19266                items,
19267                item_defaults: Some(lsp::CompletionListItemDefaults {
19268                    data: Some(default_data.clone()),
19269                    commit_characters: Some(default_commit_characters.clone()),
19270                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
19271                        default_edit_range,
19272                    )),
19273                    insert_text_format: Some(default_insert_text_format),
19274                    insert_text_mode: Some(default_insert_text_mode),
19275                }),
19276                ..lsp::CompletionList::default()
19277            })))
19278        }
19279    })
19280    .next()
19281    .await;
19282
19283    let resolved_items = Arc::new(Mutex::new(Vec::new()));
19284    cx.lsp
19285        .server
19286        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19287            let closure_resolved_items = resolved_items.clone();
19288            move |item_to_resolve, _| {
19289                let closure_resolved_items = closure_resolved_items.clone();
19290                async move {
19291                    closure_resolved_items.lock().push(item_to_resolve.clone());
19292                    Ok(item_to_resolve)
19293                }
19294            }
19295        })
19296        .detach();
19297
19298    cx.condition(|editor, _| editor.context_menu_visible())
19299        .await;
19300    cx.run_until_parked();
19301    cx.update_editor(|editor, _, _| {
19302        let menu = editor.context_menu.borrow_mut();
19303        match menu.as_ref().expect("should have the completions menu") {
19304            CodeContextMenu::Completions(completions_menu) => {
19305                assert_eq!(
19306                    completions_menu
19307                        .entries
19308                        .borrow()
19309                        .iter()
19310                        .map(|mat| mat.string.clone())
19311                        .collect::<Vec<String>>(),
19312                    items
19313                        .iter()
19314                        .map(|completion| completion.label.clone())
19315                        .collect::<Vec<String>>()
19316                );
19317            }
19318            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
19319        }
19320    });
19321    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
19322    // with 4 from the end.
19323    assert_eq!(
19324        *resolved_items.lock(),
19325        [&items[0..16], &items[items.len() - 4..items.len()]]
19326            .concat()
19327            .iter()
19328            .cloned()
19329            .map(|mut item| {
19330                if item.data.is_none() {
19331                    item.data = Some(default_data.clone());
19332                }
19333                item
19334            })
19335            .collect::<Vec<lsp::CompletionItem>>(),
19336        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
19337    );
19338    resolved_items.lock().clear();
19339
19340    cx.update_editor(|editor, window, cx| {
19341        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19342    });
19343    cx.run_until_parked();
19344    // Completions that have already been resolved are skipped.
19345    assert_eq!(
19346        *resolved_items.lock(),
19347        items[items.len() - 17..items.len() - 4]
19348            .iter()
19349            .cloned()
19350            .map(|mut item| {
19351                if item.data.is_none() {
19352                    item.data = Some(default_data.clone());
19353                }
19354                item
19355            })
19356            .collect::<Vec<lsp::CompletionItem>>()
19357    );
19358    resolved_items.lock().clear();
19359}
19360
19361#[gpui::test]
19362async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
19363    init_test(cx, |_| {});
19364
19365    let mut cx = EditorLspTestContext::new(
19366        Language::new(
19367            LanguageConfig {
19368                matcher: LanguageMatcher {
19369                    path_suffixes: vec!["jsx".into()],
19370                    ..Default::default()
19371                },
19372                overrides: [(
19373                    "element".into(),
19374                    LanguageConfigOverride {
19375                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
19376                        ..Default::default()
19377                    },
19378                )]
19379                .into_iter()
19380                .collect(),
19381                ..Default::default()
19382            },
19383            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
19384        )
19385        .with_override_query("(jsx_self_closing_element) @element")
19386        .unwrap(),
19387        lsp::ServerCapabilities {
19388            completion_provider: Some(lsp::CompletionOptions {
19389                trigger_characters: Some(vec![":".to_string()]),
19390                ..Default::default()
19391            }),
19392            ..Default::default()
19393        },
19394        cx,
19395    )
19396    .await;
19397
19398    cx.lsp
19399        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19400            Ok(Some(lsp::CompletionResponse::Array(vec![
19401                lsp::CompletionItem {
19402                    label: "bg-blue".into(),
19403                    ..Default::default()
19404                },
19405                lsp::CompletionItem {
19406                    label: "bg-red".into(),
19407                    ..Default::default()
19408                },
19409                lsp::CompletionItem {
19410                    label: "bg-yellow".into(),
19411                    ..Default::default()
19412                },
19413            ])))
19414        });
19415
19416    cx.set_state(r#"<p class="bgˇ" />"#);
19417
19418    // Trigger completion when typing a dash, because the dash is an extra
19419    // word character in the 'element' scope, which contains the cursor.
19420    cx.simulate_keystroke("-");
19421    cx.executor().run_until_parked();
19422    cx.update_editor(|editor, _, _| {
19423        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19424        {
19425            assert_eq!(
19426                completion_menu_entries(menu),
19427                &["bg-blue", "bg-red", "bg-yellow"]
19428            );
19429        } else {
19430            panic!("expected completion menu to be open");
19431        }
19432    });
19433
19434    cx.simulate_keystroke("l");
19435    cx.executor().run_until_parked();
19436    cx.update_editor(|editor, _, _| {
19437        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19438        {
19439            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
19440        } else {
19441            panic!("expected completion menu to be open");
19442        }
19443    });
19444
19445    // When filtering completions, consider the character after the '-' to
19446    // be the start of a subword.
19447    cx.set_state(r#"<p class="yelˇ" />"#);
19448    cx.simulate_keystroke("l");
19449    cx.executor().run_until_parked();
19450    cx.update_editor(|editor, _, _| {
19451        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19452        {
19453            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
19454        } else {
19455            panic!("expected completion menu to be open");
19456        }
19457    });
19458}
19459
19460fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19461    let entries = menu.entries.borrow();
19462    entries.iter().map(|mat| mat.string.clone()).collect()
19463}
19464
19465#[gpui::test]
19466async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19467    init_test(cx, |settings| {
19468        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19469    });
19470
19471    let fs = FakeFs::new(cx.executor());
19472    fs.insert_file(path!("/file.ts"), Default::default()).await;
19473
19474    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19475    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19476
19477    language_registry.add(Arc::new(Language::new(
19478        LanguageConfig {
19479            name: "TypeScript".into(),
19480            matcher: LanguageMatcher {
19481                path_suffixes: vec!["ts".to_string()],
19482                ..Default::default()
19483            },
19484            ..Default::default()
19485        },
19486        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19487    )));
19488    update_test_language_settings(cx, |settings| {
19489        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19490    });
19491
19492    let test_plugin = "test_plugin";
19493    let _ = language_registry.register_fake_lsp(
19494        "TypeScript",
19495        FakeLspAdapter {
19496            prettier_plugins: vec![test_plugin],
19497            ..Default::default()
19498        },
19499    );
19500
19501    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19502    let buffer = project
19503        .update(cx, |project, cx| {
19504            project.open_local_buffer(path!("/file.ts"), cx)
19505        })
19506        .await
19507        .unwrap();
19508
19509    let buffer_text = "one\ntwo\nthree\n";
19510    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19511    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19512    editor.update_in(cx, |editor, window, cx| {
19513        editor.set_text(buffer_text, window, cx)
19514    });
19515
19516    editor
19517        .update_in(cx, |editor, window, cx| {
19518            editor.perform_format(
19519                project.clone(),
19520                FormatTrigger::Manual,
19521                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19522                window,
19523                cx,
19524            )
19525        })
19526        .unwrap()
19527        .await;
19528    assert_eq!(
19529        editor.update(cx, |editor, cx| editor.text(cx)),
19530        buffer_text.to_string() + prettier_format_suffix,
19531        "Test prettier formatting was not applied to the original buffer text",
19532    );
19533
19534    update_test_language_settings(cx, |settings| {
19535        settings.defaults.formatter = Some(FormatterList::default())
19536    });
19537    let format = editor.update_in(cx, |editor, window, cx| {
19538        editor.perform_format(
19539            project.clone(),
19540            FormatTrigger::Manual,
19541            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19542            window,
19543            cx,
19544        )
19545    });
19546    format.await.unwrap();
19547    assert_eq!(
19548        editor.update(cx, |editor, cx| editor.text(cx)),
19549        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19550        "Autoformatting (via test prettier) was not applied to the original buffer text",
19551    );
19552}
19553
19554#[gpui::test]
19555async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
19556    init_test(cx, |settings| {
19557        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19558    });
19559
19560    let fs = FakeFs::new(cx.executor());
19561    fs.insert_file(path!("/file.settings"), Default::default())
19562        .await;
19563
19564    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
19565    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19566
19567    let ts_lang = Arc::new(Language::new(
19568        LanguageConfig {
19569            name: "TypeScript".into(),
19570            matcher: LanguageMatcher {
19571                path_suffixes: vec!["ts".to_string()],
19572                ..LanguageMatcher::default()
19573            },
19574            prettier_parser_name: Some("typescript".to_string()),
19575            ..LanguageConfig::default()
19576        },
19577        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19578    ));
19579
19580    language_registry.add(ts_lang.clone());
19581
19582    update_test_language_settings(cx, |settings| {
19583        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19584    });
19585
19586    let test_plugin = "test_plugin";
19587    let _ = language_registry.register_fake_lsp(
19588        "TypeScript",
19589        FakeLspAdapter {
19590            prettier_plugins: vec![test_plugin],
19591            ..Default::default()
19592        },
19593    );
19594
19595    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19596    let buffer = project
19597        .update(cx, |project, cx| {
19598            project.open_local_buffer(path!("/file.settings"), cx)
19599        })
19600        .await
19601        .unwrap();
19602
19603    project.update(cx, |project, cx| {
19604        project.set_language_for_buffer(&buffer, ts_lang, cx)
19605    });
19606
19607    let buffer_text = "one\ntwo\nthree\n";
19608    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19609    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19610    editor.update_in(cx, |editor, window, cx| {
19611        editor.set_text(buffer_text, window, cx)
19612    });
19613
19614    editor
19615        .update_in(cx, |editor, window, cx| {
19616            editor.perform_format(
19617                project.clone(),
19618                FormatTrigger::Manual,
19619                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19620                window,
19621                cx,
19622            )
19623        })
19624        .unwrap()
19625        .await;
19626    assert_eq!(
19627        editor.update(cx, |editor, cx| editor.text(cx)),
19628        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
19629        "Test prettier formatting was not applied to the original buffer text",
19630    );
19631
19632    update_test_language_settings(cx, |settings| {
19633        settings.defaults.formatter = Some(FormatterList::default())
19634    });
19635    let format = editor.update_in(cx, |editor, window, cx| {
19636        editor.perform_format(
19637            project.clone(),
19638            FormatTrigger::Manual,
19639            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19640            window,
19641            cx,
19642        )
19643    });
19644    format.await.unwrap();
19645
19646    assert_eq!(
19647        editor.update(cx, |editor, cx| editor.text(cx)),
19648        buffer_text.to_string()
19649            + prettier_format_suffix
19650            + "\ntypescript\n"
19651            + prettier_format_suffix
19652            + "\ntypescript",
19653        "Autoformatting (via test prettier) was not applied to the original buffer text",
19654    );
19655}
19656
19657#[gpui::test]
19658async fn test_addition_reverts(cx: &mut TestAppContext) {
19659    init_test(cx, |_| {});
19660    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19661    let base_text = indoc! {r#"
19662        struct Row;
19663        struct Row1;
19664        struct Row2;
19665
19666        struct Row4;
19667        struct Row5;
19668        struct Row6;
19669
19670        struct Row8;
19671        struct Row9;
19672        struct Row10;"#};
19673
19674    // When addition hunks are not adjacent to carets, no hunk revert is performed
19675    assert_hunk_revert(
19676        indoc! {r#"struct Row;
19677                   struct Row1;
19678                   struct Row1.1;
19679                   struct Row1.2;
19680                   struct Row2;ˇ
19681
19682                   struct Row4;
19683                   struct Row5;
19684                   struct Row6;
19685
19686                   struct Row8;
19687                   ˇstruct Row9;
19688                   struct Row9.1;
19689                   struct Row9.2;
19690                   struct Row9.3;
19691                   struct Row10;"#},
19692        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19693        indoc! {r#"struct Row;
19694                   struct Row1;
19695                   struct Row1.1;
19696                   struct Row1.2;
19697                   struct Row2;ˇ
19698
19699                   struct Row4;
19700                   struct Row5;
19701                   struct Row6;
19702
19703                   struct Row8;
19704                   ˇstruct Row9;
19705                   struct Row9.1;
19706                   struct Row9.2;
19707                   struct Row9.3;
19708                   struct Row10;"#},
19709        base_text,
19710        &mut cx,
19711    );
19712    // Same for selections
19713    assert_hunk_revert(
19714        indoc! {r#"struct Row;
19715                   struct Row1;
19716                   struct Row2;
19717                   struct Row2.1;
19718                   struct Row2.2;
19719                   «ˇ
19720                   struct Row4;
19721                   struct» Row5;
19722                   «struct Row6;
19723                   ˇ»
19724                   struct Row9.1;
19725                   struct Row9.2;
19726                   struct Row9.3;
19727                   struct Row8;
19728                   struct Row9;
19729                   struct Row10;"#},
19730        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19731        indoc! {r#"struct Row;
19732                   struct Row1;
19733                   struct Row2;
19734                   struct Row2.1;
19735                   struct Row2.2;
19736                   «ˇ
19737                   struct Row4;
19738                   struct» Row5;
19739                   «struct Row6;
19740                   ˇ»
19741                   struct Row9.1;
19742                   struct Row9.2;
19743                   struct Row9.3;
19744                   struct Row8;
19745                   struct Row9;
19746                   struct Row10;"#},
19747        base_text,
19748        &mut cx,
19749    );
19750
19751    // When carets and selections intersect the addition hunks, those are reverted.
19752    // Adjacent carets got merged.
19753    assert_hunk_revert(
19754        indoc! {r#"struct Row;
19755                   ˇ// something on the top
19756                   struct Row1;
19757                   struct Row2;
19758                   struct Roˇw3.1;
19759                   struct Row2.2;
19760                   struct Row2.3;ˇ
19761
19762                   struct Row4;
19763                   struct ˇRow5.1;
19764                   struct Row5.2;
19765                   struct «Rowˇ»5.3;
19766                   struct Row5;
19767                   struct Row6;
19768                   ˇ
19769                   struct Row9.1;
19770                   struct «Rowˇ»9.2;
19771                   struct «ˇRow»9.3;
19772                   struct Row8;
19773                   struct Row9;
19774                   «ˇ// something on bottom»
19775                   struct Row10;"#},
19776        vec![
19777            DiffHunkStatusKind::Added,
19778            DiffHunkStatusKind::Added,
19779            DiffHunkStatusKind::Added,
19780            DiffHunkStatusKind::Added,
19781            DiffHunkStatusKind::Added,
19782        ],
19783        indoc! {r#"struct Row;
19784                   ˇstruct Row1;
19785                   struct Row2;
19786                   ˇ
19787                   struct Row4;
19788                   ˇstruct Row5;
19789                   struct Row6;
19790                   ˇ
19791                   ˇstruct Row8;
19792                   struct Row9;
19793                   ˇstruct Row10;"#},
19794        base_text,
19795        &mut cx,
19796    );
19797}
19798
19799#[gpui::test]
19800async fn test_modification_reverts(cx: &mut TestAppContext) {
19801    init_test(cx, |_| {});
19802    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19803    let base_text = indoc! {r#"
19804        struct Row;
19805        struct Row1;
19806        struct Row2;
19807
19808        struct Row4;
19809        struct Row5;
19810        struct Row6;
19811
19812        struct Row8;
19813        struct Row9;
19814        struct Row10;"#};
19815
19816    // Modification hunks behave the same as the addition ones.
19817    assert_hunk_revert(
19818        indoc! {r#"struct Row;
19819                   struct Row1;
19820                   struct Row33;
19821                   ˇ
19822                   struct Row4;
19823                   struct Row5;
19824                   struct Row6;
19825                   ˇ
19826                   struct Row99;
19827                   struct Row9;
19828                   struct Row10;"#},
19829        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19830        indoc! {r#"struct Row;
19831                   struct Row1;
19832                   struct Row33;
19833                   ˇ
19834                   struct Row4;
19835                   struct Row5;
19836                   struct Row6;
19837                   ˇ
19838                   struct Row99;
19839                   struct Row9;
19840                   struct Row10;"#},
19841        base_text,
19842        &mut cx,
19843    );
19844    assert_hunk_revert(
19845        indoc! {r#"struct Row;
19846                   struct Row1;
19847                   struct Row33;
19848                   «ˇ
19849                   struct Row4;
19850                   struct» Row5;
19851                   «struct Row6;
19852                   ˇ»
19853                   struct Row99;
19854                   struct Row9;
19855                   struct Row10;"#},
19856        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19857        indoc! {r#"struct Row;
19858                   struct Row1;
19859                   struct Row33;
19860                   «ˇ
19861                   struct Row4;
19862                   struct» Row5;
19863                   «struct Row6;
19864                   ˇ»
19865                   struct Row99;
19866                   struct Row9;
19867                   struct Row10;"#},
19868        base_text,
19869        &mut cx,
19870    );
19871
19872    assert_hunk_revert(
19873        indoc! {r#"ˇstruct Row1.1;
19874                   struct Row1;
19875                   «ˇstr»uct Row22;
19876
19877                   struct ˇRow44;
19878                   struct Row5;
19879                   struct «Rˇ»ow66;ˇ
19880
19881                   «struˇ»ct Row88;
19882                   struct Row9;
19883                   struct Row1011;ˇ"#},
19884        vec![
19885            DiffHunkStatusKind::Modified,
19886            DiffHunkStatusKind::Modified,
19887            DiffHunkStatusKind::Modified,
19888            DiffHunkStatusKind::Modified,
19889            DiffHunkStatusKind::Modified,
19890            DiffHunkStatusKind::Modified,
19891        ],
19892        indoc! {r#"struct Row;
19893                   ˇstruct Row1;
19894                   struct Row2;
19895                   ˇ
19896                   struct Row4;
19897                   ˇstruct Row5;
19898                   struct Row6;
19899                   ˇ
19900                   struct Row8;
19901                   ˇstruct Row9;
19902                   struct Row10;ˇ"#},
19903        base_text,
19904        &mut cx,
19905    );
19906}
19907
19908#[gpui::test]
19909async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19910    init_test(cx, |_| {});
19911    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19912    let base_text = indoc! {r#"
19913        one
19914
19915        two
19916        three
19917        "#};
19918
19919    cx.set_head_text(base_text);
19920    cx.set_state("\nˇ\n");
19921    cx.executor().run_until_parked();
19922    cx.update_editor(|editor, _window, cx| {
19923        editor.expand_selected_diff_hunks(cx);
19924    });
19925    cx.executor().run_until_parked();
19926    cx.update_editor(|editor, window, cx| {
19927        editor.backspace(&Default::default(), window, cx);
19928    });
19929    cx.run_until_parked();
19930    cx.assert_state_with_diff(
19931        indoc! {r#"
19932
19933        - two
19934        - threeˇ
19935        +
19936        "#}
19937        .to_string(),
19938    );
19939}
19940
19941#[gpui::test]
19942async fn test_deletion_reverts(cx: &mut TestAppContext) {
19943    init_test(cx, |_| {});
19944    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19945    let base_text = indoc! {r#"struct Row;
19946struct Row1;
19947struct Row2;
19948
19949struct Row4;
19950struct Row5;
19951struct Row6;
19952
19953struct Row8;
19954struct Row9;
19955struct Row10;"#};
19956
19957    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19958    assert_hunk_revert(
19959        indoc! {r#"struct Row;
19960                   struct Row2;
19961
19962                   ˇstruct Row4;
19963                   struct Row5;
19964                   struct Row6;
19965                   ˇ
19966                   struct Row8;
19967                   struct Row10;"#},
19968        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19969        indoc! {r#"struct Row;
19970                   struct Row2;
19971
19972                   ˇstruct Row4;
19973                   struct Row5;
19974                   struct Row6;
19975                   ˇ
19976                   struct Row8;
19977                   struct Row10;"#},
19978        base_text,
19979        &mut cx,
19980    );
19981    assert_hunk_revert(
19982        indoc! {r#"struct Row;
19983                   struct Row2;
19984
19985                   «ˇstruct Row4;
19986                   struct» Row5;
19987                   «struct Row6;
19988                   ˇ»
19989                   struct Row8;
19990                   struct Row10;"#},
19991        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19992        indoc! {r#"struct Row;
19993                   struct Row2;
19994
19995                   «ˇstruct Row4;
19996                   struct» Row5;
19997                   «struct Row6;
19998                   ˇ»
19999                   struct Row8;
20000                   struct Row10;"#},
20001        base_text,
20002        &mut cx,
20003    );
20004
20005    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
20006    assert_hunk_revert(
20007        indoc! {r#"struct Row;
20008                   ˇstruct Row2;
20009
20010                   struct Row4;
20011                   struct Row5;
20012                   struct Row6;
20013
20014                   struct Row8;ˇ
20015                   struct Row10;"#},
20016        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
20017        indoc! {r#"struct Row;
20018                   struct Row1;
20019                   ˇstruct Row2;
20020
20021                   struct Row4;
20022                   struct Row5;
20023                   struct Row6;
20024
20025                   struct Row8;ˇ
20026                   struct Row9;
20027                   struct Row10;"#},
20028        base_text,
20029        &mut cx,
20030    );
20031    assert_hunk_revert(
20032        indoc! {r#"struct Row;
20033                   struct Row2«ˇ;
20034                   struct Row4;
20035                   struct» Row5;
20036                   «struct Row6;
20037
20038                   struct Row8;ˇ»
20039                   struct Row10;"#},
20040        vec![
20041            DiffHunkStatusKind::Deleted,
20042            DiffHunkStatusKind::Deleted,
20043            DiffHunkStatusKind::Deleted,
20044        ],
20045        indoc! {r#"struct Row;
20046                   struct Row1;
20047                   struct Row2«ˇ;
20048
20049                   struct Row4;
20050                   struct» Row5;
20051                   «struct Row6;
20052
20053                   struct Row8;ˇ»
20054                   struct Row9;
20055                   struct Row10;"#},
20056        base_text,
20057        &mut cx,
20058    );
20059}
20060
20061#[gpui::test]
20062async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
20063    init_test(cx, |_| {});
20064
20065    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
20066    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
20067    let base_text_3 =
20068        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
20069
20070    let text_1 = edit_first_char_of_every_line(base_text_1);
20071    let text_2 = edit_first_char_of_every_line(base_text_2);
20072    let text_3 = edit_first_char_of_every_line(base_text_3);
20073
20074    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
20075    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
20076    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
20077
20078    let multibuffer = cx.new(|cx| {
20079        let mut multibuffer = MultiBuffer::new(ReadWrite);
20080        multibuffer.push_excerpts(
20081            buffer_1.clone(),
20082            [
20083                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20084                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20085                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20086            ],
20087            cx,
20088        );
20089        multibuffer.push_excerpts(
20090            buffer_2.clone(),
20091            [
20092                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20093                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20094                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20095            ],
20096            cx,
20097        );
20098        multibuffer.push_excerpts(
20099            buffer_3.clone(),
20100            [
20101                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20102                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20103                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20104            ],
20105            cx,
20106        );
20107        multibuffer
20108    });
20109
20110    let fs = FakeFs::new(cx.executor());
20111    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
20112    let (editor, cx) = cx
20113        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
20114    editor.update_in(cx, |editor, _window, cx| {
20115        for (buffer, diff_base) in [
20116            (buffer_1.clone(), base_text_1),
20117            (buffer_2.clone(), base_text_2),
20118            (buffer_3.clone(), base_text_3),
20119        ] {
20120            let diff = cx.new(|cx| {
20121                BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20122            });
20123            editor
20124                .buffer
20125                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20126        }
20127    });
20128    cx.executor().run_until_parked();
20129
20130    editor.update_in(cx, |editor, window, cx| {
20131        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}");
20132        editor.select_all(&SelectAll, window, cx);
20133        editor.git_restore(&Default::default(), window, cx);
20134    });
20135    cx.executor().run_until_parked();
20136
20137    // When all ranges are selected, all buffer hunks are reverted.
20138    editor.update(cx, |editor, cx| {
20139        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");
20140    });
20141    buffer_1.update(cx, |buffer, _| {
20142        assert_eq!(buffer.text(), base_text_1);
20143    });
20144    buffer_2.update(cx, |buffer, _| {
20145        assert_eq!(buffer.text(), base_text_2);
20146    });
20147    buffer_3.update(cx, |buffer, _| {
20148        assert_eq!(buffer.text(), base_text_3);
20149    });
20150
20151    editor.update_in(cx, |editor, window, cx| {
20152        editor.undo(&Default::default(), window, cx);
20153    });
20154
20155    editor.update_in(cx, |editor, window, cx| {
20156        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20157            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
20158        });
20159        editor.git_restore(&Default::default(), window, cx);
20160    });
20161
20162    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
20163    // but not affect buffer_2 and its related excerpts.
20164    editor.update(cx, |editor, cx| {
20165        assert_eq!(
20166            editor.text(cx),
20167            "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}"
20168        );
20169    });
20170    buffer_1.update(cx, |buffer, _| {
20171        assert_eq!(buffer.text(), base_text_1);
20172    });
20173    buffer_2.update(cx, |buffer, _| {
20174        assert_eq!(
20175            buffer.text(),
20176            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
20177        );
20178    });
20179    buffer_3.update(cx, |buffer, _| {
20180        assert_eq!(
20181            buffer.text(),
20182            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
20183        );
20184    });
20185
20186    fn edit_first_char_of_every_line(text: &str) -> String {
20187        text.split('\n')
20188            .map(|line| format!("X{}", &line[1..]))
20189            .collect::<Vec<_>>()
20190            .join("\n")
20191    }
20192}
20193
20194#[gpui::test]
20195async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
20196    init_test(cx, |_| {});
20197
20198    let cols = 4;
20199    let rows = 10;
20200    let sample_text_1 = sample_text(rows, cols, 'a');
20201    assert_eq!(
20202        sample_text_1,
20203        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
20204    );
20205    let sample_text_2 = sample_text(rows, cols, 'l');
20206    assert_eq!(
20207        sample_text_2,
20208        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
20209    );
20210    let sample_text_3 = sample_text(rows, cols, 'v');
20211    assert_eq!(
20212        sample_text_3,
20213        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
20214    );
20215
20216    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
20217    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
20218    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
20219
20220    let multi_buffer = cx.new(|cx| {
20221        let mut multibuffer = MultiBuffer::new(ReadWrite);
20222        multibuffer.push_excerpts(
20223            buffer_1.clone(),
20224            [
20225                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20226                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20227                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20228            ],
20229            cx,
20230        );
20231        multibuffer.push_excerpts(
20232            buffer_2.clone(),
20233            [
20234                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20235                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20236                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20237            ],
20238            cx,
20239        );
20240        multibuffer.push_excerpts(
20241            buffer_3.clone(),
20242            [
20243                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20244                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20245                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20246            ],
20247            cx,
20248        );
20249        multibuffer
20250    });
20251
20252    let fs = FakeFs::new(cx.executor());
20253    fs.insert_tree(
20254        "/a",
20255        json!({
20256            "main.rs": sample_text_1,
20257            "other.rs": sample_text_2,
20258            "lib.rs": sample_text_3,
20259        }),
20260    )
20261    .await;
20262    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20263    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20264    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20265    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20266        Editor::new(
20267            EditorMode::full(),
20268            multi_buffer,
20269            Some(project.clone()),
20270            window,
20271            cx,
20272        )
20273    });
20274    let multibuffer_item_id = workspace
20275        .update(cx, |workspace, window, cx| {
20276            assert!(
20277                workspace.active_item(cx).is_none(),
20278                "active item should be None before the first item is added"
20279            );
20280            workspace.add_item_to_active_pane(
20281                Box::new(multi_buffer_editor.clone()),
20282                None,
20283                true,
20284                window,
20285                cx,
20286            );
20287            let active_item = workspace
20288                .active_item(cx)
20289                .expect("should have an active item after adding the multi buffer");
20290            assert_eq!(
20291                active_item.buffer_kind(cx),
20292                ItemBufferKind::Multibuffer,
20293                "A multi buffer was expected to active after adding"
20294            );
20295            active_item.item_id()
20296        })
20297        .unwrap();
20298    cx.executor().run_until_parked();
20299
20300    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20301        editor.change_selections(
20302            SelectionEffects::scroll(Autoscroll::Next),
20303            window,
20304            cx,
20305            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
20306        );
20307        editor.open_excerpts(&OpenExcerpts, window, cx);
20308    });
20309    cx.executor().run_until_parked();
20310    let first_item_id = workspace
20311        .update(cx, |workspace, window, cx| {
20312            let active_item = workspace
20313                .active_item(cx)
20314                .expect("should have an active item after navigating into the 1st buffer");
20315            let first_item_id = active_item.item_id();
20316            assert_ne!(
20317                first_item_id, multibuffer_item_id,
20318                "Should navigate into the 1st buffer and activate it"
20319            );
20320            assert_eq!(
20321                active_item.buffer_kind(cx),
20322                ItemBufferKind::Singleton,
20323                "New active item should be a singleton buffer"
20324            );
20325            assert_eq!(
20326                active_item
20327                    .act_as::<Editor>(cx)
20328                    .expect("should have navigated into an editor for the 1st buffer")
20329                    .read(cx)
20330                    .text(cx),
20331                sample_text_1
20332            );
20333
20334            workspace
20335                .go_back(workspace.active_pane().downgrade(), window, cx)
20336                .detach_and_log_err(cx);
20337
20338            first_item_id
20339        })
20340        .unwrap();
20341    cx.executor().run_until_parked();
20342    workspace
20343        .update(cx, |workspace, _, cx| {
20344            let active_item = workspace
20345                .active_item(cx)
20346                .expect("should have an active item after navigating back");
20347            assert_eq!(
20348                active_item.item_id(),
20349                multibuffer_item_id,
20350                "Should navigate back to the multi buffer"
20351            );
20352            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20353        })
20354        .unwrap();
20355
20356    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20357        editor.change_selections(
20358            SelectionEffects::scroll(Autoscroll::Next),
20359            window,
20360            cx,
20361            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
20362        );
20363        editor.open_excerpts(&OpenExcerpts, window, cx);
20364    });
20365    cx.executor().run_until_parked();
20366    let second_item_id = workspace
20367        .update(cx, |workspace, window, cx| {
20368            let active_item = workspace
20369                .active_item(cx)
20370                .expect("should have an active item after navigating into the 2nd buffer");
20371            let second_item_id = active_item.item_id();
20372            assert_ne!(
20373                second_item_id, multibuffer_item_id,
20374                "Should navigate away from the multibuffer"
20375            );
20376            assert_ne!(
20377                second_item_id, first_item_id,
20378                "Should navigate into the 2nd buffer and activate it"
20379            );
20380            assert_eq!(
20381                active_item.buffer_kind(cx),
20382                ItemBufferKind::Singleton,
20383                "New active item should be a singleton buffer"
20384            );
20385            assert_eq!(
20386                active_item
20387                    .act_as::<Editor>(cx)
20388                    .expect("should have navigated into an editor")
20389                    .read(cx)
20390                    .text(cx),
20391                sample_text_2
20392            );
20393
20394            workspace
20395                .go_back(workspace.active_pane().downgrade(), window, cx)
20396                .detach_and_log_err(cx);
20397
20398            second_item_id
20399        })
20400        .unwrap();
20401    cx.executor().run_until_parked();
20402    workspace
20403        .update(cx, |workspace, _, cx| {
20404            let active_item = workspace
20405                .active_item(cx)
20406                .expect("should have an active item after navigating back from the 2nd buffer");
20407            assert_eq!(
20408                active_item.item_id(),
20409                multibuffer_item_id,
20410                "Should navigate back from the 2nd buffer to the multi buffer"
20411            );
20412            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20413        })
20414        .unwrap();
20415
20416    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20417        editor.change_selections(
20418            SelectionEffects::scroll(Autoscroll::Next),
20419            window,
20420            cx,
20421            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
20422        );
20423        editor.open_excerpts(&OpenExcerpts, window, cx);
20424    });
20425    cx.executor().run_until_parked();
20426    workspace
20427        .update(cx, |workspace, window, cx| {
20428            let active_item = workspace
20429                .active_item(cx)
20430                .expect("should have an active item after navigating into the 3rd buffer");
20431            let third_item_id = active_item.item_id();
20432            assert_ne!(
20433                third_item_id, multibuffer_item_id,
20434                "Should navigate into the 3rd buffer and activate it"
20435            );
20436            assert_ne!(third_item_id, first_item_id);
20437            assert_ne!(third_item_id, second_item_id);
20438            assert_eq!(
20439                active_item.buffer_kind(cx),
20440                ItemBufferKind::Singleton,
20441                "New active item should be a singleton buffer"
20442            );
20443            assert_eq!(
20444                active_item
20445                    .act_as::<Editor>(cx)
20446                    .expect("should have navigated into an editor")
20447                    .read(cx)
20448                    .text(cx),
20449                sample_text_3
20450            );
20451
20452            workspace
20453                .go_back(workspace.active_pane().downgrade(), window, cx)
20454                .detach_and_log_err(cx);
20455        })
20456        .unwrap();
20457    cx.executor().run_until_parked();
20458    workspace
20459        .update(cx, |workspace, _, cx| {
20460            let active_item = workspace
20461                .active_item(cx)
20462                .expect("should have an active item after navigating back from the 3rd buffer");
20463            assert_eq!(
20464                active_item.item_id(),
20465                multibuffer_item_id,
20466                "Should navigate back from the 3rd buffer to the multi buffer"
20467            );
20468            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20469        })
20470        .unwrap();
20471}
20472
20473#[gpui::test]
20474async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20475    init_test(cx, |_| {});
20476
20477    let mut cx = EditorTestContext::new(cx).await;
20478
20479    let diff_base = r#"
20480        use some::mod;
20481
20482        const A: u32 = 42;
20483
20484        fn main() {
20485            println!("hello");
20486
20487            println!("world");
20488        }
20489        "#
20490    .unindent();
20491
20492    cx.set_state(
20493        &r#"
20494        use some::modified;
20495
20496        ˇ
20497        fn main() {
20498            println!("hello there");
20499
20500            println!("around the");
20501            println!("world");
20502        }
20503        "#
20504        .unindent(),
20505    );
20506
20507    cx.set_head_text(&diff_base);
20508    executor.run_until_parked();
20509
20510    cx.update_editor(|editor, window, cx| {
20511        editor.go_to_next_hunk(&GoToHunk, window, cx);
20512        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20513    });
20514    executor.run_until_parked();
20515    cx.assert_state_with_diff(
20516        r#"
20517          use some::modified;
20518
20519
20520          fn main() {
20521        -     println!("hello");
20522        + ˇ    println!("hello there");
20523
20524              println!("around the");
20525              println!("world");
20526          }
20527        "#
20528        .unindent(),
20529    );
20530
20531    cx.update_editor(|editor, window, cx| {
20532        for _ in 0..2 {
20533            editor.go_to_next_hunk(&GoToHunk, window, cx);
20534            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20535        }
20536    });
20537    executor.run_until_parked();
20538    cx.assert_state_with_diff(
20539        r#"
20540        - use some::mod;
20541        + ˇuse some::modified;
20542
20543
20544          fn main() {
20545        -     println!("hello");
20546        +     println!("hello there");
20547
20548        +     println!("around the");
20549              println!("world");
20550          }
20551        "#
20552        .unindent(),
20553    );
20554
20555    cx.update_editor(|editor, window, cx| {
20556        editor.go_to_next_hunk(&GoToHunk, window, cx);
20557        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20558    });
20559    executor.run_until_parked();
20560    cx.assert_state_with_diff(
20561        r#"
20562        - use some::mod;
20563        + use some::modified;
20564
20565        - const A: u32 = 42;
20566          ˇ
20567          fn main() {
20568        -     println!("hello");
20569        +     println!("hello there");
20570
20571        +     println!("around the");
20572              println!("world");
20573          }
20574        "#
20575        .unindent(),
20576    );
20577
20578    cx.update_editor(|editor, window, cx| {
20579        editor.cancel(&Cancel, window, cx);
20580    });
20581
20582    cx.assert_state_with_diff(
20583        r#"
20584          use some::modified;
20585
20586          ˇ
20587          fn main() {
20588              println!("hello there");
20589
20590              println!("around the");
20591              println!("world");
20592          }
20593        "#
20594        .unindent(),
20595    );
20596}
20597
20598#[gpui::test]
20599async fn test_diff_base_change_with_expanded_diff_hunks(
20600    executor: BackgroundExecutor,
20601    cx: &mut TestAppContext,
20602) {
20603    init_test(cx, |_| {});
20604
20605    let mut cx = EditorTestContext::new(cx).await;
20606
20607    let diff_base = r#"
20608        use some::mod1;
20609        use some::mod2;
20610
20611        const A: u32 = 42;
20612        const B: u32 = 42;
20613        const C: u32 = 42;
20614
20615        fn main() {
20616            println!("hello");
20617
20618            println!("world");
20619        }
20620        "#
20621    .unindent();
20622
20623    cx.set_state(
20624        &r#"
20625        use some::mod2;
20626
20627        const A: u32 = 42;
20628        const C: u32 = 42;
20629
20630        fn main(ˇ) {
20631            //println!("hello");
20632
20633            println!("world");
20634            //
20635            //
20636        }
20637        "#
20638        .unindent(),
20639    );
20640
20641    cx.set_head_text(&diff_base);
20642    executor.run_until_parked();
20643
20644    cx.update_editor(|editor, window, cx| {
20645        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20646    });
20647    executor.run_until_parked();
20648    cx.assert_state_with_diff(
20649        r#"
20650        - use some::mod1;
20651          use some::mod2;
20652
20653          const A: u32 = 42;
20654        - const B: u32 = 42;
20655          const C: u32 = 42;
20656
20657          fn main(ˇ) {
20658        -     println!("hello");
20659        +     //println!("hello");
20660
20661              println!("world");
20662        +     //
20663        +     //
20664          }
20665        "#
20666        .unindent(),
20667    );
20668
20669    cx.set_head_text("new diff base!");
20670    executor.run_until_parked();
20671    cx.assert_state_with_diff(
20672        r#"
20673        - new diff base!
20674        + use some::mod2;
20675        +
20676        + const A: u32 = 42;
20677        + const C: u32 = 42;
20678        +
20679        + fn main(ˇ) {
20680        +     //println!("hello");
20681        +
20682        +     println!("world");
20683        +     //
20684        +     //
20685        + }
20686        "#
20687        .unindent(),
20688    );
20689}
20690
20691#[gpui::test]
20692async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20693    init_test(cx, |_| {});
20694
20695    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20696    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20697    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20698    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20699    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20700    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20701
20702    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20703    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20704    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20705
20706    let multi_buffer = cx.new(|cx| {
20707        let mut multibuffer = MultiBuffer::new(ReadWrite);
20708        multibuffer.push_excerpts(
20709            buffer_1.clone(),
20710            [
20711                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20712                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20713                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20714            ],
20715            cx,
20716        );
20717        multibuffer.push_excerpts(
20718            buffer_2.clone(),
20719            [
20720                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20721                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20722                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20723            ],
20724            cx,
20725        );
20726        multibuffer.push_excerpts(
20727            buffer_3.clone(),
20728            [
20729                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20730                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20731                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20732            ],
20733            cx,
20734        );
20735        multibuffer
20736    });
20737
20738    let editor =
20739        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20740    editor
20741        .update(cx, |editor, _window, cx| {
20742            for (buffer, diff_base) in [
20743                (buffer_1.clone(), file_1_old),
20744                (buffer_2.clone(), file_2_old),
20745                (buffer_3.clone(), file_3_old),
20746            ] {
20747                let diff = cx.new(|cx| {
20748                    BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
20749                });
20750                editor
20751                    .buffer
20752                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20753            }
20754        })
20755        .unwrap();
20756
20757    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20758    cx.run_until_parked();
20759
20760    cx.assert_editor_state(
20761        &"
20762            ˇaaa
20763            ccc
20764            ddd
20765
20766            ggg
20767            hhh
20768
20769
20770            lll
20771            mmm
20772            NNN
20773
20774            qqq
20775            rrr
20776
20777            uuu
20778            111
20779            222
20780            333
20781
20782            666
20783            777
20784
20785            000
20786            !!!"
20787        .unindent(),
20788    );
20789
20790    cx.update_editor(|editor, window, cx| {
20791        editor.select_all(&SelectAll, window, cx);
20792        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20793    });
20794    cx.executor().run_until_parked();
20795
20796    cx.assert_state_with_diff(
20797        "
20798            «aaa
20799          - bbb
20800            ccc
20801            ddd
20802
20803            ggg
20804            hhh
20805
20806
20807            lll
20808            mmm
20809          - nnn
20810          + NNN
20811
20812            qqq
20813            rrr
20814
20815            uuu
20816            111
20817            222
20818            333
20819
20820          + 666
20821            777
20822
20823            000
20824            !!!ˇ»"
20825            .unindent(),
20826    );
20827}
20828
20829#[gpui::test]
20830async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20831    init_test(cx, |_| {});
20832
20833    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20834    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20835
20836    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20837    let multi_buffer = cx.new(|cx| {
20838        let mut multibuffer = MultiBuffer::new(ReadWrite);
20839        multibuffer.push_excerpts(
20840            buffer.clone(),
20841            [
20842                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20843                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20844                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20845            ],
20846            cx,
20847        );
20848        multibuffer
20849    });
20850
20851    let editor =
20852        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20853    editor
20854        .update(cx, |editor, _window, cx| {
20855            let diff = cx.new(|cx| {
20856                BufferDiff::new_with_base_text(base, &buffer.read(cx).text_snapshot(), cx)
20857            });
20858            editor
20859                .buffer
20860                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20861        })
20862        .unwrap();
20863
20864    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20865    cx.run_until_parked();
20866
20867    cx.update_editor(|editor, window, cx| {
20868        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20869    });
20870    cx.executor().run_until_parked();
20871
20872    // When the start of a hunk coincides with the start of its excerpt,
20873    // the hunk is expanded. When the start of a hunk is earlier than
20874    // the start of its excerpt, the hunk is not expanded.
20875    cx.assert_state_with_diff(
20876        "
20877            ˇaaa
20878          - bbb
20879          + BBB
20880
20881          - ddd
20882          - eee
20883          + DDD
20884          + EEE
20885            fff
20886
20887            iii
20888        "
20889        .unindent(),
20890    );
20891}
20892
20893#[gpui::test]
20894async fn test_edits_around_expanded_insertion_hunks(
20895    executor: BackgroundExecutor,
20896    cx: &mut TestAppContext,
20897) {
20898    init_test(cx, |_| {});
20899
20900    let mut cx = EditorTestContext::new(cx).await;
20901
20902    let diff_base = r#"
20903        use some::mod1;
20904        use some::mod2;
20905
20906        const A: u32 = 42;
20907
20908        fn main() {
20909            println!("hello");
20910
20911            println!("world");
20912        }
20913        "#
20914    .unindent();
20915    executor.run_until_parked();
20916    cx.set_state(
20917        &r#"
20918        use some::mod1;
20919        use some::mod2;
20920
20921        const A: u32 = 42;
20922        const B: u32 = 42;
20923        const C: u32 = 42;
20924        ˇ
20925
20926        fn main() {
20927            println!("hello");
20928
20929            println!("world");
20930        }
20931        "#
20932        .unindent(),
20933    );
20934
20935    cx.set_head_text(&diff_base);
20936    executor.run_until_parked();
20937
20938    cx.update_editor(|editor, window, cx| {
20939        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20940    });
20941    executor.run_until_parked();
20942
20943    cx.assert_state_with_diff(
20944        r#"
20945        use some::mod1;
20946        use some::mod2;
20947
20948        const A: u32 = 42;
20949      + const B: u32 = 42;
20950      + const C: u32 = 42;
20951      + ˇ
20952
20953        fn main() {
20954            println!("hello");
20955
20956            println!("world");
20957        }
20958      "#
20959        .unindent(),
20960    );
20961
20962    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20963    executor.run_until_parked();
20964
20965    cx.assert_state_with_diff(
20966        r#"
20967        use some::mod1;
20968        use some::mod2;
20969
20970        const A: u32 = 42;
20971      + const B: u32 = 42;
20972      + const C: u32 = 42;
20973      + const D: u32 = 42;
20974      + ˇ
20975
20976        fn main() {
20977            println!("hello");
20978
20979            println!("world");
20980        }
20981      "#
20982        .unindent(),
20983    );
20984
20985    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20986    executor.run_until_parked();
20987
20988    cx.assert_state_with_diff(
20989        r#"
20990        use some::mod1;
20991        use some::mod2;
20992
20993        const A: u32 = 42;
20994      + const B: u32 = 42;
20995      + const C: u32 = 42;
20996      + const D: u32 = 42;
20997      + const E: u32 = 42;
20998      + ˇ
20999
21000        fn main() {
21001            println!("hello");
21002
21003            println!("world");
21004        }
21005      "#
21006        .unindent(),
21007    );
21008
21009    cx.update_editor(|editor, window, cx| {
21010        editor.delete_line(&DeleteLine, window, cx);
21011    });
21012    executor.run_until_parked();
21013
21014    cx.assert_state_with_diff(
21015        r#"
21016        use some::mod1;
21017        use some::mod2;
21018
21019        const A: u32 = 42;
21020      + const B: u32 = 42;
21021      + const C: u32 = 42;
21022      + const D: u32 = 42;
21023      + const E: u32 = 42;
21024        ˇ
21025        fn main() {
21026            println!("hello");
21027
21028            println!("world");
21029        }
21030      "#
21031        .unindent(),
21032    );
21033
21034    cx.update_editor(|editor, window, cx| {
21035        editor.move_up(&MoveUp, window, cx);
21036        editor.delete_line(&DeleteLine, window, cx);
21037        editor.move_up(&MoveUp, window, cx);
21038        editor.delete_line(&DeleteLine, window, cx);
21039        editor.move_up(&MoveUp, window, cx);
21040        editor.delete_line(&DeleteLine, window, cx);
21041    });
21042    executor.run_until_parked();
21043    cx.assert_state_with_diff(
21044        r#"
21045        use some::mod1;
21046        use some::mod2;
21047
21048        const A: u32 = 42;
21049      + const B: u32 = 42;
21050        ˇ
21051        fn main() {
21052            println!("hello");
21053
21054            println!("world");
21055        }
21056      "#
21057        .unindent(),
21058    );
21059
21060    cx.update_editor(|editor, window, cx| {
21061        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
21062        editor.delete_line(&DeleteLine, window, cx);
21063    });
21064    executor.run_until_parked();
21065    cx.assert_state_with_diff(
21066        r#"
21067        ˇ
21068        fn main() {
21069            println!("hello");
21070
21071            println!("world");
21072        }
21073      "#
21074        .unindent(),
21075    );
21076}
21077
21078#[gpui::test]
21079async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
21080    init_test(cx, |_| {});
21081
21082    let mut cx = EditorTestContext::new(cx).await;
21083    cx.set_head_text(indoc! { "
21084        one
21085        two
21086        three
21087        four
21088        five
21089        "
21090    });
21091    cx.set_state(indoc! { "
21092        one
21093        ˇthree
21094        five
21095    "});
21096    cx.run_until_parked();
21097    cx.update_editor(|editor, window, cx| {
21098        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21099    });
21100    cx.assert_state_with_diff(
21101        indoc! { "
21102        one
21103      - two
21104        ˇthree
21105      - four
21106        five
21107    "}
21108        .to_string(),
21109    );
21110    cx.update_editor(|editor, window, cx| {
21111        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21112    });
21113
21114    cx.assert_state_with_diff(
21115        indoc! { "
21116        one
21117        ˇthree
21118        five
21119    "}
21120        .to_string(),
21121    );
21122
21123    cx.update_editor(|editor, window, cx| {
21124        editor.move_up(&MoveUp, window, cx);
21125        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21126    });
21127    cx.assert_state_with_diff(
21128        indoc! { "
21129        ˇone
21130      - two
21131        three
21132        five
21133    "}
21134        .to_string(),
21135    );
21136
21137    cx.update_editor(|editor, window, cx| {
21138        editor.move_down(&MoveDown, window, cx);
21139        editor.move_down(&MoveDown, window, cx);
21140        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21141    });
21142    cx.assert_state_with_diff(
21143        indoc! { "
21144        one
21145      - two
21146        ˇthree
21147      - four
21148        five
21149    "}
21150        .to_string(),
21151    );
21152
21153    cx.set_state(indoc! { "
21154        one
21155        ˇTWO
21156        three
21157        four
21158        five
21159    "});
21160    cx.run_until_parked();
21161    cx.update_editor(|editor, window, cx| {
21162        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21163    });
21164
21165    cx.assert_state_with_diff(
21166        indoc! { "
21167            one
21168          - two
21169          + ˇTWO
21170            three
21171            four
21172            five
21173        "}
21174        .to_string(),
21175    );
21176    cx.update_editor(|editor, window, cx| {
21177        editor.move_up(&Default::default(), window, cx);
21178        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
21179    });
21180    cx.assert_state_with_diff(
21181        indoc! { "
21182            one
21183            ˇTWO
21184            three
21185            four
21186            five
21187        "}
21188        .to_string(),
21189    );
21190}
21191
21192#[gpui::test]
21193async fn test_toggling_adjacent_diff_hunks_2(
21194    executor: BackgroundExecutor,
21195    cx: &mut TestAppContext,
21196) {
21197    init_test(cx, |_| {});
21198
21199    let mut cx = EditorTestContext::new(cx).await;
21200
21201    let diff_base = r#"
21202        lineA
21203        lineB
21204        lineC
21205        lineD
21206        "#
21207    .unindent();
21208
21209    cx.set_state(
21210        &r#"
21211        ˇlineA1
21212        lineB
21213        lineD
21214        "#
21215        .unindent(),
21216    );
21217    cx.set_head_text(&diff_base);
21218    executor.run_until_parked();
21219
21220    cx.update_editor(|editor, window, cx| {
21221        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21222    });
21223    executor.run_until_parked();
21224    cx.assert_state_with_diff(
21225        r#"
21226        - lineA
21227        + ˇlineA1
21228          lineB
21229          lineD
21230        "#
21231        .unindent(),
21232    );
21233
21234    cx.update_editor(|editor, window, cx| {
21235        editor.move_down(&MoveDown, window, cx);
21236        editor.move_right(&MoveRight, window, cx);
21237        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21238    });
21239    executor.run_until_parked();
21240    cx.assert_state_with_diff(
21241        r#"
21242        - lineA
21243        + lineA1
21244          lˇineB
21245        - lineC
21246          lineD
21247        "#
21248        .unindent(),
21249    );
21250}
21251
21252#[gpui::test]
21253async fn test_edits_around_expanded_deletion_hunks(
21254    executor: BackgroundExecutor,
21255    cx: &mut TestAppContext,
21256) {
21257    init_test(cx, |_| {});
21258
21259    let mut cx = EditorTestContext::new(cx).await;
21260
21261    let diff_base = r#"
21262        use some::mod1;
21263        use some::mod2;
21264
21265        const A: u32 = 42;
21266        const B: u32 = 42;
21267        const C: u32 = 42;
21268
21269
21270        fn main() {
21271            println!("hello");
21272
21273            println!("world");
21274        }
21275    "#
21276    .unindent();
21277    executor.run_until_parked();
21278    cx.set_state(
21279        &r#"
21280        use some::mod1;
21281        use some::mod2;
21282
21283        ˇconst B: u32 = 42;
21284        const C: u32 = 42;
21285
21286
21287        fn main() {
21288            println!("hello");
21289
21290            println!("world");
21291        }
21292        "#
21293        .unindent(),
21294    );
21295
21296    cx.set_head_text(&diff_base);
21297    executor.run_until_parked();
21298
21299    cx.update_editor(|editor, window, cx| {
21300        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21301    });
21302    executor.run_until_parked();
21303
21304    cx.assert_state_with_diff(
21305        r#"
21306        use some::mod1;
21307        use some::mod2;
21308
21309      - const A: u32 = 42;
21310        ˇconst B: u32 = 42;
21311        const C: u32 = 42;
21312
21313
21314        fn main() {
21315            println!("hello");
21316
21317            println!("world");
21318        }
21319      "#
21320        .unindent(),
21321    );
21322
21323    cx.update_editor(|editor, window, cx| {
21324        editor.delete_line(&DeleteLine, window, cx);
21325    });
21326    executor.run_until_parked();
21327    cx.assert_state_with_diff(
21328        r#"
21329        use some::mod1;
21330        use some::mod2;
21331
21332      - const A: u32 = 42;
21333      - const B: u32 = 42;
21334        ˇconst C: u32 = 42;
21335
21336
21337        fn main() {
21338            println!("hello");
21339
21340            println!("world");
21341        }
21342      "#
21343        .unindent(),
21344    );
21345
21346    cx.update_editor(|editor, window, cx| {
21347        editor.delete_line(&DeleteLine, window, cx);
21348    });
21349    executor.run_until_parked();
21350    cx.assert_state_with_diff(
21351        r#"
21352        use some::mod1;
21353        use some::mod2;
21354
21355      - const A: u32 = 42;
21356      - const B: u32 = 42;
21357      - const C: u32 = 42;
21358        ˇ
21359
21360        fn main() {
21361            println!("hello");
21362
21363            println!("world");
21364        }
21365      "#
21366        .unindent(),
21367    );
21368
21369    cx.update_editor(|editor, window, cx| {
21370        editor.handle_input("replacement", window, cx);
21371    });
21372    executor.run_until_parked();
21373    cx.assert_state_with_diff(
21374        r#"
21375        use some::mod1;
21376        use some::mod2;
21377
21378      - const A: u32 = 42;
21379      - const B: u32 = 42;
21380      - const C: u32 = 42;
21381      -
21382      + replacementˇ
21383
21384        fn main() {
21385            println!("hello");
21386
21387            println!("world");
21388        }
21389      "#
21390        .unindent(),
21391    );
21392}
21393
21394#[gpui::test]
21395async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21396    init_test(cx, |_| {});
21397
21398    let mut cx = EditorTestContext::new(cx).await;
21399
21400    let base_text = r#"
21401        one
21402        two
21403        three
21404        four
21405        five
21406    "#
21407    .unindent();
21408    executor.run_until_parked();
21409    cx.set_state(
21410        &r#"
21411        one
21412        two
21413        fˇour
21414        five
21415        "#
21416        .unindent(),
21417    );
21418
21419    cx.set_head_text(&base_text);
21420    executor.run_until_parked();
21421
21422    cx.update_editor(|editor, window, cx| {
21423        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21424    });
21425    executor.run_until_parked();
21426
21427    cx.assert_state_with_diff(
21428        r#"
21429          one
21430          two
21431        - three
21432          fˇour
21433          five
21434        "#
21435        .unindent(),
21436    );
21437
21438    cx.update_editor(|editor, window, cx| {
21439        editor.backspace(&Backspace, window, cx);
21440        editor.backspace(&Backspace, window, cx);
21441    });
21442    executor.run_until_parked();
21443    cx.assert_state_with_diff(
21444        r#"
21445          one
21446          two
21447        - threeˇ
21448        - four
21449        + our
21450          five
21451        "#
21452        .unindent(),
21453    );
21454}
21455
21456#[gpui::test]
21457async fn test_edit_after_expanded_modification_hunk(
21458    executor: BackgroundExecutor,
21459    cx: &mut TestAppContext,
21460) {
21461    init_test(cx, |_| {});
21462
21463    let mut cx = EditorTestContext::new(cx).await;
21464
21465    let diff_base = r#"
21466        use some::mod1;
21467        use some::mod2;
21468
21469        const A: u32 = 42;
21470        const B: u32 = 42;
21471        const C: u32 = 42;
21472        const D: u32 = 42;
21473
21474
21475        fn main() {
21476            println!("hello");
21477
21478            println!("world");
21479        }"#
21480    .unindent();
21481
21482    cx.set_state(
21483        &r#"
21484        use some::mod1;
21485        use some::mod2;
21486
21487        const A: u32 = 42;
21488        const B: u32 = 42;
21489        const C: u32 = 43ˇ
21490        const D: u32 = 42;
21491
21492
21493        fn main() {
21494            println!("hello");
21495
21496            println!("world");
21497        }"#
21498        .unindent(),
21499    );
21500
21501    cx.set_head_text(&diff_base);
21502    executor.run_until_parked();
21503    cx.update_editor(|editor, window, cx| {
21504        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21505    });
21506    executor.run_until_parked();
21507
21508    cx.assert_state_with_diff(
21509        r#"
21510        use some::mod1;
21511        use some::mod2;
21512
21513        const A: u32 = 42;
21514        const B: u32 = 42;
21515      - const C: u32 = 42;
21516      + const C: u32 = 43ˇ
21517        const D: u32 = 42;
21518
21519
21520        fn main() {
21521            println!("hello");
21522
21523            println!("world");
21524        }"#
21525        .unindent(),
21526    );
21527
21528    cx.update_editor(|editor, window, cx| {
21529        editor.handle_input("\nnew_line\n", window, cx);
21530    });
21531    executor.run_until_parked();
21532
21533    cx.assert_state_with_diff(
21534        r#"
21535        use some::mod1;
21536        use some::mod2;
21537
21538        const A: u32 = 42;
21539        const B: u32 = 42;
21540      - const C: u32 = 42;
21541      + const C: u32 = 43
21542      + new_line
21543      + ˇ
21544        const D: u32 = 42;
21545
21546
21547        fn main() {
21548            println!("hello");
21549
21550            println!("world");
21551        }"#
21552        .unindent(),
21553    );
21554}
21555
21556#[gpui::test]
21557async fn test_stage_and_unstage_added_file_hunk(
21558    executor: BackgroundExecutor,
21559    cx: &mut TestAppContext,
21560) {
21561    init_test(cx, |_| {});
21562
21563    let mut cx = EditorTestContext::new(cx).await;
21564    cx.update_editor(|editor, _, cx| {
21565        editor.set_expand_all_diff_hunks(cx);
21566    });
21567
21568    let working_copy = r#"
21569            ˇfn main() {
21570                println!("hello, world!");
21571            }
21572        "#
21573    .unindent();
21574
21575    cx.set_state(&working_copy);
21576    executor.run_until_parked();
21577
21578    cx.assert_state_with_diff(
21579        r#"
21580            + ˇfn main() {
21581            +     println!("hello, world!");
21582            + }
21583        "#
21584        .unindent(),
21585    );
21586    cx.assert_index_text(None);
21587
21588    cx.update_editor(|editor, window, cx| {
21589        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21590    });
21591    executor.run_until_parked();
21592    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21593    cx.assert_state_with_diff(
21594        r#"
21595            + ˇfn main() {
21596            +     println!("hello, world!");
21597            + }
21598        "#
21599        .unindent(),
21600    );
21601
21602    cx.update_editor(|editor, window, cx| {
21603        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21604    });
21605    executor.run_until_parked();
21606    cx.assert_index_text(None);
21607}
21608
21609async fn setup_indent_guides_editor(
21610    text: &str,
21611    cx: &mut TestAppContext,
21612) -> (BufferId, EditorTestContext) {
21613    init_test(cx, |_| {});
21614
21615    let mut cx = EditorTestContext::new(cx).await;
21616
21617    let buffer_id = cx.update_editor(|editor, window, cx| {
21618        editor.set_text(text, window, cx);
21619        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21620
21621        buffer_ids[0]
21622    });
21623
21624    (buffer_id, cx)
21625}
21626
21627fn assert_indent_guides(
21628    range: Range<u32>,
21629    expected: Vec<IndentGuide>,
21630    active_indices: Option<Vec<usize>>,
21631    cx: &mut EditorTestContext,
21632) {
21633    let indent_guides = cx.update_editor(|editor, window, cx| {
21634        let snapshot = editor.snapshot(window, cx).display_snapshot;
21635        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21636            editor,
21637            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21638            true,
21639            &snapshot,
21640            cx,
21641        );
21642
21643        indent_guides.sort_by(|a, b| {
21644            a.depth.cmp(&b.depth).then(
21645                a.start_row
21646                    .cmp(&b.start_row)
21647                    .then(a.end_row.cmp(&b.end_row)),
21648            )
21649        });
21650        indent_guides
21651    });
21652
21653    if let Some(expected) = active_indices {
21654        let active_indices = cx.update_editor(|editor, window, cx| {
21655            let snapshot = editor.snapshot(window, cx).display_snapshot;
21656            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21657        });
21658
21659        assert_eq!(
21660            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21661            expected,
21662            "Active indent guide indices do not match"
21663        );
21664    }
21665
21666    assert_eq!(indent_guides, expected, "Indent guides do not match");
21667}
21668
21669fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21670    IndentGuide {
21671        buffer_id,
21672        start_row: MultiBufferRow(start_row),
21673        end_row: MultiBufferRow(end_row),
21674        depth,
21675        tab_size: 4,
21676        settings: IndentGuideSettings {
21677            enabled: true,
21678            line_width: 1,
21679            active_line_width: 1,
21680            coloring: IndentGuideColoring::default(),
21681            background_coloring: IndentGuideBackgroundColoring::default(),
21682        },
21683    }
21684}
21685
21686#[gpui::test]
21687async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21688    let (buffer_id, mut cx) = setup_indent_guides_editor(
21689        &"
21690        fn main() {
21691            let a = 1;
21692        }"
21693        .unindent(),
21694        cx,
21695    )
21696    .await;
21697
21698    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21699}
21700
21701#[gpui::test]
21702async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21703    let (buffer_id, mut cx) = setup_indent_guides_editor(
21704        &"
21705        fn main() {
21706            let a = 1;
21707            let b = 2;
21708        }"
21709        .unindent(),
21710        cx,
21711    )
21712    .await;
21713
21714    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21715}
21716
21717#[gpui::test]
21718async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21719    let (buffer_id, mut cx) = setup_indent_guides_editor(
21720        &"
21721        fn main() {
21722            let a = 1;
21723            if a == 3 {
21724                let b = 2;
21725            } else {
21726                let c = 3;
21727            }
21728        }"
21729        .unindent(),
21730        cx,
21731    )
21732    .await;
21733
21734    assert_indent_guides(
21735        0..8,
21736        vec![
21737            indent_guide(buffer_id, 1, 6, 0),
21738            indent_guide(buffer_id, 3, 3, 1),
21739            indent_guide(buffer_id, 5, 5, 1),
21740        ],
21741        None,
21742        &mut cx,
21743    );
21744}
21745
21746#[gpui::test]
21747async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21748    let (buffer_id, mut cx) = setup_indent_guides_editor(
21749        &"
21750        fn main() {
21751            let a = 1;
21752                let b = 2;
21753            let c = 3;
21754        }"
21755        .unindent(),
21756        cx,
21757    )
21758    .await;
21759
21760    assert_indent_guides(
21761        0..5,
21762        vec![
21763            indent_guide(buffer_id, 1, 3, 0),
21764            indent_guide(buffer_id, 2, 2, 1),
21765        ],
21766        None,
21767        &mut cx,
21768    );
21769}
21770
21771#[gpui::test]
21772async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21773    let (buffer_id, mut cx) = setup_indent_guides_editor(
21774        &"
21775        fn main() {
21776            let a = 1;
21777
21778            let c = 3;
21779        }"
21780        .unindent(),
21781        cx,
21782    )
21783    .await;
21784
21785    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21786}
21787
21788#[gpui::test]
21789async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21790    let (buffer_id, mut cx) = setup_indent_guides_editor(
21791        &"
21792        fn main() {
21793            let a = 1;
21794
21795            let c = 3;
21796
21797            if a == 3 {
21798                let b = 2;
21799            } else {
21800                let c = 3;
21801            }
21802        }"
21803        .unindent(),
21804        cx,
21805    )
21806    .await;
21807
21808    assert_indent_guides(
21809        0..11,
21810        vec![
21811            indent_guide(buffer_id, 1, 9, 0),
21812            indent_guide(buffer_id, 6, 6, 1),
21813            indent_guide(buffer_id, 8, 8, 1),
21814        ],
21815        None,
21816        &mut cx,
21817    );
21818}
21819
21820#[gpui::test]
21821async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
21822    let (buffer_id, mut cx) = setup_indent_guides_editor(
21823        &"
21824        fn main() {
21825            let a = 1;
21826
21827            let c = 3;
21828
21829            if a == 3 {
21830                let b = 2;
21831            } else {
21832                let c = 3;
21833            }
21834        }"
21835        .unindent(),
21836        cx,
21837    )
21838    .await;
21839
21840    assert_indent_guides(
21841        1..11,
21842        vec![
21843            indent_guide(buffer_id, 1, 9, 0),
21844            indent_guide(buffer_id, 6, 6, 1),
21845            indent_guide(buffer_id, 8, 8, 1),
21846        ],
21847        None,
21848        &mut cx,
21849    );
21850}
21851
21852#[gpui::test]
21853async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21854    let (buffer_id, mut cx) = setup_indent_guides_editor(
21855        &"
21856        fn main() {
21857            let a = 1;
21858
21859            let c = 3;
21860
21861            if a == 3 {
21862                let b = 2;
21863            } else {
21864                let c = 3;
21865            }
21866        }"
21867        .unindent(),
21868        cx,
21869    )
21870    .await;
21871
21872    assert_indent_guides(
21873        1..10,
21874        vec![
21875            indent_guide(buffer_id, 1, 9, 0),
21876            indent_guide(buffer_id, 6, 6, 1),
21877            indent_guide(buffer_id, 8, 8, 1),
21878        ],
21879        None,
21880        &mut cx,
21881    );
21882}
21883
21884#[gpui::test]
21885async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21886    let (buffer_id, mut cx) = setup_indent_guides_editor(
21887        &"
21888        fn main() {
21889            if a {
21890                b(
21891                    c,
21892                    d,
21893                )
21894            } else {
21895                e(
21896                    f
21897                )
21898            }
21899        }"
21900        .unindent(),
21901        cx,
21902    )
21903    .await;
21904
21905    assert_indent_guides(
21906        0..11,
21907        vec![
21908            indent_guide(buffer_id, 1, 10, 0),
21909            indent_guide(buffer_id, 2, 5, 1),
21910            indent_guide(buffer_id, 7, 9, 1),
21911            indent_guide(buffer_id, 3, 4, 2),
21912            indent_guide(buffer_id, 8, 8, 2),
21913        ],
21914        None,
21915        &mut cx,
21916    );
21917
21918    cx.update_editor(|editor, window, cx| {
21919        editor.fold_at(MultiBufferRow(2), window, cx);
21920        assert_eq!(
21921            editor.display_text(cx),
21922            "
21923            fn main() {
21924                if a {
21925                    b(⋯
21926                    )
21927                } else {
21928                    e(
21929                        f
21930                    )
21931                }
21932            }"
21933            .unindent()
21934        );
21935    });
21936
21937    assert_indent_guides(
21938        0..11,
21939        vec![
21940            indent_guide(buffer_id, 1, 10, 0),
21941            indent_guide(buffer_id, 2, 5, 1),
21942            indent_guide(buffer_id, 7, 9, 1),
21943            indent_guide(buffer_id, 8, 8, 2),
21944        ],
21945        None,
21946        &mut cx,
21947    );
21948}
21949
21950#[gpui::test]
21951async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21952    let (buffer_id, mut cx) = setup_indent_guides_editor(
21953        &"
21954        block1
21955            block2
21956                block3
21957                    block4
21958            block2
21959        block1
21960        block1"
21961            .unindent(),
21962        cx,
21963    )
21964    .await;
21965
21966    assert_indent_guides(
21967        1..10,
21968        vec![
21969            indent_guide(buffer_id, 1, 4, 0),
21970            indent_guide(buffer_id, 2, 3, 1),
21971            indent_guide(buffer_id, 3, 3, 2),
21972        ],
21973        None,
21974        &mut cx,
21975    );
21976}
21977
21978#[gpui::test]
21979async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21980    let (buffer_id, mut cx) = setup_indent_guides_editor(
21981        &"
21982        block1
21983            block2
21984                block3
21985
21986        block1
21987        block1"
21988            .unindent(),
21989        cx,
21990    )
21991    .await;
21992
21993    assert_indent_guides(
21994        0..6,
21995        vec![
21996            indent_guide(buffer_id, 1, 2, 0),
21997            indent_guide(buffer_id, 2, 2, 1),
21998        ],
21999        None,
22000        &mut cx,
22001    );
22002}
22003
22004#[gpui::test]
22005async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
22006    let (buffer_id, mut cx) = setup_indent_guides_editor(
22007        &"
22008        function component() {
22009        \treturn (
22010        \t\t\t
22011        \t\t<div>
22012        \t\t\t<abc></abc>
22013        \t\t</div>
22014        \t)
22015        }"
22016        .unindent(),
22017        cx,
22018    )
22019    .await;
22020
22021    assert_indent_guides(
22022        0..8,
22023        vec![
22024            indent_guide(buffer_id, 1, 6, 0),
22025            indent_guide(buffer_id, 2, 5, 1),
22026            indent_guide(buffer_id, 4, 4, 2),
22027        ],
22028        None,
22029        &mut cx,
22030    );
22031}
22032
22033#[gpui::test]
22034async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
22035    let (buffer_id, mut cx) = setup_indent_guides_editor(
22036        &"
22037        function component() {
22038        \treturn (
22039        \t
22040        \t\t<div>
22041        \t\t\t<abc></abc>
22042        \t\t</div>
22043        \t)
22044        }"
22045        .unindent(),
22046        cx,
22047    )
22048    .await;
22049
22050    assert_indent_guides(
22051        0..8,
22052        vec![
22053            indent_guide(buffer_id, 1, 6, 0),
22054            indent_guide(buffer_id, 2, 5, 1),
22055            indent_guide(buffer_id, 4, 4, 2),
22056        ],
22057        None,
22058        &mut cx,
22059    );
22060}
22061
22062#[gpui::test]
22063async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
22064    let (buffer_id, mut cx) = setup_indent_guides_editor(
22065        &"
22066        block1
22067
22068
22069
22070            block2
22071        "
22072        .unindent(),
22073        cx,
22074    )
22075    .await;
22076
22077    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
22078}
22079
22080#[gpui::test]
22081async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
22082    let (buffer_id, mut cx) = setup_indent_guides_editor(
22083        &"
22084        def a:
22085        \tb = 3
22086        \tif True:
22087        \t\tc = 4
22088        \t\td = 5
22089        \tprint(b)
22090        "
22091        .unindent(),
22092        cx,
22093    )
22094    .await;
22095
22096    assert_indent_guides(
22097        0..6,
22098        vec![
22099            indent_guide(buffer_id, 1, 5, 0),
22100            indent_guide(buffer_id, 3, 4, 1),
22101        ],
22102        None,
22103        &mut cx,
22104    );
22105}
22106
22107#[gpui::test]
22108async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
22109    let (buffer_id, mut cx) = setup_indent_guides_editor(
22110        &"
22111    fn main() {
22112        let a = 1;
22113    }"
22114        .unindent(),
22115        cx,
22116    )
22117    .await;
22118
22119    cx.update_editor(|editor, window, cx| {
22120        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22121            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22122        });
22123    });
22124
22125    assert_indent_guides(
22126        0..3,
22127        vec![indent_guide(buffer_id, 1, 1, 0)],
22128        Some(vec![0]),
22129        &mut cx,
22130    );
22131}
22132
22133#[gpui::test]
22134async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
22135    let (buffer_id, mut cx) = setup_indent_guides_editor(
22136        &"
22137    fn main() {
22138        if 1 == 2 {
22139            let a = 1;
22140        }
22141    }"
22142        .unindent(),
22143        cx,
22144    )
22145    .await;
22146
22147    cx.update_editor(|editor, window, cx| {
22148        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22149            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22150        });
22151    });
22152
22153    assert_indent_guides(
22154        0..4,
22155        vec![
22156            indent_guide(buffer_id, 1, 3, 0),
22157            indent_guide(buffer_id, 2, 2, 1),
22158        ],
22159        Some(vec![1]),
22160        &mut cx,
22161    );
22162
22163    cx.update_editor(|editor, window, cx| {
22164        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22165            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22166        });
22167    });
22168
22169    assert_indent_guides(
22170        0..4,
22171        vec![
22172            indent_guide(buffer_id, 1, 3, 0),
22173            indent_guide(buffer_id, 2, 2, 1),
22174        ],
22175        Some(vec![1]),
22176        &mut cx,
22177    );
22178
22179    cx.update_editor(|editor, window, cx| {
22180        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22181            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
22182        });
22183    });
22184
22185    assert_indent_guides(
22186        0..4,
22187        vec![
22188            indent_guide(buffer_id, 1, 3, 0),
22189            indent_guide(buffer_id, 2, 2, 1),
22190        ],
22191        Some(vec![0]),
22192        &mut cx,
22193    );
22194}
22195
22196#[gpui::test]
22197async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
22198    let (buffer_id, mut cx) = setup_indent_guides_editor(
22199        &"
22200    fn main() {
22201        let a = 1;
22202
22203        let b = 2;
22204    }"
22205        .unindent(),
22206        cx,
22207    )
22208    .await;
22209
22210    cx.update_editor(|editor, window, cx| {
22211        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22212            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
22213        });
22214    });
22215
22216    assert_indent_guides(
22217        0..5,
22218        vec![indent_guide(buffer_id, 1, 3, 0)],
22219        Some(vec![0]),
22220        &mut cx,
22221    );
22222}
22223
22224#[gpui::test]
22225async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
22226    let (buffer_id, mut cx) = setup_indent_guides_editor(
22227        &"
22228    def m:
22229        a = 1
22230        pass"
22231            .unindent(),
22232        cx,
22233    )
22234    .await;
22235
22236    cx.update_editor(|editor, window, cx| {
22237        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22238            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
22239        });
22240    });
22241
22242    assert_indent_guides(
22243        0..3,
22244        vec![indent_guide(buffer_id, 1, 2, 0)],
22245        Some(vec![0]),
22246        &mut cx,
22247    );
22248}
22249
22250#[gpui::test]
22251async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
22252    init_test(cx, |_| {});
22253    let mut cx = EditorTestContext::new(cx).await;
22254    let text = indoc! {
22255        "
22256        impl A {
22257            fn b() {
22258                0;
22259                3;
22260                5;
22261                6;
22262                7;
22263            }
22264        }
22265        "
22266    };
22267    let base_text = indoc! {
22268        "
22269        impl A {
22270            fn b() {
22271                0;
22272                1;
22273                2;
22274                3;
22275                4;
22276            }
22277            fn c() {
22278                5;
22279                6;
22280                7;
22281            }
22282        }
22283        "
22284    };
22285
22286    cx.update_editor(|editor, window, cx| {
22287        editor.set_text(text, window, cx);
22288
22289        editor.buffer().update(cx, |multibuffer, cx| {
22290            let buffer = multibuffer.as_singleton().unwrap();
22291            let diff = cx.new(|cx| {
22292                BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
22293            });
22294
22295            multibuffer.set_all_diff_hunks_expanded(cx);
22296            multibuffer.add_diff(diff, cx);
22297
22298            buffer.read(cx).remote_id()
22299        })
22300    });
22301    cx.run_until_parked();
22302
22303    cx.assert_state_with_diff(
22304        indoc! { "
22305          impl A {
22306              fn b() {
22307                  0;
22308        -         1;
22309        -         2;
22310                  3;
22311        -         4;
22312        -     }
22313        -     fn c() {
22314                  5;
22315                  6;
22316                  7;
22317              }
22318          }
22319          ˇ"
22320        }
22321        .to_string(),
22322    );
22323
22324    let mut actual_guides = cx.update_editor(|editor, window, cx| {
22325        editor
22326            .snapshot(window, cx)
22327            .buffer_snapshot()
22328            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
22329            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
22330            .collect::<Vec<_>>()
22331    });
22332    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
22333    assert_eq!(
22334        actual_guides,
22335        vec![
22336            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
22337            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
22338            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
22339        ]
22340    );
22341}
22342
22343#[gpui::test]
22344async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22345    init_test(cx, |_| {});
22346    let mut cx = EditorTestContext::new(cx).await;
22347
22348    let diff_base = r#"
22349        a
22350        b
22351        c
22352        "#
22353    .unindent();
22354
22355    cx.set_state(
22356        &r#"
22357        ˇA
22358        b
22359        C
22360        "#
22361        .unindent(),
22362    );
22363    cx.set_head_text(&diff_base);
22364    cx.update_editor(|editor, window, cx| {
22365        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22366    });
22367    executor.run_until_parked();
22368
22369    let both_hunks_expanded = r#"
22370        - a
22371        + ˇA
22372          b
22373        - c
22374        + C
22375        "#
22376    .unindent();
22377
22378    cx.assert_state_with_diff(both_hunks_expanded.clone());
22379
22380    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22381        let snapshot = editor.snapshot(window, cx);
22382        let hunks = editor
22383            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22384            .collect::<Vec<_>>();
22385        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22386        hunks
22387            .into_iter()
22388            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22389            .collect::<Vec<_>>()
22390    });
22391    assert_eq!(hunk_ranges.len(), 2);
22392
22393    cx.update_editor(|editor, _, cx| {
22394        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22395    });
22396    executor.run_until_parked();
22397
22398    let second_hunk_expanded = r#"
22399          ˇA
22400          b
22401        - c
22402        + C
22403        "#
22404    .unindent();
22405
22406    cx.assert_state_with_diff(second_hunk_expanded);
22407
22408    cx.update_editor(|editor, _, cx| {
22409        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22410    });
22411    executor.run_until_parked();
22412
22413    cx.assert_state_with_diff(both_hunks_expanded.clone());
22414
22415    cx.update_editor(|editor, _, cx| {
22416        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22417    });
22418    executor.run_until_parked();
22419
22420    let first_hunk_expanded = r#"
22421        - a
22422        + ˇA
22423          b
22424          C
22425        "#
22426    .unindent();
22427
22428    cx.assert_state_with_diff(first_hunk_expanded);
22429
22430    cx.update_editor(|editor, _, cx| {
22431        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22432    });
22433    executor.run_until_parked();
22434
22435    cx.assert_state_with_diff(both_hunks_expanded);
22436
22437    cx.set_state(
22438        &r#"
22439        ˇA
22440        b
22441        "#
22442        .unindent(),
22443    );
22444    cx.run_until_parked();
22445
22446    // TODO this cursor position seems bad
22447    cx.assert_state_with_diff(
22448        r#"
22449        - ˇa
22450        + A
22451          b
22452        "#
22453        .unindent(),
22454    );
22455
22456    cx.update_editor(|editor, window, cx| {
22457        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22458    });
22459
22460    cx.assert_state_with_diff(
22461        r#"
22462            - ˇa
22463            + A
22464              b
22465            - c
22466            "#
22467        .unindent(),
22468    );
22469
22470    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22471        let snapshot = editor.snapshot(window, cx);
22472        let hunks = editor
22473            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22474            .collect::<Vec<_>>();
22475        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22476        hunks
22477            .into_iter()
22478            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22479            .collect::<Vec<_>>()
22480    });
22481    assert_eq!(hunk_ranges.len(), 2);
22482
22483    cx.update_editor(|editor, _, cx| {
22484        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22485    });
22486    executor.run_until_parked();
22487
22488    cx.assert_state_with_diff(
22489        r#"
22490        - ˇa
22491        + A
22492          b
22493        "#
22494        .unindent(),
22495    );
22496}
22497
22498#[gpui::test]
22499async fn test_toggle_deletion_hunk_at_start_of_file(
22500    executor: BackgroundExecutor,
22501    cx: &mut TestAppContext,
22502) {
22503    init_test(cx, |_| {});
22504    let mut cx = EditorTestContext::new(cx).await;
22505
22506    let diff_base = r#"
22507        a
22508        b
22509        c
22510        "#
22511    .unindent();
22512
22513    cx.set_state(
22514        &r#"
22515        ˇb
22516        c
22517        "#
22518        .unindent(),
22519    );
22520    cx.set_head_text(&diff_base);
22521    cx.update_editor(|editor, window, cx| {
22522        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22523    });
22524    executor.run_until_parked();
22525
22526    let hunk_expanded = r#"
22527        - a
22528          ˇb
22529          c
22530        "#
22531    .unindent();
22532
22533    cx.assert_state_with_diff(hunk_expanded.clone());
22534
22535    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22536        let snapshot = editor.snapshot(window, cx);
22537        let hunks = editor
22538            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22539            .collect::<Vec<_>>();
22540        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22541        hunks
22542            .into_iter()
22543            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22544            .collect::<Vec<_>>()
22545    });
22546    assert_eq!(hunk_ranges.len(), 1);
22547
22548    cx.update_editor(|editor, _, cx| {
22549        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22550    });
22551    executor.run_until_parked();
22552
22553    let hunk_collapsed = r#"
22554          ˇb
22555          c
22556        "#
22557    .unindent();
22558
22559    cx.assert_state_with_diff(hunk_collapsed);
22560
22561    cx.update_editor(|editor, _, cx| {
22562        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22563    });
22564    executor.run_until_parked();
22565
22566    cx.assert_state_with_diff(hunk_expanded);
22567}
22568
22569#[gpui::test]
22570async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
22571    executor: BackgroundExecutor,
22572    cx: &mut TestAppContext,
22573) {
22574    init_test(cx, |_| {});
22575    let mut cx = EditorTestContext::new(cx).await;
22576
22577    cx.set_state("ˇnew\nsecond\nthird\n");
22578    cx.set_head_text("old\nsecond\nthird\n");
22579    cx.update_editor(|editor, window, cx| {
22580        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
22581    });
22582    executor.run_until_parked();
22583    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22584
22585    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
22586    cx.update_editor(|editor, window, cx| {
22587        let snapshot = editor.snapshot(window, cx);
22588        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22589        let hunks = editor
22590            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22591            .collect::<Vec<_>>();
22592        assert_eq!(hunks.len(), 1);
22593        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
22594        editor.toggle_single_diff_hunk(hunk_range, cx)
22595    });
22596    executor.run_until_parked();
22597    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
22598
22599    // Keep the editor scrolled to the top so the full hunk remains visible.
22600    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22601}
22602
22603#[gpui::test]
22604async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22605    init_test(cx, |_| {});
22606
22607    let fs = FakeFs::new(cx.executor());
22608    fs.insert_tree(
22609        path!("/test"),
22610        json!({
22611            ".git": {},
22612            "file-1": "ONE\n",
22613            "file-2": "TWO\n",
22614            "file-3": "THREE\n",
22615        }),
22616    )
22617    .await;
22618
22619    fs.set_head_for_repo(
22620        path!("/test/.git").as_ref(),
22621        &[
22622            ("file-1", "one\n".into()),
22623            ("file-2", "two\n".into()),
22624            ("file-3", "three\n".into()),
22625        ],
22626        "deadbeef",
22627    );
22628
22629    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22630    let mut buffers = vec![];
22631    for i in 1..=3 {
22632        let buffer = project
22633            .update(cx, |project, cx| {
22634                let path = format!(path!("/test/file-{}"), i);
22635                project.open_local_buffer(path, cx)
22636            })
22637            .await
22638            .unwrap();
22639        buffers.push(buffer);
22640    }
22641
22642    let multibuffer = cx.new(|cx| {
22643        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22644        multibuffer.set_all_diff_hunks_expanded(cx);
22645        for buffer in &buffers {
22646            let snapshot = buffer.read(cx).snapshot();
22647            multibuffer.set_excerpts_for_path(
22648                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22649                buffer.clone(),
22650                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22651                2,
22652                cx,
22653            );
22654        }
22655        multibuffer
22656    });
22657
22658    let editor = cx.add_window(|window, cx| {
22659        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22660    });
22661    cx.run_until_parked();
22662
22663    let snapshot = editor
22664        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22665        .unwrap();
22666    let hunks = snapshot
22667        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22668        .map(|hunk| match hunk {
22669            DisplayDiffHunk::Unfolded {
22670                display_row_range, ..
22671            } => display_row_range,
22672            DisplayDiffHunk::Folded { .. } => unreachable!(),
22673        })
22674        .collect::<Vec<_>>();
22675    assert_eq!(
22676        hunks,
22677        [
22678            DisplayRow(2)..DisplayRow(4),
22679            DisplayRow(7)..DisplayRow(9),
22680            DisplayRow(12)..DisplayRow(14),
22681        ]
22682    );
22683}
22684
22685#[gpui::test]
22686async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22687    init_test(cx, |_| {});
22688
22689    let mut cx = EditorTestContext::new(cx).await;
22690    cx.set_head_text(indoc! { "
22691        one
22692        two
22693        three
22694        four
22695        five
22696        "
22697    });
22698    cx.set_index_text(indoc! { "
22699        one
22700        two
22701        three
22702        four
22703        five
22704        "
22705    });
22706    cx.set_state(indoc! {"
22707        one
22708        TWO
22709        ˇTHREE
22710        FOUR
22711        five
22712    "});
22713    cx.run_until_parked();
22714    cx.update_editor(|editor, window, cx| {
22715        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22716    });
22717    cx.run_until_parked();
22718    cx.assert_index_text(Some(indoc! {"
22719        one
22720        TWO
22721        THREE
22722        FOUR
22723        five
22724    "}));
22725    cx.set_state(indoc! { "
22726        one
22727        TWO
22728        ˇTHREE-HUNDRED
22729        FOUR
22730        five
22731    "});
22732    cx.run_until_parked();
22733    cx.update_editor(|editor, window, cx| {
22734        let snapshot = editor.snapshot(window, cx);
22735        let hunks = editor
22736            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22737            .collect::<Vec<_>>();
22738        assert_eq!(hunks.len(), 1);
22739        assert_eq!(
22740            hunks[0].status(),
22741            DiffHunkStatus {
22742                kind: DiffHunkStatusKind::Modified,
22743                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22744            }
22745        );
22746
22747        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22748    });
22749    cx.run_until_parked();
22750    cx.assert_index_text(Some(indoc! {"
22751        one
22752        TWO
22753        THREE-HUNDRED
22754        FOUR
22755        five
22756    "}));
22757}
22758
22759#[gpui::test]
22760fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22761    init_test(cx, |_| {});
22762
22763    let editor = cx.add_window(|window, cx| {
22764        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22765        build_editor(buffer, window, cx)
22766    });
22767
22768    let render_args = Arc::new(Mutex::new(None));
22769    let snapshot = editor
22770        .update(cx, |editor, window, cx| {
22771            let snapshot = editor.buffer().read(cx).snapshot(cx);
22772            let range =
22773                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22774
22775            struct RenderArgs {
22776                row: MultiBufferRow,
22777                folded: bool,
22778                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22779            }
22780
22781            let crease = Crease::inline(
22782                range,
22783                FoldPlaceholder::test(),
22784                {
22785                    let toggle_callback = render_args.clone();
22786                    move |row, folded, callback, _window, _cx| {
22787                        *toggle_callback.lock() = Some(RenderArgs {
22788                            row,
22789                            folded,
22790                            callback,
22791                        });
22792                        div()
22793                    }
22794                },
22795                |_row, _folded, _window, _cx| div(),
22796            );
22797
22798            editor.insert_creases(Some(crease), cx);
22799            let snapshot = editor.snapshot(window, cx);
22800            let _div =
22801                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22802            snapshot
22803        })
22804        .unwrap();
22805
22806    let render_args = render_args.lock().take().unwrap();
22807    assert_eq!(render_args.row, MultiBufferRow(1));
22808    assert!(!render_args.folded);
22809    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22810
22811    cx.update_window(*editor, |_, window, cx| {
22812        (render_args.callback)(true, window, cx)
22813    })
22814    .unwrap();
22815    let snapshot = editor
22816        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22817        .unwrap();
22818    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22819
22820    cx.update_window(*editor, |_, window, cx| {
22821        (render_args.callback)(false, window, cx)
22822    })
22823    .unwrap();
22824    let snapshot = editor
22825        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22826        .unwrap();
22827    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22828}
22829
22830#[gpui::test]
22831async fn test_input_text(cx: &mut TestAppContext) {
22832    init_test(cx, |_| {});
22833    let mut cx = EditorTestContext::new(cx).await;
22834
22835    cx.set_state(
22836        &r#"ˇone
22837        two
22838
22839        three
22840        fourˇ
22841        five
22842
22843        siˇx"#
22844            .unindent(),
22845    );
22846
22847    cx.dispatch_action(HandleInput(String::new()));
22848    cx.assert_editor_state(
22849        &r#"ˇone
22850        two
22851
22852        three
22853        fourˇ
22854        five
22855
22856        siˇx"#
22857            .unindent(),
22858    );
22859
22860    cx.dispatch_action(HandleInput("AAAA".to_string()));
22861    cx.assert_editor_state(
22862        &r#"AAAAˇone
22863        two
22864
22865        three
22866        fourAAAAˇ
22867        five
22868
22869        siAAAAˇx"#
22870            .unindent(),
22871    );
22872}
22873
22874#[gpui::test]
22875async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22876    init_test(cx, |_| {});
22877
22878    let mut cx = EditorTestContext::new(cx).await;
22879    cx.set_state(
22880        r#"let foo = 1;
22881let foo = 2;
22882let foo = 3;
22883let fooˇ = 4;
22884let foo = 5;
22885let foo = 6;
22886let foo = 7;
22887let foo = 8;
22888let foo = 9;
22889let foo = 10;
22890let foo = 11;
22891let foo = 12;
22892let foo = 13;
22893let foo = 14;
22894let foo = 15;"#,
22895    );
22896
22897    cx.update_editor(|e, window, cx| {
22898        assert_eq!(
22899            e.next_scroll_position,
22900            NextScrollCursorCenterTopBottom::Center,
22901            "Default next scroll direction is center",
22902        );
22903
22904        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22905        assert_eq!(
22906            e.next_scroll_position,
22907            NextScrollCursorCenterTopBottom::Top,
22908            "After center, next scroll direction should be top",
22909        );
22910
22911        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22912        assert_eq!(
22913            e.next_scroll_position,
22914            NextScrollCursorCenterTopBottom::Bottom,
22915            "After top, next scroll direction should be bottom",
22916        );
22917
22918        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22919        assert_eq!(
22920            e.next_scroll_position,
22921            NextScrollCursorCenterTopBottom::Center,
22922            "After bottom, scrolling should start over",
22923        );
22924
22925        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22926        assert_eq!(
22927            e.next_scroll_position,
22928            NextScrollCursorCenterTopBottom::Top,
22929            "Scrolling continues if retriggered fast enough"
22930        );
22931    });
22932
22933    cx.executor()
22934        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22935    cx.executor().run_until_parked();
22936    cx.update_editor(|e, _, _| {
22937        assert_eq!(
22938            e.next_scroll_position,
22939            NextScrollCursorCenterTopBottom::Center,
22940            "If scrolling is not triggered fast enough, it should reset"
22941        );
22942    });
22943}
22944
22945#[gpui::test]
22946async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22947    init_test(cx, |_| {});
22948    let mut cx = EditorLspTestContext::new_rust(
22949        lsp::ServerCapabilities {
22950            definition_provider: Some(lsp::OneOf::Left(true)),
22951            references_provider: Some(lsp::OneOf::Left(true)),
22952            ..lsp::ServerCapabilities::default()
22953        },
22954        cx,
22955    )
22956    .await;
22957
22958    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22959        let go_to_definition = cx
22960            .lsp
22961            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22962                move |params, _| async move {
22963                    if empty_go_to_definition {
22964                        Ok(None)
22965                    } else {
22966                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22967                            uri: params.text_document_position_params.text_document.uri,
22968                            range: lsp::Range::new(
22969                                lsp::Position::new(4, 3),
22970                                lsp::Position::new(4, 6),
22971                            ),
22972                        })))
22973                    }
22974                },
22975            );
22976        let references = cx
22977            .lsp
22978            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22979                Ok(Some(vec![lsp::Location {
22980                    uri: params.text_document_position.text_document.uri,
22981                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22982                }]))
22983            });
22984        (go_to_definition, references)
22985    };
22986
22987    cx.set_state(
22988        &r#"fn one() {
22989            let mut a = ˇtwo();
22990        }
22991
22992        fn two() {}"#
22993            .unindent(),
22994    );
22995    set_up_lsp_handlers(false, &mut cx);
22996    let navigated = cx
22997        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22998        .await
22999        .expect("Failed to navigate to definition");
23000    assert_eq!(
23001        navigated,
23002        Navigated::Yes,
23003        "Should have navigated to definition from the GetDefinition response"
23004    );
23005    cx.assert_editor_state(
23006        &r#"fn one() {
23007            let mut a = two();
23008        }
23009
23010        fn «twoˇ»() {}"#
23011            .unindent(),
23012    );
23013
23014    let editors = cx.update_workspace(|workspace, _, cx| {
23015        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23016    });
23017    cx.update_editor(|_, _, test_editor_cx| {
23018        assert_eq!(
23019            editors.len(),
23020            1,
23021            "Initially, only one, test, editor should be open in the workspace"
23022        );
23023        assert_eq!(
23024            test_editor_cx.entity(),
23025            editors.last().expect("Asserted len is 1").clone()
23026        );
23027    });
23028
23029    set_up_lsp_handlers(true, &mut cx);
23030    let navigated = cx
23031        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23032        .await
23033        .expect("Failed to navigate to lookup references");
23034    assert_eq!(
23035        navigated,
23036        Navigated::Yes,
23037        "Should have navigated to references as a fallback after empty GoToDefinition response"
23038    );
23039    // We should not change the selections in the existing file,
23040    // if opening another milti buffer with the references
23041    cx.assert_editor_state(
23042        &r#"fn one() {
23043            let mut a = two();
23044        }
23045
23046        fn «twoˇ»() {}"#
23047            .unindent(),
23048    );
23049    let editors = cx.update_workspace(|workspace, _, cx| {
23050        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23051    });
23052    cx.update_editor(|_, _, test_editor_cx| {
23053        assert_eq!(
23054            editors.len(),
23055            2,
23056            "After falling back to references search, we open a new editor with the results"
23057        );
23058        let references_fallback_text = editors
23059            .into_iter()
23060            .find(|new_editor| *new_editor != test_editor_cx.entity())
23061            .expect("Should have one non-test editor now")
23062            .read(test_editor_cx)
23063            .text(test_editor_cx);
23064        assert_eq!(
23065            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
23066            "Should use the range from the references response and not the GoToDefinition one"
23067        );
23068    });
23069}
23070
23071#[gpui::test]
23072async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
23073    init_test(cx, |_| {});
23074    cx.update(|cx| {
23075        let mut editor_settings = EditorSettings::get_global(cx).clone();
23076        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
23077        EditorSettings::override_global(editor_settings, cx);
23078    });
23079    let mut cx = EditorLspTestContext::new_rust(
23080        lsp::ServerCapabilities {
23081            definition_provider: Some(lsp::OneOf::Left(true)),
23082            references_provider: Some(lsp::OneOf::Left(true)),
23083            ..lsp::ServerCapabilities::default()
23084        },
23085        cx,
23086    )
23087    .await;
23088    let original_state = r#"fn one() {
23089        let mut a = ˇtwo();
23090    }
23091
23092    fn two() {}"#
23093        .unindent();
23094    cx.set_state(&original_state);
23095
23096    let mut go_to_definition = cx
23097        .lsp
23098        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
23099            move |_, _| async move { Ok(None) },
23100        );
23101    let _references = cx
23102        .lsp
23103        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
23104            panic!("Should not call for references with no go to definition fallback")
23105        });
23106
23107    let navigated = cx
23108        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
23109        .await
23110        .expect("Failed to navigate to lookup references");
23111    go_to_definition
23112        .next()
23113        .await
23114        .expect("Should have called the go_to_definition handler");
23115
23116    assert_eq!(
23117        navigated,
23118        Navigated::No,
23119        "Should have navigated to references as a fallback after empty GoToDefinition response"
23120    );
23121    cx.assert_editor_state(&original_state);
23122    let editors = cx.update_workspace(|workspace, _, cx| {
23123        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23124    });
23125    cx.update_editor(|_, _, _| {
23126        assert_eq!(
23127            editors.len(),
23128            1,
23129            "After unsuccessful fallback, no other editor should have been opened"
23130        );
23131    });
23132}
23133
23134#[gpui::test]
23135async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
23136    init_test(cx, |_| {});
23137    let mut cx = EditorLspTestContext::new_rust(
23138        lsp::ServerCapabilities {
23139            references_provider: Some(lsp::OneOf::Left(true)),
23140            ..lsp::ServerCapabilities::default()
23141        },
23142        cx,
23143    )
23144    .await;
23145
23146    cx.set_state(
23147        &r#"
23148        fn one() {
23149            let mut a = two();
23150        }
23151
23152        fn ˇtwo() {}"#
23153            .unindent(),
23154    );
23155    cx.lsp
23156        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23157            Ok(Some(vec![
23158                lsp::Location {
23159                    uri: params.text_document_position.text_document.uri.clone(),
23160                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23161                },
23162                lsp::Location {
23163                    uri: params.text_document_position.text_document.uri,
23164                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
23165                },
23166            ]))
23167        });
23168    let navigated = cx
23169        .update_editor(|editor, window, cx| {
23170            editor.find_all_references(&FindAllReferences::default(), window, cx)
23171        })
23172        .unwrap()
23173        .await
23174        .expect("Failed to navigate to references");
23175    assert_eq!(
23176        navigated,
23177        Navigated::Yes,
23178        "Should have navigated to references from the FindAllReferences response"
23179    );
23180    cx.assert_editor_state(
23181        &r#"fn one() {
23182            let mut a = two();
23183        }
23184
23185        fn ˇtwo() {}"#
23186            .unindent(),
23187    );
23188
23189    let editors = cx.update_workspace(|workspace, _, cx| {
23190        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23191    });
23192    cx.update_editor(|_, _, _| {
23193        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
23194    });
23195
23196    cx.set_state(
23197        &r#"fn one() {
23198            let mut a = ˇtwo();
23199        }
23200
23201        fn two() {}"#
23202            .unindent(),
23203    );
23204    let navigated = cx
23205        .update_editor(|editor, window, cx| {
23206            editor.find_all_references(&FindAllReferences::default(), window, cx)
23207        })
23208        .unwrap()
23209        .await
23210        .expect("Failed to navigate to references");
23211    assert_eq!(
23212        navigated,
23213        Navigated::Yes,
23214        "Should have navigated to references from the FindAllReferences response"
23215    );
23216    cx.assert_editor_state(
23217        &r#"fn one() {
23218            let mut a = ˇtwo();
23219        }
23220
23221        fn two() {}"#
23222            .unindent(),
23223    );
23224    let editors = cx.update_workspace(|workspace, _, cx| {
23225        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23226    });
23227    cx.update_editor(|_, _, _| {
23228        assert_eq!(
23229            editors.len(),
23230            2,
23231            "should have re-used the previous multibuffer"
23232        );
23233    });
23234
23235    cx.set_state(
23236        &r#"fn one() {
23237            let mut a = ˇtwo();
23238        }
23239        fn three() {}
23240        fn two() {}"#
23241            .unindent(),
23242    );
23243    cx.lsp
23244        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
23245            Ok(Some(vec![
23246                lsp::Location {
23247                    uri: params.text_document_position.text_document.uri.clone(),
23248                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
23249                },
23250                lsp::Location {
23251                    uri: params.text_document_position.text_document.uri,
23252                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
23253                },
23254            ]))
23255        });
23256    let navigated = cx
23257        .update_editor(|editor, window, cx| {
23258            editor.find_all_references(&FindAllReferences::default(), window, cx)
23259        })
23260        .unwrap()
23261        .await
23262        .expect("Failed to navigate to references");
23263    assert_eq!(
23264        navigated,
23265        Navigated::Yes,
23266        "Should have navigated to references from the FindAllReferences response"
23267    );
23268    cx.assert_editor_state(
23269        &r#"fn one() {
23270                let mut a = ˇtwo();
23271            }
23272            fn three() {}
23273            fn two() {}"#
23274            .unindent(),
23275    );
23276    let editors = cx.update_workspace(|workspace, _, cx| {
23277        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
23278    });
23279    cx.update_editor(|_, _, _| {
23280        assert_eq!(
23281            editors.len(),
23282            3,
23283            "should have used a new multibuffer as offsets changed"
23284        );
23285    });
23286}
23287#[gpui::test]
23288async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
23289    init_test(cx, |_| {});
23290
23291    let language = Arc::new(Language::new(
23292        LanguageConfig::default(),
23293        Some(tree_sitter_rust::LANGUAGE.into()),
23294    ));
23295
23296    let text = r#"
23297        #[cfg(test)]
23298        mod tests() {
23299            #[test]
23300            fn runnable_1() {
23301                let a = 1;
23302            }
23303
23304            #[test]
23305            fn runnable_2() {
23306                let a = 1;
23307                let b = 2;
23308            }
23309        }
23310    "#
23311    .unindent();
23312
23313    let fs = FakeFs::new(cx.executor());
23314    fs.insert_file("/file.rs", Default::default()).await;
23315
23316    let project = Project::test(fs, ["/a".as_ref()], cx).await;
23317    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23318    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23319    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
23320    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
23321
23322    let editor = cx.new_window_entity(|window, cx| {
23323        Editor::new(
23324            EditorMode::full(),
23325            multi_buffer,
23326            Some(project.clone()),
23327            window,
23328            cx,
23329        )
23330    });
23331
23332    editor.update_in(cx, |editor, window, cx| {
23333        let snapshot = editor.buffer().read(cx).snapshot(cx);
23334        editor.tasks.insert(
23335            (buffer.read(cx).remote_id(), 3),
23336            RunnableTasks {
23337                templates: vec![],
23338                offset: snapshot.anchor_before(MultiBufferOffset(43)),
23339                column: 0,
23340                extra_variables: HashMap::default(),
23341                context_range: BufferOffset(43)..BufferOffset(85),
23342            },
23343        );
23344        editor.tasks.insert(
23345            (buffer.read(cx).remote_id(), 8),
23346            RunnableTasks {
23347                templates: vec![],
23348                offset: snapshot.anchor_before(MultiBufferOffset(86)),
23349                column: 0,
23350                extra_variables: HashMap::default(),
23351                context_range: BufferOffset(86)..BufferOffset(191),
23352            },
23353        );
23354
23355        // Test finding task when cursor is inside function body
23356        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23357            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
23358        });
23359        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23360        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
23361
23362        // Test finding task when cursor is on function name
23363        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23364            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
23365        });
23366        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23367        assert_eq!(row, 8, "Should find task when cursor is on function name");
23368    });
23369}
23370
23371#[gpui::test]
23372async fn test_folding_buffers(cx: &mut TestAppContext) {
23373    init_test(cx, |_| {});
23374
23375    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23376    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
23377    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
23378
23379    let fs = FakeFs::new(cx.executor());
23380    fs.insert_tree(
23381        path!("/a"),
23382        json!({
23383            "first.rs": sample_text_1,
23384            "second.rs": sample_text_2,
23385            "third.rs": sample_text_3,
23386        }),
23387    )
23388    .await;
23389    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23390    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23391    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23392    let worktree = project.update(cx, |project, cx| {
23393        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23394        assert_eq!(worktrees.len(), 1);
23395        worktrees.pop().unwrap()
23396    });
23397    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23398
23399    let buffer_1 = project
23400        .update(cx, |project, cx| {
23401            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23402        })
23403        .await
23404        .unwrap();
23405    let buffer_2 = project
23406        .update(cx, |project, cx| {
23407            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23408        })
23409        .await
23410        .unwrap();
23411    let buffer_3 = project
23412        .update(cx, |project, cx| {
23413            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23414        })
23415        .await
23416        .unwrap();
23417
23418    let multi_buffer = cx.new(|cx| {
23419        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23420        multi_buffer.push_excerpts(
23421            buffer_1.clone(),
23422            [
23423                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23424                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23425                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23426            ],
23427            cx,
23428        );
23429        multi_buffer.push_excerpts(
23430            buffer_2.clone(),
23431            [
23432                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23433                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23434                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23435            ],
23436            cx,
23437        );
23438        multi_buffer.push_excerpts(
23439            buffer_3.clone(),
23440            [
23441                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23442                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23443                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23444            ],
23445            cx,
23446        );
23447        multi_buffer
23448    });
23449    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23450        Editor::new(
23451            EditorMode::full(),
23452            multi_buffer.clone(),
23453            Some(project.clone()),
23454            window,
23455            cx,
23456        )
23457    });
23458
23459    assert_eq!(
23460        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23461        "\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",
23462    );
23463
23464    multi_buffer_editor.update(cx, |editor, cx| {
23465        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23466    });
23467    assert_eq!(
23468        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23469        "\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",
23470        "After folding the first buffer, its text should not be displayed"
23471    );
23472
23473    multi_buffer_editor.update(cx, |editor, cx| {
23474        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23475    });
23476    assert_eq!(
23477        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23478        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
23479        "After folding the second buffer, its text should not be displayed"
23480    );
23481
23482    multi_buffer_editor.update(cx, |editor, cx| {
23483        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23484    });
23485    assert_eq!(
23486        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23487        "\n\n\n\n\n",
23488        "After folding the third buffer, its text should not be displayed"
23489    );
23490
23491    // Emulate selection inside the fold logic, that should work
23492    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23493        editor
23494            .snapshot(window, cx)
23495            .next_line_boundary(Point::new(0, 4));
23496    });
23497
23498    multi_buffer_editor.update(cx, |editor, cx| {
23499        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23500    });
23501    assert_eq!(
23502        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23503        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23504        "After unfolding the second buffer, its text should be displayed"
23505    );
23506
23507    // Typing inside of buffer 1 causes that buffer to be unfolded.
23508    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23509        assert_eq!(
23510            multi_buffer
23511                .read(cx)
23512                .snapshot(cx)
23513                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
23514                .collect::<String>(),
23515            "bbbb"
23516        );
23517        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23518            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
23519        });
23520        editor.handle_input("B", window, cx);
23521    });
23522
23523    assert_eq!(
23524        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23525        "\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",
23526        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
23527    );
23528
23529    multi_buffer_editor.update(cx, |editor, cx| {
23530        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23531    });
23532    assert_eq!(
23533        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23534        "\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",
23535        "After unfolding the all buffers, all original text should be displayed"
23536    );
23537}
23538
23539#[gpui::test]
23540async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
23541    init_test(cx, |_| {});
23542
23543    let sample_text_1 = "1111\n2222\n3333".to_string();
23544    let sample_text_2 = "4444\n5555\n6666".to_string();
23545    let sample_text_3 = "7777\n8888\n9999".to_string();
23546
23547    let fs = FakeFs::new(cx.executor());
23548    fs.insert_tree(
23549        path!("/a"),
23550        json!({
23551            "first.rs": sample_text_1,
23552            "second.rs": sample_text_2,
23553            "third.rs": sample_text_3,
23554        }),
23555    )
23556    .await;
23557    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23558    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23559    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23560    let worktree = project.update(cx, |project, cx| {
23561        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23562        assert_eq!(worktrees.len(), 1);
23563        worktrees.pop().unwrap()
23564    });
23565    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23566
23567    let buffer_1 = project
23568        .update(cx, |project, cx| {
23569            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23570        })
23571        .await
23572        .unwrap();
23573    let buffer_2 = project
23574        .update(cx, |project, cx| {
23575            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23576        })
23577        .await
23578        .unwrap();
23579    let buffer_3 = project
23580        .update(cx, |project, cx| {
23581            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23582        })
23583        .await
23584        .unwrap();
23585
23586    let multi_buffer = cx.new(|cx| {
23587        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23588        multi_buffer.push_excerpts(
23589            buffer_1.clone(),
23590            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23591            cx,
23592        );
23593        multi_buffer.push_excerpts(
23594            buffer_2.clone(),
23595            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23596            cx,
23597        );
23598        multi_buffer.push_excerpts(
23599            buffer_3.clone(),
23600            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23601            cx,
23602        );
23603        multi_buffer
23604    });
23605
23606    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23607        Editor::new(
23608            EditorMode::full(),
23609            multi_buffer,
23610            Some(project.clone()),
23611            window,
23612            cx,
23613        )
23614    });
23615
23616    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23617    assert_eq!(
23618        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23619        full_text,
23620    );
23621
23622    multi_buffer_editor.update(cx, |editor, cx| {
23623        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23624    });
23625    assert_eq!(
23626        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23627        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23628        "After folding the first buffer, its text should not be displayed"
23629    );
23630
23631    multi_buffer_editor.update(cx, |editor, cx| {
23632        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23633    });
23634
23635    assert_eq!(
23636        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23637        "\n\n\n\n\n\n7777\n8888\n9999",
23638        "After folding the second buffer, its text should not be displayed"
23639    );
23640
23641    multi_buffer_editor.update(cx, |editor, cx| {
23642        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23643    });
23644    assert_eq!(
23645        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23646        "\n\n\n\n\n",
23647        "After folding the third buffer, its text should not be displayed"
23648    );
23649
23650    multi_buffer_editor.update(cx, |editor, cx| {
23651        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23652    });
23653    assert_eq!(
23654        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23655        "\n\n\n\n4444\n5555\n6666\n\n",
23656        "After unfolding the second buffer, its text should be displayed"
23657    );
23658
23659    multi_buffer_editor.update(cx, |editor, cx| {
23660        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23661    });
23662    assert_eq!(
23663        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23664        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23665        "After unfolding the first buffer, its text should be displayed"
23666    );
23667
23668    multi_buffer_editor.update(cx, |editor, cx| {
23669        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23670    });
23671    assert_eq!(
23672        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23673        full_text,
23674        "After unfolding all buffers, all original text should be displayed"
23675    );
23676}
23677
23678#[gpui::test]
23679async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23680    init_test(cx, |_| {});
23681
23682    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23683
23684    let fs = FakeFs::new(cx.executor());
23685    fs.insert_tree(
23686        path!("/a"),
23687        json!({
23688            "main.rs": sample_text,
23689        }),
23690    )
23691    .await;
23692    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23693    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23694    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23695    let worktree = project.update(cx, |project, cx| {
23696        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23697        assert_eq!(worktrees.len(), 1);
23698        worktrees.pop().unwrap()
23699    });
23700    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23701
23702    let buffer_1 = project
23703        .update(cx, |project, cx| {
23704            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23705        })
23706        .await
23707        .unwrap();
23708
23709    let multi_buffer = cx.new(|cx| {
23710        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23711        multi_buffer.push_excerpts(
23712            buffer_1.clone(),
23713            [ExcerptRange::new(
23714                Point::new(0, 0)
23715                    ..Point::new(
23716                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23717                        0,
23718                    ),
23719            )],
23720            cx,
23721        );
23722        multi_buffer
23723    });
23724    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23725        Editor::new(
23726            EditorMode::full(),
23727            multi_buffer,
23728            Some(project.clone()),
23729            window,
23730            cx,
23731        )
23732    });
23733
23734    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23735    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23736        enum TestHighlight {}
23737        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23738        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23739        editor.highlight_text::<TestHighlight>(
23740            vec![highlight_range.clone()],
23741            HighlightStyle::color(Hsla::green()),
23742            cx,
23743        );
23744        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23745            s.select_ranges(Some(highlight_range))
23746        });
23747    });
23748
23749    let full_text = format!("\n\n{sample_text}");
23750    assert_eq!(
23751        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23752        full_text,
23753    );
23754}
23755
23756#[gpui::test]
23757async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23758    init_test(cx, |_| {});
23759    cx.update(|cx| {
23760        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23761            "keymaps/default-linux.json",
23762            cx,
23763        )
23764        .unwrap();
23765        cx.bind_keys(default_key_bindings);
23766    });
23767
23768    let (editor, cx) = cx.add_window_view(|window, cx| {
23769        let multi_buffer = MultiBuffer::build_multi(
23770            [
23771                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23772                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23773                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23774                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23775            ],
23776            cx,
23777        );
23778        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23779
23780        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23781        // fold all but the second buffer, so that we test navigating between two
23782        // adjacent folded buffers, as well as folded buffers at the start and
23783        // end the multibuffer
23784        editor.fold_buffer(buffer_ids[0], cx);
23785        editor.fold_buffer(buffer_ids[2], cx);
23786        editor.fold_buffer(buffer_ids[3], cx);
23787
23788        editor
23789    });
23790    cx.simulate_resize(size(px(1000.), px(1000.)));
23791
23792    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23793    cx.assert_excerpts_with_selections(indoc! {"
23794        [EXCERPT]
23795        ˇ[FOLDED]
23796        [EXCERPT]
23797        a1
23798        b1
23799        [EXCERPT]
23800        [FOLDED]
23801        [EXCERPT]
23802        [FOLDED]
23803        "
23804    });
23805    cx.simulate_keystroke("down");
23806    cx.assert_excerpts_with_selections(indoc! {"
23807        [EXCERPT]
23808        [FOLDED]
23809        [EXCERPT]
23810        ˇa1
23811        b1
23812        [EXCERPT]
23813        [FOLDED]
23814        [EXCERPT]
23815        [FOLDED]
23816        "
23817    });
23818    cx.simulate_keystroke("down");
23819    cx.assert_excerpts_with_selections(indoc! {"
23820        [EXCERPT]
23821        [FOLDED]
23822        [EXCERPT]
23823        a1
23824        ˇb1
23825        [EXCERPT]
23826        [FOLDED]
23827        [EXCERPT]
23828        [FOLDED]
23829        "
23830    });
23831    cx.simulate_keystroke("down");
23832    cx.assert_excerpts_with_selections(indoc! {"
23833        [EXCERPT]
23834        [FOLDED]
23835        [EXCERPT]
23836        a1
23837        b1
23838        ˇ[EXCERPT]
23839        [FOLDED]
23840        [EXCERPT]
23841        [FOLDED]
23842        "
23843    });
23844    cx.simulate_keystroke("down");
23845    cx.assert_excerpts_with_selections(indoc! {"
23846        [EXCERPT]
23847        [FOLDED]
23848        [EXCERPT]
23849        a1
23850        b1
23851        [EXCERPT]
23852        ˇ[FOLDED]
23853        [EXCERPT]
23854        [FOLDED]
23855        "
23856    });
23857    for _ in 0..5 {
23858        cx.simulate_keystroke("down");
23859        cx.assert_excerpts_with_selections(indoc! {"
23860            [EXCERPT]
23861            [FOLDED]
23862            [EXCERPT]
23863            a1
23864            b1
23865            [EXCERPT]
23866            [FOLDED]
23867            [EXCERPT]
23868            ˇ[FOLDED]
23869            "
23870        });
23871    }
23872
23873    cx.simulate_keystroke("up");
23874    cx.assert_excerpts_with_selections(indoc! {"
23875        [EXCERPT]
23876        [FOLDED]
23877        [EXCERPT]
23878        a1
23879        b1
23880        [EXCERPT]
23881        ˇ[FOLDED]
23882        [EXCERPT]
23883        [FOLDED]
23884        "
23885    });
23886    cx.simulate_keystroke("up");
23887    cx.assert_excerpts_with_selections(indoc! {"
23888        [EXCERPT]
23889        [FOLDED]
23890        [EXCERPT]
23891        a1
23892        b1
23893        ˇ[EXCERPT]
23894        [FOLDED]
23895        [EXCERPT]
23896        [FOLDED]
23897        "
23898    });
23899    cx.simulate_keystroke("up");
23900    cx.assert_excerpts_with_selections(indoc! {"
23901        [EXCERPT]
23902        [FOLDED]
23903        [EXCERPT]
23904        a1
23905        ˇb1
23906        [EXCERPT]
23907        [FOLDED]
23908        [EXCERPT]
23909        [FOLDED]
23910        "
23911    });
23912    cx.simulate_keystroke("up");
23913    cx.assert_excerpts_with_selections(indoc! {"
23914        [EXCERPT]
23915        [FOLDED]
23916        [EXCERPT]
23917        ˇa1
23918        b1
23919        [EXCERPT]
23920        [FOLDED]
23921        [EXCERPT]
23922        [FOLDED]
23923        "
23924    });
23925    for _ in 0..5 {
23926        cx.simulate_keystroke("up");
23927        cx.assert_excerpts_with_selections(indoc! {"
23928            [EXCERPT]
23929            ˇ[FOLDED]
23930            [EXCERPT]
23931            a1
23932            b1
23933            [EXCERPT]
23934            [FOLDED]
23935            [EXCERPT]
23936            [FOLDED]
23937            "
23938        });
23939    }
23940}
23941
23942#[gpui::test]
23943async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23944    init_test(cx, |_| {});
23945
23946    // Simple insertion
23947    assert_highlighted_edits(
23948        "Hello, world!",
23949        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23950        true,
23951        cx,
23952        |highlighted_edits, cx| {
23953            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23954            assert_eq!(highlighted_edits.highlights.len(), 1);
23955            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23956            assert_eq!(
23957                highlighted_edits.highlights[0].1.background_color,
23958                Some(cx.theme().status().created_background)
23959            );
23960        },
23961    )
23962    .await;
23963
23964    // Replacement
23965    assert_highlighted_edits(
23966        "This is a test.",
23967        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23968        false,
23969        cx,
23970        |highlighted_edits, cx| {
23971            assert_eq!(highlighted_edits.text, "That is a test.");
23972            assert_eq!(highlighted_edits.highlights.len(), 1);
23973            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23974            assert_eq!(
23975                highlighted_edits.highlights[0].1.background_color,
23976                Some(cx.theme().status().created_background)
23977            );
23978        },
23979    )
23980    .await;
23981
23982    // Multiple edits
23983    assert_highlighted_edits(
23984        "Hello, world!",
23985        vec![
23986            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23987            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23988        ],
23989        false,
23990        cx,
23991        |highlighted_edits, cx| {
23992            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23993            assert_eq!(highlighted_edits.highlights.len(), 2);
23994            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23995            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23996            assert_eq!(
23997                highlighted_edits.highlights[0].1.background_color,
23998                Some(cx.theme().status().created_background)
23999            );
24000            assert_eq!(
24001                highlighted_edits.highlights[1].1.background_color,
24002                Some(cx.theme().status().created_background)
24003            );
24004        },
24005    )
24006    .await;
24007
24008    // Multiple lines with edits
24009    assert_highlighted_edits(
24010        "First line\nSecond line\nThird line\nFourth line",
24011        vec![
24012            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
24013            (
24014                Point::new(2, 0)..Point::new(2, 10),
24015                "New third line".to_string(),
24016            ),
24017            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
24018        ],
24019        false,
24020        cx,
24021        |highlighted_edits, cx| {
24022            assert_eq!(
24023                highlighted_edits.text,
24024                "Second modified\nNew third line\nFourth updated line"
24025            );
24026            assert_eq!(highlighted_edits.highlights.len(), 3);
24027            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
24028            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
24029            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
24030            for highlight in &highlighted_edits.highlights {
24031                assert_eq!(
24032                    highlight.1.background_color,
24033                    Some(cx.theme().status().created_background)
24034                );
24035            }
24036        },
24037    )
24038    .await;
24039}
24040
24041#[gpui::test]
24042async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
24043    init_test(cx, |_| {});
24044
24045    // Deletion
24046    assert_highlighted_edits(
24047        "Hello, world!",
24048        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
24049        true,
24050        cx,
24051        |highlighted_edits, cx| {
24052            assert_eq!(highlighted_edits.text, "Hello, world!");
24053            assert_eq!(highlighted_edits.highlights.len(), 1);
24054            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
24055            assert_eq!(
24056                highlighted_edits.highlights[0].1.background_color,
24057                Some(cx.theme().status().deleted_background)
24058            );
24059        },
24060    )
24061    .await;
24062
24063    // Insertion
24064    assert_highlighted_edits(
24065        "Hello, world!",
24066        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
24067        true,
24068        cx,
24069        |highlighted_edits, cx| {
24070            assert_eq!(highlighted_edits.highlights.len(), 1);
24071            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
24072            assert_eq!(
24073                highlighted_edits.highlights[0].1.background_color,
24074                Some(cx.theme().status().created_background)
24075            );
24076        },
24077    )
24078    .await;
24079}
24080
24081async fn assert_highlighted_edits(
24082    text: &str,
24083    edits: Vec<(Range<Point>, String)>,
24084    include_deletions: bool,
24085    cx: &mut TestAppContext,
24086    assertion_fn: impl Fn(HighlightedText, &App),
24087) {
24088    let window = cx.add_window(|window, cx| {
24089        let buffer = MultiBuffer::build_simple(text, cx);
24090        Editor::new(EditorMode::full(), buffer, None, window, cx)
24091    });
24092    let cx = &mut VisualTestContext::from_window(*window, cx);
24093
24094    let (buffer, snapshot) = window
24095        .update(cx, |editor, _window, cx| {
24096            (
24097                editor.buffer().clone(),
24098                editor.buffer().read(cx).snapshot(cx),
24099            )
24100        })
24101        .unwrap();
24102
24103    let edits = edits
24104        .into_iter()
24105        .map(|(range, edit)| {
24106            (
24107                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
24108                edit,
24109            )
24110        })
24111        .collect::<Vec<_>>();
24112
24113    let text_anchor_edits = edits
24114        .clone()
24115        .into_iter()
24116        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
24117        .collect::<Vec<_>>();
24118
24119    let edit_preview = window
24120        .update(cx, |_, _window, cx| {
24121            buffer
24122                .read(cx)
24123                .as_singleton()
24124                .unwrap()
24125                .read(cx)
24126                .preview_edits(text_anchor_edits.into(), cx)
24127        })
24128        .unwrap()
24129        .await;
24130
24131    cx.update(|_window, cx| {
24132        let highlighted_edits = edit_prediction_edit_text(
24133            snapshot.as_singleton().unwrap().2,
24134            &edits,
24135            &edit_preview,
24136            include_deletions,
24137            cx,
24138        );
24139        assertion_fn(highlighted_edits, cx)
24140    });
24141}
24142
24143#[track_caller]
24144fn assert_breakpoint(
24145    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
24146    path: &Arc<Path>,
24147    expected: Vec<(u32, Breakpoint)>,
24148) {
24149    if expected.is_empty() {
24150        assert!(!breakpoints.contains_key(path), "{}", path.display());
24151    } else {
24152        let mut breakpoint = breakpoints
24153            .get(path)
24154            .unwrap()
24155            .iter()
24156            .map(|breakpoint| {
24157                (
24158                    breakpoint.row,
24159                    Breakpoint {
24160                        message: breakpoint.message.clone(),
24161                        state: breakpoint.state,
24162                        condition: breakpoint.condition.clone(),
24163                        hit_condition: breakpoint.hit_condition.clone(),
24164                    },
24165                )
24166            })
24167            .collect::<Vec<_>>();
24168
24169        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
24170
24171        assert_eq!(expected, breakpoint);
24172    }
24173}
24174
24175fn add_log_breakpoint_at_cursor(
24176    editor: &mut Editor,
24177    log_message: &str,
24178    window: &mut Window,
24179    cx: &mut Context<Editor>,
24180) {
24181    let (anchor, bp) = editor
24182        .breakpoints_at_cursors(window, cx)
24183        .first()
24184        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
24185        .unwrap_or_else(|| {
24186            let snapshot = editor.snapshot(window, cx);
24187            let cursor_position: Point =
24188                editor.selections.newest(&snapshot.display_snapshot).head();
24189
24190            let breakpoint_position = snapshot
24191                .buffer_snapshot()
24192                .anchor_before(Point::new(cursor_position.row, 0));
24193
24194            (breakpoint_position, Breakpoint::new_log(log_message))
24195        });
24196
24197    editor.edit_breakpoint_at_anchor(
24198        anchor,
24199        bp,
24200        BreakpointEditAction::EditLogMessage(log_message.into()),
24201        cx,
24202    );
24203}
24204
24205#[gpui::test]
24206async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
24207    init_test(cx, |_| {});
24208
24209    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24210    let fs = FakeFs::new(cx.executor());
24211    fs.insert_tree(
24212        path!("/a"),
24213        json!({
24214            "main.rs": sample_text,
24215        }),
24216    )
24217    .await;
24218    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24219    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24220    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24221
24222    let fs = FakeFs::new(cx.executor());
24223    fs.insert_tree(
24224        path!("/a"),
24225        json!({
24226            "main.rs": sample_text,
24227        }),
24228    )
24229    .await;
24230    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24231    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24232    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24233    let worktree_id = workspace
24234        .update(cx, |workspace, _window, cx| {
24235            workspace.project().update(cx, |project, cx| {
24236                project.worktrees(cx).next().unwrap().read(cx).id()
24237            })
24238        })
24239        .unwrap();
24240
24241    let buffer = project
24242        .update(cx, |project, cx| {
24243            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24244        })
24245        .await
24246        .unwrap();
24247
24248    let (editor, cx) = cx.add_window_view(|window, cx| {
24249        Editor::new(
24250            EditorMode::full(),
24251            MultiBuffer::build_from_buffer(buffer, cx),
24252            Some(project.clone()),
24253            window,
24254            cx,
24255        )
24256    });
24257
24258    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24259    let abs_path = project.read_with(cx, |project, cx| {
24260        project
24261            .absolute_path(&project_path, cx)
24262            .map(Arc::from)
24263            .unwrap()
24264    });
24265
24266    // assert we can add breakpoint on the first line
24267    editor.update_in(cx, |editor, window, cx| {
24268        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24269        editor.move_to_end(&MoveToEnd, window, cx);
24270        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24271    });
24272
24273    let breakpoints = editor.update(cx, |editor, cx| {
24274        editor
24275            .breakpoint_store()
24276            .as_ref()
24277            .unwrap()
24278            .read(cx)
24279            .all_source_breakpoints(cx)
24280    });
24281
24282    assert_eq!(1, breakpoints.len());
24283    assert_breakpoint(
24284        &breakpoints,
24285        &abs_path,
24286        vec![
24287            (0, Breakpoint::new_standard()),
24288            (3, Breakpoint::new_standard()),
24289        ],
24290    );
24291
24292    editor.update_in(cx, |editor, window, cx| {
24293        editor.move_to_beginning(&MoveToBeginning, window, cx);
24294        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24295    });
24296
24297    let breakpoints = editor.update(cx, |editor, cx| {
24298        editor
24299            .breakpoint_store()
24300            .as_ref()
24301            .unwrap()
24302            .read(cx)
24303            .all_source_breakpoints(cx)
24304    });
24305
24306    assert_eq!(1, breakpoints.len());
24307    assert_breakpoint(
24308        &breakpoints,
24309        &abs_path,
24310        vec![(3, Breakpoint::new_standard())],
24311    );
24312
24313    editor.update_in(cx, |editor, window, cx| {
24314        editor.move_to_end(&MoveToEnd, window, cx);
24315        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24316    });
24317
24318    let breakpoints = editor.update(cx, |editor, cx| {
24319        editor
24320            .breakpoint_store()
24321            .as_ref()
24322            .unwrap()
24323            .read(cx)
24324            .all_source_breakpoints(cx)
24325    });
24326
24327    assert_eq!(0, breakpoints.len());
24328    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24329}
24330
24331#[gpui::test]
24332async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
24333    init_test(cx, |_| {});
24334
24335    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24336
24337    let fs = FakeFs::new(cx.executor());
24338    fs.insert_tree(
24339        path!("/a"),
24340        json!({
24341            "main.rs": sample_text,
24342        }),
24343    )
24344    .await;
24345    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24346    let (workspace, cx) =
24347        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24348
24349    let worktree_id = workspace.update(cx, |workspace, cx| {
24350        workspace.project().update(cx, |project, cx| {
24351            project.worktrees(cx).next().unwrap().read(cx).id()
24352        })
24353    });
24354
24355    let buffer = project
24356        .update(cx, |project, cx| {
24357            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24358        })
24359        .await
24360        .unwrap();
24361
24362    let (editor, cx) = cx.add_window_view(|window, cx| {
24363        Editor::new(
24364            EditorMode::full(),
24365            MultiBuffer::build_from_buffer(buffer, cx),
24366            Some(project.clone()),
24367            window,
24368            cx,
24369        )
24370    });
24371
24372    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24373    let abs_path = project.read_with(cx, |project, cx| {
24374        project
24375            .absolute_path(&project_path, cx)
24376            .map(Arc::from)
24377            .unwrap()
24378    });
24379
24380    editor.update_in(cx, |editor, window, cx| {
24381        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24382    });
24383
24384    let breakpoints = editor.update(cx, |editor, cx| {
24385        editor
24386            .breakpoint_store()
24387            .as_ref()
24388            .unwrap()
24389            .read(cx)
24390            .all_source_breakpoints(cx)
24391    });
24392
24393    assert_breakpoint(
24394        &breakpoints,
24395        &abs_path,
24396        vec![(0, Breakpoint::new_log("hello world"))],
24397    );
24398
24399    // Removing a log message from a log breakpoint should remove it
24400    editor.update_in(cx, |editor, window, cx| {
24401        add_log_breakpoint_at_cursor(editor, "", window, cx);
24402    });
24403
24404    let breakpoints = editor.update(cx, |editor, cx| {
24405        editor
24406            .breakpoint_store()
24407            .as_ref()
24408            .unwrap()
24409            .read(cx)
24410            .all_source_breakpoints(cx)
24411    });
24412
24413    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24414
24415    editor.update_in(cx, |editor, window, cx| {
24416        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24417        editor.move_to_end(&MoveToEnd, window, cx);
24418        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24419        // Not adding a log message to a standard breakpoint shouldn't remove it
24420        add_log_breakpoint_at_cursor(editor, "", window, cx);
24421    });
24422
24423    let breakpoints = editor.update(cx, |editor, cx| {
24424        editor
24425            .breakpoint_store()
24426            .as_ref()
24427            .unwrap()
24428            .read(cx)
24429            .all_source_breakpoints(cx)
24430    });
24431
24432    assert_breakpoint(
24433        &breakpoints,
24434        &abs_path,
24435        vec![
24436            (0, Breakpoint::new_standard()),
24437            (3, Breakpoint::new_standard()),
24438        ],
24439    );
24440
24441    editor.update_in(cx, |editor, window, cx| {
24442        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24443    });
24444
24445    let breakpoints = editor.update(cx, |editor, cx| {
24446        editor
24447            .breakpoint_store()
24448            .as_ref()
24449            .unwrap()
24450            .read(cx)
24451            .all_source_breakpoints(cx)
24452    });
24453
24454    assert_breakpoint(
24455        &breakpoints,
24456        &abs_path,
24457        vec![
24458            (0, Breakpoint::new_standard()),
24459            (3, Breakpoint::new_log("hello world")),
24460        ],
24461    );
24462
24463    editor.update_in(cx, |editor, window, cx| {
24464        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
24465    });
24466
24467    let breakpoints = editor.update(cx, |editor, cx| {
24468        editor
24469            .breakpoint_store()
24470            .as_ref()
24471            .unwrap()
24472            .read(cx)
24473            .all_source_breakpoints(cx)
24474    });
24475
24476    assert_breakpoint(
24477        &breakpoints,
24478        &abs_path,
24479        vec![
24480            (0, Breakpoint::new_standard()),
24481            (3, Breakpoint::new_log("hello Earth!!")),
24482        ],
24483    );
24484}
24485
24486/// This also tests that Editor::breakpoint_at_cursor_head is working properly
24487/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
24488/// or when breakpoints were placed out of order. This tests for a regression too
24489#[gpui::test]
24490async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
24491    init_test(cx, |_| {});
24492
24493    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24494    let fs = FakeFs::new(cx.executor());
24495    fs.insert_tree(
24496        path!("/a"),
24497        json!({
24498            "main.rs": sample_text,
24499        }),
24500    )
24501    .await;
24502    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24503    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24504    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24505
24506    let fs = FakeFs::new(cx.executor());
24507    fs.insert_tree(
24508        path!("/a"),
24509        json!({
24510            "main.rs": sample_text,
24511        }),
24512    )
24513    .await;
24514    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24515    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24516    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24517    let worktree_id = workspace
24518        .update(cx, |workspace, _window, cx| {
24519            workspace.project().update(cx, |project, cx| {
24520                project.worktrees(cx).next().unwrap().read(cx).id()
24521            })
24522        })
24523        .unwrap();
24524
24525    let buffer = project
24526        .update(cx, |project, cx| {
24527            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24528        })
24529        .await
24530        .unwrap();
24531
24532    let (editor, cx) = cx.add_window_view(|window, cx| {
24533        Editor::new(
24534            EditorMode::full(),
24535            MultiBuffer::build_from_buffer(buffer, cx),
24536            Some(project.clone()),
24537            window,
24538            cx,
24539        )
24540    });
24541
24542    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24543    let abs_path = project.read_with(cx, |project, cx| {
24544        project
24545            .absolute_path(&project_path, cx)
24546            .map(Arc::from)
24547            .unwrap()
24548    });
24549
24550    // assert we can add breakpoint on the first line
24551    editor.update_in(cx, |editor, window, cx| {
24552        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24553        editor.move_to_end(&MoveToEnd, window, cx);
24554        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24555        editor.move_up(&MoveUp, window, cx);
24556        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24557    });
24558
24559    let breakpoints = editor.update(cx, |editor, cx| {
24560        editor
24561            .breakpoint_store()
24562            .as_ref()
24563            .unwrap()
24564            .read(cx)
24565            .all_source_breakpoints(cx)
24566    });
24567
24568    assert_eq!(1, breakpoints.len());
24569    assert_breakpoint(
24570        &breakpoints,
24571        &abs_path,
24572        vec![
24573            (0, Breakpoint::new_standard()),
24574            (2, Breakpoint::new_standard()),
24575            (3, Breakpoint::new_standard()),
24576        ],
24577    );
24578
24579    editor.update_in(cx, |editor, window, cx| {
24580        editor.move_to_beginning(&MoveToBeginning, window, cx);
24581        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24582        editor.move_to_end(&MoveToEnd, window, cx);
24583        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24584        // Disabling a breakpoint that doesn't exist should do nothing
24585        editor.move_up(&MoveUp, window, cx);
24586        editor.move_up(&MoveUp, window, cx);
24587        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24588    });
24589
24590    let breakpoints = editor.update(cx, |editor, cx| {
24591        editor
24592            .breakpoint_store()
24593            .as_ref()
24594            .unwrap()
24595            .read(cx)
24596            .all_source_breakpoints(cx)
24597    });
24598
24599    let disable_breakpoint = {
24600        let mut bp = Breakpoint::new_standard();
24601        bp.state = BreakpointState::Disabled;
24602        bp
24603    };
24604
24605    assert_eq!(1, breakpoints.len());
24606    assert_breakpoint(
24607        &breakpoints,
24608        &abs_path,
24609        vec![
24610            (0, disable_breakpoint.clone()),
24611            (2, Breakpoint::new_standard()),
24612            (3, disable_breakpoint.clone()),
24613        ],
24614    );
24615
24616    editor.update_in(cx, |editor, window, cx| {
24617        editor.move_to_beginning(&MoveToBeginning, window, cx);
24618        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24619        editor.move_to_end(&MoveToEnd, window, cx);
24620        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24621        editor.move_up(&MoveUp, window, cx);
24622        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24623    });
24624
24625    let breakpoints = editor.update(cx, |editor, cx| {
24626        editor
24627            .breakpoint_store()
24628            .as_ref()
24629            .unwrap()
24630            .read(cx)
24631            .all_source_breakpoints(cx)
24632    });
24633
24634    assert_eq!(1, breakpoints.len());
24635    assert_breakpoint(
24636        &breakpoints,
24637        &abs_path,
24638        vec![
24639            (0, Breakpoint::new_standard()),
24640            (2, disable_breakpoint),
24641            (3, Breakpoint::new_standard()),
24642        ],
24643    );
24644}
24645
24646#[gpui::test]
24647async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24648    init_test(cx, |_| {});
24649    let capabilities = lsp::ServerCapabilities {
24650        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24651            prepare_provider: Some(true),
24652            work_done_progress_options: Default::default(),
24653        })),
24654        ..Default::default()
24655    };
24656    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24657
24658    cx.set_state(indoc! {"
24659        struct Fˇoo {}
24660    "});
24661
24662    cx.update_editor(|editor, _, cx| {
24663        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24664        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24665        editor.highlight_background::<DocumentHighlightRead>(
24666            &[highlight_range],
24667            |_, theme| theme.colors().editor_document_highlight_read_background,
24668            cx,
24669        );
24670    });
24671
24672    let mut prepare_rename_handler = cx
24673        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24674            move |_, _, _| async move {
24675                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24676                    start: lsp::Position {
24677                        line: 0,
24678                        character: 7,
24679                    },
24680                    end: lsp::Position {
24681                        line: 0,
24682                        character: 10,
24683                    },
24684                })))
24685            },
24686        );
24687    let prepare_rename_task = cx
24688        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24689        .expect("Prepare rename was not started");
24690    prepare_rename_handler.next().await.unwrap();
24691    prepare_rename_task.await.expect("Prepare rename failed");
24692
24693    let mut rename_handler =
24694        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24695            let edit = lsp::TextEdit {
24696                range: lsp::Range {
24697                    start: lsp::Position {
24698                        line: 0,
24699                        character: 7,
24700                    },
24701                    end: lsp::Position {
24702                        line: 0,
24703                        character: 10,
24704                    },
24705                },
24706                new_text: "FooRenamed".to_string(),
24707            };
24708            Ok(Some(lsp::WorkspaceEdit::new(
24709                // Specify the same edit twice
24710                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24711            )))
24712        });
24713    let rename_task = cx
24714        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24715        .expect("Confirm rename was not started");
24716    rename_handler.next().await.unwrap();
24717    rename_task.await.expect("Confirm rename failed");
24718    cx.run_until_parked();
24719
24720    // Despite two edits, only one is actually applied as those are identical
24721    cx.assert_editor_state(indoc! {"
24722        struct FooRenamedˇ {}
24723    "});
24724}
24725
24726#[gpui::test]
24727async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24728    init_test(cx, |_| {});
24729    // These capabilities indicate that the server does not support prepare rename.
24730    let capabilities = lsp::ServerCapabilities {
24731        rename_provider: Some(lsp::OneOf::Left(true)),
24732        ..Default::default()
24733    };
24734    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24735
24736    cx.set_state(indoc! {"
24737        struct Fˇoo {}
24738    "});
24739
24740    cx.update_editor(|editor, _window, cx| {
24741        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24742        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24743        editor.highlight_background::<DocumentHighlightRead>(
24744            &[highlight_range],
24745            |_, theme| theme.colors().editor_document_highlight_read_background,
24746            cx,
24747        );
24748    });
24749
24750    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24751        .expect("Prepare rename was not started")
24752        .await
24753        .expect("Prepare rename failed");
24754
24755    let mut rename_handler =
24756        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24757            let edit = lsp::TextEdit {
24758                range: lsp::Range {
24759                    start: lsp::Position {
24760                        line: 0,
24761                        character: 7,
24762                    },
24763                    end: lsp::Position {
24764                        line: 0,
24765                        character: 10,
24766                    },
24767                },
24768                new_text: "FooRenamed".to_string(),
24769            };
24770            Ok(Some(lsp::WorkspaceEdit::new(
24771                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24772            )))
24773        });
24774    let rename_task = cx
24775        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24776        .expect("Confirm rename was not started");
24777    rename_handler.next().await.unwrap();
24778    rename_task.await.expect("Confirm rename failed");
24779    cx.run_until_parked();
24780
24781    // Correct range is renamed, as `surrounding_word` is used to find it.
24782    cx.assert_editor_state(indoc! {"
24783        struct FooRenamedˇ {}
24784    "});
24785}
24786
24787#[gpui::test]
24788async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24789    init_test(cx, |_| {});
24790    let mut cx = EditorTestContext::new(cx).await;
24791
24792    let language = Arc::new(
24793        Language::new(
24794            LanguageConfig::default(),
24795            Some(tree_sitter_html::LANGUAGE.into()),
24796        )
24797        .with_brackets_query(
24798            r#"
24799            ("<" @open "/>" @close)
24800            ("</" @open ">" @close)
24801            ("<" @open ">" @close)
24802            ("\"" @open "\"" @close)
24803            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24804        "#,
24805        )
24806        .unwrap(),
24807    );
24808    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24809
24810    cx.set_state(indoc! {"
24811        <span>ˇ</span>
24812    "});
24813    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24814    cx.assert_editor_state(indoc! {"
24815        <span>
24816        ˇ
24817        </span>
24818    "});
24819
24820    cx.set_state(indoc! {"
24821        <span><span></span>ˇ</span>
24822    "});
24823    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24824    cx.assert_editor_state(indoc! {"
24825        <span><span></span>
24826        ˇ</span>
24827    "});
24828
24829    cx.set_state(indoc! {"
24830        <span>ˇ
24831        </span>
24832    "});
24833    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24834    cx.assert_editor_state(indoc! {"
24835        <span>
24836        ˇ
24837        </span>
24838    "});
24839}
24840
24841#[gpui::test(iterations = 10)]
24842async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
24843    init_test(cx, |_| {});
24844
24845    let fs = FakeFs::new(cx.executor());
24846    fs.insert_tree(
24847        path!("/dir"),
24848        json!({
24849            "a.ts": "a",
24850        }),
24851    )
24852    .await;
24853
24854    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
24855    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24856    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24857
24858    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24859    language_registry.add(Arc::new(Language::new(
24860        LanguageConfig {
24861            name: "TypeScript".into(),
24862            matcher: LanguageMatcher {
24863                path_suffixes: vec!["ts".to_string()],
24864                ..Default::default()
24865            },
24866            ..Default::default()
24867        },
24868        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24869    )));
24870    let mut fake_language_servers = language_registry.register_fake_lsp(
24871        "TypeScript",
24872        FakeLspAdapter {
24873            capabilities: lsp::ServerCapabilities {
24874                code_lens_provider: Some(lsp::CodeLensOptions {
24875                    resolve_provider: Some(true),
24876                }),
24877                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24878                    commands: vec!["_the/command".to_string()],
24879                    ..lsp::ExecuteCommandOptions::default()
24880                }),
24881                ..lsp::ServerCapabilities::default()
24882            },
24883            ..FakeLspAdapter::default()
24884        },
24885    );
24886
24887    let editor = workspace
24888        .update(cx, |workspace, window, cx| {
24889            workspace.open_abs_path(
24890                PathBuf::from(path!("/dir/a.ts")),
24891                OpenOptions::default(),
24892                window,
24893                cx,
24894            )
24895        })
24896        .unwrap()
24897        .await
24898        .unwrap()
24899        .downcast::<Editor>()
24900        .unwrap();
24901    cx.executor().run_until_parked();
24902
24903    let fake_server = fake_language_servers.next().await.unwrap();
24904
24905    let buffer = editor.update(cx, |editor, cx| {
24906        editor
24907            .buffer()
24908            .read(cx)
24909            .as_singleton()
24910            .expect("have opened a single file by path")
24911    });
24912
24913    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24914    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24915    drop(buffer_snapshot);
24916    let actions = cx
24917        .update_window(*workspace, |_, window, cx| {
24918            project.code_actions(&buffer, anchor..anchor, window, cx)
24919        })
24920        .unwrap();
24921
24922    fake_server
24923        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24924            Ok(Some(vec![
24925                lsp::CodeLens {
24926                    range: lsp::Range::default(),
24927                    command: Some(lsp::Command {
24928                        title: "Code lens command".to_owned(),
24929                        command: "_the/command".to_owned(),
24930                        arguments: None,
24931                    }),
24932                    data: None,
24933                },
24934                lsp::CodeLens {
24935                    range: lsp::Range::default(),
24936                    command: Some(lsp::Command {
24937                        title: "Command not in capabilities".to_owned(),
24938                        command: "not in capabilities".to_owned(),
24939                        arguments: None,
24940                    }),
24941                    data: None,
24942                },
24943                lsp::CodeLens {
24944                    range: lsp::Range {
24945                        start: lsp::Position {
24946                            line: 1,
24947                            character: 1,
24948                        },
24949                        end: lsp::Position {
24950                            line: 1,
24951                            character: 1,
24952                        },
24953                    },
24954                    command: Some(lsp::Command {
24955                        title: "Command not in range".to_owned(),
24956                        command: "_the/command".to_owned(),
24957                        arguments: None,
24958                    }),
24959                    data: None,
24960                },
24961            ]))
24962        })
24963        .next()
24964        .await;
24965
24966    let actions = actions.await.unwrap();
24967    assert_eq!(
24968        actions.len(),
24969        1,
24970        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24971    );
24972    let action = actions[0].clone();
24973    let apply = project.update(cx, |project, cx| {
24974        project.apply_code_action(buffer.clone(), action, true, cx)
24975    });
24976
24977    // Resolving the code action does not populate its edits. In absence of
24978    // edits, we must execute the given command.
24979    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24980        |mut lens, _| async move {
24981            let lens_command = lens.command.as_mut().expect("should have a command");
24982            assert_eq!(lens_command.title, "Code lens command");
24983            lens_command.arguments = Some(vec![json!("the-argument")]);
24984            Ok(lens)
24985        },
24986    );
24987
24988    // While executing the command, the language server sends the editor
24989    // a `workspaceEdit` request.
24990    fake_server
24991        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24992            let fake = fake_server.clone();
24993            move |params, _| {
24994                assert_eq!(params.command, "_the/command");
24995                let fake = fake.clone();
24996                async move {
24997                    fake.server
24998                        .request::<lsp::request::ApplyWorkspaceEdit>(
24999                            lsp::ApplyWorkspaceEditParams {
25000                                label: None,
25001                                edit: lsp::WorkspaceEdit {
25002                                    changes: Some(
25003                                        [(
25004                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
25005                                            vec![lsp::TextEdit {
25006                                                range: lsp::Range::new(
25007                                                    lsp::Position::new(0, 0),
25008                                                    lsp::Position::new(0, 0),
25009                                                ),
25010                                                new_text: "X".into(),
25011                                            }],
25012                                        )]
25013                                        .into_iter()
25014                                        .collect(),
25015                                    ),
25016                                    ..lsp::WorkspaceEdit::default()
25017                                },
25018                            },
25019                        )
25020                        .await
25021                        .into_response()
25022                        .unwrap();
25023                    Ok(Some(json!(null)))
25024                }
25025            }
25026        })
25027        .next()
25028        .await;
25029
25030    // Applying the code lens command returns a project transaction containing the edits
25031    // sent by the language server in its `workspaceEdit` request.
25032    let transaction = apply.await.unwrap();
25033    assert!(transaction.0.contains_key(&buffer));
25034    buffer.update(cx, |buffer, cx| {
25035        assert_eq!(buffer.text(), "Xa");
25036        buffer.undo(cx);
25037        assert_eq!(buffer.text(), "a");
25038    });
25039
25040    let actions_after_edits = cx
25041        .update_window(*workspace, |_, window, cx| {
25042            project.code_actions(&buffer, anchor..anchor, window, cx)
25043        })
25044        .unwrap()
25045        .await
25046        .unwrap();
25047    assert_eq!(
25048        actions, actions_after_edits,
25049        "For the same selection, same code lens actions should be returned"
25050    );
25051
25052    let _responses =
25053        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
25054            panic!("No more code lens requests are expected");
25055        });
25056    editor.update_in(cx, |editor, window, cx| {
25057        editor.select_all(&SelectAll, window, cx);
25058    });
25059    cx.executor().run_until_parked();
25060    let new_actions = cx
25061        .update_window(*workspace, |_, window, cx| {
25062            project.code_actions(&buffer, anchor..anchor, window, cx)
25063        })
25064        .unwrap()
25065        .await
25066        .unwrap();
25067    assert_eq!(
25068        actions, new_actions,
25069        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
25070    );
25071}
25072
25073#[gpui::test]
25074async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
25075    init_test(cx, |_| {});
25076
25077    let fs = FakeFs::new(cx.executor());
25078    let main_text = r#"fn main() {
25079println!("1");
25080println!("2");
25081println!("3");
25082println!("4");
25083println!("5");
25084}"#;
25085    let lib_text = "mod foo {}";
25086    fs.insert_tree(
25087        path!("/a"),
25088        json!({
25089            "lib.rs": lib_text,
25090            "main.rs": main_text,
25091        }),
25092    )
25093    .await;
25094
25095    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25096    let (workspace, cx) =
25097        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25098    let worktree_id = workspace.update(cx, |workspace, cx| {
25099        workspace.project().update(cx, |project, cx| {
25100            project.worktrees(cx).next().unwrap().read(cx).id()
25101        })
25102    });
25103
25104    let expected_ranges = vec![
25105        Point::new(0, 0)..Point::new(0, 0),
25106        Point::new(1, 0)..Point::new(1, 1),
25107        Point::new(2, 0)..Point::new(2, 2),
25108        Point::new(3, 0)..Point::new(3, 3),
25109    ];
25110
25111    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25112    let editor_1 = workspace
25113        .update_in(cx, |workspace, window, cx| {
25114            workspace.open_path(
25115                (worktree_id, rel_path("main.rs")),
25116                Some(pane_1.downgrade()),
25117                true,
25118                window,
25119                cx,
25120            )
25121        })
25122        .unwrap()
25123        .await
25124        .downcast::<Editor>()
25125        .unwrap();
25126    pane_1.update(cx, |pane, cx| {
25127        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25128        open_editor.update(cx, |editor, cx| {
25129            assert_eq!(
25130                editor.display_text(cx),
25131                main_text,
25132                "Original main.rs text on initial open",
25133            );
25134            assert_eq!(
25135                editor
25136                    .selections
25137                    .all::<Point>(&editor.display_snapshot(cx))
25138                    .into_iter()
25139                    .map(|s| s.range())
25140                    .collect::<Vec<_>>(),
25141                vec![Point::zero()..Point::zero()],
25142                "Default selections on initial open",
25143            );
25144        })
25145    });
25146    editor_1.update_in(cx, |editor, window, cx| {
25147        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25148            s.select_ranges(expected_ranges.clone());
25149        });
25150    });
25151
25152    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
25153        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
25154    });
25155    let editor_2 = workspace
25156        .update_in(cx, |workspace, window, cx| {
25157            workspace.open_path(
25158                (worktree_id, rel_path("main.rs")),
25159                Some(pane_2.downgrade()),
25160                true,
25161                window,
25162                cx,
25163            )
25164        })
25165        .unwrap()
25166        .await
25167        .downcast::<Editor>()
25168        .unwrap();
25169    pane_2.update(cx, |pane, cx| {
25170        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25171        open_editor.update(cx, |editor, cx| {
25172            assert_eq!(
25173                editor.display_text(cx),
25174                main_text,
25175                "Original main.rs text on initial open in another panel",
25176            );
25177            assert_eq!(
25178                editor
25179                    .selections
25180                    .all::<Point>(&editor.display_snapshot(cx))
25181                    .into_iter()
25182                    .map(|s| s.range())
25183                    .collect::<Vec<_>>(),
25184                vec![Point::zero()..Point::zero()],
25185                "Default selections on initial open in another panel",
25186            );
25187        })
25188    });
25189
25190    editor_2.update_in(cx, |editor, window, cx| {
25191        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
25192    });
25193
25194    let _other_editor_1 = workspace
25195        .update_in(cx, |workspace, window, cx| {
25196            workspace.open_path(
25197                (worktree_id, rel_path("lib.rs")),
25198                Some(pane_1.downgrade()),
25199                true,
25200                window,
25201                cx,
25202            )
25203        })
25204        .unwrap()
25205        .await
25206        .downcast::<Editor>()
25207        .unwrap();
25208    pane_1
25209        .update_in(cx, |pane, window, cx| {
25210            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25211        })
25212        .await
25213        .unwrap();
25214    drop(editor_1);
25215    pane_1.update(cx, |pane, cx| {
25216        pane.active_item()
25217            .unwrap()
25218            .downcast::<Editor>()
25219            .unwrap()
25220            .update(cx, |editor, cx| {
25221                assert_eq!(
25222                    editor.display_text(cx),
25223                    lib_text,
25224                    "Other file should be open and active",
25225                );
25226            });
25227        assert_eq!(pane.items().count(), 1, "No other editors should be open");
25228    });
25229
25230    let _other_editor_2 = workspace
25231        .update_in(cx, |workspace, window, cx| {
25232            workspace.open_path(
25233                (worktree_id, rel_path("lib.rs")),
25234                Some(pane_2.downgrade()),
25235                true,
25236                window,
25237                cx,
25238            )
25239        })
25240        .unwrap()
25241        .await
25242        .downcast::<Editor>()
25243        .unwrap();
25244    pane_2
25245        .update_in(cx, |pane, window, cx| {
25246            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
25247        })
25248        .await
25249        .unwrap();
25250    drop(editor_2);
25251    pane_2.update(cx, |pane, cx| {
25252        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25253        open_editor.update(cx, |editor, cx| {
25254            assert_eq!(
25255                editor.display_text(cx),
25256                lib_text,
25257                "Other file should be open and active in another panel too",
25258            );
25259        });
25260        assert_eq!(
25261            pane.items().count(),
25262            1,
25263            "No other editors should be open in another pane",
25264        );
25265    });
25266
25267    let _editor_1_reopened = workspace
25268        .update_in(cx, |workspace, window, cx| {
25269            workspace.open_path(
25270                (worktree_id, rel_path("main.rs")),
25271                Some(pane_1.downgrade()),
25272                true,
25273                window,
25274                cx,
25275            )
25276        })
25277        .unwrap()
25278        .await
25279        .downcast::<Editor>()
25280        .unwrap();
25281    let _editor_2_reopened = workspace
25282        .update_in(cx, |workspace, window, cx| {
25283            workspace.open_path(
25284                (worktree_id, rel_path("main.rs")),
25285                Some(pane_2.downgrade()),
25286                true,
25287                window,
25288                cx,
25289            )
25290        })
25291        .unwrap()
25292        .await
25293        .downcast::<Editor>()
25294        .unwrap();
25295    pane_1.update(cx, |pane, cx| {
25296        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25297        open_editor.update(cx, |editor, cx| {
25298            assert_eq!(
25299                editor.display_text(cx),
25300                main_text,
25301                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
25302            );
25303            assert_eq!(
25304                editor
25305                    .selections
25306                    .all::<Point>(&editor.display_snapshot(cx))
25307                    .into_iter()
25308                    .map(|s| s.range())
25309                    .collect::<Vec<_>>(),
25310                expected_ranges,
25311                "Previous editor in the 1st panel had selections and should get them restored on reopen",
25312            );
25313        })
25314    });
25315    pane_2.update(cx, |pane, cx| {
25316        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25317        open_editor.update(cx, |editor, cx| {
25318            assert_eq!(
25319                editor.display_text(cx),
25320                r#"fn main() {
25321⋯rintln!("1");
25322⋯intln!("2");
25323⋯ntln!("3");
25324println!("4");
25325println!("5");
25326}"#,
25327                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
25328            );
25329            assert_eq!(
25330                editor
25331                    .selections
25332                    .all::<Point>(&editor.display_snapshot(cx))
25333                    .into_iter()
25334                    .map(|s| s.range())
25335                    .collect::<Vec<_>>(),
25336                vec![Point::zero()..Point::zero()],
25337                "Previous editor in the 2nd pane had no selections changed hence should restore none",
25338            );
25339        })
25340    });
25341}
25342
25343#[gpui::test]
25344async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
25345    init_test(cx, |_| {});
25346
25347    let fs = FakeFs::new(cx.executor());
25348    let main_text = r#"fn main() {
25349println!("1");
25350println!("2");
25351println!("3");
25352println!("4");
25353println!("5");
25354}"#;
25355    let lib_text = "mod foo {}";
25356    fs.insert_tree(
25357        path!("/a"),
25358        json!({
25359            "lib.rs": lib_text,
25360            "main.rs": main_text,
25361        }),
25362    )
25363    .await;
25364
25365    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25366    let (workspace, cx) =
25367        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25368    let worktree_id = workspace.update(cx, |workspace, cx| {
25369        workspace.project().update(cx, |project, cx| {
25370            project.worktrees(cx).next().unwrap().read(cx).id()
25371        })
25372    });
25373
25374    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25375    let editor = workspace
25376        .update_in(cx, |workspace, window, cx| {
25377            workspace.open_path(
25378                (worktree_id, rel_path("main.rs")),
25379                Some(pane.downgrade()),
25380                true,
25381                window,
25382                cx,
25383            )
25384        })
25385        .unwrap()
25386        .await
25387        .downcast::<Editor>()
25388        .unwrap();
25389    pane.update(cx, |pane, cx| {
25390        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25391        open_editor.update(cx, |editor, cx| {
25392            assert_eq!(
25393                editor.display_text(cx),
25394                main_text,
25395                "Original main.rs text on initial open",
25396            );
25397        })
25398    });
25399    editor.update_in(cx, |editor, window, cx| {
25400        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
25401    });
25402
25403    cx.update_global(|store: &mut SettingsStore, cx| {
25404        store.update_user_settings(cx, |s| {
25405            s.workspace.restore_on_file_reopen = Some(false);
25406        });
25407    });
25408    editor.update_in(cx, |editor, window, cx| {
25409        editor.fold_ranges(
25410            vec![
25411                Point::new(1, 0)..Point::new(1, 1),
25412                Point::new(2, 0)..Point::new(2, 2),
25413                Point::new(3, 0)..Point::new(3, 3),
25414            ],
25415            false,
25416            window,
25417            cx,
25418        );
25419    });
25420    pane.update_in(cx, |pane, window, cx| {
25421        pane.close_all_items(&CloseAllItems::default(), window, cx)
25422    })
25423    .await
25424    .unwrap();
25425    pane.update(cx, |pane, _| {
25426        assert!(pane.active_item().is_none());
25427    });
25428    cx.update_global(|store: &mut SettingsStore, cx| {
25429        store.update_user_settings(cx, |s| {
25430            s.workspace.restore_on_file_reopen = Some(true);
25431        });
25432    });
25433
25434    let _editor_reopened = workspace
25435        .update_in(cx, |workspace, window, cx| {
25436            workspace.open_path(
25437                (worktree_id, rel_path("main.rs")),
25438                Some(pane.downgrade()),
25439                true,
25440                window,
25441                cx,
25442            )
25443        })
25444        .unwrap()
25445        .await
25446        .downcast::<Editor>()
25447        .unwrap();
25448    pane.update(cx, |pane, cx| {
25449        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25450        open_editor.update(cx, |editor, cx| {
25451            assert_eq!(
25452                editor.display_text(cx),
25453                main_text,
25454                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
25455            );
25456        })
25457    });
25458}
25459
25460#[gpui::test]
25461async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
25462    struct EmptyModalView {
25463        focus_handle: gpui::FocusHandle,
25464    }
25465    impl EventEmitter<DismissEvent> for EmptyModalView {}
25466    impl Render for EmptyModalView {
25467        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
25468            div()
25469        }
25470    }
25471    impl Focusable for EmptyModalView {
25472        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
25473            self.focus_handle.clone()
25474        }
25475    }
25476    impl workspace::ModalView for EmptyModalView {}
25477    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
25478        EmptyModalView {
25479            focus_handle: cx.focus_handle(),
25480        }
25481    }
25482
25483    init_test(cx, |_| {});
25484
25485    let fs = FakeFs::new(cx.executor());
25486    let project = Project::test(fs, [], cx).await;
25487    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25488    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
25489    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25490    let editor = cx.new_window_entity(|window, cx| {
25491        Editor::new(
25492            EditorMode::full(),
25493            buffer,
25494            Some(project.clone()),
25495            window,
25496            cx,
25497        )
25498    });
25499    workspace
25500        .update(cx, |workspace, window, cx| {
25501            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
25502        })
25503        .unwrap();
25504    editor.update_in(cx, |editor, window, cx| {
25505        editor.open_context_menu(&OpenContextMenu, window, cx);
25506        assert!(editor.mouse_context_menu.is_some());
25507    });
25508    workspace
25509        .update(cx, |workspace, window, cx| {
25510            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
25511        })
25512        .unwrap();
25513    cx.read(|cx| {
25514        assert!(editor.read(cx).mouse_context_menu.is_none());
25515    });
25516}
25517
25518fn set_linked_edit_ranges(
25519    opening: (Point, Point),
25520    closing: (Point, Point),
25521    editor: &mut Editor,
25522    cx: &mut Context<Editor>,
25523) {
25524    let Some((buffer, _)) = editor
25525        .buffer
25526        .read(cx)
25527        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
25528    else {
25529        panic!("Failed to get buffer for selection position");
25530    };
25531    let buffer = buffer.read(cx);
25532    let buffer_id = buffer.remote_id();
25533    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
25534    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
25535    let mut linked_ranges = HashMap::default();
25536    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
25537    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
25538}
25539
25540#[gpui::test]
25541async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
25542    init_test(cx, |_| {});
25543
25544    let fs = FakeFs::new(cx.executor());
25545    fs.insert_file(path!("/file.html"), Default::default())
25546        .await;
25547
25548    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
25549
25550    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25551    let html_language = Arc::new(Language::new(
25552        LanguageConfig {
25553            name: "HTML".into(),
25554            matcher: LanguageMatcher {
25555                path_suffixes: vec!["html".to_string()],
25556                ..LanguageMatcher::default()
25557            },
25558            brackets: BracketPairConfig {
25559                pairs: vec![BracketPair {
25560                    start: "<".into(),
25561                    end: ">".into(),
25562                    close: true,
25563                    ..Default::default()
25564                }],
25565                ..Default::default()
25566            },
25567            ..Default::default()
25568        },
25569        Some(tree_sitter_html::LANGUAGE.into()),
25570    ));
25571    language_registry.add(html_language);
25572    let mut fake_servers = language_registry.register_fake_lsp(
25573        "HTML",
25574        FakeLspAdapter {
25575            capabilities: lsp::ServerCapabilities {
25576                completion_provider: Some(lsp::CompletionOptions {
25577                    resolve_provider: Some(true),
25578                    ..Default::default()
25579                }),
25580                ..Default::default()
25581            },
25582            ..Default::default()
25583        },
25584    );
25585
25586    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25587    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25588
25589    let worktree_id = workspace
25590        .update(cx, |workspace, _window, cx| {
25591            workspace.project().update(cx, |project, cx| {
25592                project.worktrees(cx).next().unwrap().read(cx).id()
25593            })
25594        })
25595        .unwrap();
25596    project
25597        .update(cx, |project, cx| {
25598            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25599        })
25600        .await
25601        .unwrap();
25602    let editor = workspace
25603        .update(cx, |workspace, window, cx| {
25604            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25605        })
25606        .unwrap()
25607        .await
25608        .unwrap()
25609        .downcast::<Editor>()
25610        .unwrap();
25611
25612    let fake_server = fake_servers.next().await.unwrap();
25613    cx.run_until_parked();
25614    editor.update_in(cx, |editor, window, cx| {
25615        editor.set_text("<ad></ad>", window, cx);
25616        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25617            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25618        });
25619        set_linked_edit_ranges(
25620            (Point::new(0, 1), Point::new(0, 3)),
25621            (Point::new(0, 6), Point::new(0, 8)),
25622            editor,
25623            cx,
25624        );
25625    });
25626    let mut completion_handle =
25627        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25628            Ok(Some(lsp::CompletionResponse::Array(vec![
25629                lsp::CompletionItem {
25630                    label: "head".to_string(),
25631                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25632                        lsp::InsertReplaceEdit {
25633                            new_text: "head".to_string(),
25634                            insert: lsp::Range::new(
25635                                lsp::Position::new(0, 1),
25636                                lsp::Position::new(0, 3),
25637                            ),
25638                            replace: lsp::Range::new(
25639                                lsp::Position::new(0, 1),
25640                                lsp::Position::new(0, 3),
25641                            ),
25642                        },
25643                    )),
25644                    ..Default::default()
25645                },
25646            ])))
25647        });
25648    editor.update_in(cx, |editor, window, cx| {
25649        editor.show_completions(&ShowCompletions, window, cx);
25650    });
25651    cx.run_until_parked();
25652    completion_handle.next().await.unwrap();
25653    editor.update(cx, |editor, _| {
25654        assert!(
25655            editor.context_menu_visible(),
25656            "Completion menu should be visible"
25657        );
25658    });
25659    editor.update_in(cx, |editor, window, cx| {
25660        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25661    });
25662    cx.executor().run_until_parked();
25663    editor.update(cx, |editor, cx| {
25664        assert_eq!(editor.text(cx), "<head></head>");
25665    });
25666}
25667
25668#[gpui::test]
25669async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25670    init_test(cx, |_| {});
25671
25672    let mut cx = EditorTestContext::new(cx).await;
25673    let language = Arc::new(Language::new(
25674        LanguageConfig {
25675            name: "TSX".into(),
25676            matcher: LanguageMatcher {
25677                path_suffixes: vec!["tsx".to_string()],
25678                ..LanguageMatcher::default()
25679            },
25680            brackets: BracketPairConfig {
25681                pairs: vec![BracketPair {
25682                    start: "<".into(),
25683                    end: ">".into(),
25684                    close: true,
25685                    ..Default::default()
25686                }],
25687                ..Default::default()
25688            },
25689            linked_edit_characters: HashSet::from_iter(['.']),
25690            ..Default::default()
25691        },
25692        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25693    ));
25694    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25695
25696    // Test typing > does not extend linked pair
25697    cx.set_state("<divˇ<div></div>");
25698    cx.update_editor(|editor, _, cx| {
25699        set_linked_edit_ranges(
25700            (Point::new(0, 1), Point::new(0, 4)),
25701            (Point::new(0, 11), Point::new(0, 14)),
25702            editor,
25703            cx,
25704        );
25705    });
25706    cx.update_editor(|editor, window, cx| {
25707        editor.handle_input(">", window, cx);
25708    });
25709    cx.assert_editor_state("<div>ˇ<div></div>");
25710
25711    // Test typing . do extend linked pair
25712    cx.set_state("<Animatedˇ></Animated>");
25713    cx.update_editor(|editor, _, cx| {
25714        set_linked_edit_ranges(
25715            (Point::new(0, 1), Point::new(0, 9)),
25716            (Point::new(0, 12), Point::new(0, 20)),
25717            editor,
25718            cx,
25719        );
25720    });
25721    cx.update_editor(|editor, window, cx| {
25722        editor.handle_input(".", window, cx);
25723    });
25724    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25725    cx.update_editor(|editor, _, cx| {
25726        set_linked_edit_ranges(
25727            (Point::new(0, 1), Point::new(0, 10)),
25728            (Point::new(0, 13), Point::new(0, 21)),
25729            editor,
25730            cx,
25731        );
25732    });
25733    cx.update_editor(|editor, window, cx| {
25734        editor.handle_input("V", window, cx);
25735    });
25736    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25737}
25738
25739#[gpui::test]
25740async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25741    init_test(cx, |_| {});
25742
25743    let fs = FakeFs::new(cx.executor());
25744    fs.insert_tree(
25745        path!("/root"),
25746        json!({
25747            "a": {
25748                "main.rs": "fn main() {}",
25749            },
25750            "foo": {
25751                "bar": {
25752                    "external_file.rs": "pub mod external {}",
25753                }
25754            }
25755        }),
25756    )
25757    .await;
25758
25759    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25760    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25761    language_registry.add(rust_lang());
25762    let _fake_servers = language_registry.register_fake_lsp(
25763        "Rust",
25764        FakeLspAdapter {
25765            ..FakeLspAdapter::default()
25766        },
25767    );
25768    let (workspace, cx) =
25769        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25770    let worktree_id = workspace.update(cx, |workspace, cx| {
25771        workspace.project().update(cx, |project, cx| {
25772            project.worktrees(cx).next().unwrap().read(cx).id()
25773        })
25774    });
25775
25776    let assert_language_servers_count =
25777        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25778            project.update(cx, |project, cx| {
25779                let current = project
25780                    .lsp_store()
25781                    .read(cx)
25782                    .as_local()
25783                    .unwrap()
25784                    .language_servers
25785                    .len();
25786                assert_eq!(expected, current, "{context}");
25787            });
25788        };
25789
25790    assert_language_servers_count(
25791        0,
25792        "No servers should be running before any file is open",
25793        cx,
25794    );
25795    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25796    let main_editor = workspace
25797        .update_in(cx, |workspace, window, cx| {
25798            workspace.open_path(
25799                (worktree_id, rel_path("main.rs")),
25800                Some(pane.downgrade()),
25801                true,
25802                window,
25803                cx,
25804            )
25805        })
25806        .unwrap()
25807        .await
25808        .downcast::<Editor>()
25809        .unwrap();
25810    pane.update(cx, |pane, cx| {
25811        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25812        open_editor.update(cx, |editor, cx| {
25813            assert_eq!(
25814                editor.display_text(cx),
25815                "fn main() {}",
25816                "Original main.rs text on initial open",
25817            );
25818        });
25819        assert_eq!(open_editor, main_editor);
25820    });
25821    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
25822
25823    let external_editor = workspace
25824        .update_in(cx, |workspace, window, cx| {
25825            workspace.open_abs_path(
25826                PathBuf::from("/root/foo/bar/external_file.rs"),
25827                OpenOptions::default(),
25828                window,
25829                cx,
25830            )
25831        })
25832        .await
25833        .expect("opening external file")
25834        .downcast::<Editor>()
25835        .expect("downcasted external file's open element to editor");
25836    pane.update(cx, |pane, cx| {
25837        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25838        open_editor.update(cx, |editor, cx| {
25839            assert_eq!(
25840                editor.display_text(cx),
25841                "pub mod external {}",
25842                "External file is open now",
25843            );
25844        });
25845        assert_eq!(open_editor, external_editor);
25846    });
25847    assert_language_servers_count(
25848        1,
25849        "Second, external, *.rs file should join the existing server",
25850        cx,
25851    );
25852
25853    pane.update_in(cx, |pane, window, cx| {
25854        pane.close_active_item(&CloseActiveItem::default(), window, cx)
25855    })
25856    .await
25857    .unwrap();
25858    pane.update_in(cx, |pane, window, cx| {
25859        pane.navigate_backward(&Default::default(), window, cx);
25860    });
25861    cx.run_until_parked();
25862    pane.update(cx, |pane, cx| {
25863        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25864        open_editor.update(cx, |editor, cx| {
25865            assert_eq!(
25866                editor.display_text(cx),
25867                "pub mod external {}",
25868                "External file is open now",
25869            );
25870        });
25871    });
25872    assert_language_servers_count(
25873        1,
25874        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25875        cx,
25876    );
25877
25878    cx.update(|_, cx| {
25879        workspace::reload(cx);
25880    });
25881    assert_language_servers_count(
25882        1,
25883        "After reloading the worktree with local and external files opened, only one project should be started",
25884        cx,
25885    );
25886}
25887
25888#[gpui::test]
25889async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25890    init_test(cx, |_| {});
25891
25892    let mut cx = EditorTestContext::new(cx).await;
25893    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25894    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25895
25896    // test cursor move to start of each line on tab
25897    // for `if`, `elif`, `else`, `while`, `with` and `for`
25898    cx.set_state(indoc! {"
25899        def main():
25900        ˇ    for item in items:
25901        ˇ        while item.active:
25902        ˇ            if item.value > 10:
25903        ˇ                continue
25904        ˇ            elif item.value < 0:
25905        ˇ                break
25906        ˇ            else:
25907        ˇ                with item.context() as ctx:
25908        ˇ                    yield count
25909        ˇ        else:
25910        ˇ            log('while else')
25911        ˇ    else:
25912        ˇ        log('for else')
25913    "});
25914    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25915    cx.wait_for_autoindent_applied().await;
25916    cx.assert_editor_state(indoc! {"
25917        def main():
25918            ˇfor item in items:
25919                ˇwhile item.active:
25920                    ˇif item.value > 10:
25921                        ˇcontinue
25922                    ˇelif item.value < 0:
25923                        ˇbreak
25924                    ˇelse:
25925                        ˇwith item.context() as ctx:
25926                            ˇyield count
25927                ˇelse:
25928                    ˇlog('while else')
25929            ˇelse:
25930                ˇlog('for else')
25931    "});
25932    // test relative indent is preserved when tab
25933    // for `if`, `elif`, `else`, `while`, `with` and `for`
25934    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25935    cx.wait_for_autoindent_applied().await;
25936    cx.assert_editor_state(indoc! {"
25937        def main():
25938                ˇfor item in items:
25939                    ˇwhile item.active:
25940                        ˇif item.value > 10:
25941                            ˇcontinue
25942                        ˇelif item.value < 0:
25943                            ˇbreak
25944                        ˇelse:
25945                            ˇwith item.context() as ctx:
25946                                ˇyield count
25947                    ˇelse:
25948                        ˇlog('while else')
25949                ˇelse:
25950                    ˇlog('for else')
25951    "});
25952
25953    // test cursor move to start of each line on tab
25954    // for `try`, `except`, `else`, `finally`, `match` and `def`
25955    cx.set_state(indoc! {"
25956        def main():
25957        ˇ    try:
25958        ˇ        fetch()
25959        ˇ    except ValueError:
25960        ˇ        handle_error()
25961        ˇ    else:
25962        ˇ        match value:
25963        ˇ            case _:
25964        ˇ    finally:
25965        ˇ        def status():
25966        ˇ            return 0
25967    "});
25968    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25969    cx.wait_for_autoindent_applied().await;
25970    cx.assert_editor_state(indoc! {"
25971        def main():
25972            ˇtry:
25973                ˇfetch()
25974            ˇexcept ValueError:
25975                ˇhandle_error()
25976            ˇelse:
25977                ˇmatch value:
25978                    ˇcase _:
25979            ˇfinally:
25980                ˇdef status():
25981                    ˇreturn 0
25982    "});
25983    // test relative indent is preserved when tab
25984    // for `try`, `except`, `else`, `finally`, `match` and `def`
25985    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25986    cx.wait_for_autoindent_applied().await;
25987    cx.assert_editor_state(indoc! {"
25988        def main():
25989                ˇtry:
25990                    ˇfetch()
25991                ˇexcept ValueError:
25992                    ˇhandle_error()
25993                ˇelse:
25994                    ˇmatch value:
25995                        ˇcase _:
25996                ˇfinally:
25997                    ˇdef status():
25998                        ˇreturn 0
25999    "});
26000}
26001
26002#[gpui::test]
26003async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
26004    init_test(cx, |_| {});
26005
26006    let mut cx = EditorTestContext::new(cx).await;
26007    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26008    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26009
26010    // test `else` auto outdents when typed inside `if` block
26011    cx.set_state(indoc! {"
26012        def main():
26013            if i == 2:
26014                return
26015                ˇ
26016    "});
26017    cx.update_editor(|editor, window, cx| {
26018        editor.handle_input("else:", window, cx);
26019    });
26020    cx.wait_for_autoindent_applied().await;
26021    cx.assert_editor_state(indoc! {"
26022        def main():
26023            if i == 2:
26024                return
26025            else:ˇ
26026    "});
26027
26028    // test `except` auto outdents when typed inside `try` block
26029    cx.set_state(indoc! {"
26030        def main():
26031            try:
26032                i = 2
26033                ˇ
26034    "});
26035    cx.update_editor(|editor, window, cx| {
26036        editor.handle_input("except:", window, cx);
26037    });
26038    cx.wait_for_autoindent_applied().await;
26039    cx.assert_editor_state(indoc! {"
26040        def main():
26041            try:
26042                i = 2
26043            except:ˇ
26044    "});
26045
26046    // test `else` auto outdents when typed inside `except` block
26047    cx.set_state(indoc! {"
26048        def main():
26049            try:
26050                i = 2
26051            except:
26052                j = 2
26053                ˇ
26054    "});
26055    cx.update_editor(|editor, window, cx| {
26056        editor.handle_input("else:", window, cx);
26057    });
26058    cx.wait_for_autoindent_applied().await;
26059    cx.assert_editor_state(indoc! {"
26060        def main():
26061            try:
26062                i = 2
26063            except:
26064                j = 2
26065            else:ˇ
26066    "});
26067
26068    // test `finally` auto outdents when typed inside `else` block
26069    cx.set_state(indoc! {"
26070        def main():
26071            try:
26072                i = 2
26073            except:
26074                j = 2
26075            else:
26076                k = 2
26077                ˇ
26078    "});
26079    cx.update_editor(|editor, window, cx| {
26080        editor.handle_input("finally:", window, cx);
26081    });
26082    cx.wait_for_autoindent_applied().await;
26083    cx.assert_editor_state(indoc! {"
26084        def main():
26085            try:
26086                i = 2
26087            except:
26088                j = 2
26089            else:
26090                k = 2
26091            finally:ˇ
26092    "});
26093
26094    // test `else` does not outdents when typed inside `except` block right after for block
26095    cx.set_state(indoc! {"
26096        def main():
26097            try:
26098                i = 2
26099            except:
26100                for i in range(n):
26101                    pass
26102                ˇ
26103    "});
26104    cx.update_editor(|editor, window, cx| {
26105        editor.handle_input("else:", window, cx);
26106    });
26107    cx.wait_for_autoindent_applied().await;
26108    cx.assert_editor_state(indoc! {"
26109        def main():
26110            try:
26111                i = 2
26112            except:
26113                for i in range(n):
26114                    pass
26115                else:ˇ
26116    "});
26117
26118    // test `finally` auto outdents when typed inside `else` block right after for block
26119    cx.set_state(indoc! {"
26120        def main():
26121            try:
26122                i = 2
26123            except:
26124                j = 2
26125            else:
26126                for i in range(n):
26127                    pass
26128                ˇ
26129    "});
26130    cx.update_editor(|editor, window, cx| {
26131        editor.handle_input("finally:", window, cx);
26132    });
26133    cx.wait_for_autoindent_applied().await;
26134    cx.assert_editor_state(indoc! {"
26135        def main():
26136            try:
26137                i = 2
26138            except:
26139                j = 2
26140            else:
26141                for i in range(n):
26142                    pass
26143            finally:ˇ
26144    "});
26145
26146    // test `except` outdents to inner "try" block
26147    cx.set_state(indoc! {"
26148        def main():
26149            try:
26150                i = 2
26151                if i == 2:
26152                    try:
26153                        i = 3
26154                        ˇ
26155    "});
26156    cx.update_editor(|editor, window, cx| {
26157        editor.handle_input("except:", window, cx);
26158    });
26159    cx.wait_for_autoindent_applied().await;
26160    cx.assert_editor_state(indoc! {"
26161        def main():
26162            try:
26163                i = 2
26164                if i == 2:
26165                    try:
26166                        i = 3
26167                    except:ˇ
26168    "});
26169
26170    // test `except` outdents to outer "try" block
26171    cx.set_state(indoc! {"
26172        def main():
26173            try:
26174                i = 2
26175                if i == 2:
26176                    try:
26177                        i = 3
26178                ˇ
26179    "});
26180    cx.update_editor(|editor, window, cx| {
26181        editor.handle_input("except:", window, cx);
26182    });
26183    cx.wait_for_autoindent_applied().await;
26184    cx.assert_editor_state(indoc! {"
26185        def main():
26186            try:
26187                i = 2
26188                if i == 2:
26189                    try:
26190                        i = 3
26191            except:ˇ
26192    "});
26193
26194    // test `else` stays at correct indent when typed after `for` block
26195    cx.set_state(indoc! {"
26196        def main():
26197            for i in range(10):
26198                if i == 3:
26199                    break
26200            ˇ
26201    "});
26202    cx.update_editor(|editor, window, cx| {
26203        editor.handle_input("else:", window, cx);
26204    });
26205    cx.wait_for_autoindent_applied().await;
26206    cx.assert_editor_state(indoc! {"
26207        def main():
26208            for i in range(10):
26209                if i == 3:
26210                    break
26211            else:ˇ
26212    "});
26213
26214    // test does not outdent on typing after line with square brackets
26215    cx.set_state(indoc! {"
26216        def f() -> list[str]:
26217            ˇ
26218    "});
26219    cx.update_editor(|editor, window, cx| {
26220        editor.handle_input("a", window, cx);
26221    });
26222    cx.wait_for_autoindent_applied().await;
26223    cx.assert_editor_state(indoc! {"
26224        def f() -> list[str]:
2622526226    "});
26227
26228    // test does not outdent on typing : after case keyword
26229    cx.set_state(indoc! {"
26230        match 1:
26231            caseˇ
26232    "});
26233    cx.update_editor(|editor, window, cx| {
26234        editor.handle_input(":", window, cx);
26235    });
26236    cx.wait_for_autoindent_applied().await;
26237    cx.assert_editor_state(indoc! {"
26238        match 1:
26239            case:ˇ
26240    "});
26241}
26242
26243#[gpui::test]
26244async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
26245    init_test(cx, |_| {});
26246    update_test_language_settings(cx, |settings| {
26247        settings.defaults.extend_comment_on_newline = Some(false);
26248    });
26249    let mut cx = EditorTestContext::new(cx).await;
26250    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
26251    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26252
26253    // test correct indent after newline on comment
26254    cx.set_state(indoc! {"
26255        # COMMENT:ˇ
26256    "});
26257    cx.update_editor(|editor, window, cx| {
26258        editor.newline(&Newline, window, cx);
26259    });
26260    cx.wait_for_autoindent_applied().await;
26261    cx.assert_editor_state(indoc! {"
26262        # COMMENT:
26263        ˇ
26264    "});
26265
26266    // test correct indent after newline in brackets
26267    cx.set_state(indoc! {"
26268        {ˇ}
26269    "});
26270    cx.update_editor(|editor, window, cx| {
26271        editor.newline(&Newline, window, cx);
26272    });
26273    cx.wait_for_autoindent_applied().await;
26274    cx.assert_editor_state(indoc! {"
26275        {
26276            ˇ
26277        }
26278    "});
26279
26280    cx.set_state(indoc! {"
26281        (ˇ)
26282    "});
26283    cx.update_editor(|editor, window, cx| {
26284        editor.newline(&Newline, window, cx);
26285    });
26286    cx.run_until_parked();
26287    cx.assert_editor_state(indoc! {"
26288        (
26289            ˇ
26290        )
26291    "});
26292
26293    // do not indent after empty lists or dictionaries
26294    cx.set_state(indoc! {"
26295        a = []ˇ
26296    "});
26297    cx.update_editor(|editor, window, cx| {
26298        editor.newline(&Newline, window, cx);
26299    });
26300    cx.run_until_parked();
26301    cx.assert_editor_state(indoc! {"
26302        a = []
26303        ˇ
26304    "});
26305}
26306
26307#[gpui::test]
26308async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
26309    init_test(cx, |_| {});
26310
26311    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
26312    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
26313    language_registry.add(markdown_lang());
26314    language_registry.add(python_lang);
26315
26316    let mut cx = EditorTestContext::new(cx).await;
26317    cx.update_buffer(|buffer, cx| {
26318        buffer.set_language_registry(language_registry);
26319        buffer.set_language(Some(markdown_lang()), cx);
26320    });
26321
26322    // Test that `else:` correctly outdents to match `if:` inside the Python code block
26323    cx.set_state(indoc! {"
26324        # Heading
26325
26326        ```python
26327        def main():
26328            if condition:
26329                pass
26330                ˇ
26331        ```
26332    "});
26333    cx.update_editor(|editor, window, cx| {
26334        editor.handle_input("else:", window, cx);
26335    });
26336    cx.run_until_parked();
26337    cx.assert_editor_state(indoc! {"
26338        # Heading
26339
26340        ```python
26341        def main():
26342            if condition:
26343                pass
26344            else:ˇ
26345        ```
26346    "});
26347}
26348
26349#[gpui::test]
26350async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
26351    init_test(cx, |_| {});
26352
26353    let mut cx = EditorTestContext::new(cx).await;
26354    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26355    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26356
26357    // test cursor move to start of each line on tab
26358    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
26359    cx.set_state(indoc! {"
26360        function main() {
26361        ˇ    for item in $items; do
26362        ˇ        while [ -n \"$item\" ]; do
26363        ˇ            if [ \"$value\" -gt 10 ]; then
26364        ˇ                continue
26365        ˇ            elif [ \"$value\" -lt 0 ]; then
26366        ˇ                break
26367        ˇ            else
26368        ˇ                echo \"$item\"
26369        ˇ            fi
26370        ˇ        done
26371        ˇ    done
26372        ˇ}
26373    "});
26374    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26375    cx.wait_for_autoindent_applied().await;
26376    cx.assert_editor_state(indoc! {"
26377        function main() {
26378            ˇfor item in $items; do
26379                ˇwhile [ -n \"$item\" ]; do
26380                    ˇif [ \"$value\" -gt 10 ]; then
26381                        ˇcontinue
26382                    ˇelif [ \"$value\" -lt 0 ]; then
26383                        ˇbreak
26384                    ˇelse
26385                        ˇecho \"$item\"
26386                    ˇfi
26387                ˇdone
26388            ˇdone
26389        ˇ}
26390    "});
26391    // test relative indent is preserved when tab
26392    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26393    cx.wait_for_autoindent_applied().await;
26394    cx.assert_editor_state(indoc! {"
26395        function main() {
26396                ˇfor item in $items; do
26397                    ˇwhile [ -n \"$item\" ]; do
26398                        ˇif [ \"$value\" -gt 10 ]; then
26399                            ˇcontinue
26400                        ˇelif [ \"$value\" -lt 0 ]; then
26401                            ˇbreak
26402                        ˇelse
26403                            ˇecho \"$item\"
26404                        ˇfi
26405                    ˇdone
26406                ˇdone
26407            ˇ}
26408    "});
26409
26410    // test cursor move to start of each line on tab
26411    // for `case` statement with patterns
26412    cx.set_state(indoc! {"
26413        function handle() {
26414        ˇ    case \"$1\" in
26415        ˇ        start)
26416        ˇ            echo \"a\"
26417        ˇ            ;;
26418        ˇ        stop)
26419        ˇ            echo \"b\"
26420        ˇ            ;;
26421        ˇ        *)
26422        ˇ            echo \"c\"
26423        ˇ            ;;
26424        ˇ    esac
26425        ˇ}
26426    "});
26427    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26428    cx.wait_for_autoindent_applied().await;
26429    cx.assert_editor_state(indoc! {"
26430        function handle() {
26431            ˇcase \"$1\" in
26432                ˇstart)
26433                    ˇecho \"a\"
26434                    ˇ;;
26435                ˇstop)
26436                    ˇecho \"b\"
26437                    ˇ;;
26438                ˇ*)
26439                    ˇecho \"c\"
26440                    ˇ;;
26441            ˇesac
26442        ˇ}
26443    "});
26444}
26445
26446#[gpui::test]
26447async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
26448    init_test(cx, |_| {});
26449
26450    let mut cx = EditorTestContext::new(cx).await;
26451    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26452    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26453
26454    // test indents on comment insert
26455    cx.set_state(indoc! {"
26456        function main() {
26457        ˇ    for item in $items; do
26458        ˇ        while [ -n \"$item\" ]; do
26459        ˇ            if [ \"$value\" -gt 10 ]; then
26460        ˇ                continue
26461        ˇ            elif [ \"$value\" -lt 0 ]; then
26462        ˇ                break
26463        ˇ            else
26464        ˇ                echo \"$item\"
26465        ˇ            fi
26466        ˇ        done
26467        ˇ    done
26468        ˇ}
26469    "});
26470    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
26471    cx.wait_for_autoindent_applied().await;
26472    cx.assert_editor_state(indoc! {"
26473        function main() {
26474        #ˇ    for item in $items; do
26475        #ˇ        while [ -n \"$item\" ]; do
26476        #ˇ            if [ \"$value\" -gt 10 ]; then
26477        #ˇ                continue
26478        #ˇ            elif [ \"$value\" -lt 0 ]; then
26479        #ˇ                break
26480        #ˇ            else
26481        #ˇ                echo \"$item\"
26482        #ˇ            fi
26483        #ˇ        done
26484        #ˇ    done
26485        #ˇ}
26486    "});
26487}
26488
26489#[gpui::test]
26490async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
26491    init_test(cx, |_| {});
26492
26493    let mut cx = EditorTestContext::new(cx).await;
26494    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26495    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26496
26497    // test `else` auto outdents when typed inside `if` block
26498    cx.set_state(indoc! {"
26499        if [ \"$1\" = \"test\" ]; then
26500            echo \"foo bar\"
26501            ˇ
26502    "});
26503    cx.update_editor(|editor, window, cx| {
26504        editor.handle_input("else", window, cx);
26505    });
26506    cx.wait_for_autoindent_applied().await;
26507    cx.assert_editor_state(indoc! {"
26508        if [ \"$1\" = \"test\" ]; then
26509            echo \"foo bar\"
26510        elseˇ
26511    "});
26512
26513    // test `elif` auto outdents when typed inside `if` block
26514    cx.set_state(indoc! {"
26515        if [ \"$1\" = \"test\" ]; then
26516            echo \"foo bar\"
26517            ˇ
26518    "});
26519    cx.update_editor(|editor, window, cx| {
26520        editor.handle_input("elif", window, cx);
26521    });
26522    cx.wait_for_autoindent_applied().await;
26523    cx.assert_editor_state(indoc! {"
26524        if [ \"$1\" = \"test\" ]; then
26525            echo \"foo bar\"
26526        elifˇ
26527    "});
26528
26529    // test `fi` auto outdents when typed inside `else` block
26530    cx.set_state(indoc! {"
26531        if [ \"$1\" = \"test\" ]; then
26532            echo \"foo bar\"
26533        else
26534            echo \"bar baz\"
26535            ˇ
26536    "});
26537    cx.update_editor(|editor, window, cx| {
26538        editor.handle_input("fi", window, cx);
26539    });
26540    cx.wait_for_autoindent_applied().await;
26541    cx.assert_editor_state(indoc! {"
26542        if [ \"$1\" = \"test\" ]; then
26543            echo \"foo bar\"
26544        else
26545            echo \"bar baz\"
26546        fiˇ
26547    "});
26548
26549    // test `done` auto outdents when typed inside `while` block
26550    cx.set_state(indoc! {"
26551        while read line; do
26552            echo \"$line\"
26553            ˇ
26554    "});
26555    cx.update_editor(|editor, window, cx| {
26556        editor.handle_input("done", window, cx);
26557    });
26558    cx.wait_for_autoindent_applied().await;
26559    cx.assert_editor_state(indoc! {"
26560        while read line; do
26561            echo \"$line\"
26562        doneˇ
26563    "});
26564
26565    // test `done` auto outdents when typed inside `for` block
26566    cx.set_state(indoc! {"
26567        for file in *.txt; do
26568            cat \"$file\"
26569            ˇ
26570    "});
26571    cx.update_editor(|editor, window, cx| {
26572        editor.handle_input("done", window, cx);
26573    });
26574    cx.wait_for_autoindent_applied().await;
26575    cx.assert_editor_state(indoc! {"
26576        for file in *.txt; do
26577            cat \"$file\"
26578        doneˇ
26579    "});
26580
26581    // test `esac` auto outdents when typed inside `case` block
26582    cx.set_state(indoc! {"
26583        case \"$1\" in
26584            start)
26585                echo \"foo bar\"
26586                ;;
26587            stop)
26588                echo \"bar baz\"
26589                ;;
26590            ˇ
26591    "});
26592    cx.update_editor(|editor, window, cx| {
26593        editor.handle_input("esac", window, cx);
26594    });
26595    cx.wait_for_autoindent_applied().await;
26596    cx.assert_editor_state(indoc! {"
26597        case \"$1\" in
26598            start)
26599                echo \"foo bar\"
26600                ;;
26601            stop)
26602                echo \"bar baz\"
26603                ;;
26604        esacˇ
26605    "});
26606
26607    // test `*)` auto outdents when typed inside `case` block
26608    cx.set_state(indoc! {"
26609        case \"$1\" in
26610            start)
26611                echo \"foo bar\"
26612                ;;
26613                ˇ
26614    "});
26615    cx.update_editor(|editor, window, cx| {
26616        editor.handle_input("*)", window, cx);
26617    });
26618    cx.wait_for_autoindent_applied().await;
26619    cx.assert_editor_state(indoc! {"
26620        case \"$1\" in
26621            start)
26622                echo \"foo bar\"
26623                ;;
26624            *)ˇ
26625    "});
26626
26627    // test `fi` outdents to correct level with nested if blocks
26628    cx.set_state(indoc! {"
26629        if [ \"$1\" = \"test\" ]; then
26630            echo \"outer if\"
26631            if [ \"$2\" = \"debug\" ]; then
26632                echo \"inner if\"
26633                ˇ
26634    "});
26635    cx.update_editor(|editor, window, cx| {
26636        editor.handle_input("fi", window, cx);
26637    });
26638    cx.wait_for_autoindent_applied().await;
26639    cx.assert_editor_state(indoc! {"
26640        if [ \"$1\" = \"test\" ]; then
26641            echo \"outer if\"
26642            if [ \"$2\" = \"debug\" ]; then
26643                echo \"inner if\"
26644            fiˇ
26645    "});
26646}
26647
26648#[gpui::test]
26649async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
26650    init_test(cx, |_| {});
26651    update_test_language_settings(cx, |settings| {
26652        settings.defaults.extend_comment_on_newline = Some(false);
26653    });
26654    let mut cx = EditorTestContext::new(cx).await;
26655    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26656    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26657
26658    // test correct indent after newline on comment
26659    cx.set_state(indoc! {"
26660        # COMMENT:ˇ
26661    "});
26662    cx.update_editor(|editor, window, cx| {
26663        editor.newline(&Newline, window, cx);
26664    });
26665    cx.wait_for_autoindent_applied().await;
26666    cx.assert_editor_state(indoc! {"
26667        # COMMENT:
26668        ˇ
26669    "});
26670
26671    // test correct indent after newline after `then`
26672    cx.set_state(indoc! {"
26673
26674        if [ \"$1\" = \"test\" ]; thenˇ
26675    "});
26676    cx.update_editor(|editor, window, cx| {
26677        editor.newline(&Newline, window, cx);
26678    });
26679    cx.wait_for_autoindent_applied().await;
26680    cx.assert_editor_state(indoc! {"
26681
26682        if [ \"$1\" = \"test\" ]; then
26683            ˇ
26684    "});
26685
26686    // test correct indent after newline after `else`
26687    cx.set_state(indoc! {"
26688        if [ \"$1\" = \"test\" ]; then
26689        elseˇ
26690    "});
26691    cx.update_editor(|editor, window, cx| {
26692        editor.newline(&Newline, window, cx);
26693    });
26694    cx.wait_for_autoindent_applied().await;
26695    cx.assert_editor_state(indoc! {"
26696        if [ \"$1\" = \"test\" ]; then
26697        else
26698            ˇ
26699    "});
26700
26701    // test correct indent after newline after `elif`
26702    cx.set_state(indoc! {"
26703        if [ \"$1\" = \"test\" ]; then
26704        elifˇ
26705    "});
26706    cx.update_editor(|editor, window, cx| {
26707        editor.newline(&Newline, window, cx);
26708    });
26709    cx.wait_for_autoindent_applied().await;
26710    cx.assert_editor_state(indoc! {"
26711        if [ \"$1\" = \"test\" ]; then
26712        elif
26713            ˇ
26714    "});
26715
26716    // test correct indent after newline after `do`
26717    cx.set_state(indoc! {"
26718        for file in *.txt; doˇ
26719    "});
26720    cx.update_editor(|editor, window, cx| {
26721        editor.newline(&Newline, window, cx);
26722    });
26723    cx.wait_for_autoindent_applied().await;
26724    cx.assert_editor_state(indoc! {"
26725        for file in *.txt; do
26726            ˇ
26727    "});
26728
26729    // test correct indent after newline after case pattern
26730    cx.set_state(indoc! {"
26731        case \"$1\" in
26732            start)ˇ
26733    "});
26734    cx.update_editor(|editor, window, cx| {
26735        editor.newline(&Newline, window, cx);
26736    });
26737    cx.wait_for_autoindent_applied().await;
26738    cx.assert_editor_state(indoc! {"
26739        case \"$1\" in
26740            start)
26741                ˇ
26742    "});
26743
26744    // test correct indent after newline after case pattern
26745    cx.set_state(indoc! {"
26746        case \"$1\" in
26747            start)
26748                ;;
26749            *)ˇ
26750    "});
26751    cx.update_editor(|editor, window, cx| {
26752        editor.newline(&Newline, window, cx);
26753    });
26754    cx.wait_for_autoindent_applied().await;
26755    cx.assert_editor_state(indoc! {"
26756        case \"$1\" in
26757            start)
26758                ;;
26759            *)
26760                ˇ
26761    "});
26762
26763    // test correct indent after newline after function opening brace
26764    cx.set_state(indoc! {"
26765        function test() {ˇ}
26766    "});
26767    cx.update_editor(|editor, window, cx| {
26768        editor.newline(&Newline, window, cx);
26769    });
26770    cx.wait_for_autoindent_applied().await;
26771    cx.assert_editor_state(indoc! {"
26772        function test() {
26773            ˇ
26774        }
26775    "});
26776
26777    // test no extra indent after semicolon on same line
26778    cx.set_state(indoc! {"
26779        echo \"test\"26780    "});
26781    cx.update_editor(|editor, window, cx| {
26782        editor.newline(&Newline, window, cx);
26783    });
26784    cx.wait_for_autoindent_applied().await;
26785    cx.assert_editor_state(indoc! {"
26786        echo \"test\";
26787        ˇ
26788    "});
26789}
26790
26791fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26792    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26793    point..point
26794}
26795
26796#[track_caller]
26797fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26798    let (text, ranges) = marked_text_ranges(marked_text, true);
26799    assert_eq!(editor.text(cx), text);
26800    assert_eq!(
26801        editor.selections.ranges(&editor.display_snapshot(cx)),
26802        ranges
26803            .iter()
26804            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26805            .collect::<Vec<_>>(),
26806        "Assert selections are {}",
26807        marked_text
26808    );
26809}
26810
26811pub fn handle_signature_help_request(
26812    cx: &mut EditorLspTestContext,
26813    mocked_response: lsp::SignatureHelp,
26814) -> impl Future<Output = ()> + use<> {
26815    let mut request =
26816        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26817            let mocked_response = mocked_response.clone();
26818            async move { Ok(Some(mocked_response)) }
26819        });
26820
26821    async move {
26822        request.next().await;
26823    }
26824}
26825
26826#[track_caller]
26827pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
26828    cx.update_editor(|editor, _, _| {
26829        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
26830            let entries = menu.entries.borrow();
26831            let entries = entries
26832                .iter()
26833                .map(|entry| entry.string.as_str())
26834                .collect::<Vec<_>>();
26835            assert_eq!(entries, expected);
26836        } else {
26837            panic!("Expected completions menu");
26838        }
26839    });
26840}
26841
26842#[gpui::test]
26843async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
26844    init_test(cx, |_| {});
26845    let mut cx = EditorLspTestContext::new_rust(
26846        lsp::ServerCapabilities {
26847            completion_provider: Some(lsp::CompletionOptions {
26848                ..Default::default()
26849            }),
26850            ..Default::default()
26851        },
26852        cx,
26853    )
26854    .await;
26855    cx.lsp
26856        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26857            Ok(Some(lsp::CompletionResponse::Array(vec![
26858                lsp::CompletionItem {
26859                    label: "unsafe".into(),
26860                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26861                        range: lsp::Range {
26862                            start: lsp::Position {
26863                                line: 0,
26864                                character: 9,
26865                            },
26866                            end: lsp::Position {
26867                                line: 0,
26868                                character: 11,
26869                            },
26870                        },
26871                        new_text: "unsafe".to_string(),
26872                    })),
26873                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
26874                    ..Default::default()
26875                },
26876            ])))
26877        });
26878
26879    cx.update_editor(|editor, _, cx| {
26880        editor.project().unwrap().update(cx, |project, cx| {
26881            project.snippets().update(cx, |snippets, _cx| {
26882                snippets.add_snippet_for_test(
26883                    None,
26884                    PathBuf::from("test_snippets.json"),
26885                    vec![
26886                        Arc::new(project::snippet_provider::Snippet {
26887                            prefix: vec![
26888                                "unlimited word count".to_string(),
26889                                "unlimit word count".to_string(),
26890                                "unlimited unknown".to_string(),
26891                            ],
26892                            body: "this is many words".to_string(),
26893                            description: Some("description".to_string()),
26894                            name: "multi-word snippet test".to_string(),
26895                        }),
26896                        Arc::new(project::snippet_provider::Snippet {
26897                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
26898                            body: "fewer words".to_string(),
26899                            description: Some("alt description".to_string()),
26900                            name: "other name".to_string(),
26901                        }),
26902                        Arc::new(project::snippet_provider::Snippet {
26903                            prefix: vec!["ab aa".to_string()],
26904                            body: "abcd".to_string(),
26905                            description: None,
26906                            name: "alphabet".to_string(),
26907                        }),
26908                    ],
26909                );
26910            });
26911        })
26912    });
26913
26914    let get_completions = |cx: &mut EditorLspTestContext| {
26915        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
26916            Some(CodeContextMenu::Completions(context_menu)) => {
26917                let entries = context_menu.entries.borrow();
26918                entries
26919                    .iter()
26920                    .map(|entry| entry.string.clone())
26921                    .collect_vec()
26922            }
26923            _ => vec![],
26924        })
26925    };
26926
26927    // snippets:
26928    //  @foo
26929    //  foo bar
26930    //
26931    // when typing:
26932    //
26933    // when typing:
26934    //  - if I type a symbol "open the completions with snippets only"
26935    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26936    //
26937    // stuff we need:
26938    //  - filtering logic change?
26939    //  - remember how far back the completion started.
26940
26941    let test_cases: &[(&str, &[&str])] = &[
26942        (
26943            "un",
26944            &[
26945                "unsafe",
26946                "unlimit word count",
26947                "unlimited unknown",
26948                "unlimited word count",
26949                "unsnip",
26950            ],
26951        ),
26952        (
26953            "u ",
26954            &[
26955                "unlimit word count",
26956                "unlimited unknown",
26957                "unlimited word count",
26958            ],
26959        ),
26960        ("u a", &["ab aa", "unsafe"]), // unsAfe
26961        (
26962            "u u",
26963            &[
26964                "unsafe",
26965                "unlimit word count",
26966                "unlimited unknown", // ranked highest among snippets
26967                "unlimited word count",
26968                "unsnip",
26969            ],
26970        ),
26971        ("uw c", &["unlimit word count", "unlimited word count"]),
26972        (
26973            "u w",
26974            &[
26975                "unlimit word count",
26976                "unlimited word count",
26977                "unlimited unknown",
26978            ],
26979        ),
26980        ("u w ", &["unlimit word count", "unlimited word count"]),
26981        (
26982            "u ",
26983            &[
26984                "unlimit word count",
26985                "unlimited unknown",
26986                "unlimited word count",
26987            ],
26988        ),
26989        ("wor", &[]),
26990        ("uf", &["unsafe"]),
26991        ("af", &["unsafe"]),
26992        ("afu", &[]),
26993        (
26994            "ue",
26995            &["unsafe", "unlimited unknown", "unlimited word count"],
26996        ),
26997        ("@", &["@few"]),
26998        ("@few", &["@few"]),
26999        ("@ ", &[]),
27000        ("a@", &["@few"]),
27001        ("a@f", &["@few", "unsafe"]),
27002        ("a@fw", &["@few"]),
27003        ("a", &["ab aa", "unsafe"]),
27004        ("aa", &["ab aa"]),
27005        ("aaa", &["ab aa"]),
27006        ("ab", &["ab aa"]),
27007        ("ab ", &["ab aa"]),
27008        ("ab a", &["ab aa", "unsafe"]),
27009        ("ab ab", &["ab aa"]),
27010        ("ab ab aa", &["ab aa"]),
27011    ];
27012
27013    for &(input_to_simulate, expected_completions) in test_cases {
27014        cx.set_state("fn a() { ˇ }\n");
27015        for c in input_to_simulate.split("") {
27016            cx.simulate_input(c);
27017            cx.run_until_parked();
27018        }
27019        let expected_completions = expected_completions
27020            .iter()
27021            .map(|s| s.to_string())
27022            .collect_vec();
27023        assert_eq!(
27024            get_completions(&mut cx),
27025            expected_completions,
27026            "< actual / expected >, input = {input_to_simulate:?}",
27027        );
27028    }
27029}
27030
27031/// Handle completion request passing a marked string specifying where the completion
27032/// should be triggered from using '|' character, what range should be replaced, and what completions
27033/// should be returned using '<' and '>' to delimit the range.
27034///
27035/// Also see `handle_completion_request_with_insert_and_replace`.
27036#[track_caller]
27037pub fn handle_completion_request(
27038    marked_string: &str,
27039    completions: Vec<&'static str>,
27040    is_incomplete: bool,
27041    counter: Arc<AtomicUsize>,
27042    cx: &mut EditorLspTestContext,
27043) -> impl Future<Output = ()> {
27044    let complete_from_marker: TextRangeMarker = '|'.into();
27045    let replace_range_marker: TextRangeMarker = ('<', '>').into();
27046    let (_, mut marked_ranges) = marked_text_ranges_by(
27047        marked_string,
27048        vec![complete_from_marker.clone(), replace_range_marker.clone()],
27049    );
27050
27051    let complete_from_position = cx.to_lsp(MultiBufferOffset(
27052        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
27053    ));
27054    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
27055    let replace_range =
27056        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
27057
27058    let mut request =
27059        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
27060            let completions = completions.clone();
27061            counter.fetch_add(1, atomic::Ordering::Release);
27062            async move {
27063                assert_eq!(params.text_document_position.text_document.uri, url.clone());
27064                assert_eq!(
27065                    params.text_document_position.position,
27066                    complete_from_position
27067                );
27068                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
27069                    is_incomplete,
27070                    item_defaults: None,
27071                    items: completions
27072                        .iter()
27073                        .map(|completion_text| lsp::CompletionItem {
27074                            label: completion_text.to_string(),
27075                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
27076                                range: replace_range,
27077                                new_text: completion_text.to_string(),
27078                            })),
27079                            ..Default::default()
27080                        })
27081                        .collect(),
27082                })))
27083            }
27084        });
27085
27086    async move {
27087        request.next().await;
27088    }
27089}
27090
27091/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
27092/// given instead, which also contains an `insert` range.
27093///
27094/// This function uses markers to define ranges:
27095/// - `|` marks the cursor position
27096/// - `<>` marks the replace range
27097/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
27098pub fn handle_completion_request_with_insert_and_replace(
27099    cx: &mut EditorLspTestContext,
27100    marked_string: &str,
27101    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
27102    counter: Arc<AtomicUsize>,
27103) -> impl Future<Output = ()> {
27104    let complete_from_marker: TextRangeMarker = '|'.into();
27105    let replace_range_marker: TextRangeMarker = ('<', '>').into();
27106    let insert_range_marker: TextRangeMarker = ('{', '}').into();
27107
27108    let (_, mut marked_ranges) = marked_text_ranges_by(
27109        marked_string,
27110        vec![
27111            complete_from_marker.clone(),
27112            replace_range_marker.clone(),
27113            insert_range_marker.clone(),
27114        ],
27115    );
27116
27117    let complete_from_position = cx.to_lsp(MultiBufferOffset(
27118        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
27119    ));
27120    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
27121    let replace_range =
27122        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
27123
27124    let insert_range = match marked_ranges.remove(&insert_range_marker) {
27125        Some(ranges) if !ranges.is_empty() => {
27126            let range1 = ranges[0].clone();
27127            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
27128        }
27129        _ => lsp::Range {
27130            start: replace_range.start,
27131            end: complete_from_position,
27132        },
27133    };
27134
27135    let mut request =
27136        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
27137            let completions = completions.clone();
27138            counter.fetch_add(1, atomic::Ordering::Release);
27139            async move {
27140                assert_eq!(params.text_document_position.text_document.uri, url.clone());
27141                assert_eq!(
27142                    params.text_document_position.position, complete_from_position,
27143                    "marker `|` position doesn't match",
27144                );
27145                Ok(Some(lsp::CompletionResponse::Array(
27146                    completions
27147                        .iter()
27148                        .map(|(label, new_text)| lsp::CompletionItem {
27149                            label: label.to_string(),
27150                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27151                                lsp::InsertReplaceEdit {
27152                                    insert: insert_range,
27153                                    replace: replace_range,
27154                                    new_text: new_text.to_string(),
27155                                },
27156                            )),
27157                            ..Default::default()
27158                        })
27159                        .collect(),
27160                )))
27161            }
27162        });
27163
27164    async move {
27165        request.next().await;
27166    }
27167}
27168
27169fn handle_resolve_completion_request(
27170    cx: &mut EditorLspTestContext,
27171    edits: Option<Vec<(&'static str, &'static str)>>,
27172) -> impl Future<Output = ()> {
27173    let edits = edits.map(|edits| {
27174        edits
27175            .iter()
27176            .map(|(marked_string, new_text)| {
27177                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
27178                let replace_range = cx.to_lsp_range(
27179                    MultiBufferOffset(marked_ranges[0].start)
27180                        ..MultiBufferOffset(marked_ranges[0].end),
27181                );
27182                lsp::TextEdit::new(replace_range, new_text.to_string())
27183            })
27184            .collect::<Vec<_>>()
27185    });
27186
27187    let mut request =
27188        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
27189            let edits = edits.clone();
27190            async move {
27191                Ok(lsp::CompletionItem {
27192                    additional_text_edits: edits,
27193                    ..Default::default()
27194                })
27195            }
27196        });
27197
27198    async move {
27199        request.next().await;
27200    }
27201}
27202
27203pub(crate) fn update_test_language_settings(
27204    cx: &mut TestAppContext,
27205    f: impl Fn(&mut AllLanguageSettingsContent),
27206) {
27207    cx.update(|cx| {
27208        SettingsStore::update_global(cx, |store, cx| {
27209            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
27210        });
27211    });
27212}
27213
27214pub(crate) fn update_test_project_settings(
27215    cx: &mut TestAppContext,
27216    f: impl Fn(&mut ProjectSettingsContent),
27217) {
27218    cx.update(|cx| {
27219        SettingsStore::update_global(cx, |store, cx| {
27220            store.update_user_settings(cx, |settings| f(&mut settings.project));
27221        });
27222    });
27223}
27224
27225pub(crate) fn update_test_editor_settings(
27226    cx: &mut TestAppContext,
27227    f: impl Fn(&mut EditorSettingsContent),
27228) {
27229    cx.update(|cx| {
27230        SettingsStore::update_global(cx, |store, cx| {
27231            store.update_user_settings(cx, |settings| f(&mut settings.editor));
27232        })
27233    })
27234}
27235
27236pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
27237    cx.update(|cx| {
27238        assets::Assets.load_test_fonts(cx);
27239        let store = SettingsStore::test(cx);
27240        cx.set_global(store);
27241        theme::init(theme::LoadThemes::JustBase, cx);
27242        release_channel::init(semver::Version::new(0, 0, 0), cx);
27243        crate::init(cx);
27244    });
27245    zlog::init_test();
27246    update_test_language_settings(cx, f);
27247}
27248
27249#[track_caller]
27250fn assert_hunk_revert(
27251    not_reverted_text_with_selections: &str,
27252    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
27253    expected_reverted_text_with_selections: &str,
27254    base_text: &str,
27255    cx: &mut EditorLspTestContext,
27256) {
27257    cx.set_state(not_reverted_text_with_selections);
27258    cx.set_head_text(base_text);
27259    cx.executor().run_until_parked();
27260
27261    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
27262        let snapshot = editor.snapshot(window, cx);
27263        let reverted_hunk_statuses = snapshot
27264            .buffer_snapshot()
27265            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
27266            .map(|hunk| hunk.status().kind)
27267            .collect::<Vec<_>>();
27268
27269        editor.git_restore(&Default::default(), window, cx);
27270        reverted_hunk_statuses
27271    });
27272    cx.executor().run_until_parked();
27273    cx.assert_editor_state(expected_reverted_text_with_selections);
27274    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
27275}
27276
27277#[gpui::test(iterations = 10)]
27278async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
27279    init_test(cx, |_| {});
27280
27281    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
27282    let counter = diagnostic_requests.clone();
27283
27284    let fs = FakeFs::new(cx.executor());
27285    fs.insert_tree(
27286        path!("/a"),
27287        json!({
27288            "first.rs": "fn main() { let a = 5; }",
27289            "second.rs": "// Test file",
27290        }),
27291    )
27292    .await;
27293
27294    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27295    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27296    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27297
27298    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27299    language_registry.add(rust_lang());
27300    let mut fake_servers = language_registry.register_fake_lsp(
27301        "Rust",
27302        FakeLspAdapter {
27303            capabilities: lsp::ServerCapabilities {
27304                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
27305                    lsp::DiagnosticOptions {
27306                        identifier: None,
27307                        inter_file_dependencies: true,
27308                        workspace_diagnostics: true,
27309                        work_done_progress_options: Default::default(),
27310                    },
27311                )),
27312                ..Default::default()
27313            },
27314            ..Default::default()
27315        },
27316    );
27317
27318    let editor = workspace
27319        .update(cx, |workspace, window, cx| {
27320            workspace.open_abs_path(
27321                PathBuf::from(path!("/a/first.rs")),
27322                OpenOptions::default(),
27323                window,
27324                cx,
27325            )
27326        })
27327        .unwrap()
27328        .await
27329        .unwrap()
27330        .downcast::<Editor>()
27331        .unwrap();
27332    let fake_server = fake_servers.next().await.unwrap();
27333    let server_id = fake_server.server.server_id();
27334    let mut first_request = fake_server
27335        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
27336            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
27337            let result_id = Some(new_result_id.to_string());
27338            assert_eq!(
27339                params.text_document.uri,
27340                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27341            );
27342            async move {
27343                Ok(lsp::DocumentDiagnosticReportResult::Report(
27344                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
27345                        related_documents: None,
27346                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
27347                            items: Vec::new(),
27348                            result_id,
27349                        },
27350                    }),
27351                ))
27352            }
27353        });
27354
27355    let ensure_result_id = |expected: Option<SharedString>, cx: &mut TestAppContext| {
27356        project.update(cx, |project, cx| {
27357            let buffer_id = editor
27358                .read(cx)
27359                .buffer()
27360                .read(cx)
27361                .as_singleton()
27362                .expect("created a singleton buffer")
27363                .read(cx)
27364                .remote_id();
27365            let buffer_result_id = project
27366                .lsp_store()
27367                .read(cx)
27368                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
27369            assert_eq!(expected, buffer_result_id);
27370        });
27371    };
27372
27373    ensure_result_id(None, cx);
27374    cx.executor().advance_clock(Duration::from_millis(60));
27375    cx.executor().run_until_parked();
27376    assert_eq!(
27377        diagnostic_requests.load(atomic::Ordering::Acquire),
27378        1,
27379        "Opening file should trigger diagnostic request"
27380    );
27381    first_request
27382        .next()
27383        .await
27384        .expect("should have sent the first diagnostics pull request");
27385    ensure_result_id(Some(SharedString::new("1")), cx);
27386
27387    // Editing should trigger diagnostics
27388    editor.update_in(cx, |editor, window, cx| {
27389        editor.handle_input("2", window, cx)
27390    });
27391    cx.executor().advance_clock(Duration::from_millis(60));
27392    cx.executor().run_until_parked();
27393    assert_eq!(
27394        diagnostic_requests.load(atomic::Ordering::Acquire),
27395        2,
27396        "Editing should trigger diagnostic request"
27397    );
27398    ensure_result_id(Some(SharedString::new("2")), cx);
27399
27400    // Moving cursor should not trigger diagnostic request
27401    editor.update_in(cx, |editor, window, cx| {
27402        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27403            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
27404        });
27405    });
27406    cx.executor().advance_clock(Duration::from_millis(60));
27407    cx.executor().run_until_parked();
27408    assert_eq!(
27409        diagnostic_requests.load(atomic::Ordering::Acquire),
27410        2,
27411        "Cursor movement should not trigger diagnostic request"
27412    );
27413    ensure_result_id(Some(SharedString::new("2")), cx);
27414    // Multiple rapid edits should be debounced
27415    for _ in 0..5 {
27416        editor.update_in(cx, |editor, window, cx| {
27417            editor.handle_input("x", window, cx)
27418        });
27419    }
27420    cx.executor().advance_clock(Duration::from_millis(60));
27421    cx.executor().run_until_parked();
27422
27423    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
27424    assert!(
27425        final_requests <= 4,
27426        "Multiple rapid edits should be debounced (got {final_requests} requests)",
27427    );
27428    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
27429}
27430
27431#[gpui::test]
27432async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
27433    // Regression test for issue #11671
27434    // Previously, adding a cursor after moving multiple cursors would reset
27435    // the cursor count instead of adding to the existing cursors.
27436    init_test(cx, |_| {});
27437    let mut cx = EditorTestContext::new(cx).await;
27438
27439    // Create a simple buffer with cursor at start
27440    cx.set_state(indoc! {"
27441        ˇaaaa
27442        bbbb
27443        cccc
27444        dddd
27445        eeee
27446        ffff
27447        gggg
27448        hhhh"});
27449
27450    // Add 2 cursors below (so we have 3 total)
27451    cx.update_editor(|editor, window, cx| {
27452        editor.add_selection_below(&Default::default(), window, cx);
27453        editor.add_selection_below(&Default::default(), window, cx);
27454    });
27455
27456    // Verify we have 3 cursors
27457    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
27458    assert_eq!(
27459        initial_count, 3,
27460        "Should have 3 cursors after adding 2 below"
27461    );
27462
27463    // Move down one line
27464    cx.update_editor(|editor, window, cx| {
27465        editor.move_down(&MoveDown, window, cx);
27466    });
27467
27468    // Add another cursor below
27469    cx.update_editor(|editor, window, cx| {
27470        editor.add_selection_below(&Default::default(), window, cx);
27471    });
27472
27473    // Should now have 4 cursors (3 original + 1 new)
27474    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
27475    assert_eq!(
27476        final_count, 4,
27477        "Should have 4 cursors after moving and adding another"
27478    );
27479}
27480
27481#[gpui::test]
27482async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
27483    init_test(cx, |_| {});
27484
27485    let mut cx = EditorTestContext::new(cx).await;
27486
27487    cx.set_state(indoc!(
27488        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
27489           Second line here"#
27490    ));
27491
27492    cx.update_editor(|editor, window, cx| {
27493        // Enable soft wrapping with a narrow width to force soft wrapping and
27494        // confirm that more than 2 rows are being displayed.
27495        editor.set_wrap_width(Some(100.0.into()), cx);
27496        assert!(editor.display_text(cx).lines().count() > 2);
27497
27498        editor.add_selection_below(
27499            &AddSelectionBelow {
27500                skip_soft_wrap: true,
27501            },
27502            window,
27503            cx,
27504        );
27505
27506        assert_eq!(
27507            display_ranges(editor, cx),
27508            &[
27509                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27510                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
27511            ]
27512        );
27513
27514        editor.add_selection_above(
27515            &AddSelectionAbove {
27516                skip_soft_wrap: true,
27517            },
27518            window,
27519            cx,
27520        );
27521
27522        assert_eq!(
27523            display_ranges(editor, cx),
27524            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27525        );
27526
27527        editor.add_selection_below(
27528            &AddSelectionBelow {
27529                skip_soft_wrap: false,
27530            },
27531            window,
27532            cx,
27533        );
27534
27535        assert_eq!(
27536            display_ranges(editor, cx),
27537            &[
27538                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27539                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
27540            ]
27541        );
27542
27543        editor.add_selection_above(
27544            &AddSelectionAbove {
27545                skip_soft_wrap: false,
27546            },
27547            window,
27548            cx,
27549        );
27550
27551        assert_eq!(
27552            display_ranges(editor, cx),
27553            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27554        );
27555    });
27556
27557    // Set up text where selections are in the middle of a soft-wrapped line.
27558    // When adding selection below with `skip_soft_wrap` set to `true`, the new
27559    // selection should be at the same buffer column, not the same pixel
27560    // position.
27561    cx.set_state(indoc!(
27562        r#"1. Very long line to show «howˇ» a wrapped line would look
27563           2. Very long line to show how a wrapped line would look"#
27564    ));
27565
27566    cx.update_editor(|editor, window, cx| {
27567        // Enable soft wrapping with a narrow width to force soft wrapping and
27568        // confirm that more than 2 rows are being displayed.
27569        editor.set_wrap_width(Some(100.0.into()), cx);
27570        assert!(editor.display_text(cx).lines().count() > 2);
27571
27572        editor.add_selection_below(
27573            &AddSelectionBelow {
27574                skip_soft_wrap: true,
27575            },
27576            window,
27577            cx,
27578        );
27579
27580        // Assert that there's now 2 selections, both selecting the same column
27581        // range in the buffer row.
27582        let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
27583        let selections = editor.selections.all::<Point>(&display_map);
27584        assert_eq!(selections.len(), 2);
27585        assert_eq!(selections[0].start.column, selections[1].start.column);
27586        assert_eq!(selections[0].end.column, selections[1].end.column);
27587    });
27588}
27589
27590#[gpui::test]
27591async fn test_insert_snippet(cx: &mut TestAppContext) {
27592    init_test(cx, |_| {});
27593    let mut cx = EditorTestContext::new(cx).await;
27594
27595    cx.update_editor(|editor, _, cx| {
27596        editor.project().unwrap().update(cx, |project, cx| {
27597            project.snippets().update(cx, |snippets, _cx| {
27598                let snippet = project::snippet_provider::Snippet {
27599                    prefix: vec![], // no prefix needed!
27600                    body: "an Unspecified".to_string(),
27601                    description: Some("shhhh it's a secret".to_string()),
27602                    name: "super secret snippet".to_string(),
27603                };
27604                snippets.add_snippet_for_test(
27605                    None,
27606                    PathBuf::from("test_snippets.json"),
27607                    vec![Arc::new(snippet)],
27608                );
27609
27610                let snippet = project::snippet_provider::Snippet {
27611                    prefix: vec![], // no prefix needed!
27612                    body: " Location".to_string(),
27613                    description: Some("the word 'location'".to_string()),
27614                    name: "location word".to_string(),
27615                };
27616                snippets.add_snippet_for_test(
27617                    Some("Markdown".to_string()),
27618                    PathBuf::from("test_snippets.json"),
27619                    vec![Arc::new(snippet)],
27620                );
27621            });
27622        })
27623    });
27624
27625    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
27626
27627    cx.update_editor(|editor, window, cx| {
27628        editor.insert_snippet_at_selections(
27629            &InsertSnippet {
27630                language: None,
27631                name: Some("super secret snippet".to_string()),
27632                snippet: None,
27633            },
27634            window,
27635            cx,
27636        );
27637
27638        // Language is specified in the action,
27639        // so the buffer language does not need to match
27640        editor.insert_snippet_at_selections(
27641            &InsertSnippet {
27642                language: Some("Markdown".to_string()),
27643                name: Some("location word".to_string()),
27644                snippet: None,
27645            },
27646            window,
27647            cx,
27648        );
27649
27650        editor.insert_snippet_at_selections(
27651            &InsertSnippet {
27652                language: None,
27653                name: None,
27654                snippet: Some("$0 after".to_string()),
27655            },
27656            window,
27657            cx,
27658        );
27659    });
27660
27661    cx.assert_editor_state(
27662        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
27663    );
27664}
27665
27666#[gpui::test(iterations = 10)]
27667async fn test_document_colors(cx: &mut TestAppContext) {
27668    let expected_color = Rgba {
27669        r: 0.33,
27670        g: 0.33,
27671        b: 0.33,
27672        a: 0.33,
27673    };
27674
27675    init_test(cx, |_| {});
27676
27677    let fs = FakeFs::new(cx.executor());
27678    fs.insert_tree(
27679        path!("/a"),
27680        json!({
27681            "first.rs": "fn main() { let a = 5; }",
27682        }),
27683    )
27684    .await;
27685
27686    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27687    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27688    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27689
27690    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27691    language_registry.add(rust_lang());
27692    let mut fake_servers = language_registry.register_fake_lsp(
27693        "Rust",
27694        FakeLspAdapter {
27695            capabilities: lsp::ServerCapabilities {
27696                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
27697                ..lsp::ServerCapabilities::default()
27698            },
27699            name: "rust-analyzer",
27700            ..FakeLspAdapter::default()
27701        },
27702    );
27703    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
27704        "Rust",
27705        FakeLspAdapter {
27706            capabilities: lsp::ServerCapabilities {
27707                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
27708                ..lsp::ServerCapabilities::default()
27709            },
27710            name: "not-rust-analyzer",
27711            ..FakeLspAdapter::default()
27712        },
27713    );
27714
27715    let editor = workspace
27716        .update(cx, |workspace, window, cx| {
27717            workspace.open_abs_path(
27718                PathBuf::from(path!("/a/first.rs")),
27719                OpenOptions::default(),
27720                window,
27721                cx,
27722            )
27723        })
27724        .unwrap()
27725        .await
27726        .unwrap()
27727        .downcast::<Editor>()
27728        .unwrap();
27729    let fake_language_server = fake_servers.next().await.unwrap();
27730    let fake_language_server_without_capabilities =
27731        fake_servers_without_capabilities.next().await.unwrap();
27732    let requests_made = Arc::new(AtomicUsize::new(0));
27733    let closure_requests_made = Arc::clone(&requests_made);
27734    let mut color_request_handle = fake_language_server
27735        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27736            let requests_made = Arc::clone(&closure_requests_made);
27737            async move {
27738                assert_eq!(
27739                    params.text_document.uri,
27740                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27741                );
27742                requests_made.fetch_add(1, atomic::Ordering::Release);
27743                Ok(vec![
27744                    lsp::ColorInformation {
27745                        range: lsp::Range {
27746                            start: lsp::Position {
27747                                line: 0,
27748                                character: 0,
27749                            },
27750                            end: lsp::Position {
27751                                line: 0,
27752                                character: 1,
27753                            },
27754                        },
27755                        color: lsp::Color {
27756                            red: 0.33,
27757                            green: 0.33,
27758                            blue: 0.33,
27759                            alpha: 0.33,
27760                        },
27761                    },
27762                    lsp::ColorInformation {
27763                        range: lsp::Range {
27764                            start: lsp::Position {
27765                                line: 0,
27766                                character: 0,
27767                            },
27768                            end: lsp::Position {
27769                                line: 0,
27770                                character: 1,
27771                            },
27772                        },
27773                        color: lsp::Color {
27774                            red: 0.33,
27775                            green: 0.33,
27776                            blue: 0.33,
27777                            alpha: 0.33,
27778                        },
27779                    },
27780                ])
27781            }
27782        });
27783
27784    let _handle = fake_language_server_without_capabilities
27785        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
27786            panic!("Should not be called");
27787        });
27788    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27789    color_request_handle.next().await.unwrap();
27790    cx.run_until_parked();
27791    assert_eq!(
27792        1,
27793        requests_made.load(atomic::Ordering::Acquire),
27794        "Should query for colors once per editor open"
27795    );
27796    editor.update_in(cx, |editor, _, cx| {
27797        assert_eq!(
27798            vec![expected_color],
27799            extract_color_inlays(editor, cx),
27800            "Should have an initial inlay"
27801        );
27802    });
27803
27804    // opening another file in a split should not influence the LSP query counter
27805    workspace
27806        .update(cx, |workspace, window, cx| {
27807            assert_eq!(
27808                workspace.panes().len(),
27809                1,
27810                "Should have one pane with one editor"
27811            );
27812            workspace.move_item_to_pane_in_direction(
27813                &MoveItemToPaneInDirection {
27814                    direction: SplitDirection::Right,
27815                    focus: false,
27816                    clone: true,
27817                },
27818                window,
27819                cx,
27820            );
27821        })
27822        .unwrap();
27823    cx.run_until_parked();
27824    workspace
27825        .update(cx, |workspace, _, cx| {
27826            let panes = workspace.panes();
27827            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
27828            for pane in panes {
27829                let editor = pane
27830                    .read(cx)
27831                    .active_item()
27832                    .and_then(|item| item.downcast::<Editor>())
27833                    .expect("Should have opened an editor in each split");
27834                let editor_file = editor
27835                    .read(cx)
27836                    .buffer()
27837                    .read(cx)
27838                    .as_singleton()
27839                    .expect("test deals with singleton buffers")
27840                    .read(cx)
27841                    .file()
27842                    .expect("test buffese should have a file")
27843                    .path();
27844                assert_eq!(
27845                    editor_file.as_ref(),
27846                    rel_path("first.rs"),
27847                    "Both editors should be opened for the same file"
27848                )
27849            }
27850        })
27851        .unwrap();
27852
27853    cx.executor().advance_clock(Duration::from_millis(500));
27854    let save = editor.update_in(cx, |editor, window, cx| {
27855        editor.move_to_end(&MoveToEnd, window, cx);
27856        editor.handle_input("dirty", window, cx);
27857        editor.save(
27858            SaveOptions {
27859                format: true,
27860                autosave: true,
27861            },
27862            project.clone(),
27863            window,
27864            cx,
27865        )
27866    });
27867    save.await.unwrap();
27868
27869    color_request_handle.next().await.unwrap();
27870    cx.run_until_parked();
27871    assert_eq!(
27872        2,
27873        requests_made.load(atomic::Ordering::Acquire),
27874        "Should query for colors once per save (deduplicated) and once per formatting after save"
27875    );
27876
27877    drop(editor);
27878    let close = workspace
27879        .update(cx, |workspace, window, cx| {
27880            workspace.active_pane().update(cx, |pane, cx| {
27881                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27882            })
27883        })
27884        .unwrap();
27885    close.await.unwrap();
27886    let close = workspace
27887        .update(cx, |workspace, window, cx| {
27888            workspace.active_pane().update(cx, |pane, cx| {
27889                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27890            })
27891        })
27892        .unwrap();
27893    close.await.unwrap();
27894    assert_eq!(
27895        2,
27896        requests_made.load(atomic::Ordering::Acquire),
27897        "After saving and closing all editors, no extra requests should be made"
27898    );
27899    workspace
27900        .update(cx, |workspace, _, cx| {
27901            assert!(
27902                workspace.active_item(cx).is_none(),
27903                "Should close all editors"
27904            )
27905        })
27906        .unwrap();
27907
27908    workspace
27909        .update(cx, |workspace, window, cx| {
27910            workspace.active_pane().update(cx, |pane, cx| {
27911                pane.navigate_backward(&workspace::GoBack, window, cx);
27912            })
27913        })
27914        .unwrap();
27915    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27916    cx.run_until_parked();
27917    let editor = workspace
27918        .update(cx, |workspace, _, cx| {
27919            workspace
27920                .active_item(cx)
27921                .expect("Should have reopened the editor again after navigating back")
27922                .downcast::<Editor>()
27923                .expect("Should be an editor")
27924        })
27925        .unwrap();
27926
27927    assert_eq!(
27928        2,
27929        requests_made.load(atomic::Ordering::Acquire),
27930        "Cache should be reused on buffer close and reopen"
27931    );
27932    editor.update(cx, |editor, cx| {
27933        assert_eq!(
27934            vec![expected_color],
27935            extract_color_inlays(editor, cx),
27936            "Should have an initial inlay"
27937        );
27938    });
27939
27940    drop(color_request_handle);
27941    let closure_requests_made = Arc::clone(&requests_made);
27942    let mut empty_color_request_handle = fake_language_server
27943        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27944            let requests_made = Arc::clone(&closure_requests_made);
27945            async move {
27946                assert_eq!(
27947                    params.text_document.uri,
27948                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27949                );
27950                requests_made.fetch_add(1, atomic::Ordering::Release);
27951                Ok(Vec::new())
27952            }
27953        });
27954    let save = editor.update_in(cx, |editor, window, cx| {
27955        editor.move_to_end(&MoveToEnd, window, cx);
27956        editor.handle_input("dirty_again", window, cx);
27957        editor.save(
27958            SaveOptions {
27959                format: false,
27960                autosave: true,
27961            },
27962            project.clone(),
27963            window,
27964            cx,
27965        )
27966    });
27967    save.await.unwrap();
27968
27969    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27970    empty_color_request_handle.next().await.unwrap();
27971    cx.run_until_parked();
27972    assert_eq!(
27973        3,
27974        requests_made.load(atomic::Ordering::Acquire),
27975        "Should query for colors once per save only, as formatting was not requested"
27976    );
27977    editor.update(cx, |editor, cx| {
27978        assert_eq!(
27979            Vec::<Rgba>::new(),
27980            extract_color_inlays(editor, cx),
27981            "Should clear all colors when the server returns an empty response"
27982        );
27983    });
27984}
27985
27986#[gpui::test]
27987async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
27988    init_test(cx, |_| {});
27989    let (editor, cx) = cx.add_window_view(Editor::single_line);
27990    editor.update_in(cx, |editor, window, cx| {
27991        editor.set_text("oops\n\nwow\n", window, cx)
27992    });
27993    cx.run_until_parked();
27994    editor.update(cx, |editor, cx| {
27995        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
27996    });
27997    editor.update(cx, |editor, cx| {
27998        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
27999    });
28000    cx.run_until_parked();
28001    editor.update(cx, |editor, cx| {
28002        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
28003    });
28004}
28005
28006#[gpui::test]
28007async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
28008    init_test(cx, |_| {});
28009
28010    cx.update(|cx| {
28011        register_project_item::<Editor>(cx);
28012    });
28013
28014    let fs = FakeFs::new(cx.executor());
28015    fs.insert_tree("/root1", json!({})).await;
28016    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
28017        .await;
28018
28019    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
28020    let (workspace, cx) =
28021        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
28022
28023    let worktree_id = project.update(cx, |project, cx| {
28024        project.worktrees(cx).next().unwrap().read(cx).id()
28025    });
28026
28027    let handle = workspace
28028        .update_in(cx, |workspace, window, cx| {
28029            let project_path = (worktree_id, rel_path("one.pdf"));
28030            workspace.open_path(project_path, None, true, window, cx)
28031        })
28032        .await
28033        .unwrap();
28034    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
28035    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
28036    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
28037    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
28038}
28039
28040#[gpui::test]
28041async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
28042    init_test(cx, |_| {});
28043
28044    let language = Arc::new(Language::new(
28045        LanguageConfig::default(),
28046        Some(tree_sitter_rust::LANGUAGE.into()),
28047    ));
28048
28049    // Test hierarchical sibling navigation
28050    let text = r#"
28051        fn outer() {
28052            if condition {
28053                let a = 1;
28054            }
28055            let b = 2;
28056        }
28057
28058        fn another() {
28059            let c = 3;
28060        }
28061    "#;
28062
28063    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
28064    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
28065    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
28066
28067    // Wait for parsing to complete
28068    editor
28069        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
28070        .await;
28071
28072    editor.update_in(cx, |editor, window, cx| {
28073        // Start by selecting "let a = 1;" inside the if block
28074        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28075            s.select_display_ranges([
28076                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
28077            ]);
28078        });
28079
28080        let initial_selection = editor
28081            .selections
28082            .display_ranges(&editor.display_snapshot(cx));
28083        assert_eq!(initial_selection.len(), 1, "Should have one selection");
28084
28085        // Test select next sibling - should move up levels to find the next sibling
28086        // Since "let a = 1;" has no siblings in the if block, it should move up
28087        // to find "let b = 2;" which is a sibling of the if block
28088        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28089        let next_selection = editor
28090            .selections
28091            .display_ranges(&editor.display_snapshot(cx));
28092
28093        // Should have a selection and it should be different from the initial
28094        assert_eq!(
28095            next_selection.len(),
28096            1,
28097            "Should have one selection after next"
28098        );
28099        assert_ne!(
28100            next_selection[0], initial_selection[0],
28101            "Next sibling selection should be different"
28102        );
28103
28104        // Test hierarchical navigation by going to the end of the current function
28105        // and trying to navigate to the next function
28106        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28107            s.select_display_ranges([
28108                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
28109            ]);
28110        });
28111
28112        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
28113        let function_next_selection = editor
28114            .selections
28115            .display_ranges(&editor.display_snapshot(cx));
28116
28117        // Should move to the next function
28118        assert_eq!(
28119            function_next_selection.len(),
28120            1,
28121            "Should have one selection after function next"
28122        );
28123
28124        // Test select previous sibling navigation
28125        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
28126        let prev_selection = editor
28127            .selections
28128            .display_ranges(&editor.display_snapshot(cx));
28129
28130        // Should have a selection and it should be different
28131        assert_eq!(
28132            prev_selection.len(),
28133            1,
28134            "Should have one selection after prev"
28135        );
28136        assert_ne!(
28137            prev_selection[0], function_next_selection[0],
28138            "Previous sibling selection should be different from next"
28139        );
28140    });
28141}
28142
28143#[gpui::test]
28144async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
28145    init_test(cx, |_| {});
28146
28147    let mut cx = EditorTestContext::new(cx).await;
28148    cx.set_state(
28149        "let ˇvariable = 42;
28150let another = variable + 1;
28151let result = variable * 2;",
28152    );
28153
28154    // Set up document highlights manually (simulating LSP response)
28155    cx.update_editor(|editor, _window, cx| {
28156        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
28157
28158        // Create highlights for "variable" occurrences
28159        let highlight_ranges = [
28160            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
28161            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
28162            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
28163        ];
28164
28165        let anchor_ranges: Vec<_> = highlight_ranges
28166            .iter()
28167            .map(|range| range.clone().to_anchors(&buffer_snapshot))
28168            .collect();
28169
28170        editor.highlight_background::<DocumentHighlightRead>(
28171            &anchor_ranges,
28172            |_, theme| theme.colors().editor_document_highlight_read_background,
28173            cx,
28174        );
28175    });
28176
28177    // Go to next highlight - should move to second "variable"
28178    cx.update_editor(|editor, window, cx| {
28179        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28180    });
28181    cx.assert_editor_state(
28182        "let variable = 42;
28183let another = ˇvariable + 1;
28184let result = variable * 2;",
28185    );
28186
28187    // Go to next highlight - should move to third "variable"
28188    cx.update_editor(|editor, window, cx| {
28189        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28190    });
28191    cx.assert_editor_state(
28192        "let variable = 42;
28193let another = variable + 1;
28194let result = ˇvariable * 2;",
28195    );
28196
28197    // Go to next highlight - should stay at third "variable" (no wrap-around)
28198    cx.update_editor(|editor, window, cx| {
28199        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
28200    });
28201    cx.assert_editor_state(
28202        "let variable = 42;
28203let another = variable + 1;
28204let result = ˇvariable * 2;",
28205    );
28206
28207    // Now test going backwards from third position
28208    cx.update_editor(|editor, window, cx| {
28209        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28210    });
28211    cx.assert_editor_state(
28212        "let variable = 42;
28213let another = ˇvariable + 1;
28214let result = variable * 2;",
28215    );
28216
28217    // Go to previous highlight - should move to first "variable"
28218    cx.update_editor(|editor, window, cx| {
28219        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28220    });
28221    cx.assert_editor_state(
28222        "let ˇvariable = 42;
28223let another = variable + 1;
28224let result = variable * 2;",
28225    );
28226
28227    // Go to previous highlight - should stay on first "variable"
28228    cx.update_editor(|editor, window, cx| {
28229        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
28230    });
28231    cx.assert_editor_state(
28232        "let ˇvariable = 42;
28233let another = variable + 1;
28234let result = variable * 2;",
28235    );
28236}
28237
28238#[gpui::test]
28239async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
28240    cx: &mut gpui::TestAppContext,
28241) {
28242    init_test(cx, |_| {});
28243
28244    let url = "https://zed.dev";
28245
28246    let markdown_language = Arc::new(Language::new(
28247        LanguageConfig {
28248            name: "Markdown".into(),
28249            ..LanguageConfig::default()
28250        },
28251        None,
28252    ));
28253
28254    let mut cx = EditorTestContext::new(cx).await;
28255    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28256    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
28257
28258    cx.update_editor(|editor, window, cx| {
28259        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28260        editor.paste(&Paste, window, cx);
28261    });
28262
28263    cx.assert_editor_state(&format!(
28264        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
28265    ));
28266}
28267
28268#[gpui::test]
28269async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
28270    init_test(cx, |_| {});
28271
28272    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
28273    let mut cx = EditorTestContext::new(cx).await;
28274
28275    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28276
28277    // Case 1: Test if adding a character with multi cursors preserves nested list indents
28278    cx.set_state(&indoc! {"
28279        - [ ] Item 1
28280            - [ ] Item 1.a
28281        - [ˇ] Item 2
28282            - [ˇ] Item 2.a
28283            - [ˇ] Item 2.b
28284        "
28285    });
28286    cx.update_editor(|editor, window, cx| {
28287        editor.handle_input("x", window, cx);
28288    });
28289    cx.run_until_parked();
28290    cx.assert_editor_state(indoc! {"
28291        - [ ] Item 1
28292            - [ ] Item 1.a
28293        - [xˇ] Item 2
28294            - [xˇ] Item 2.a
28295            - [xˇ] Item 2.b
28296        "
28297    });
28298
28299    // Case 2: Test adding new line after nested list continues the list with unchecked task
28300    cx.set_state(&indoc! {"
28301        - [ ] Item 1
28302            - [ ] Item 1.a
28303        - [x] Item 2
28304            - [x] Item 2.a
28305            - [x] Item 2.bˇ"
28306    });
28307    cx.update_editor(|editor, window, cx| {
28308        editor.newline(&Newline, window, cx);
28309    });
28310    cx.assert_editor_state(indoc! {"
28311        - [ ] Item 1
28312            - [ ] Item 1.a
28313        - [x] Item 2
28314            - [x] Item 2.a
28315            - [x] Item 2.b
28316            - [ ] ˇ"
28317    });
28318
28319    // Case 3: Test adding content to continued list item
28320    cx.update_editor(|editor, window, cx| {
28321        editor.handle_input("Item 2.c", window, cx);
28322    });
28323    cx.run_until_parked();
28324    cx.assert_editor_state(indoc! {"
28325        - [ ] Item 1
28326            - [ ] Item 1.a
28327        - [x] Item 2
28328            - [x] Item 2.a
28329            - [x] Item 2.b
28330            - [ ] Item 2.cˇ"
28331    });
28332
28333    // Case 4: Test adding new line after nested ordered list continues with next number
28334    cx.set_state(indoc! {"
28335        1. Item 1
28336            1. Item 1.a
28337        2. Item 2
28338            1. Item 2.a
28339            2. Item 2.bˇ"
28340    });
28341    cx.update_editor(|editor, window, cx| {
28342        editor.newline(&Newline, window, cx);
28343    });
28344    cx.assert_editor_state(indoc! {"
28345        1. Item 1
28346            1. Item 1.a
28347        2. Item 2
28348            1. Item 2.a
28349            2. Item 2.b
28350            3. ˇ"
28351    });
28352
28353    // Case 5: Adding content to continued ordered list item
28354    cx.update_editor(|editor, window, cx| {
28355        editor.handle_input("Item 2.c", window, cx);
28356    });
28357    cx.run_until_parked();
28358    cx.assert_editor_state(indoc! {"
28359        1. Item 1
28360            1. Item 1.a
28361        2. Item 2
28362            1. Item 2.a
28363            2. Item 2.b
28364            3. Item 2.cˇ"
28365    });
28366
28367    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
28368    cx.set_state(indoc! {"
28369        - Item 1
28370            - Item 1.a
28371            - Item 1.a
28372        ˇ"});
28373    cx.update_editor(|editor, window, cx| {
28374        editor.handle_input("-", window, cx);
28375    });
28376    cx.run_until_parked();
28377    cx.assert_editor_state(indoc! {"
28378        - Item 1
28379            - Item 1.a
28380            - Item 1.a
28381"});
28382
28383    // Case 7: Test blockquote newline preserves something
28384    cx.set_state(indoc! {"
28385        > Item 1ˇ"
28386    });
28387    cx.update_editor(|editor, window, cx| {
28388        editor.newline(&Newline, window, cx);
28389    });
28390    cx.assert_editor_state(indoc! {"
28391        > Item 1
28392        ˇ"
28393    });
28394}
28395
28396#[gpui::test]
28397async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
28398    cx: &mut gpui::TestAppContext,
28399) {
28400    init_test(cx, |_| {});
28401
28402    let url = "https://zed.dev";
28403
28404    let markdown_language = Arc::new(Language::new(
28405        LanguageConfig {
28406            name: "Markdown".into(),
28407            ..LanguageConfig::default()
28408        },
28409        None,
28410    ));
28411
28412    let mut cx = EditorTestContext::new(cx).await;
28413    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28414    cx.set_state(&format!(
28415        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
28416    ));
28417
28418    cx.update_editor(|editor, window, cx| {
28419        editor.copy(&Copy, window, cx);
28420    });
28421
28422    cx.set_state(&format!(
28423        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
28424    ));
28425
28426    cx.update_editor(|editor, window, cx| {
28427        editor.paste(&Paste, window, cx);
28428    });
28429
28430    cx.assert_editor_state(&format!(
28431        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
28432    ));
28433}
28434
28435#[gpui::test]
28436async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
28437    cx: &mut gpui::TestAppContext,
28438) {
28439    init_test(cx, |_| {});
28440
28441    let url = "https://zed.dev";
28442
28443    let markdown_language = Arc::new(Language::new(
28444        LanguageConfig {
28445            name: "Markdown".into(),
28446            ..LanguageConfig::default()
28447        },
28448        None,
28449    ));
28450
28451    let mut cx = EditorTestContext::new(cx).await;
28452    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28453    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
28454
28455    cx.update_editor(|editor, window, cx| {
28456        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28457        editor.paste(&Paste, window, cx);
28458    });
28459
28460    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
28461}
28462
28463#[gpui::test]
28464async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
28465    cx: &mut gpui::TestAppContext,
28466) {
28467    init_test(cx, |_| {});
28468
28469    let text = "Awesome";
28470
28471    let markdown_language = Arc::new(Language::new(
28472        LanguageConfig {
28473            name: "Markdown".into(),
28474            ..LanguageConfig::default()
28475        },
28476        None,
28477    ));
28478
28479    let mut cx = EditorTestContext::new(cx).await;
28480    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28481    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
28482
28483    cx.update_editor(|editor, window, cx| {
28484        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
28485        editor.paste(&Paste, window, cx);
28486    });
28487
28488    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
28489}
28490
28491#[gpui::test]
28492async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
28493    cx: &mut gpui::TestAppContext,
28494) {
28495    init_test(cx, |_| {});
28496
28497    let url = "https://zed.dev";
28498
28499    let markdown_language = Arc::new(Language::new(
28500        LanguageConfig {
28501            name: "Rust".into(),
28502            ..LanguageConfig::default()
28503        },
28504        None,
28505    ));
28506
28507    let mut cx = EditorTestContext::new(cx).await;
28508    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28509    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
28510
28511    cx.update_editor(|editor, window, cx| {
28512        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28513        editor.paste(&Paste, window, cx);
28514    });
28515
28516    cx.assert_editor_state(&format!(
28517        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
28518    ));
28519}
28520
28521#[gpui::test]
28522async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
28523    cx: &mut TestAppContext,
28524) {
28525    init_test(cx, |_| {});
28526
28527    let url = "https://zed.dev";
28528
28529    let markdown_language = Arc::new(Language::new(
28530        LanguageConfig {
28531            name: "Markdown".into(),
28532            ..LanguageConfig::default()
28533        },
28534        None,
28535    ));
28536
28537    let (editor, cx) = cx.add_window_view(|window, cx| {
28538        let multi_buffer = MultiBuffer::build_multi(
28539            [
28540                ("this will embed -> link", vec![Point::row_range(0..1)]),
28541                ("this will replace -> link", vec![Point::row_range(0..1)]),
28542            ],
28543            cx,
28544        );
28545        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
28546        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28547            s.select_ranges(vec![
28548                Point::new(0, 19)..Point::new(0, 23),
28549                Point::new(1, 21)..Point::new(1, 25),
28550            ])
28551        });
28552        let first_buffer_id = multi_buffer
28553            .read(cx)
28554            .excerpt_buffer_ids()
28555            .into_iter()
28556            .next()
28557            .unwrap();
28558        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
28559        first_buffer.update(cx, |buffer, cx| {
28560            buffer.set_language(Some(markdown_language.clone()), cx);
28561        });
28562
28563        editor
28564    });
28565    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28566
28567    cx.update_editor(|editor, window, cx| {
28568        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28569        editor.paste(&Paste, window, cx);
28570    });
28571
28572    cx.assert_editor_state(&format!(
28573        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
28574    ));
28575}
28576
28577#[gpui::test]
28578async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
28579    init_test(cx, |_| {});
28580
28581    let fs = FakeFs::new(cx.executor());
28582    fs.insert_tree(
28583        path!("/project"),
28584        json!({
28585            "first.rs": "# First Document\nSome content here.",
28586            "second.rs": "Plain text content for second file.",
28587        }),
28588    )
28589    .await;
28590
28591    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
28592    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
28593    let cx = &mut VisualTestContext::from_window(*workspace, cx);
28594
28595    let language = rust_lang();
28596    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28597    language_registry.add(language.clone());
28598    let mut fake_servers = language_registry.register_fake_lsp(
28599        "Rust",
28600        FakeLspAdapter {
28601            ..FakeLspAdapter::default()
28602        },
28603    );
28604
28605    let buffer1 = project
28606        .update(cx, |project, cx| {
28607            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
28608        })
28609        .await
28610        .unwrap();
28611    let buffer2 = project
28612        .update(cx, |project, cx| {
28613            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
28614        })
28615        .await
28616        .unwrap();
28617
28618    let multi_buffer = cx.new(|cx| {
28619        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
28620        multi_buffer.set_excerpts_for_path(
28621            PathKey::for_buffer(&buffer1, cx),
28622            buffer1.clone(),
28623            [Point::zero()..buffer1.read(cx).max_point()],
28624            3,
28625            cx,
28626        );
28627        multi_buffer.set_excerpts_for_path(
28628            PathKey::for_buffer(&buffer2, cx),
28629            buffer2.clone(),
28630            [Point::zero()..buffer1.read(cx).max_point()],
28631            3,
28632            cx,
28633        );
28634        multi_buffer
28635    });
28636
28637    let (editor, cx) = cx.add_window_view(|window, cx| {
28638        Editor::new(
28639            EditorMode::full(),
28640            multi_buffer,
28641            Some(project.clone()),
28642            window,
28643            cx,
28644        )
28645    });
28646
28647    let fake_language_server = fake_servers.next().await.unwrap();
28648
28649    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
28650
28651    let save = editor.update_in(cx, |editor, window, cx| {
28652        assert!(editor.is_dirty(cx));
28653
28654        editor.save(
28655            SaveOptions {
28656                format: true,
28657                autosave: true,
28658            },
28659            project,
28660            window,
28661            cx,
28662        )
28663    });
28664    let (start_edit_tx, start_edit_rx) = oneshot::channel();
28665    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28666    let mut done_edit_rx = Some(done_edit_rx);
28667    let mut start_edit_tx = Some(start_edit_tx);
28668
28669    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28670        start_edit_tx.take().unwrap().send(()).unwrap();
28671        let done_edit_rx = done_edit_rx.take().unwrap();
28672        async move {
28673            done_edit_rx.await.unwrap();
28674            Ok(None)
28675        }
28676    });
28677
28678    start_edit_rx.await.unwrap();
28679    buffer2
28680        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28681        .unwrap();
28682
28683    done_edit_tx.send(()).unwrap();
28684
28685    save.await.unwrap();
28686    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28687}
28688
28689#[track_caller]
28690fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
28691    editor
28692        .all_inlays(cx)
28693        .into_iter()
28694        .filter_map(|inlay| inlay.get_color())
28695        .map(Rgba::from)
28696        .collect()
28697}
28698
28699#[gpui::test]
28700fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28701    init_test(cx, |_| {});
28702
28703    let editor = cx.add_window(|window, cx| {
28704        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28705        build_editor(buffer, window, cx)
28706    });
28707
28708    editor
28709        .update(cx, |editor, window, cx| {
28710            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28711                s.select_display_ranges([
28712                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28713                ])
28714            });
28715
28716            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28717
28718            assert_eq!(
28719                editor.display_text(cx),
28720                "line1\nline2\nline2",
28721                "Duplicating last line upward should create duplicate above, not on same line"
28722            );
28723
28724            assert_eq!(
28725                editor
28726                    .selections
28727                    .display_ranges(&editor.display_snapshot(cx)),
28728                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28729                "Selection should move to the duplicated line"
28730            );
28731        })
28732        .unwrap();
28733}
28734
28735#[gpui::test]
28736async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28737    init_test(cx, |_| {});
28738
28739    let mut cx = EditorTestContext::new(cx).await;
28740
28741    cx.set_state("line1\nline2ˇ");
28742
28743    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28744
28745    let clipboard_text = cx
28746        .read_from_clipboard()
28747        .and_then(|item| item.text().as_deref().map(str::to_string));
28748
28749    assert_eq!(
28750        clipboard_text,
28751        Some("line2\n".to_string()),
28752        "Copying a line without trailing newline should include a newline"
28753    );
28754
28755    cx.set_state("line1\nˇ");
28756
28757    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28758
28759    cx.assert_editor_state("line1\nline2\nˇ");
28760}
28761
28762#[gpui::test]
28763async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28764    init_test(cx, |_| {});
28765
28766    let mut cx = EditorTestContext::new(cx).await;
28767
28768    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28769
28770    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28771
28772    let clipboard_text = cx
28773        .read_from_clipboard()
28774        .and_then(|item| item.text().as_deref().map(str::to_string));
28775
28776    assert_eq!(
28777        clipboard_text,
28778        Some("line1\nline2\nline3\n".to_string()),
28779        "Copying multiple lines should include a single newline between lines"
28780    );
28781
28782    cx.set_state("lineA\nˇ");
28783
28784    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28785
28786    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28787}
28788
28789#[gpui::test]
28790async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28791    init_test(cx, |_| {});
28792
28793    let mut cx = EditorTestContext::new(cx).await;
28794
28795    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28796
28797    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28798
28799    let clipboard_text = cx
28800        .read_from_clipboard()
28801        .and_then(|item| item.text().as_deref().map(str::to_string));
28802
28803    assert_eq!(
28804        clipboard_text,
28805        Some("line1\nline2\nline3\n".to_string()),
28806        "Copying multiple lines should include a single newline between lines"
28807    );
28808
28809    cx.set_state("lineA\nˇ");
28810
28811    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28812
28813    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28814}
28815
28816#[gpui::test]
28817async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28818    init_test(cx, |_| {});
28819
28820    let mut cx = EditorTestContext::new(cx).await;
28821
28822    cx.set_state("line1\nline2ˇ");
28823    cx.update_editor(|e, window, cx| {
28824        e.set_mode(EditorMode::SingleLine);
28825        assert!(e.key_context(window, cx).contains("end_of_input"));
28826    });
28827    cx.set_state("ˇline1\nline2");
28828    cx.update_editor(|e, window, cx| {
28829        assert!(!e.key_context(window, cx).contains("end_of_input"));
28830    });
28831    cx.set_state("line1ˇ\nline2");
28832    cx.update_editor(|e, window, cx| {
28833        assert!(!e.key_context(window, cx).contains("end_of_input"));
28834    });
28835}
28836
28837#[gpui::test]
28838async fn test_sticky_scroll(cx: &mut TestAppContext) {
28839    init_test(cx, |_| {});
28840    let mut cx = EditorTestContext::new(cx).await;
28841
28842    let buffer = indoc! {"
28843            ˇfn foo() {
28844                let abc = 123;
28845            }
28846            struct Bar;
28847            impl Bar {
28848                fn new() -> Self {
28849                    Self
28850                }
28851            }
28852            fn baz() {
28853            }
28854        "};
28855    cx.set_state(&buffer);
28856
28857    cx.update_editor(|e, _, cx| {
28858        e.buffer()
28859            .read(cx)
28860            .as_singleton()
28861            .unwrap()
28862            .update(cx, |buffer, cx| {
28863                buffer.set_language(Some(rust_lang()), cx);
28864            })
28865    });
28866
28867    let mut sticky_headers = |offset: ScrollOffset| {
28868        cx.update_editor(|e, window, cx| {
28869            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
28870            let style = e.style(cx).clone();
28871            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
28872                .into_iter()
28873                .map(
28874                    |StickyHeader {
28875                         start_point,
28876                         offset,
28877                         ..
28878                     }| { (start_point, offset) },
28879                )
28880                .collect::<Vec<_>>()
28881        })
28882    };
28883
28884    let fn_foo = Point { row: 0, column: 0 };
28885    let impl_bar = Point { row: 4, column: 0 };
28886    let fn_new = Point { row: 5, column: 4 };
28887
28888    assert_eq!(sticky_headers(0.0), vec![]);
28889    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
28890    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
28891    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
28892    assert_eq!(sticky_headers(2.0), vec![]);
28893    assert_eq!(sticky_headers(2.5), vec![]);
28894    assert_eq!(sticky_headers(3.0), vec![]);
28895    assert_eq!(sticky_headers(3.5), vec![]);
28896    assert_eq!(sticky_headers(4.0), vec![]);
28897    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28898    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28899    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
28900    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
28901    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
28902    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
28903    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
28904    assert_eq!(sticky_headers(8.0), vec![]);
28905    assert_eq!(sticky_headers(8.5), vec![]);
28906    assert_eq!(sticky_headers(9.0), vec![]);
28907    assert_eq!(sticky_headers(9.5), vec![]);
28908    assert_eq!(sticky_headers(10.0), vec![]);
28909}
28910
28911#[gpui::test]
28912fn test_relative_line_numbers(cx: &mut TestAppContext) {
28913    init_test(cx, |_| {});
28914
28915    let buffer_1 = cx.new(|cx| Buffer::local("aaaaaaaaaa\nbbb\n", cx));
28916    let buffer_2 = cx.new(|cx| Buffer::local("cccccccccc\nddd\n", cx));
28917    let buffer_3 = cx.new(|cx| Buffer::local("eee\nffffffffff\n", cx));
28918
28919    let multibuffer = cx.new(|cx| {
28920        let mut multibuffer = MultiBuffer::new(ReadWrite);
28921        multibuffer.push_excerpts(
28922            buffer_1.clone(),
28923            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28924            cx,
28925        );
28926        multibuffer.push_excerpts(
28927            buffer_2.clone(),
28928            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28929            cx,
28930        );
28931        multibuffer.push_excerpts(
28932            buffer_3.clone(),
28933            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
28934            cx,
28935        );
28936        multibuffer
28937    });
28938
28939    // wrapped contents of multibuffer:
28940    //    aaa
28941    //    aaa
28942    //    aaa
28943    //    a
28944    //    bbb
28945    //
28946    //    ccc
28947    //    ccc
28948    //    ccc
28949    //    c
28950    //    ddd
28951    //
28952    //    eee
28953    //    fff
28954    //    fff
28955    //    fff
28956    //    f
28957
28958    let editor = cx.add_window(|window, cx| build_editor(multibuffer, window, cx));
28959    _ = editor.update(cx, |editor, window, cx| {
28960        editor.set_wrap_width(Some(30.0.into()), cx); // every 3 characters
28961
28962        // includes trailing newlines.
28963        let expected_line_numbers = [2, 6, 7, 10, 14, 15, 18, 19, 23];
28964        let expected_wrapped_line_numbers = [
28965            2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22, 23,
28966        ];
28967
28968        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28969            s.select_ranges([
28970                Point::new(7, 0)..Point::new(7, 1), // second row of `ccc`
28971            ]);
28972        });
28973
28974        let snapshot = editor.snapshot(window, cx);
28975
28976        // these are all 0-indexed
28977        let base_display_row = DisplayRow(11);
28978        let base_row = 3;
28979        let wrapped_base_row = 7;
28980
28981        // test not counting wrapped lines
28982        let expected_relative_numbers = expected_line_numbers
28983            .into_iter()
28984            .enumerate()
28985            .map(|(i, row)| (DisplayRow(row), i.abs_diff(base_row) as u32))
28986            .collect_vec();
28987        let actual_relative_numbers = snapshot
28988            .calculate_relative_line_numbers(
28989                &(DisplayRow(0)..DisplayRow(24)),
28990                base_display_row,
28991                false,
28992            )
28993            .into_iter()
28994            .sorted()
28995            .collect_vec();
28996        assert_eq!(expected_relative_numbers, actual_relative_numbers);
28997        // check `calculate_relative_line_numbers()` against `relative_line_delta()` for each line
28998        for (display_row, relative_number) in expected_relative_numbers {
28999            assert_eq!(
29000                relative_number,
29001                snapshot
29002                    .relative_line_delta(display_row, base_display_row, false)
29003                    .unsigned_abs() as u32,
29004            );
29005        }
29006
29007        // test counting wrapped lines
29008        let expected_wrapped_relative_numbers = expected_wrapped_line_numbers
29009            .into_iter()
29010            .enumerate()
29011            .map(|(i, row)| (DisplayRow(row), i.abs_diff(wrapped_base_row) as u32))
29012            .filter(|(row, _)| *row != base_display_row)
29013            .collect_vec();
29014        let actual_relative_numbers = snapshot
29015            .calculate_relative_line_numbers(
29016                &(DisplayRow(0)..DisplayRow(24)),
29017                base_display_row,
29018                true,
29019            )
29020            .into_iter()
29021            .sorted()
29022            .collect_vec();
29023        assert_eq!(expected_wrapped_relative_numbers, actual_relative_numbers);
29024        // check `calculate_relative_line_numbers()` against `relative_wrapped_line_delta()` for each line
29025        for (display_row, relative_number) in expected_wrapped_relative_numbers {
29026            assert_eq!(
29027                relative_number,
29028                snapshot
29029                    .relative_line_delta(display_row, base_display_row, true)
29030                    .unsigned_abs() as u32,
29031            );
29032        }
29033    });
29034}
29035
29036#[gpui::test]
29037async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
29038    init_test(cx, |_| {});
29039    cx.update(|cx| {
29040        SettingsStore::update_global(cx, |store, cx| {
29041            store.update_user_settings(cx, |settings| {
29042                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
29043                    enabled: Some(true),
29044                })
29045            });
29046        });
29047    });
29048    let mut cx = EditorTestContext::new(cx).await;
29049
29050    let line_height = cx.update_editor(|editor, window, cx| {
29051        editor
29052            .style(cx)
29053            .text
29054            .line_height_in_pixels(window.rem_size())
29055    });
29056
29057    let buffer = indoc! {"
29058            ˇfn foo() {
29059                let abc = 123;
29060            }
29061            struct Bar;
29062            impl Bar {
29063                fn new() -> Self {
29064                    Self
29065                }
29066            }
29067            fn baz() {
29068            }
29069        "};
29070    cx.set_state(&buffer);
29071
29072    cx.update_editor(|e, _, cx| {
29073        e.buffer()
29074            .read(cx)
29075            .as_singleton()
29076            .unwrap()
29077            .update(cx, |buffer, cx| {
29078                buffer.set_language(Some(rust_lang()), cx);
29079            })
29080    });
29081
29082    let fn_foo = || empty_range(0, 0);
29083    let impl_bar = || empty_range(4, 0);
29084    let fn_new = || empty_range(5, 4);
29085
29086    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
29087        cx.update_editor(|e, window, cx| {
29088            e.scroll(
29089                gpui::Point {
29090                    x: 0.,
29091                    y: scroll_offset,
29092                },
29093                None,
29094                window,
29095                cx,
29096            );
29097        });
29098        cx.simulate_click(
29099            gpui::Point {
29100                x: px(0.),
29101                y: click_offset as f32 * line_height,
29102            },
29103            Modifiers::none(),
29104        );
29105        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
29106    };
29107
29108    assert_eq!(
29109        scroll_and_click(
29110            4.5, // impl Bar is halfway off the screen
29111            0.0  // click top of screen
29112        ),
29113        // scrolled to impl Bar
29114        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29115    );
29116
29117    assert_eq!(
29118        scroll_and_click(
29119            4.5,  // impl Bar is halfway off the screen
29120            0.25  // click middle of impl Bar
29121        ),
29122        // scrolled to impl Bar
29123        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29124    );
29125
29126    assert_eq!(
29127        scroll_and_click(
29128            4.5, // impl Bar is halfway off the screen
29129            1.5  // click below impl Bar (e.g. fn new())
29130        ),
29131        // scrolled to fn new() - this is below the impl Bar header which has persisted
29132        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29133    );
29134
29135    assert_eq!(
29136        scroll_and_click(
29137            5.5,  // fn new is halfway underneath impl Bar
29138            0.75  // click on the overlap of impl Bar and fn new()
29139        ),
29140        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
29141    );
29142
29143    assert_eq!(
29144        scroll_and_click(
29145            5.5,  // fn new is halfway underneath impl Bar
29146            1.25  // click on the visible part of fn new()
29147        ),
29148        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
29149    );
29150
29151    assert_eq!(
29152        scroll_and_click(
29153            1.5, // fn foo is halfway off the screen
29154            0.0  // click top of screen
29155        ),
29156        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
29157    );
29158
29159    assert_eq!(
29160        scroll_and_click(
29161            1.5,  // fn foo is halfway off the screen
29162            0.75  // click visible part of let abc...
29163        )
29164        .0,
29165        // no change in scroll
29166        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
29167        (gpui::Point { x: 0., y: 1.5 })
29168    );
29169}
29170
29171#[gpui::test]
29172async fn test_next_prev_reference(cx: &mut TestAppContext) {
29173    const CYCLE_POSITIONS: &[&'static str] = &[
29174        indoc! {"
29175            fn foo() {
29176                let ˇabc = 123;
29177                let x = abc + 1;
29178                let y = abc + 2;
29179                let z = abc + 2;
29180            }
29181        "},
29182        indoc! {"
29183            fn foo() {
29184                let abc = 123;
29185                let x = ˇabc + 1;
29186                let y = abc + 2;
29187                let z = abc + 2;
29188            }
29189        "},
29190        indoc! {"
29191            fn foo() {
29192                let abc = 123;
29193                let x = abc + 1;
29194                let y = ˇabc + 2;
29195                let z = abc + 2;
29196            }
29197        "},
29198        indoc! {"
29199            fn foo() {
29200                let abc = 123;
29201                let x = abc + 1;
29202                let y = abc + 2;
29203                let z = ˇabc + 2;
29204            }
29205        "},
29206    ];
29207
29208    init_test(cx, |_| {});
29209
29210    let mut cx = EditorLspTestContext::new_rust(
29211        lsp::ServerCapabilities {
29212            references_provider: Some(lsp::OneOf::Left(true)),
29213            ..Default::default()
29214        },
29215        cx,
29216    )
29217    .await;
29218
29219    // importantly, the cursor is in the middle
29220    cx.set_state(indoc! {"
29221        fn foo() {
29222            let aˇbc = 123;
29223            let x = abc + 1;
29224            let y = abc + 2;
29225            let z = abc + 2;
29226        }
29227    "});
29228
29229    let reference_ranges = [
29230        lsp::Position::new(1, 8),
29231        lsp::Position::new(2, 12),
29232        lsp::Position::new(3, 12),
29233        lsp::Position::new(4, 12),
29234    ]
29235    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
29236
29237    cx.lsp
29238        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
29239            Ok(Some(
29240                reference_ranges
29241                    .map(|range| lsp::Location {
29242                        uri: params.text_document_position.text_document.uri.clone(),
29243                        range,
29244                    })
29245                    .to_vec(),
29246            ))
29247        });
29248
29249    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
29250        cx.update_editor(|editor, window, cx| {
29251            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
29252        })
29253        .unwrap()
29254        .await
29255        .unwrap()
29256    };
29257
29258    _move(Direction::Next, 1, &mut cx).await;
29259    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29260
29261    _move(Direction::Next, 1, &mut cx).await;
29262    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29263
29264    _move(Direction::Next, 1, &mut cx).await;
29265    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29266
29267    // loops back to the start
29268    _move(Direction::Next, 1, &mut cx).await;
29269    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29270
29271    // loops back to the end
29272    _move(Direction::Prev, 1, &mut cx).await;
29273    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29274
29275    _move(Direction::Prev, 1, &mut cx).await;
29276    cx.assert_editor_state(CYCLE_POSITIONS[2]);
29277
29278    _move(Direction::Prev, 1, &mut cx).await;
29279    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29280
29281    _move(Direction::Prev, 1, &mut cx).await;
29282    cx.assert_editor_state(CYCLE_POSITIONS[0]);
29283
29284    _move(Direction::Next, 3, &mut cx).await;
29285    cx.assert_editor_state(CYCLE_POSITIONS[3]);
29286
29287    _move(Direction::Prev, 2, &mut cx).await;
29288    cx.assert_editor_state(CYCLE_POSITIONS[1]);
29289}
29290
29291#[gpui::test]
29292async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
29293    init_test(cx, |_| {});
29294
29295    let (editor, cx) = cx.add_window_view(|window, cx| {
29296        let multi_buffer = MultiBuffer::build_multi(
29297            [
29298                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29299                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29300            ],
29301            cx,
29302        );
29303        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29304    });
29305
29306    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29307    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
29308
29309    cx.assert_excerpts_with_selections(indoc! {"
29310        [EXCERPT]
29311        ˇ1
29312        2
29313        3
29314        [EXCERPT]
29315        1
29316        2
29317        3
29318        "});
29319
29320    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
29321    cx.update_editor(|editor, window, cx| {
29322        editor.change_selections(None.into(), window, cx, |s| {
29323            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29324        });
29325    });
29326    cx.assert_excerpts_with_selections(indoc! {"
29327        [EXCERPT]
29328        1
2932929330        3
29331        [EXCERPT]
29332        1
29333        2
29334        3
29335        "});
29336
29337    cx.update_editor(|editor, window, cx| {
29338        editor
29339            .select_all_matches(&SelectAllMatches, window, cx)
29340            .unwrap();
29341    });
29342    cx.assert_excerpts_with_selections(indoc! {"
29343        [EXCERPT]
29344        1
2934529346        3
29347        [EXCERPT]
29348        1
2934929350        3
29351        "});
29352
29353    cx.update_editor(|editor, window, cx| {
29354        editor.handle_input("X", window, cx);
29355    });
29356    cx.assert_excerpts_with_selections(indoc! {"
29357        [EXCERPT]
29358        1
2935929360        3
29361        [EXCERPT]
29362        1
2936329364        3
29365        "});
29366
29367    // Scenario 2: Select "2", then fold second buffer before insertion
29368    cx.update_multibuffer(|mb, cx| {
29369        for buffer_id in buffer_ids.iter() {
29370            let buffer = mb.buffer(*buffer_id).unwrap();
29371            buffer.update(cx, |buffer, cx| {
29372                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29373            });
29374        }
29375    });
29376
29377    // Select "2" and select all matches
29378    cx.update_editor(|editor, window, cx| {
29379        editor.change_selections(None.into(), window, cx, |s| {
29380            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29381        });
29382        editor
29383            .select_all_matches(&SelectAllMatches, window, cx)
29384            .unwrap();
29385    });
29386
29387    // Fold second buffer - should remove selections from folded buffer
29388    cx.update_editor(|editor, _, cx| {
29389        editor.fold_buffer(buffer_ids[1], cx);
29390    });
29391    cx.assert_excerpts_with_selections(indoc! {"
29392        [EXCERPT]
29393        1
2939429395        3
29396        [EXCERPT]
29397        [FOLDED]
29398        "});
29399
29400    // Insert text - should only affect first buffer
29401    cx.update_editor(|editor, window, cx| {
29402        editor.handle_input("Y", window, cx);
29403    });
29404    cx.update_editor(|editor, _, cx| {
29405        editor.unfold_buffer(buffer_ids[1], cx);
29406    });
29407    cx.assert_excerpts_with_selections(indoc! {"
29408        [EXCERPT]
29409        1
2941029411        3
29412        [EXCERPT]
29413        1
29414        2
29415        3
29416        "});
29417
29418    // Scenario 3: Select "2", then fold first buffer before insertion
29419    cx.update_multibuffer(|mb, cx| {
29420        for buffer_id in buffer_ids.iter() {
29421            let buffer = mb.buffer(*buffer_id).unwrap();
29422            buffer.update(cx, |buffer, cx| {
29423                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
29424            });
29425        }
29426    });
29427
29428    // Select "2" and select all matches
29429    cx.update_editor(|editor, window, cx| {
29430        editor.change_selections(None.into(), window, cx, |s| {
29431            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
29432        });
29433        editor
29434            .select_all_matches(&SelectAllMatches, window, cx)
29435            .unwrap();
29436    });
29437
29438    // Fold first buffer - should remove selections from folded buffer
29439    cx.update_editor(|editor, _, cx| {
29440        editor.fold_buffer(buffer_ids[0], cx);
29441    });
29442    cx.assert_excerpts_with_selections(indoc! {"
29443        [EXCERPT]
29444        [FOLDED]
29445        [EXCERPT]
29446        1
2944729448        3
29449        "});
29450
29451    // Insert text - should only affect second buffer
29452    cx.update_editor(|editor, window, cx| {
29453        editor.handle_input("Z", window, cx);
29454    });
29455    cx.update_editor(|editor, _, cx| {
29456        editor.unfold_buffer(buffer_ids[0], cx);
29457    });
29458    cx.assert_excerpts_with_selections(indoc! {"
29459        [EXCERPT]
29460        1
29461        2
29462        3
29463        [EXCERPT]
29464        1
2946529466        3
29467        "});
29468
29469    // Test correct folded header is selected upon fold
29470    cx.update_editor(|editor, _, cx| {
29471        editor.fold_buffer(buffer_ids[0], cx);
29472        editor.fold_buffer(buffer_ids[1], cx);
29473    });
29474    cx.assert_excerpts_with_selections(indoc! {"
29475        [EXCERPT]
29476        [FOLDED]
29477        [EXCERPT]
29478        ˇ[FOLDED]
29479        "});
29480
29481    // Test selection inside folded buffer unfolds it on type
29482    cx.update_editor(|editor, window, cx| {
29483        editor.handle_input("W", window, cx);
29484    });
29485    cx.update_editor(|editor, _, cx| {
29486        editor.unfold_buffer(buffer_ids[0], cx);
29487    });
29488    cx.assert_excerpts_with_selections(indoc! {"
29489        [EXCERPT]
29490        1
29491        2
29492        3
29493        [EXCERPT]
29494        Wˇ1
29495        Z
29496        3
29497        "});
29498}
29499
29500#[gpui::test]
29501async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
29502    init_test(cx, |_| {});
29503
29504    let (editor, cx) = cx.add_window_view(|window, cx| {
29505        let multi_buffer = MultiBuffer::build_multi(
29506            [
29507                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29508                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
29509            ],
29510            cx,
29511        );
29512        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29513    });
29514
29515    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29516
29517    cx.assert_excerpts_with_selections(indoc! {"
29518        [EXCERPT]
29519        ˇ1
29520        2
29521        3
29522        [EXCERPT]
29523        1
29524        2
29525        3
29526        4
29527        5
29528        6
29529        7
29530        8
29531        9
29532        "});
29533
29534    cx.update_editor(|editor, window, cx| {
29535        editor.change_selections(None.into(), window, cx, |s| {
29536            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
29537        });
29538    });
29539
29540    cx.assert_excerpts_with_selections(indoc! {"
29541        [EXCERPT]
29542        1
29543        2
29544        3
29545        [EXCERPT]
29546        1
29547        2
29548        3
29549        4
29550        5
29551        6
29552        ˇ7
29553        8
29554        9
29555        "});
29556
29557    cx.update_editor(|editor, _window, cx| {
29558        editor.set_vertical_scroll_margin(0, cx);
29559    });
29560
29561    cx.update_editor(|editor, window, cx| {
29562        assert_eq!(editor.vertical_scroll_margin(), 0);
29563        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29564        assert_eq!(
29565            editor.snapshot(window, cx).scroll_position(),
29566            gpui::Point::new(0., 12.0)
29567        );
29568    });
29569
29570    cx.update_editor(|editor, _window, cx| {
29571        editor.set_vertical_scroll_margin(3, cx);
29572    });
29573
29574    cx.update_editor(|editor, window, cx| {
29575        assert_eq!(editor.vertical_scroll_margin(), 3);
29576        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29577        assert_eq!(
29578            editor.snapshot(window, cx).scroll_position(),
29579            gpui::Point::new(0., 9.0)
29580        );
29581    });
29582}
29583
29584#[gpui::test]
29585async fn test_find_references_single_case(cx: &mut TestAppContext) {
29586    init_test(cx, |_| {});
29587    let mut cx = EditorLspTestContext::new_rust(
29588        lsp::ServerCapabilities {
29589            references_provider: Some(lsp::OneOf::Left(true)),
29590            ..lsp::ServerCapabilities::default()
29591        },
29592        cx,
29593    )
29594    .await;
29595
29596    let before = indoc!(
29597        r#"
29598        fn main() {
29599            let aˇbc = 123;
29600            let xyz = abc;
29601        }
29602        "#
29603    );
29604    let after = indoc!(
29605        r#"
29606        fn main() {
29607            let abc = 123;
29608            let xyz = ˇabc;
29609        }
29610        "#
29611    );
29612
29613    cx.lsp
29614        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29615            Ok(Some(vec![
29616                lsp::Location {
29617                    uri: params.text_document_position.text_document.uri.clone(),
29618                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29619                },
29620                lsp::Location {
29621                    uri: params.text_document_position.text_document.uri,
29622                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29623                },
29624            ]))
29625        });
29626
29627    cx.set_state(before);
29628
29629    let action = FindAllReferences {
29630        always_open_multibuffer: false,
29631    };
29632
29633    let navigated = cx
29634        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29635        .expect("should have spawned a task")
29636        .await
29637        .unwrap();
29638
29639    assert_eq!(navigated, Navigated::No);
29640
29641    cx.run_until_parked();
29642
29643    cx.assert_editor_state(after);
29644}
29645
29646#[gpui::test]
29647async fn test_newline_task_list_continuation(cx: &mut TestAppContext) {
29648    init_test(cx, |settings| {
29649        settings.defaults.tab_size = Some(2.try_into().unwrap());
29650    });
29651
29652    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29653    let mut cx = EditorTestContext::new(cx).await;
29654    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29655
29656    // Case 1: Adding newline after (whitespace + prefix + any non-whitespace) adds marker
29657    cx.set_state(indoc! {"
29658        - [ ] taskˇ
29659    "});
29660    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29661    cx.wait_for_autoindent_applied().await;
29662    cx.assert_editor_state(indoc! {"
29663        - [ ] task
29664        - [ ] ˇ
29665    "});
29666
29667    // Case 2: Works with checked task items too
29668    cx.set_state(indoc! {"
29669        - [x] completed taskˇ
29670    "});
29671    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29672    cx.wait_for_autoindent_applied().await;
29673    cx.assert_editor_state(indoc! {"
29674        - [x] completed task
29675        - [ ] ˇ
29676    "});
29677
29678    // Case 2.1: Works with uppercase checked marker too
29679    cx.set_state(indoc! {"
29680        - [X] completed taskˇ
29681    "});
29682    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29683    cx.wait_for_autoindent_applied().await;
29684    cx.assert_editor_state(indoc! {"
29685        - [X] completed task
29686        - [ ] ˇ
29687    "});
29688
29689    // Case 3: Cursor position doesn't matter - content after marker is what counts
29690    cx.set_state(indoc! {"
29691        - [ ] taˇsk
29692    "});
29693    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29694    cx.wait_for_autoindent_applied().await;
29695    cx.assert_editor_state(indoc! {"
29696        - [ ] ta
29697        - [ ] ˇsk
29698    "});
29699
29700    // Case 4: Adding newline after (whitespace + prefix + some whitespace) does NOT add marker
29701    cx.set_state(indoc! {"
29702        - [ ]  ˇ
29703    "});
29704    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29705    cx.wait_for_autoindent_applied().await;
29706    cx.assert_editor_state(
29707        indoc! {"
29708        - [ ]$$
29709        ˇ
29710    "}
29711        .replace("$", " ")
29712        .as_str(),
29713    );
29714
29715    // Case 5: Adding newline with content adds marker preserving indentation
29716    cx.set_state(indoc! {"
29717        - [ ] task
29718          - [ ] indentedˇ
29719    "});
29720    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29721    cx.wait_for_autoindent_applied().await;
29722    cx.assert_editor_state(indoc! {"
29723        - [ ] task
29724          - [ ] indented
29725          - [ ] ˇ
29726    "});
29727
29728    // Case 6: Adding newline with cursor right after prefix, unindents
29729    cx.set_state(indoc! {"
29730        - [ ] task
29731          - [ ] sub task
29732            - [ ] ˇ
29733    "});
29734    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29735    cx.wait_for_autoindent_applied().await;
29736    cx.assert_editor_state(indoc! {"
29737        - [ ] task
29738          - [ ] sub task
29739          - [ ] ˇ
29740    "});
29741    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29742    cx.wait_for_autoindent_applied().await;
29743
29744    // Case 7: Adding newline with cursor right after prefix, removes marker
29745    cx.assert_editor_state(indoc! {"
29746        - [ ] task
29747          - [ ] sub task
29748        - [ ] ˇ
29749    "});
29750    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29751    cx.wait_for_autoindent_applied().await;
29752    cx.assert_editor_state(indoc! {"
29753        - [ ] task
29754          - [ ] sub task
29755        ˇ
29756    "});
29757
29758    // Case 8: Cursor before or inside prefix does not add marker
29759    cx.set_state(indoc! {"
29760        ˇ- [ ] task
29761    "});
29762    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29763    cx.wait_for_autoindent_applied().await;
29764    cx.assert_editor_state(indoc! {"
29765
29766        ˇ- [ ] task
29767    "});
29768
29769    cx.set_state(indoc! {"
29770        - [ˇ ] task
29771    "});
29772    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29773    cx.wait_for_autoindent_applied().await;
29774    cx.assert_editor_state(indoc! {"
29775        - [
29776        ˇ
29777        ] task
29778    "});
29779}
29780
29781#[gpui::test]
29782async fn test_newline_unordered_list_continuation(cx: &mut TestAppContext) {
29783    init_test(cx, |settings| {
29784        settings.defaults.tab_size = Some(2.try_into().unwrap());
29785    });
29786
29787    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29788    let mut cx = EditorTestContext::new(cx).await;
29789    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29790
29791    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) adds marker
29792    cx.set_state(indoc! {"
29793        - itemˇ
29794    "});
29795    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29796    cx.wait_for_autoindent_applied().await;
29797    cx.assert_editor_state(indoc! {"
29798        - item
29799        - ˇ
29800    "});
29801
29802    // Case 2: Works with different markers
29803    cx.set_state(indoc! {"
29804        * starred itemˇ
29805    "});
29806    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29807    cx.wait_for_autoindent_applied().await;
29808    cx.assert_editor_state(indoc! {"
29809        * starred item
29810        * ˇ
29811    "});
29812
29813    cx.set_state(indoc! {"
29814        + plus itemˇ
29815    "});
29816    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29817    cx.wait_for_autoindent_applied().await;
29818    cx.assert_editor_state(indoc! {"
29819        + plus item
29820        + ˇ
29821    "});
29822
29823    // Case 3: Cursor position doesn't matter - content after marker is what counts
29824    cx.set_state(indoc! {"
29825        - itˇem
29826    "});
29827    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29828    cx.wait_for_autoindent_applied().await;
29829    cx.assert_editor_state(indoc! {"
29830        - it
29831        - ˇem
29832    "});
29833
29834    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
29835    cx.set_state(indoc! {"
29836        -  ˇ
29837    "});
29838    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29839    cx.wait_for_autoindent_applied().await;
29840    cx.assert_editor_state(
29841        indoc! {"
29842        - $
29843        ˇ
29844    "}
29845        .replace("$", " ")
29846        .as_str(),
29847    );
29848
29849    // Case 5: Adding newline with content adds marker preserving indentation
29850    cx.set_state(indoc! {"
29851        - item
29852          - indentedˇ
29853    "});
29854    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29855    cx.wait_for_autoindent_applied().await;
29856    cx.assert_editor_state(indoc! {"
29857        - item
29858          - indented
29859          - ˇ
29860    "});
29861
29862    // Case 6: Adding newline with cursor right after marker, unindents
29863    cx.set_state(indoc! {"
29864        - item
29865          - sub item
29866            - ˇ
29867    "});
29868    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29869    cx.wait_for_autoindent_applied().await;
29870    cx.assert_editor_state(indoc! {"
29871        - item
29872          - sub item
29873          - ˇ
29874    "});
29875    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29876    cx.wait_for_autoindent_applied().await;
29877
29878    // Case 7: Adding newline with cursor right after marker, removes marker
29879    cx.assert_editor_state(indoc! {"
29880        - item
29881          - sub item
29882        - ˇ
29883    "});
29884    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29885    cx.wait_for_autoindent_applied().await;
29886    cx.assert_editor_state(indoc! {"
29887        - item
29888          - sub item
29889        ˇ
29890    "});
29891
29892    // Case 8: Cursor before or inside prefix does not add marker
29893    cx.set_state(indoc! {"
29894        ˇ- item
29895    "});
29896    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29897    cx.wait_for_autoindent_applied().await;
29898    cx.assert_editor_state(indoc! {"
29899
29900        ˇ- item
29901    "});
29902
29903    cx.set_state(indoc! {"
29904        -ˇ item
29905    "});
29906    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29907    cx.wait_for_autoindent_applied().await;
29908    cx.assert_editor_state(indoc! {"
29909        -
29910        ˇitem
29911    "});
29912}
29913
29914#[gpui::test]
29915async fn test_newline_ordered_list_continuation(cx: &mut TestAppContext) {
29916    init_test(cx, |settings| {
29917        settings.defaults.tab_size = Some(2.try_into().unwrap());
29918    });
29919
29920    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29921    let mut cx = EditorTestContext::new(cx).await;
29922    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29923
29924    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
29925    cx.set_state(indoc! {"
29926        1. first itemˇ
29927    "});
29928    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29929    cx.wait_for_autoindent_applied().await;
29930    cx.assert_editor_state(indoc! {"
29931        1. first item
29932        2. ˇ
29933    "});
29934
29935    // Case 2: Works with larger numbers
29936    cx.set_state(indoc! {"
29937        10. tenth itemˇ
29938    "});
29939    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29940    cx.wait_for_autoindent_applied().await;
29941    cx.assert_editor_state(indoc! {"
29942        10. tenth item
29943        11. ˇ
29944    "});
29945
29946    // Case 3: Cursor position doesn't matter - content after marker is what counts
29947    cx.set_state(indoc! {"
29948        1. itˇem
29949    "});
29950    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29951    cx.wait_for_autoindent_applied().await;
29952    cx.assert_editor_state(indoc! {"
29953        1. it
29954        2. ˇem
29955    "});
29956
29957    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
29958    cx.set_state(indoc! {"
29959        1.  ˇ
29960    "});
29961    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29962    cx.wait_for_autoindent_applied().await;
29963    cx.assert_editor_state(
29964        indoc! {"
29965        1. $
29966        ˇ
29967    "}
29968        .replace("$", " ")
29969        .as_str(),
29970    );
29971
29972    // Case 5: Adding newline with content adds marker preserving indentation
29973    cx.set_state(indoc! {"
29974        1. item
29975          2. indentedˇ
29976    "});
29977    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29978    cx.wait_for_autoindent_applied().await;
29979    cx.assert_editor_state(indoc! {"
29980        1. item
29981          2. indented
29982          3. ˇ
29983    "});
29984
29985    // Case 6: Adding newline with cursor right after marker, unindents
29986    cx.set_state(indoc! {"
29987        1. item
29988          2. sub item
29989            3. ˇ
29990    "});
29991    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29992    cx.wait_for_autoindent_applied().await;
29993    cx.assert_editor_state(indoc! {"
29994        1. item
29995          2. sub item
29996          1. ˇ
29997    "});
29998    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
29999    cx.wait_for_autoindent_applied().await;
30000
30001    // Case 7: Adding newline with cursor right after marker, removes marker
30002    cx.assert_editor_state(indoc! {"
30003        1. item
30004          2. sub item
30005        1. ˇ
30006    "});
30007    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30008    cx.wait_for_autoindent_applied().await;
30009    cx.assert_editor_state(indoc! {"
30010        1. item
30011          2. sub item
30012        ˇ
30013    "});
30014
30015    // Case 8: Cursor before or inside prefix does not add marker
30016    cx.set_state(indoc! {"
30017        ˇ1. item
30018    "});
30019    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30020    cx.wait_for_autoindent_applied().await;
30021    cx.assert_editor_state(indoc! {"
30022
30023        ˇ1. item
30024    "});
30025
30026    cx.set_state(indoc! {"
30027        1ˇ. item
30028    "});
30029    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30030    cx.wait_for_autoindent_applied().await;
30031    cx.assert_editor_state(indoc! {"
30032        1
30033        ˇ. item
30034    "});
30035}
30036
30037#[gpui::test]
30038async fn test_newline_should_not_autoindent_ordered_list(cx: &mut TestAppContext) {
30039    init_test(cx, |settings| {
30040        settings.defaults.tab_size = Some(2.try_into().unwrap());
30041    });
30042
30043    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30044    let mut cx = EditorTestContext::new(cx).await;
30045    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30046
30047    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
30048    cx.set_state(indoc! {"
30049        1. first item
30050          1. sub first item
30051          2. sub second item
30052          3. ˇ
30053    "});
30054    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
30055    cx.wait_for_autoindent_applied().await;
30056    cx.assert_editor_state(indoc! {"
30057        1. first item
30058          1. sub first item
30059          2. sub second item
30060        1. ˇ
30061    "});
30062}
30063
30064#[gpui::test]
30065async fn test_tab_list_indent(cx: &mut TestAppContext) {
30066    init_test(cx, |settings| {
30067        settings.defaults.tab_size = Some(2.try_into().unwrap());
30068    });
30069
30070    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
30071    let mut cx = EditorTestContext::new(cx).await;
30072    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30073
30074    // Case 1: Unordered list - cursor after prefix, adds indent before prefix
30075    cx.set_state(indoc! {"
30076        - ˇitem
30077    "});
30078    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30079    cx.wait_for_autoindent_applied().await;
30080    let expected = indoc! {"
30081        $$- ˇitem
30082    "};
30083    cx.assert_editor_state(expected.replace("$", " ").as_str());
30084
30085    // Case 2: Task list - cursor after prefix
30086    cx.set_state(indoc! {"
30087        - [ ] ˇtask
30088    "});
30089    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30090    cx.wait_for_autoindent_applied().await;
30091    let expected = indoc! {"
30092        $$- [ ] ˇtask
30093    "};
30094    cx.assert_editor_state(expected.replace("$", " ").as_str());
30095
30096    // Case 3: Ordered list - cursor after prefix
30097    cx.set_state(indoc! {"
30098        1. ˇfirst
30099    "});
30100    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30101    cx.wait_for_autoindent_applied().await;
30102    let expected = indoc! {"
30103        $$1. ˇfirst
30104    "};
30105    cx.assert_editor_state(expected.replace("$", " ").as_str());
30106
30107    // Case 4: With existing indentation - adds more indent
30108    let initial = indoc! {"
30109        $$- ˇitem
30110    "};
30111    cx.set_state(initial.replace("$", " ").as_str());
30112    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30113    cx.wait_for_autoindent_applied().await;
30114    let expected = indoc! {"
30115        $$$$- ˇitem
30116    "};
30117    cx.assert_editor_state(expected.replace("$", " ").as_str());
30118
30119    // Case 5: Empty list item
30120    cx.set_state(indoc! {"
30121        - ˇ
30122    "});
30123    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30124    cx.wait_for_autoindent_applied().await;
30125    let expected = indoc! {"
30126        $$- ˇ
30127    "};
30128    cx.assert_editor_state(expected.replace("$", " ").as_str());
30129
30130    // Case 6: Cursor at end of line with content
30131    cx.set_state(indoc! {"
30132        - itemˇ
30133    "});
30134    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30135    cx.wait_for_autoindent_applied().await;
30136    let expected = indoc! {"
30137        $$- itemˇ
30138    "};
30139    cx.assert_editor_state(expected.replace("$", " ").as_str());
30140
30141    // Case 7: Cursor at start of list item, indents it
30142    cx.set_state(indoc! {"
30143        - item
30144        ˇ  - sub item
30145    "});
30146    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30147    cx.wait_for_autoindent_applied().await;
30148    let expected = indoc! {"
30149        - item
30150          ˇ  - sub item
30151    "};
30152    cx.assert_editor_state(expected);
30153
30154    // Case 8: Cursor at start of list item, moves the cursor when "indent_list_on_tab" is false
30155    cx.update_editor(|_, _, cx| {
30156        SettingsStore::update_global(cx, |store, cx| {
30157            store.update_user_settings(cx, |settings| {
30158                settings.project.all_languages.defaults.indent_list_on_tab = Some(false);
30159            });
30160        });
30161    });
30162    cx.set_state(indoc! {"
30163        - item
30164        ˇ  - sub item
30165    "});
30166    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
30167    cx.wait_for_autoindent_applied().await;
30168    let expected = indoc! {"
30169        - item
30170          ˇ- sub item
30171    "};
30172    cx.assert_editor_state(expected);
30173}
30174
30175#[gpui::test]
30176async fn test_local_worktree_trust(cx: &mut TestAppContext) {
30177    init_test(cx, |_| {});
30178    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), cx));
30179
30180    cx.update(|cx| {
30181        SettingsStore::update_global(cx, |store, cx| {
30182            store.update_user_settings(cx, |settings| {
30183                settings.project.all_languages.defaults.inlay_hints =
30184                    Some(InlayHintSettingsContent {
30185                        enabled: Some(true),
30186                        ..InlayHintSettingsContent::default()
30187                    });
30188            });
30189        });
30190    });
30191
30192    let fs = FakeFs::new(cx.executor());
30193    fs.insert_tree(
30194        path!("/project"),
30195        json!({
30196            ".zed": {
30197                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
30198            },
30199            "main.rs": "fn main() {}"
30200        }),
30201    )
30202    .await;
30203
30204    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
30205    let server_name = "override-rust-analyzer";
30206    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
30207
30208    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
30209    language_registry.add(rust_lang());
30210
30211    let capabilities = lsp::ServerCapabilities {
30212        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
30213        ..lsp::ServerCapabilities::default()
30214    };
30215    let mut fake_language_servers = language_registry.register_fake_lsp(
30216        "Rust",
30217        FakeLspAdapter {
30218            name: server_name,
30219            capabilities,
30220            initializer: Some(Box::new({
30221                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30222                move |fake_server| {
30223                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
30224                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
30225                        move |_params, _| {
30226                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
30227                            async move {
30228                                Ok(Some(vec![lsp::InlayHint {
30229                                    position: lsp::Position::new(0, 0),
30230                                    label: lsp::InlayHintLabel::String("hint".to_string()),
30231                                    kind: None,
30232                                    text_edits: None,
30233                                    tooltip: None,
30234                                    padding_left: None,
30235                                    padding_right: None,
30236                                    data: None,
30237                                }]))
30238                            }
30239                        },
30240                    );
30241                }
30242            })),
30243            ..FakeLspAdapter::default()
30244        },
30245    );
30246
30247    cx.run_until_parked();
30248
30249    let worktree_id = project.read_with(cx, |project, cx| {
30250        project
30251            .worktrees(cx)
30252            .next()
30253            .map(|wt| wt.read(cx).id())
30254            .expect("should have a worktree")
30255    });
30256    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
30257
30258    let trusted_worktrees =
30259        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
30260
30261    let can_trust = trusted_worktrees.update(cx, |store, cx| {
30262        store.can_trust(&worktree_store, worktree_id, cx)
30263    });
30264    assert!(!can_trust, "worktree should be restricted initially");
30265
30266    let buffer_before_approval = project
30267        .update(cx, |project, cx| {
30268            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
30269        })
30270        .await
30271        .unwrap();
30272
30273    let (editor, cx) = cx.add_window_view(|window, cx| {
30274        Editor::new(
30275            EditorMode::full(),
30276            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
30277            Some(project.clone()),
30278            window,
30279            cx,
30280        )
30281    });
30282    cx.run_until_parked();
30283    let fake_language_server = fake_language_servers.next();
30284
30285    cx.read(|cx| {
30286        let file = buffer_before_approval.read(cx).file();
30287        assert_eq!(
30288            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30289                .language_servers,
30290            ["...".to_string()],
30291            "local .zed/settings.json must not apply before trust approval"
30292        )
30293    });
30294
30295    editor.update_in(cx, |editor, window, cx| {
30296        editor.handle_input("1", window, cx);
30297    });
30298    cx.run_until_parked();
30299    cx.executor()
30300        .advance_clock(std::time::Duration::from_secs(1));
30301    assert_eq!(
30302        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
30303        0,
30304        "inlay hints must not be queried before trust approval"
30305    );
30306
30307    trusted_worktrees.update(cx, |store, cx| {
30308        store.trust(
30309            &worktree_store,
30310            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
30311            cx,
30312        );
30313    });
30314    cx.run_until_parked();
30315
30316    cx.read(|cx| {
30317        let file = buffer_before_approval.read(cx).file();
30318        assert_eq!(
30319            language::language_settings::language_settings(Some("Rust".into()), file, cx)
30320                .language_servers,
30321            ["override-rust-analyzer".to_string()],
30322            "local .zed/settings.json should apply after trust approval"
30323        )
30324    });
30325    let _fake_language_server = fake_language_server.await.unwrap();
30326    editor.update_in(cx, |editor, window, cx| {
30327        editor.handle_input("1", window, cx);
30328    });
30329    cx.run_until_parked();
30330    cx.executor()
30331        .advance_clock(std::time::Duration::from_secs(1));
30332    assert!(
30333        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
30334        "inlay hints should be queried after trust approval"
30335    );
30336
30337    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
30338        store.can_trust(&worktree_store, worktree_id, cx)
30339    });
30340    assert!(can_trust_after, "worktree should be trusted after trust()");
30341}
30342
30343#[gpui::test]
30344fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
30345    // This test reproduces a bug where drawing an editor at a position above the viewport
30346    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
30347    // causes an infinite loop in blocks_in_range.
30348    //
30349    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
30350    // the content mask intersection produces visible_bounds with origin at the viewport top.
30351    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
30352    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
30353    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
30354    init_test(cx, |_| {});
30355
30356    let window = cx.add_window(|_, _| gpui::Empty);
30357    let mut cx = VisualTestContext::from_window(*window, cx);
30358
30359    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
30360    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
30361
30362    // Simulate a small viewport (500x500 pixels at origin 0,0)
30363    cx.simulate_resize(gpui::size(px(500.), px(500.)));
30364
30365    // Draw the editor at a very negative Y position, simulating an editor that's been
30366    // scrolled way above the visible viewport (like in a List that has scrolled past it).
30367    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
30368    // This should NOT hang - it should just render nothing.
30369    cx.draw(
30370        gpui::point(px(0.), px(-10000.)),
30371        gpui::size(px(500.), px(3000.)),
30372        |_, _| editor.clone(),
30373    );
30374
30375    // If we get here without hanging, the test passes
30376}
30377
30378#[gpui::test]
30379async fn test_diff_review_indicator_created_on_gutter_hover(cx: &mut TestAppContext) {
30380    init_test(cx, |_| {});
30381
30382    let fs = FakeFs::new(cx.executor());
30383    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30384        .await;
30385
30386    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30387    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30388    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30389
30390    let editor = workspace
30391        .update(cx, |workspace, window, cx| {
30392            workspace.open_abs_path(
30393                PathBuf::from(path!("/root/file.txt")),
30394                OpenOptions::default(),
30395                window,
30396                cx,
30397            )
30398        })
30399        .unwrap()
30400        .await
30401        .unwrap()
30402        .downcast::<Editor>()
30403        .unwrap();
30404
30405    // Enable diff review button mode
30406    editor.update(cx, |editor, cx| {
30407        editor.set_show_diff_review_button(true, cx);
30408    });
30409
30410    // Initially, no indicator should be present
30411    editor.update(cx, |editor, _cx| {
30412        assert!(
30413            editor.gutter_diff_review_indicator.0.is_none(),
30414            "Indicator should be None initially"
30415        );
30416    });
30417}
30418
30419#[gpui::test]
30420async fn test_diff_review_button_hidden_when_ai_disabled(cx: &mut TestAppContext) {
30421    init_test(cx, |_| {});
30422
30423    // Register DisableAiSettings and set disable_ai to true
30424    cx.update(|cx| {
30425        project::DisableAiSettings::register(cx);
30426        project::DisableAiSettings::override_global(
30427            project::DisableAiSettings { disable_ai: true },
30428            cx,
30429        );
30430    });
30431
30432    let fs = FakeFs::new(cx.executor());
30433    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30434        .await;
30435
30436    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30437    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30438    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30439
30440    let editor = workspace
30441        .update(cx, |workspace, window, cx| {
30442            workspace.open_abs_path(
30443                PathBuf::from(path!("/root/file.txt")),
30444                OpenOptions::default(),
30445                window,
30446                cx,
30447            )
30448        })
30449        .unwrap()
30450        .await
30451        .unwrap()
30452        .downcast::<Editor>()
30453        .unwrap();
30454
30455    // Enable diff review button mode
30456    editor.update(cx, |editor, cx| {
30457        editor.set_show_diff_review_button(true, cx);
30458    });
30459
30460    // Verify AI is disabled
30461    cx.read(|cx| {
30462        assert!(
30463            project::DisableAiSettings::get_global(cx).disable_ai,
30464            "AI should be disabled"
30465        );
30466    });
30467
30468    // The indicator should not be created when AI is disabled
30469    // (The mouse_moved handler checks DisableAiSettings before creating the indicator)
30470    editor.update(cx, |editor, _cx| {
30471        assert!(
30472            editor.gutter_diff_review_indicator.0.is_none(),
30473            "Indicator should be None when AI is disabled"
30474        );
30475    });
30476}
30477
30478#[gpui::test]
30479async fn test_diff_review_button_shown_when_ai_enabled(cx: &mut TestAppContext) {
30480    init_test(cx, |_| {});
30481
30482    // Register DisableAiSettings and set disable_ai to false
30483    cx.update(|cx| {
30484        project::DisableAiSettings::register(cx);
30485        project::DisableAiSettings::override_global(
30486            project::DisableAiSettings { disable_ai: false },
30487            cx,
30488        );
30489    });
30490
30491    let fs = FakeFs::new(cx.executor());
30492    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
30493        .await;
30494
30495    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
30496    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
30497    let cx = &mut VisualTestContext::from_window(*workspace, cx);
30498
30499    let editor = workspace
30500        .update(cx, |workspace, window, cx| {
30501            workspace.open_abs_path(
30502                PathBuf::from(path!("/root/file.txt")),
30503                OpenOptions::default(),
30504                window,
30505                cx,
30506            )
30507        })
30508        .unwrap()
30509        .await
30510        .unwrap()
30511        .downcast::<Editor>()
30512        .unwrap();
30513
30514    // Enable diff review button mode
30515    editor.update(cx, |editor, cx| {
30516        editor.set_show_diff_review_button(true, cx);
30517    });
30518
30519    // Verify AI is enabled
30520    cx.read(|cx| {
30521        assert!(
30522            !project::DisableAiSettings::get_global(cx).disable_ai,
30523            "AI should be enabled"
30524        );
30525    });
30526
30527    // The show_diff_review_button flag should be true
30528    editor.update(cx, |editor, _cx| {
30529        assert!(
30530            editor.show_diff_review_button(),
30531            "show_diff_review_button should be true"
30532        );
30533    });
30534}
30535
30536/// Helper function to create a DiffHunkKey for testing.
30537/// Uses Anchor::min() as a placeholder anchor since these tests don't need
30538/// real buffer positioning.
30539fn test_hunk_key(file_path: &str) -> DiffHunkKey {
30540    DiffHunkKey {
30541        file_path: if file_path.is_empty() {
30542            Arc::from(util::rel_path::RelPath::empty())
30543        } else {
30544            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
30545        },
30546        hunk_start_anchor: Anchor::min(),
30547    }
30548}
30549
30550/// Helper function to create a DiffHunkKey with a specific anchor for testing.
30551fn test_hunk_key_with_anchor(file_path: &str, anchor: Anchor) -> DiffHunkKey {
30552    DiffHunkKey {
30553        file_path: if file_path.is_empty() {
30554            Arc::from(util::rel_path::RelPath::empty())
30555        } else {
30556            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
30557        },
30558        hunk_start_anchor: anchor,
30559    }
30560}
30561
30562/// Helper function to add a review comment with default anchors for testing.
30563fn add_test_comment(
30564    editor: &mut Editor,
30565    key: DiffHunkKey,
30566    comment: &str,
30567    cx: &mut Context<Editor>,
30568) -> usize {
30569    editor.add_review_comment(key, comment.to_string(), Anchor::min()..Anchor::max(), cx)
30570}
30571
30572#[gpui::test]
30573fn test_review_comment_add_to_hunk(cx: &mut TestAppContext) {
30574    init_test(cx, |_| {});
30575
30576    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30577
30578    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30579        let key = test_hunk_key("");
30580
30581        let id = add_test_comment(editor, key.clone(), "Test comment", cx);
30582
30583        let snapshot = editor.buffer().read(cx).snapshot(cx);
30584        assert_eq!(editor.total_review_comment_count(), 1);
30585        assert_eq!(editor.hunk_comment_count(&key, &snapshot), 1);
30586
30587        let comments = editor.comments_for_hunk(&key, &snapshot);
30588        assert_eq!(comments.len(), 1);
30589        assert_eq!(comments[0].comment, "Test comment");
30590        assert_eq!(comments[0].id, id);
30591    });
30592}
30593
30594#[gpui::test]
30595fn test_review_comments_are_per_hunk(cx: &mut TestAppContext) {
30596    init_test(cx, |_| {});
30597
30598    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30599
30600    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30601        let snapshot = editor.buffer().read(cx).snapshot(cx);
30602        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
30603        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
30604        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
30605        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
30606
30607        add_test_comment(editor, key1.clone(), "Comment for file1", cx);
30608        add_test_comment(editor, key2.clone(), "Comment for file2", cx);
30609
30610        let snapshot = editor.buffer().read(cx).snapshot(cx);
30611        assert_eq!(editor.total_review_comment_count(), 2);
30612        assert_eq!(editor.hunk_comment_count(&key1, &snapshot), 1);
30613        assert_eq!(editor.hunk_comment_count(&key2, &snapshot), 1);
30614
30615        assert_eq!(
30616            editor.comments_for_hunk(&key1, &snapshot)[0].comment,
30617            "Comment for file1"
30618        );
30619        assert_eq!(
30620            editor.comments_for_hunk(&key2, &snapshot)[0].comment,
30621            "Comment for file2"
30622        );
30623    });
30624}
30625
30626#[gpui::test]
30627fn test_review_comment_remove(cx: &mut TestAppContext) {
30628    init_test(cx, |_| {});
30629
30630    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30631
30632    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30633        let key = test_hunk_key("");
30634
30635        let id = add_test_comment(editor, key, "To be removed", cx);
30636
30637        assert_eq!(editor.total_review_comment_count(), 1);
30638
30639        let removed = editor.remove_review_comment(id, cx);
30640        assert!(removed);
30641        assert_eq!(editor.total_review_comment_count(), 0);
30642
30643        // Try to remove again
30644        let removed_again = editor.remove_review_comment(id, cx);
30645        assert!(!removed_again);
30646    });
30647}
30648
30649#[gpui::test]
30650fn test_review_comment_update(cx: &mut TestAppContext) {
30651    init_test(cx, |_| {});
30652
30653    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30654
30655    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30656        let key = test_hunk_key("");
30657
30658        let id = add_test_comment(editor, key.clone(), "Original text", cx);
30659
30660        let updated = editor.update_review_comment(id, "Updated text".to_string(), cx);
30661        assert!(updated);
30662
30663        let snapshot = editor.buffer().read(cx).snapshot(cx);
30664        let comments = editor.comments_for_hunk(&key, &snapshot);
30665        assert_eq!(comments[0].comment, "Updated text");
30666        assert!(!comments[0].is_editing); // Should clear editing flag
30667    });
30668}
30669
30670#[gpui::test]
30671fn test_review_comment_take_all(cx: &mut TestAppContext) {
30672    init_test(cx, |_| {});
30673
30674    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30675
30676    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
30677        let snapshot = editor.buffer().read(cx).snapshot(cx);
30678        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
30679        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
30680        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
30681        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
30682
30683        let id1 = add_test_comment(editor, key1.clone(), "Comment 1", cx);
30684        let id2 = add_test_comment(editor, key1.clone(), "Comment 2", cx);
30685        let id3 = add_test_comment(editor, key2.clone(), "Comment 3", cx);
30686
30687        // IDs should be sequential starting from 0
30688        assert_eq!(id1, 0);
30689        assert_eq!(id2, 1);
30690        assert_eq!(id3, 2);
30691
30692        assert_eq!(editor.total_review_comment_count(), 3);
30693
30694        let taken = editor.take_all_review_comments(cx);
30695
30696        // Should have 2 entries (one per hunk)
30697        assert_eq!(taken.len(), 2);
30698
30699        // Total comments should be 3
30700        let total: usize = taken
30701            .iter()
30702            .map(|(_, comments): &(DiffHunkKey, Vec<StoredReviewComment>)| comments.len())
30703            .sum();
30704        assert_eq!(total, 3);
30705
30706        // Storage should be empty
30707        assert_eq!(editor.total_review_comment_count(), 0);
30708
30709        // After taking all comments, ID counter should reset
30710        // New comments should get IDs starting from 0 again
30711        let new_id1 = add_test_comment(editor, key1, "New Comment 1", cx);
30712        let new_id2 = add_test_comment(editor, key2, "New Comment 2", cx);
30713
30714        assert_eq!(new_id1, 0, "ID counter should reset after take_all");
30715        assert_eq!(new_id2, 1, "IDs should be sequential after reset");
30716    });
30717}
30718
30719#[gpui::test]
30720fn test_diff_review_overlay_show_and_dismiss(cx: &mut TestAppContext) {
30721    init_test(cx, |_| {});
30722
30723    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30724
30725    // Show overlay
30726    editor
30727        .update(cx, |editor, window, cx| {
30728            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
30729        })
30730        .unwrap();
30731
30732    // Verify overlay is shown
30733    editor
30734        .update(cx, |editor, _window, cx| {
30735            assert!(!editor.diff_review_overlays.is_empty());
30736            assert_eq!(editor.diff_review_line_range(cx), Some((0, 0)));
30737            assert!(editor.diff_review_prompt_editor().is_some());
30738        })
30739        .unwrap();
30740
30741    // Dismiss overlay
30742    editor
30743        .update(cx, |editor, _window, cx| {
30744            editor.dismiss_all_diff_review_overlays(cx);
30745        })
30746        .unwrap();
30747
30748    // Verify overlay is dismissed
30749    editor
30750        .update(cx, |editor, _window, cx| {
30751            assert!(editor.diff_review_overlays.is_empty());
30752            assert_eq!(editor.diff_review_line_range(cx), None);
30753            assert!(editor.diff_review_prompt_editor().is_none());
30754        })
30755        .unwrap();
30756}
30757
30758#[gpui::test]
30759fn test_diff_review_overlay_dismiss_via_cancel(cx: &mut TestAppContext) {
30760    init_test(cx, |_| {});
30761
30762    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30763
30764    // Show overlay
30765    editor
30766        .update(cx, |editor, window, cx| {
30767            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
30768        })
30769        .unwrap();
30770
30771    // Verify overlay is shown
30772    editor
30773        .update(cx, |editor, _window, _cx| {
30774            assert!(!editor.diff_review_overlays.is_empty());
30775        })
30776        .unwrap();
30777
30778    // Dismiss via dismiss_menus_and_popups (which is called by cancel action)
30779    editor
30780        .update(cx, |editor, window, cx| {
30781            editor.dismiss_menus_and_popups(true, window, cx);
30782        })
30783        .unwrap();
30784
30785    // Verify overlay is dismissed
30786    editor
30787        .update(cx, |editor, _window, _cx| {
30788            assert!(editor.diff_review_overlays.is_empty());
30789        })
30790        .unwrap();
30791}
30792
30793#[gpui::test]
30794fn test_diff_review_empty_comment_not_submitted(cx: &mut TestAppContext) {
30795    init_test(cx, |_| {});
30796
30797    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30798
30799    // Show overlay
30800    editor
30801        .update(cx, |editor, window, cx| {
30802            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
30803        })
30804        .unwrap();
30805
30806    // Try to submit without typing anything (empty comment)
30807    editor
30808        .update(cx, |editor, window, cx| {
30809            editor.submit_diff_review_comment(window, cx);
30810        })
30811        .unwrap();
30812
30813    // Verify no comment was added
30814    editor
30815        .update(cx, |editor, _window, _cx| {
30816            assert_eq!(editor.total_review_comment_count(), 0);
30817        })
30818        .unwrap();
30819
30820    // Try to submit with whitespace-only comment
30821    editor
30822        .update(cx, |editor, window, cx| {
30823            if let Some(prompt_editor) = editor.diff_review_prompt_editor().cloned() {
30824                prompt_editor.update(cx, |pe, cx| {
30825                    pe.insert("   \n\t  ", window, cx);
30826                });
30827            }
30828            editor.submit_diff_review_comment(window, cx);
30829        })
30830        .unwrap();
30831
30832    // Verify still no comment was added
30833    editor
30834        .update(cx, |editor, _window, _cx| {
30835            assert_eq!(editor.total_review_comment_count(), 0);
30836        })
30837        .unwrap();
30838}
30839
30840#[gpui::test]
30841fn test_diff_review_inline_edit_flow(cx: &mut TestAppContext) {
30842    init_test(cx, |_| {});
30843
30844    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30845
30846    // Add a comment directly
30847    let comment_id = editor
30848        .update(cx, |editor, _window, cx| {
30849            let key = test_hunk_key("");
30850            add_test_comment(editor, key, "Original comment", cx)
30851        })
30852        .unwrap();
30853
30854    // Set comment to editing mode
30855    editor
30856        .update(cx, |editor, _window, cx| {
30857            editor.set_comment_editing(comment_id, true, cx);
30858        })
30859        .unwrap();
30860
30861    // Verify editing flag is set
30862    editor
30863        .update(cx, |editor, _window, cx| {
30864            let key = test_hunk_key("");
30865            let snapshot = editor.buffer().read(cx).snapshot(cx);
30866            let comments = editor.comments_for_hunk(&key, &snapshot);
30867            assert_eq!(comments.len(), 1);
30868            assert!(comments[0].is_editing);
30869        })
30870        .unwrap();
30871
30872    // Update the comment
30873    editor
30874        .update(cx, |editor, _window, cx| {
30875            let updated =
30876                editor.update_review_comment(comment_id, "Updated comment".to_string(), cx);
30877            assert!(updated);
30878        })
30879        .unwrap();
30880
30881    // Verify comment was updated and editing flag is cleared
30882    editor
30883        .update(cx, |editor, _window, cx| {
30884            let key = test_hunk_key("");
30885            let snapshot = editor.buffer().read(cx).snapshot(cx);
30886            let comments = editor.comments_for_hunk(&key, &snapshot);
30887            assert_eq!(comments[0].comment, "Updated comment");
30888            assert!(!comments[0].is_editing);
30889        })
30890        .unwrap();
30891}
30892
30893#[gpui::test]
30894fn test_orphaned_comments_are_cleaned_up(cx: &mut TestAppContext) {
30895    init_test(cx, |_| {});
30896
30897    // Create an editor with some text
30898    let editor = cx.add_window(|window, cx| {
30899        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
30900        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
30901        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
30902    });
30903
30904    // Add a comment with an anchor on line 2
30905    editor
30906        .update(cx, |editor, _window, cx| {
30907            let snapshot = editor.buffer().read(cx).snapshot(cx);
30908            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
30909            let key = DiffHunkKey {
30910                file_path: Arc::from(util::rel_path::RelPath::empty()),
30911                hunk_start_anchor: anchor,
30912            };
30913            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
30914            assert_eq!(editor.total_review_comment_count(), 1);
30915        })
30916        .unwrap();
30917
30918    // Delete all content (this should orphan the comment's anchor)
30919    editor
30920        .update(cx, |editor, window, cx| {
30921            editor.select_all(&SelectAll, window, cx);
30922            editor.insert("completely new content", window, cx);
30923        })
30924        .unwrap();
30925
30926    // Trigger cleanup
30927    editor
30928        .update(cx, |editor, _window, cx| {
30929            editor.cleanup_orphaned_review_comments(cx);
30930            // Comment should be removed because its anchor is invalid
30931            assert_eq!(editor.total_review_comment_count(), 0);
30932        })
30933        .unwrap();
30934}
30935
30936#[gpui::test]
30937fn test_orphaned_comments_cleanup_called_on_buffer_edit(cx: &mut TestAppContext) {
30938    init_test(cx, |_| {});
30939
30940    // Create an editor with some text
30941    let editor = cx.add_window(|window, cx| {
30942        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
30943        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
30944        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
30945    });
30946
30947    // Add a comment with an anchor on line 2
30948    editor
30949        .update(cx, |editor, _window, cx| {
30950            let snapshot = editor.buffer().read(cx).snapshot(cx);
30951            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
30952            let key = DiffHunkKey {
30953                file_path: Arc::from(util::rel_path::RelPath::empty()),
30954                hunk_start_anchor: anchor,
30955            };
30956            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
30957            assert_eq!(editor.total_review_comment_count(), 1);
30958        })
30959        .unwrap();
30960
30961    // Edit the buffer - this should trigger cleanup via on_buffer_event
30962    // Delete all content which orphans the anchor
30963    editor
30964        .update(cx, |editor, window, cx| {
30965            editor.select_all(&SelectAll, window, cx);
30966            editor.insert("completely new content", window, cx);
30967            // The cleanup is called automatically in on_buffer_event when Edited fires
30968        })
30969        .unwrap();
30970
30971    // Verify cleanup happened automatically (not manually triggered)
30972    editor
30973        .update(cx, |editor, _window, _cx| {
30974            // Comment should be removed because its anchor became invalid
30975            // and cleanup was called automatically on buffer edit
30976            assert_eq!(editor.total_review_comment_count(), 0);
30977        })
30978        .unwrap();
30979}
30980
30981#[gpui::test]
30982fn test_comments_stored_for_multiple_hunks(cx: &mut TestAppContext) {
30983    init_test(cx, |_| {});
30984
30985    // This test verifies that comments can be stored for multiple different hunks
30986    // and that hunk_comment_count correctly identifies comments per hunk.
30987    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
30988
30989    _ = editor.update(cx, |editor, _window, cx| {
30990        let snapshot = editor.buffer().read(cx).snapshot(cx);
30991
30992        // Create two different hunk keys (simulating two different files)
30993        let anchor = snapshot.anchor_before(Point::new(0, 0));
30994        let key1 = DiffHunkKey {
30995            file_path: Arc::from(util::rel_path::RelPath::unix("file1.rs").unwrap()),
30996            hunk_start_anchor: anchor,
30997        };
30998        let key2 = DiffHunkKey {
30999            file_path: Arc::from(util::rel_path::RelPath::unix("file2.rs").unwrap()),
31000            hunk_start_anchor: anchor,
31001        };
31002
31003        // Add comments to first hunk
31004        editor.add_review_comment(
31005            key1.clone(),
31006            "Comment 1 for file1".to_string(),
31007            anchor..anchor,
31008            cx,
31009        );
31010        editor.add_review_comment(
31011            key1.clone(),
31012            "Comment 2 for file1".to_string(),
31013            anchor..anchor,
31014            cx,
31015        );
31016
31017        // Add comment to second hunk
31018        editor.add_review_comment(
31019            key2.clone(),
31020            "Comment for file2".to_string(),
31021            anchor..anchor,
31022            cx,
31023        );
31024
31025        // Verify total count
31026        assert_eq!(editor.total_review_comment_count(), 3);
31027
31028        // Verify per-hunk counts
31029        let snapshot = editor.buffer().read(cx).snapshot(cx);
31030        assert_eq!(
31031            editor.hunk_comment_count(&key1, &snapshot),
31032            2,
31033            "file1 should have 2 comments"
31034        );
31035        assert_eq!(
31036            editor.hunk_comment_count(&key2, &snapshot),
31037            1,
31038            "file2 should have 1 comment"
31039        );
31040
31041        // Verify comments_for_hunk returns correct comments
31042        let file1_comments = editor.comments_for_hunk(&key1, &snapshot);
31043        assert_eq!(file1_comments.len(), 2);
31044        assert_eq!(file1_comments[0].comment, "Comment 1 for file1");
31045        assert_eq!(file1_comments[1].comment, "Comment 2 for file1");
31046
31047        let file2_comments = editor.comments_for_hunk(&key2, &snapshot);
31048        assert_eq!(file2_comments.len(), 1);
31049        assert_eq!(file2_comments[0].comment, "Comment for file2");
31050    });
31051}
31052
31053#[gpui::test]
31054fn test_same_hunk_detected_by_matching_keys(cx: &mut TestAppContext) {
31055    init_test(cx, |_| {});
31056
31057    // This test verifies that hunk_keys_match correctly identifies when two
31058    // DiffHunkKeys refer to the same hunk (same file path and anchor point).
31059    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31060
31061    _ = editor.update(cx, |editor, _window, cx| {
31062        let snapshot = editor.buffer().read(cx).snapshot(cx);
31063        let anchor = snapshot.anchor_before(Point::new(0, 0));
31064
31065        // Create two keys with the same file path and anchor
31066        let key1 = DiffHunkKey {
31067            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
31068            hunk_start_anchor: anchor,
31069        };
31070        let key2 = DiffHunkKey {
31071            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
31072            hunk_start_anchor: anchor,
31073        };
31074
31075        // Add comment to first key
31076        editor.add_review_comment(key1, "Test comment".to_string(), anchor..anchor, cx);
31077
31078        // Verify second key (same hunk) finds the comment
31079        let snapshot = editor.buffer().read(cx).snapshot(cx);
31080        assert_eq!(
31081            editor.hunk_comment_count(&key2, &snapshot),
31082            1,
31083            "Same hunk should find the comment"
31084        );
31085
31086        // Create a key with different file path
31087        let different_file_key = DiffHunkKey {
31088            file_path: Arc::from(util::rel_path::RelPath::unix("other.rs").unwrap()),
31089            hunk_start_anchor: anchor,
31090        };
31091
31092        // Different file should not find the comment
31093        assert_eq!(
31094            editor.hunk_comment_count(&different_file_key, &snapshot),
31095            0,
31096            "Different file should not find the comment"
31097        );
31098    });
31099}
31100
31101#[gpui::test]
31102fn test_overlay_comments_expanded_state(cx: &mut TestAppContext) {
31103    init_test(cx, |_| {});
31104
31105    // This test verifies that set_diff_review_comments_expanded correctly
31106    // updates the expanded state of overlays.
31107    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31108
31109    // Show overlay
31110    editor
31111        .update(cx, |editor, window, cx| {
31112            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
31113        })
31114        .unwrap();
31115
31116    // Verify initially expanded (default)
31117    editor
31118        .update(cx, |editor, _window, _cx| {
31119            assert!(
31120                editor.diff_review_overlays[0].comments_expanded,
31121                "Should be expanded by default"
31122            );
31123        })
31124        .unwrap();
31125
31126    // Set to collapsed using the public method
31127    editor
31128        .update(cx, |editor, _window, cx| {
31129            editor.set_diff_review_comments_expanded(false, cx);
31130        })
31131        .unwrap();
31132
31133    // Verify collapsed
31134    editor
31135        .update(cx, |editor, _window, _cx| {
31136            assert!(
31137                !editor.diff_review_overlays[0].comments_expanded,
31138                "Should be collapsed after setting to false"
31139            );
31140        })
31141        .unwrap();
31142
31143    // Set back to expanded
31144    editor
31145        .update(cx, |editor, _window, cx| {
31146            editor.set_diff_review_comments_expanded(true, cx);
31147        })
31148        .unwrap();
31149
31150    // Verify expanded again
31151    editor
31152        .update(cx, |editor, _window, _cx| {
31153            assert!(
31154                editor.diff_review_overlays[0].comments_expanded,
31155                "Should be expanded after setting to true"
31156            );
31157        })
31158        .unwrap();
31159}
31160
31161#[gpui::test]
31162fn test_diff_review_multiline_selection(cx: &mut TestAppContext) {
31163    init_test(cx, |_| {});
31164
31165    // Create an editor with multiple lines of text
31166    let editor = cx.add_window(|window, cx| {
31167        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\nline 4\nline 5\n", cx));
31168        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31169        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31170    });
31171
31172    // Test showing overlay with a multi-line selection (lines 1-3, which are rows 0-2)
31173    editor
31174        .update(cx, |editor, window, cx| {
31175            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(2), window, cx);
31176        })
31177        .unwrap();
31178
31179    // Verify line range
31180    editor
31181        .update(cx, |editor, _window, cx| {
31182            assert!(!editor.diff_review_overlays.is_empty());
31183            assert_eq!(editor.diff_review_line_range(cx), Some((0, 2)));
31184        })
31185        .unwrap();
31186
31187    // Dismiss and test with reversed range (end < start)
31188    editor
31189        .update(cx, |editor, _window, cx| {
31190            editor.dismiss_all_diff_review_overlays(cx);
31191        })
31192        .unwrap();
31193
31194    // Show overlay with reversed range - should normalize it
31195    editor
31196        .update(cx, |editor, window, cx| {
31197            editor.show_diff_review_overlay(DisplayRow(3)..DisplayRow(1), window, cx);
31198        })
31199        .unwrap();
31200
31201    // Verify range is normalized (start <= end)
31202    editor
31203        .update(cx, |editor, _window, cx| {
31204            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
31205        })
31206        .unwrap();
31207}
31208
31209#[gpui::test]
31210fn test_diff_review_drag_state(cx: &mut TestAppContext) {
31211    init_test(cx, |_| {});
31212
31213    let editor = cx.add_window(|window, cx| {
31214        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
31215        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31216        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31217    });
31218
31219    // Initially no drag state
31220    editor
31221        .update(cx, |editor, _window, _cx| {
31222            assert!(editor.diff_review_drag_state.is_none());
31223        })
31224        .unwrap();
31225
31226    // Start drag at row 1
31227    editor
31228        .update(cx, |editor, window, cx| {
31229            editor.start_diff_review_drag(DisplayRow(1), window, cx);
31230        })
31231        .unwrap();
31232
31233    // Verify drag state is set
31234    editor
31235        .update(cx, |editor, window, cx| {
31236            assert!(editor.diff_review_drag_state.is_some());
31237            let snapshot = editor.snapshot(window, cx);
31238            let range = editor
31239                .diff_review_drag_state
31240                .as_ref()
31241                .unwrap()
31242                .row_range(&snapshot.display_snapshot);
31243            assert_eq!(*range.start(), DisplayRow(1));
31244            assert_eq!(*range.end(), DisplayRow(1));
31245        })
31246        .unwrap();
31247
31248    // Update drag to row 3
31249    editor
31250        .update(cx, |editor, window, cx| {
31251            editor.update_diff_review_drag(DisplayRow(3), window, cx);
31252        })
31253        .unwrap();
31254
31255    // Verify drag state is updated
31256    editor
31257        .update(cx, |editor, window, cx| {
31258            assert!(editor.diff_review_drag_state.is_some());
31259            let snapshot = editor.snapshot(window, cx);
31260            let range = editor
31261                .diff_review_drag_state
31262                .as_ref()
31263                .unwrap()
31264                .row_range(&snapshot.display_snapshot);
31265            assert_eq!(*range.start(), DisplayRow(1));
31266            assert_eq!(*range.end(), DisplayRow(3));
31267        })
31268        .unwrap();
31269
31270    // End drag - should show overlay
31271    editor
31272        .update(cx, |editor, window, cx| {
31273            editor.end_diff_review_drag(window, cx);
31274        })
31275        .unwrap();
31276
31277    // Verify drag state is cleared and overlay is shown
31278    editor
31279        .update(cx, |editor, _window, cx| {
31280            assert!(editor.diff_review_drag_state.is_none());
31281            assert!(!editor.diff_review_overlays.is_empty());
31282            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
31283        })
31284        .unwrap();
31285}
31286
31287#[gpui::test]
31288fn test_diff_review_drag_cancel(cx: &mut TestAppContext) {
31289    init_test(cx, |_| {});
31290
31291    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31292
31293    // Start drag
31294    editor
31295        .update(cx, |editor, window, cx| {
31296            editor.start_diff_review_drag(DisplayRow(0), window, cx);
31297        })
31298        .unwrap();
31299
31300    // Verify drag state is set
31301    editor
31302        .update(cx, |editor, _window, _cx| {
31303            assert!(editor.diff_review_drag_state.is_some());
31304        })
31305        .unwrap();
31306
31307    // Cancel drag
31308    editor
31309        .update(cx, |editor, _window, cx| {
31310            editor.cancel_diff_review_drag(cx);
31311        })
31312        .unwrap();
31313
31314    // Verify drag state is cleared and no overlay was created
31315    editor
31316        .update(cx, |editor, _window, _cx| {
31317            assert!(editor.diff_review_drag_state.is_none());
31318            assert!(editor.diff_review_overlays.is_empty());
31319        })
31320        .unwrap();
31321}
31322
31323#[gpui::test]
31324fn test_calculate_overlay_height(cx: &mut TestAppContext) {
31325    init_test(cx, |_| {});
31326
31327    // This test verifies that calculate_overlay_height returns correct heights
31328    // based on comment count and expanded state.
31329    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31330
31331    _ = editor.update(cx, |editor, _window, cx| {
31332        let snapshot = editor.buffer().read(cx).snapshot(cx);
31333        let anchor = snapshot.anchor_before(Point::new(0, 0));
31334        let key = DiffHunkKey {
31335            file_path: Arc::from(util::rel_path::RelPath::empty()),
31336            hunk_start_anchor: anchor,
31337        };
31338
31339        // No comments: base height of 2
31340        let height_no_comments = editor.calculate_overlay_height(&key, true, &snapshot);
31341        assert_eq!(
31342            height_no_comments, 2,
31343            "Base height should be 2 with no comments"
31344        );
31345
31346        // Add one comment
31347        editor.add_review_comment(key.clone(), "Comment 1".to_string(), anchor..anchor, cx);
31348
31349        let snapshot = editor.buffer().read(cx).snapshot(cx);
31350
31351        // With comments expanded: base (2) + header (1) + 2 per comment
31352        let height_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
31353        assert_eq!(
31354            height_expanded,
31355            2 + 1 + 2, // base + header + 1 comment * 2
31356            "Height with 1 comment expanded"
31357        );
31358
31359        // With comments collapsed: base (2) + header (1)
31360        let height_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
31361        assert_eq!(
31362            height_collapsed,
31363            2 + 1, // base + header only
31364            "Height with comments collapsed"
31365        );
31366
31367        // Add more comments
31368        editor.add_review_comment(key.clone(), "Comment 2".to_string(), anchor..anchor, cx);
31369        editor.add_review_comment(key.clone(), "Comment 3".to_string(), anchor..anchor, cx);
31370
31371        let snapshot = editor.buffer().read(cx).snapshot(cx);
31372
31373        // With 3 comments expanded
31374        let height_3_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
31375        assert_eq!(
31376            height_3_expanded,
31377            2 + 1 + (3 * 2), // base + header + 3 comments * 2
31378            "Height with 3 comments expanded"
31379        );
31380
31381        // Collapsed height stays the same regardless of comment count
31382        let height_3_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
31383        assert_eq!(
31384            height_3_collapsed,
31385            2 + 1, // base + header only
31386            "Height with 3 comments collapsed should be same as 1 comment collapsed"
31387        );
31388    });
31389}
31390
31391#[gpui::test]
31392async fn test_move_to_start_end_of_larger_syntax_node_single_cursor(cx: &mut TestAppContext) {
31393    init_test(cx, |_| {});
31394
31395    let language = Arc::new(Language::new(
31396        LanguageConfig::default(),
31397        Some(tree_sitter_rust::LANGUAGE.into()),
31398    ));
31399
31400    let text = r#"
31401        fn main() {
31402            let x = foo(1, 2);
31403        }
31404    "#
31405    .unindent();
31406
31407    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31408    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31409    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31410
31411    editor
31412        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31413        .await;
31414
31415    // Test case 1: Move to end of syntax nodes
31416    editor.update_in(cx, |editor, window, cx| {
31417        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31418            s.select_display_ranges([
31419                DisplayPoint::new(DisplayRow(1), 16)..DisplayPoint::new(DisplayRow(1), 16)
31420            ]);
31421        });
31422    });
31423    editor.update(cx, |editor, cx| {
31424        assert_text_with_selections(
31425            editor,
31426            indoc! {r#"
31427                fn main() {
31428                    let x = foo(ˇ1, 2);
31429                }
31430            "#},
31431            cx,
31432        );
31433    });
31434    editor.update_in(cx, |editor, window, cx| {
31435        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31436    });
31437    editor.update(cx, |editor, cx| {
31438        assert_text_with_selections(
31439            editor,
31440            indoc! {r#"
31441                fn main() {
31442                    let x = foo(1ˇ, 2);
31443                }
31444            "#},
31445            cx,
31446        );
31447    });
31448    editor.update_in(cx, |editor, window, cx| {
31449        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31450    });
31451    editor.update(cx, |editor, cx| {
31452        assert_text_with_selections(
31453            editor,
31454            indoc! {r#"
31455                fn main() {
31456                    let x = foo(1, 2)ˇ;
31457                }
31458            "#},
31459            cx,
31460        );
31461    });
31462    editor.update_in(cx, |editor, window, cx| {
31463        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31464    });
31465    editor.update(cx, |editor, cx| {
31466        assert_text_with_selections(
31467            editor,
31468            indoc! {r#"
31469                fn main() {
31470                    let x = foo(1, 2);ˇ
31471                }
31472            "#},
31473            cx,
31474        );
31475    });
31476    editor.update_in(cx, |editor, window, cx| {
31477        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31478    });
31479    editor.update(cx, |editor, cx| {
31480        assert_text_with_selections(
31481            editor,
31482            indoc! {r#"
31483                fn main() {
31484                    let x = foo(1, 2);
3148531486            "#},
31487            cx,
31488        );
31489    });
31490
31491    // Test case 2: Move to start of syntax nodes
31492    editor.update_in(cx, |editor, window, cx| {
31493        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31494            s.select_display_ranges([
31495                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20)
31496            ]);
31497        });
31498    });
31499    editor.update(cx, |editor, cx| {
31500        assert_text_with_selections(
31501            editor,
31502            indoc! {r#"
31503                fn main() {
31504                    let x = foo(1, 2ˇ);
31505                }
31506            "#},
31507            cx,
31508        );
31509    });
31510    editor.update_in(cx, |editor, window, cx| {
31511        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31512    });
31513    editor.update(cx, |editor, cx| {
31514        assert_text_with_selections(
31515            editor,
31516            indoc! {r#"
31517                fn main() {
31518                    let x = fooˇ(1, 2);
31519                }
31520            "#},
31521            cx,
31522        );
31523    });
31524    editor.update_in(cx, |editor, window, cx| {
31525        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31526    });
31527    editor.update(cx, |editor, cx| {
31528        assert_text_with_selections(
31529            editor,
31530            indoc! {r#"
31531                fn main() {
31532                    let x = ˇfoo(1, 2);
31533                }
31534            "#},
31535            cx,
31536        );
31537    });
31538    editor.update_in(cx, |editor, window, cx| {
31539        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31540    });
31541    editor.update(cx, |editor, cx| {
31542        assert_text_with_selections(
31543            editor,
31544            indoc! {r#"
31545                fn main() {
31546                    ˇlet x = foo(1, 2);
31547                }
31548            "#},
31549            cx,
31550        );
31551    });
31552    editor.update_in(cx, |editor, window, cx| {
31553        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31554    });
31555    editor.update(cx, |editor, cx| {
31556        assert_text_with_selections(
31557            editor,
31558            indoc! {r#"
31559                fn main() ˇ{
31560                    let x = foo(1, 2);
31561                }
31562            "#},
31563            cx,
31564        );
31565    });
31566    editor.update_in(cx, |editor, window, cx| {
31567        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31568    });
31569    editor.update(cx, |editor, cx| {
31570        assert_text_with_selections(
31571            editor,
31572            indoc! {r#"
31573                ˇfn main() {
31574                    let x = foo(1, 2);
31575                }
31576            "#},
31577            cx,
31578        );
31579    });
31580}
31581
31582#[gpui::test]
31583async fn test_move_to_start_end_of_larger_syntax_node_two_cursors(cx: &mut TestAppContext) {
31584    init_test(cx, |_| {});
31585
31586    let language = Arc::new(Language::new(
31587        LanguageConfig::default(),
31588        Some(tree_sitter_rust::LANGUAGE.into()),
31589    ));
31590
31591    let text = r#"
31592        fn main() {
31593            let x = foo(1, 2);
31594            let y = bar(3, 4);
31595        }
31596    "#
31597    .unindent();
31598
31599    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31600    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31601    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31602
31603    editor
31604        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31605        .await;
31606
31607    // Test case 1: Move to end of syntax nodes with two cursors
31608    editor.update_in(cx, |editor, window, cx| {
31609        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31610            s.select_display_ranges([
31611                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20),
31612                DisplayPoint::new(DisplayRow(2), 20)..DisplayPoint::new(DisplayRow(2), 20),
31613            ]);
31614        });
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                    let y = bar(3, 4ˇ);
31623                }
31624            "#},
31625            cx,
31626        );
31627    });
31628    editor.update_in(cx, |editor, window, cx| {
31629        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31630    });
31631    editor.update(cx, |editor, cx| {
31632        assert_text_with_selections(
31633            editor,
31634            indoc! {r#"
31635                fn main() {
31636                    let x = foo(1, 2)ˇ;
31637                    let y = bar(3, 4)ˇ;
31638                }
31639            "#},
31640            cx,
31641        );
31642    });
31643    editor.update_in(cx, |editor, window, cx| {
31644        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31645    });
31646    editor.update(cx, |editor, cx| {
31647        assert_text_with_selections(
31648            editor,
31649            indoc! {r#"
31650                fn main() {
31651                    let x = foo(1, 2);ˇ
31652                    let y = bar(3, 4);ˇ
31653                }
31654            "#},
31655            cx,
31656        );
31657    });
31658
31659    // Test case 2: Move to start of syntax nodes with two cursors
31660    editor.update_in(cx, |editor, window, cx| {
31661        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31662            s.select_display_ranges([
31663                DisplayPoint::new(DisplayRow(1), 19)..DisplayPoint::new(DisplayRow(1), 19),
31664                DisplayPoint::new(DisplayRow(2), 19)..DisplayPoint::new(DisplayRow(2), 19),
31665            ]);
31666        });
31667    });
31668    editor.update(cx, |editor, cx| {
31669        assert_text_with_selections(
31670            editor,
31671            indoc! {r#"
31672                fn main() {
31673                    let x = foo(1, ˇ2);
31674                    let y = bar(3, ˇ4);
31675                }
31676            "#},
31677            cx,
31678        );
31679    });
31680    editor.update_in(cx, |editor, window, cx| {
31681        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31682    });
31683    editor.update(cx, |editor, cx| {
31684        assert_text_with_selections(
31685            editor,
31686            indoc! {r#"
31687                fn main() {
31688                    let x = fooˇ(1, 2);
31689                    let y = barˇ(3, 4);
31690                }
31691            "#},
31692            cx,
31693        );
31694    });
31695    editor.update_in(cx, |editor, window, cx| {
31696        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31697    });
31698    editor.update(cx, |editor, cx| {
31699        assert_text_with_selections(
31700            editor,
31701            indoc! {r#"
31702                fn main() {
31703                    let x = ˇfoo(1, 2);
31704                    let y = ˇbar(3, 4);
31705                }
31706            "#},
31707            cx,
31708        );
31709    });
31710    editor.update_in(cx, |editor, window, cx| {
31711        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31712    });
31713    editor.update(cx, |editor, cx| {
31714        assert_text_with_selections(
31715            editor,
31716            indoc! {r#"
31717                fn main() {
31718                    ˇlet x = foo(1, 2);
31719                    ˇlet y = bar(3, 4);
31720                }
31721            "#},
31722            cx,
31723        );
31724    });
31725}
31726
31727#[gpui::test]
31728async fn test_move_to_start_end_of_larger_syntax_node_with_selections_and_strings(
31729    cx: &mut TestAppContext,
31730) {
31731    init_test(cx, |_| {});
31732
31733    let language = Arc::new(Language::new(
31734        LanguageConfig::default(),
31735        Some(tree_sitter_rust::LANGUAGE.into()),
31736    ));
31737
31738    let text = r#"
31739        fn main() {
31740            let x = foo(1, 2);
31741            let msg = "hello world";
31742        }
31743    "#
31744    .unindent();
31745
31746    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
31747    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
31748    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
31749
31750    editor
31751        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
31752        .await;
31753
31754    // Test case 1: With existing selection, move_to_end keeps selection
31755    editor.update_in(cx, |editor, window, cx| {
31756        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31757            s.select_display_ranges([
31758                DisplayPoint::new(DisplayRow(1), 12)..DisplayPoint::new(DisplayRow(1), 21)
31759            ]);
31760        });
31761    });
31762    editor.update(cx, |editor, cx| {
31763        assert_text_with_selections(
31764            editor,
31765            indoc! {r#"
31766                fn main() {
31767                    let x = «foo(1, 2)ˇ»;
31768                    let msg = "hello world";
31769                }
31770            "#},
31771            cx,
31772        );
31773    });
31774    editor.update_in(cx, |editor, window, cx| {
31775        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31776    });
31777    editor.update(cx, |editor, cx| {
31778        assert_text_with_selections(
31779            editor,
31780            indoc! {r#"
31781                fn main() {
31782                    let x = «foo(1, 2)ˇ»;
31783                    let msg = "hello world";
31784                }
31785            "#},
31786            cx,
31787        );
31788    });
31789
31790    // Test case 2: Move to end within a string
31791    editor.update_in(cx, |editor, window, cx| {
31792        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31793            s.select_display_ranges([
31794                DisplayPoint::new(DisplayRow(2), 15)..DisplayPoint::new(DisplayRow(2), 15)
31795            ]);
31796        });
31797    });
31798    editor.update(cx, |editor, cx| {
31799        assert_text_with_selections(
31800            editor,
31801            indoc! {r#"
31802                fn main() {
31803                    let x = foo(1, 2);
31804                    let msg = "ˇhello world";
31805                }
31806            "#},
31807            cx,
31808        );
31809    });
31810    editor.update_in(cx, |editor, window, cx| {
31811        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
31812    });
31813    editor.update(cx, |editor, cx| {
31814        assert_text_with_selections(
31815            editor,
31816            indoc! {r#"
31817                fn main() {
31818                    let x = foo(1, 2);
31819                    let msg = "hello worldˇ";
31820                }
31821            "#},
31822            cx,
31823        );
31824    });
31825
31826    // Test case 3: Move to start within a string
31827    editor.update_in(cx, |editor, window, cx| {
31828        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
31829            s.select_display_ranges([
31830                DisplayPoint::new(DisplayRow(2), 21)..DisplayPoint::new(DisplayRow(2), 21)
31831            ]);
31832        });
31833    });
31834    editor.update(cx, |editor, cx| {
31835        assert_text_with_selections(
31836            editor,
31837            indoc! {r#"
31838                fn main() {
31839                    let x = foo(1, 2);
31840                    let msg = "hello ˇworld";
31841                }
31842            "#},
31843            cx,
31844        );
31845    });
31846    editor.update_in(cx, |editor, window, cx| {
31847        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
31848    });
31849    editor.update(cx, |editor, cx| {
31850        assert_text_with_selections(
31851            editor,
31852            indoc! {r#"
31853                fn main() {
31854                    let x = foo(1, 2);
31855                    let msg = "ˇhello world";
31856                }
31857            "#},
31858            cx,
31859        );
31860    });
31861}