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    runnables::RunnableTasks,
    9    scroll::scroll_amount::ScrollAmount,
   10    test::{
   11        assert_text_with_selections, build_editor, editor_content_with_blocks,
   12        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   13        editor_test_context::EditorTestContext,
   14        select_ranges,
   15    },
   16};
   17use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   18use collections::HashMap;
   19use futures::{StreamExt, channel::oneshot};
   20use gpui::{
   21    BackgroundExecutor, DismissEvent, TestAppContext, UpdateGlobal, VisualTestContext,
   22    WindowBounds, WindowOptions, div,
   23};
   24use indoc::indoc;
   25use language::{
   26    BracketPairConfig,
   27    Capability::ReadWrite,
   28    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   29    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   30    language_settings::{
   31        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   32    },
   33    tree_sitter_python,
   34};
   35use language_settings::Formatter;
   36use languages::markdown_lang;
   37use languages::rust_lang;
   38use lsp::{CompletionParams, DEFAULT_LSP_REQUEST_TIMEOUT};
   39use multi_buffer::{IndentGuide, MultiBuffer, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey};
   40use parking_lot::Mutex;
   41use pretty_assertions::{assert_eq, assert_ne};
   42use project::{
   43    FakeFs, Project,
   44    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   45    project_settings::LspSettings,
   46    trusted_worktrees::{PathTrust, TrustedWorktrees},
   47};
   48use serde_json::{self, json};
   49use settings::{
   50    AllLanguageSettingsContent, DelayMs, EditorSettingsContent, GlobalLspSettingsContent,
   51    IndentGuideBackgroundColoring, IndentGuideColoring, InlayHintSettingsContent,
   52    ProjectSettingsContent, SearchSettingsContent, SettingsContent, SettingsStore,
   53};
   54use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   55use std::{
   56    iter,
   57    sync::atomic::{self, AtomicUsize},
   58};
   59use test::build_editor_with_project;
   60use text::ToPoint as _;
   61use unindent::Unindent;
   62use util::{
   63    assert_set_eq, path,
   64    rel_path::rel_path,
   65    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   66};
   67use workspace::{
   68    CloseActiveItem, CloseAllItems, CloseOtherItems, MultiWorkspace, NavigationEntry, OpenOptions,
   69    ViewId,
   70    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   71    register_project_item,
   72};
   73
   74fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   75    editor
   76        .selections
   77        .display_ranges(&editor.display_snapshot(cx))
   78}
   79
   80#[cfg(any(test, feature = "test-support"))]
   81pub mod property_test;
   82
   83#[gpui::test]
   84fn test_edit_events(cx: &mut TestAppContext) {
   85    init_test(cx, |_| {});
   86
   87    let buffer = cx.new(|cx| {
   88        let mut buffer = language::Buffer::local("123456", cx);
   89        buffer.set_group_interval(Duration::from_secs(1));
   90        buffer
   91    });
   92
   93    let events = Rc::new(RefCell::new(Vec::new()));
   94    let editor1 = cx.add_window({
   95        let events = events.clone();
   96        |window, cx| {
   97            let entity = cx.entity();
   98            cx.subscribe_in(
   99                &entity,
  100                window,
  101                move |_, _, event: &EditorEvent, _, _| match event {
  102                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
  103                    EditorEvent::BufferEdited => {
  104                        events.borrow_mut().push(("editor1", "buffer edited"))
  105                    }
  106                    _ => {}
  107                },
  108            )
  109            .detach();
  110            Editor::for_buffer(buffer.clone(), None, window, cx)
  111        }
  112    });
  113
  114    let editor2 = cx.add_window({
  115        let events = events.clone();
  116        |window, cx| {
  117            cx.subscribe_in(
  118                &cx.entity(),
  119                window,
  120                move |_, _, event: &EditorEvent, _, _| match event {
  121                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  122                    EditorEvent::BufferEdited => {
  123                        events.borrow_mut().push(("editor2", "buffer edited"))
  124                    }
  125                    _ => {}
  126                },
  127            )
  128            .detach();
  129            Editor::for_buffer(buffer.clone(), None, window, cx)
  130        }
  131    });
  132
  133    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  134
  135    // Mutating editor 1 will emit an `Edited` event only for that editor.
  136    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  137    assert_eq!(
  138        mem::take(&mut *events.borrow_mut()),
  139        [
  140            ("editor1", "edited"),
  141            ("editor1", "buffer edited"),
  142            ("editor2", "buffer edited"),
  143        ]
  144    );
  145
  146    // Mutating editor 2 will emit an `Edited` event only for that editor.
  147    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  148    assert_eq!(
  149        mem::take(&mut *events.borrow_mut()),
  150        [
  151            ("editor2", "edited"),
  152            ("editor1", "buffer edited"),
  153            ("editor2", "buffer edited"),
  154        ]
  155    );
  156
  157    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  158    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  159    assert_eq!(
  160        mem::take(&mut *events.borrow_mut()),
  161        [
  162            ("editor1", "edited"),
  163            ("editor1", "buffer edited"),
  164            ("editor2", "buffer edited"),
  165        ]
  166    );
  167
  168    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  169    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  170    assert_eq!(
  171        mem::take(&mut *events.borrow_mut()),
  172        [
  173            ("editor1", "edited"),
  174            ("editor1", "buffer edited"),
  175            ("editor2", "buffer edited"),
  176        ]
  177    );
  178
  179    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  180    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  181    assert_eq!(
  182        mem::take(&mut *events.borrow_mut()),
  183        [
  184            ("editor2", "edited"),
  185            ("editor1", "buffer edited"),
  186            ("editor2", "buffer edited"),
  187        ]
  188    );
  189
  190    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  191    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  192    assert_eq!(
  193        mem::take(&mut *events.borrow_mut()),
  194        [
  195            ("editor2", "edited"),
  196            ("editor1", "buffer edited"),
  197            ("editor2", "buffer edited"),
  198        ]
  199    );
  200
  201    // No event is emitted when the mutation is a no-op.
  202    _ = editor2.update(cx, |editor, window, cx| {
  203        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  204            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  205        });
  206
  207        editor.backspace(&Backspace, window, cx);
  208    });
  209    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  210}
  211
  212#[gpui::test]
  213fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  214    init_test(cx, |_| {});
  215
  216    let mut now = Instant::now();
  217    let group_interval = Duration::from_millis(1);
  218    let buffer = cx.new(|cx| {
  219        let mut buf = language::Buffer::local("123456", cx);
  220        buf.set_group_interval(group_interval);
  221        buf
  222    });
  223    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  224    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  225
  226    _ = editor.update(cx, |editor, window, cx| {
  227        editor.start_transaction_at(now, window, cx);
  228        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  229            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  230        });
  231
  232        editor.insert("cd", window, cx);
  233        editor.end_transaction_at(now, cx);
  234        assert_eq!(editor.text(cx), "12cd56");
  235        assert_eq!(
  236            editor.selections.ranges(&editor.display_snapshot(cx)),
  237            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  238        );
  239
  240        editor.start_transaction_at(now, window, cx);
  241        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  242            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  243        });
  244        editor.insert("e", window, cx);
  245        editor.end_transaction_at(now, cx);
  246        assert_eq!(editor.text(cx), "12cde6");
  247        assert_eq!(
  248            editor.selections.ranges(&editor.display_snapshot(cx)),
  249            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  250        );
  251
  252        now += group_interval + Duration::from_millis(1);
  253        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  254            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  255        });
  256
  257        // Simulate an edit in another editor
  258        buffer.update(cx, |buffer, cx| {
  259            buffer.start_transaction_at(now, cx);
  260            buffer.edit(
  261                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  262                None,
  263                cx,
  264            );
  265            buffer.edit(
  266                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  267                None,
  268                cx,
  269            );
  270            buffer.end_transaction_at(now, cx);
  271        });
  272
  273        assert_eq!(editor.text(cx), "ab2cde6");
  274        assert_eq!(
  275            editor.selections.ranges(&editor.display_snapshot(cx)),
  276            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  277        );
  278
  279        // Last transaction happened past the group interval in a different editor.
  280        // Undo it individually and don't restore selections.
  281        editor.undo(&Undo, window, cx);
  282        assert_eq!(editor.text(cx), "12cde6");
  283        assert_eq!(
  284            editor.selections.ranges(&editor.display_snapshot(cx)),
  285            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  286        );
  287
  288        // First two transactions happened within the group interval in this editor.
  289        // Undo them together and restore selections.
  290        editor.undo(&Undo, window, cx);
  291        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  292        assert_eq!(editor.text(cx), "123456");
  293        assert_eq!(
  294            editor.selections.ranges(&editor.display_snapshot(cx)),
  295            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  296        );
  297
  298        // Redo the first two transactions together.
  299        editor.redo(&Redo, window, cx);
  300        assert_eq!(editor.text(cx), "12cde6");
  301        assert_eq!(
  302            editor.selections.ranges(&editor.display_snapshot(cx)),
  303            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  304        );
  305
  306        // Redo the last transaction on its own.
  307        editor.redo(&Redo, window, cx);
  308        assert_eq!(editor.text(cx), "ab2cde6");
  309        assert_eq!(
  310            editor.selections.ranges(&editor.display_snapshot(cx)),
  311            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  312        );
  313
  314        // Test empty transactions.
  315        editor.start_transaction_at(now, window, cx);
  316        editor.end_transaction_at(now, cx);
  317        editor.undo(&Undo, window, cx);
  318        assert_eq!(editor.text(cx), "12cde6");
  319    });
  320}
  321
  322#[gpui::test]
  323fn test_ime_composition(cx: &mut TestAppContext) {
  324    init_test(cx, |_| {});
  325
  326    let buffer = cx.new(|cx| {
  327        let mut buffer = language::Buffer::local("abcde", cx);
  328        // Ensure automatic grouping doesn't occur.
  329        buffer.set_group_interval(Duration::ZERO);
  330        buffer
  331    });
  332
  333    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  334    cx.add_window(|window, cx| {
  335        let mut editor = build_editor(buffer.clone(), window, cx);
  336
  337        // Start a new IME composition.
  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        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  341        assert_eq!(editor.text(cx), "äbcde");
  342        assert_eq!(
  343            editor.marked_text_ranges(cx),
  344            Some(vec![
  345                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  346            ])
  347        );
  348
  349        // Finalize IME composition.
  350        editor.replace_text_in_range(None, "ā", window, cx);
  351        assert_eq!(editor.text(cx), "ābcde");
  352        assert_eq!(editor.marked_text_ranges(cx), None);
  353
  354        // IME composition edits are grouped and are undone/redone at once.
  355        editor.undo(&Default::default(), window, cx);
  356        assert_eq!(editor.text(cx), "abcde");
  357        assert_eq!(editor.marked_text_ranges(cx), None);
  358        editor.redo(&Default::default(), window, cx);
  359        assert_eq!(editor.text(cx), "ābcde");
  360        assert_eq!(editor.marked_text_ranges(cx), None);
  361
  362        // Start a new IME composition.
  363        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  364        assert_eq!(
  365            editor.marked_text_ranges(cx),
  366            Some(vec![
  367                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  368            ])
  369        );
  370
  371        // Undoing during an IME composition cancels it.
  372        editor.undo(&Default::default(), window, cx);
  373        assert_eq!(editor.text(cx), "ābcde");
  374        assert_eq!(editor.marked_text_ranges(cx), None);
  375
  376        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  377        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  378        assert_eq!(editor.text(cx), "ābcdè");
  379        assert_eq!(
  380            editor.marked_text_ranges(cx),
  381            Some(vec![
  382                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  383            ])
  384        );
  385
  386        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  387        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  388        assert_eq!(editor.text(cx), "ābcdę");
  389        assert_eq!(editor.marked_text_ranges(cx), None);
  390
  391        // Start a new IME composition with multiple cursors.
  392        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  393            s.select_ranges([
  394                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  395                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  396                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  397            ])
  398        });
  399        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  400        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  401        assert_eq!(
  402            editor.marked_text_ranges(cx),
  403            Some(vec![
  404                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  405                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  406                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  407            ])
  408        );
  409
  410        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  411        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  412        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  413        assert_eq!(
  414            editor.marked_text_ranges(cx),
  415            Some(vec![
  416                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  417                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  418                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  419            ])
  420        );
  421
  422        // Finalize IME composition with multiple cursors.
  423        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  424        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  425        assert_eq!(editor.marked_text_ranges(cx), None);
  426
  427        editor
  428    });
  429}
  430
  431#[gpui::test]
  432fn test_selection_with_mouse(cx: &mut TestAppContext) {
  433    init_test(cx, |_| {});
  434
  435    let editor = cx.add_window(|window, cx| {
  436        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  437        build_editor(buffer, window, cx)
  438    });
  439
  440    _ = editor.update(cx, |editor, window, cx| {
  441        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  442    });
  443    assert_eq!(
  444        editor
  445            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  446            .unwrap(),
  447        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  448    );
  449
  450    _ = editor.update(cx, |editor, window, cx| {
  451        editor.update_selection(
  452            DisplayPoint::new(DisplayRow(3), 3),
  453            0,
  454            gpui::Point::<f32>::default(),
  455            window,
  456            cx,
  457        );
  458    });
  459
  460    assert_eq!(
  461        editor
  462            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  463            .unwrap(),
  464        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  465    );
  466
  467    _ = editor.update(cx, |editor, window, cx| {
  468        editor.update_selection(
  469            DisplayPoint::new(DisplayRow(1), 1),
  470            0,
  471            gpui::Point::<f32>::default(),
  472            window,
  473            cx,
  474        );
  475    });
  476
  477    assert_eq!(
  478        editor
  479            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  480            .unwrap(),
  481        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  482    );
  483
  484    _ = editor.update(cx, |editor, window, cx| {
  485        editor.end_selection(window, cx);
  486        editor.update_selection(
  487            DisplayPoint::new(DisplayRow(3), 3),
  488            0,
  489            gpui::Point::<f32>::default(),
  490            window,
  491            cx,
  492        );
  493    });
  494
  495    assert_eq!(
  496        editor
  497            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  498            .unwrap(),
  499        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  500    );
  501
  502    _ = editor.update(cx, |editor, window, cx| {
  503        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  504        editor.update_selection(
  505            DisplayPoint::new(DisplayRow(0), 0),
  506            0,
  507            gpui::Point::<f32>::default(),
  508            window,
  509            cx,
  510        );
  511    });
  512
  513    assert_eq!(
  514        editor
  515            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  516            .unwrap(),
  517        [
  518            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  519            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  520        ]
  521    );
  522
  523    _ = editor.update(cx, |editor, window, cx| {
  524        editor.end_selection(window, cx);
  525    });
  526
  527    assert_eq!(
  528        editor
  529            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  530            .unwrap(),
  531        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  532    );
  533}
  534
  535#[gpui::test]
  536fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  537    init_test(cx, |_| {});
  538
  539    let editor = cx.add_window(|window, cx| {
  540        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  541        build_editor(buffer, window, cx)
  542    });
  543
  544    _ = editor.update(cx, |editor, window, cx| {
  545        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.end_selection(window, cx);
  550    });
  551
  552    _ = editor.update(cx, |editor, window, cx| {
  553        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  554    });
  555
  556    _ = editor.update(cx, |editor, window, cx| {
  557        editor.end_selection(window, cx);
  558    });
  559
  560    assert_eq!(
  561        editor
  562            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  563            .unwrap(),
  564        [
  565            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  566            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  567        ]
  568    );
  569
  570    _ = editor.update(cx, |editor, window, cx| {
  571        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  572    });
  573
  574    _ = editor.update(cx, |editor, window, cx| {
  575        editor.end_selection(window, cx);
  576    });
  577
  578    assert_eq!(
  579        editor
  580            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  581            .unwrap(),
  582        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  583    );
  584}
  585
  586#[gpui::test]
  587fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  588    init_test(cx, |_| {});
  589
  590    let editor = cx.add_window(|window, cx| {
  591        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  592        build_editor(buffer, window, cx)
  593    });
  594
  595    _ = editor.update(cx, |editor, window, cx| {
  596        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  597        assert_eq!(
  598            display_ranges(editor, cx),
  599            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  600        );
  601    });
  602
  603    _ = editor.update(cx, |editor, window, cx| {
  604        editor.update_selection(
  605            DisplayPoint::new(DisplayRow(3), 3),
  606            0,
  607            gpui::Point::<f32>::default(),
  608            window,
  609            cx,
  610        );
  611        assert_eq!(
  612            display_ranges(editor, cx),
  613            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  614        );
  615    });
  616
  617    _ = editor.update(cx, |editor, window, cx| {
  618        editor.cancel(&Cancel, window, cx);
  619        editor.update_selection(
  620            DisplayPoint::new(DisplayRow(1), 1),
  621            0,
  622            gpui::Point::<f32>::default(),
  623            window,
  624            cx,
  625        );
  626        assert_eq!(
  627            display_ranges(editor, cx),
  628            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  629        );
  630    });
  631}
  632
  633#[gpui::test]
  634fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  635    init_test(cx, |_| {});
  636
  637    let editor = cx.add_window(|window, cx| {
  638        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  639        build_editor(buffer, window, cx)
  640    });
  641
  642    _ = editor.update(cx, |editor, window, cx| {
  643        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  644        assert_eq!(
  645            display_ranges(editor, cx),
  646            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  647        );
  648
  649        editor.move_down(&Default::default(), window, cx);
  650        assert_eq!(
  651            display_ranges(editor, cx),
  652            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  653        );
  654
  655        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  656        assert_eq!(
  657            display_ranges(editor, cx),
  658            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  659        );
  660
  661        editor.move_up(&Default::default(), window, cx);
  662        assert_eq!(
  663            display_ranges(editor, cx),
  664            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  665        );
  666    });
  667}
  668
  669#[gpui::test]
  670fn test_extending_selection(cx: &mut TestAppContext) {
  671    init_test(cx, |_| {});
  672
  673    let editor = cx.add_window(|window, cx| {
  674        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  675        build_editor(buffer, window, cx)
  676    });
  677
  678    _ = editor.update(cx, |editor, window, cx| {
  679        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  680        editor.end_selection(window, cx);
  681        assert_eq!(
  682            display_ranges(editor, cx),
  683            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  684        );
  685
  686        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  687        editor.end_selection(window, cx);
  688        assert_eq!(
  689            display_ranges(editor, cx),
  690            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  691        );
  692
  693        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  694        editor.end_selection(window, cx);
  695        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  696        assert_eq!(
  697            display_ranges(editor, cx),
  698            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  699        );
  700
  701        editor.update_selection(
  702            DisplayPoint::new(DisplayRow(0), 1),
  703            0,
  704            gpui::Point::<f32>::default(),
  705            window,
  706            cx,
  707        );
  708        editor.end_selection(window, cx);
  709        assert_eq!(
  710            display_ranges(editor, cx),
  711            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  712        );
  713
  714        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  715        editor.end_selection(window, cx);
  716        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  717        editor.end_selection(window, cx);
  718        assert_eq!(
  719            display_ranges(editor, cx),
  720            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  721        );
  722
  723        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  724        assert_eq!(
  725            display_ranges(editor, cx),
  726            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  727        );
  728
  729        editor.update_selection(
  730            DisplayPoint::new(DisplayRow(0), 6),
  731            0,
  732            gpui::Point::<f32>::default(),
  733            window,
  734            cx,
  735        );
  736        assert_eq!(
  737            display_ranges(editor, cx),
  738            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  739        );
  740
  741        editor.update_selection(
  742            DisplayPoint::new(DisplayRow(0), 1),
  743            0,
  744            gpui::Point::<f32>::default(),
  745            window,
  746            cx,
  747        );
  748        editor.end_selection(window, cx);
  749        assert_eq!(
  750            display_ranges(editor, cx),
  751            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  752        );
  753    });
  754}
  755
  756#[gpui::test]
  757fn test_clone(cx: &mut TestAppContext) {
  758    init_test(cx, |_| {});
  759
  760    let (text, selection_ranges) = marked_text_ranges(
  761        indoc! {"
  762            one
  763            two
  764            threeˇ
  765            four
  766            fiveˇ
  767        "},
  768        true,
  769    );
  770
  771    let editor = cx.add_window(|window, cx| {
  772        let buffer = MultiBuffer::build_simple(&text, cx);
  773        build_editor(buffer, window, cx)
  774    });
  775
  776    _ = editor.update(cx, |editor, window, cx| {
  777        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  778            s.select_ranges(
  779                selection_ranges
  780                    .iter()
  781                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  782            )
  783        });
  784        editor.fold_creases(
  785            vec![
  786                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  787                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  788            ],
  789            true,
  790            window,
  791            cx,
  792        );
  793    });
  794
  795    let cloned_editor = editor
  796        .update(cx, |editor, _, cx| {
  797            cx.open_window(Default::default(), |window, cx| {
  798                cx.new(|cx| editor.clone(window, cx))
  799            })
  800        })
  801        .unwrap()
  802        .unwrap();
  803
  804    let snapshot = editor
  805        .update(cx, |e, window, cx| e.snapshot(window, cx))
  806        .unwrap();
  807    let cloned_snapshot = cloned_editor
  808        .update(cx, |e, window, cx| e.snapshot(window, cx))
  809        .unwrap();
  810
  811    assert_eq!(
  812        cloned_editor
  813            .update(cx, |e, _, cx| e.display_text(cx))
  814            .unwrap(),
  815        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  816    );
  817    assert_eq!(
  818        cloned_snapshot
  819            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  820            .collect::<Vec<_>>(),
  821        snapshot
  822            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  823            .collect::<Vec<_>>(),
  824    );
  825    assert_set_eq!(
  826        cloned_editor
  827            .update(cx, |editor, _, cx| editor
  828                .selections
  829                .ranges::<Point>(&editor.display_snapshot(cx)))
  830            .unwrap(),
  831        editor
  832            .update(cx, |editor, _, cx| editor
  833                .selections
  834                .ranges(&editor.display_snapshot(cx)))
  835            .unwrap()
  836    );
  837    assert_set_eq!(
  838        cloned_editor
  839            .update(cx, |e, _window, cx| e
  840                .selections
  841                .display_ranges(&e.display_snapshot(cx)))
  842            .unwrap(),
  843        editor
  844            .update(cx, |e, _, cx| e
  845                .selections
  846                .display_ranges(&e.display_snapshot(cx)))
  847            .unwrap()
  848    );
  849}
  850
  851#[gpui::test]
  852async fn test_navigation_history(cx: &mut TestAppContext) {
  853    init_test(cx, |_| {});
  854
  855    use workspace::item::Item;
  856
  857    let fs = FakeFs::new(cx.executor());
  858    let project = Project::test(fs, [], cx).await;
  859    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project, window, cx));
  860    let workspace = window
  861        .read_with(cx, |mw, _| mw.workspace().clone())
  862        .unwrap();
  863    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
  864
  865    _ = window.update(cx, |_mw, window, cx| {
  866        cx.new(|cx| {
  867            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  868            let mut editor = build_editor(buffer, window, cx);
  869            let handle = cx.entity();
  870            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  871
  872            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  873                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  874            }
  875
  876            // Move the cursor a small distance.
  877            // Nothing is added to the navigation history.
  878            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  879                s.select_display_ranges([
  880                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  881                ])
  882            });
  883            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  884                s.select_display_ranges([
  885                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  886                ])
  887            });
  888            assert!(pop_history(&mut editor, cx).is_none());
  889
  890            // Move the cursor a large distance.
  891            // The history can jump back to the previous position.
  892            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  893                s.select_display_ranges([
  894                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  895                ])
  896            });
  897            let nav_entry = pop_history(&mut editor, cx).unwrap();
  898            editor.navigate(nav_entry.data.unwrap(), window, cx);
  899            assert_eq!(nav_entry.item.id(), cx.entity_id());
  900            assert_eq!(
  901                editor
  902                    .selections
  903                    .display_ranges(&editor.display_snapshot(cx)),
  904                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  905            );
  906            assert!(pop_history(&mut editor, cx).is_none());
  907
  908            // Move the cursor a small distance via the mouse.
  909            // Nothing is added to the navigation history.
  910            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  911            editor.end_selection(window, cx);
  912            assert_eq!(
  913                editor
  914                    .selections
  915                    .display_ranges(&editor.display_snapshot(cx)),
  916                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  917            );
  918            assert!(pop_history(&mut editor, cx).is_none());
  919
  920            // Move the cursor a large distance via the mouse.
  921            // The history can jump back to the previous position.
  922            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  923            editor.end_selection(window, cx);
  924            assert_eq!(
  925                editor
  926                    .selections
  927                    .display_ranges(&editor.display_snapshot(cx)),
  928                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  929            );
  930            let nav_entry = pop_history(&mut editor, cx).unwrap();
  931            editor.navigate(nav_entry.data.unwrap(), window, cx);
  932            assert_eq!(nav_entry.item.id(), cx.entity_id());
  933            assert_eq!(
  934                editor
  935                    .selections
  936                    .display_ranges(&editor.display_snapshot(cx)),
  937                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  938            );
  939            assert!(pop_history(&mut editor, cx).is_none());
  940
  941            // Set scroll position to check later
  942            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  943            let original_scroll_position = editor
  944                .scroll_manager
  945                .native_anchor(&editor.display_snapshot(cx), cx);
  946
  947            // Jump to the end of the document and adjust scroll
  948            editor.move_to_end(&MoveToEnd, window, cx);
  949            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  950            assert_ne!(
  951                editor
  952                    .scroll_manager
  953                    .native_anchor(&editor.display_snapshot(cx), cx),
  954                original_scroll_position
  955            );
  956
  957            let nav_entry = pop_history(&mut editor, cx).unwrap();
  958            editor.navigate(nav_entry.data.unwrap(), window, cx);
  959            assert_eq!(
  960                editor
  961                    .scroll_manager
  962                    .native_anchor(&editor.display_snapshot(cx), cx),
  963                original_scroll_position
  964            );
  965
  966            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  967            let mut invalid_anchor = editor
  968                .scroll_manager
  969                .native_anchor(&editor.display_snapshot(cx), cx)
  970                .anchor;
  971            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  972            let invalid_point = Point::new(9999, 0);
  973            editor.navigate(
  974                Arc::new(NavigationData {
  975                    cursor_anchor: invalid_anchor,
  976                    cursor_position: invalid_point,
  977                    scroll_anchor: ScrollAnchor {
  978                        anchor: invalid_anchor,
  979                        offset: Default::default(),
  980                    },
  981                    scroll_top_row: invalid_point.row,
  982                }),
  983                window,
  984                cx,
  985            );
  986            assert_eq!(
  987                editor
  988                    .selections
  989                    .display_ranges(&editor.display_snapshot(cx)),
  990                &[editor.max_point(cx)..editor.max_point(cx)]
  991            );
  992            assert_eq!(
  993                editor.scroll_position(cx),
  994                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  995            );
  996
  997            editor
  998        })
  999    });
 1000}
 1001
 1002#[gpui::test]
 1003fn test_cancel(cx: &mut TestAppContext) {
 1004    init_test(cx, |_| {});
 1005
 1006    let editor = cx.add_window(|window, cx| {
 1007        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
 1008        build_editor(buffer, window, cx)
 1009    });
 1010
 1011    _ = editor.update(cx, |editor, window, cx| {
 1012        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
 1013        editor.update_selection(
 1014            DisplayPoint::new(DisplayRow(1), 1),
 1015            0,
 1016            gpui::Point::<f32>::default(),
 1017            window,
 1018            cx,
 1019        );
 1020        editor.end_selection(window, cx);
 1021
 1022        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1023        editor.update_selection(
 1024            DisplayPoint::new(DisplayRow(0), 3),
 1025            0,
 1026            gpui::Point::<f32>::default(),
 1027            window,
 1028            cx,
 1029        );
 1030        editor.end_selection(window, cx);
 1031        assert_eq!(
 1032            display_ranges(editor, cx),
 1033            [
 1034                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1035                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1036            ]
 1037        );
 1038    });
 1039
 1040    _ = editor.update(cx, |editor, window, cx| {
 1041        editor.cancel(&Cancel, window, cx);
 1042        assert_eq!(
 1043            display_ranges(editor, cx),
 1044            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1045        );
 1046    });
 1047
 1048    _ = editor.update(cx, |editor, window, cx| {
 1049        editor.cancel(&Cancel, window, cx);
 1050        assert_eq!(
 1051            display_ranges(editor, cx),
 1052            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1053        );
 1054    });
 1055}
 1056
 1057#[gpui::test]
 1058fn test_fold_action(cx: &mut TestAppContext) {
 1059    init_test(cx, |_| {});
 1060
 1061    let editor = cx.add_window(|window, cx| {
 1062        let buffer = MultiBuffer::build_simple(
 1063            &"
 1064                impl Foo {
 1065                    // Hello!
 1066
 1067                    fn a() {
 1068                        1
 1069                    }
 1070
 1071                    fn b() {
 1072                        2
 1073                    }
 1074
 1075                    fn c() {
 1076                        3
 1077                    }
 1078                }
 1079            "
 1080            .unindent(),
 1081            cx,
 1082        );
 1083        build_editor(buffer, window, cx)
 1084    });
 1085
 1086    _ = editor.update(cx, |editor, window, cx| {
 1087        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1088            s.select_display_ranges([
 1089                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1090            ]);
 1091        });
 1092        editor.fold(&Fold, window, cx);
 1093        assert_eq!(
 1094            editor.display_text(cx),
 1095            "
 1096                impl Foo {
 1097                    // Hello!
 1098
 1099                    fn a() {
 1100                        1
 1101                    }
 1102
 1103                    fn b() {⋯
 1104                    }
 1105
 1106                    fn c() {⋯
 1107                    }
 1108                }
 1109            "
 1110            .unindent(),
 1111        );
 1112
 1113        editor.fold(&Fold, window, cx);
 1114        assert_eq!(
 1115            editor.display_text(cx),
 1116            "
 1117                impl Foo {⋯
 1118                }
 1119            "
 1120            .unindent(),
 1121        );
 1122
 1123        editor.unfold_lines(&UnfoldLines, window, cx);
 1124        assert_eq!(
 1125            editor.display_text(cx),
 1126            "
 1127                impl Foo {
 1128                    // Hello!
 1129
 1130                    fn a() {
 1131                        1
 1132                    }
 1133
 1134                    fn b() {⋯
 1135                    }
 1136
 1137                    fn c() {⋯
 1138                    }
 1139                }
 1140            "
 1141            .unindent(),
 1142        );
 1143
 1144        editor.unfold_lines(&UnfoldLines, window, cx);
 1145        assert_eq!(
 1146            editor.display_text(cx),
 1147            editor.buffer.read(cx).read(cx).text()
 1148        );
 1149    });
 1150}
 1151
 1152#[gpui::test]
 1153fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1154    init_test(cx, |_| {});
 1155
 1156    let editor = cx.add_window(|window, cx| {
 1157        let buffer = MultiBuffer::build_simple(
 1158            &"
 1159                class Foo:
 1160                    # Hello!
 1161
 1162                    def a():
 1163                        print(1)
 1164
 1165                    def b():
 1166                        print(2)
 1167
 1168                    def c():
 1169                        print(3)
 1170            "
 1171            .unindent(),
 1172            cx,
 1173        );
 1174        build_editor(buffer, window, cx)
 1175    });
 1176
 1177    _ = editor.update(cx, |editor, window, cx| {
 1178        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1179            s.select_display_ranges([
 1180                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1181            ]);
 1182        });
 1183        editor.fold(&Fold, window, cx);
 1184        assert_eq!(
 1185            editor.display_text(cx),
 1186            "
 1187                class Foo:
 1188                    # Hello!
 1189
 1190                    def a():
 1191                        print(1)
 1192
 1193                    def b():⋯
 1194
 1195                    def c():⋯
 1196            "
 1197            .unindent(),
 1198        );
 1199
 1200        editor.fold(&Fold, window, cx);
 1201        assert_eq!(
 1202            editor.display_text(cx),
 1203            "
 1204                class Foo:⋯
 1205            "
 1206            .unindent(),
 1207        );
 1208
 1209        editor.unfold_lines(&UnfoldLines, window, cx);
 1210        assert_eq!(
 1211            editor.display_text(cx),
 1212            "
 1213                class Foo:
 1214                    # Hello!
 1215
 1216                    def a():
 1217                        print(1)
 1218
 1219                    def b():⋯
 1220
 1221                    def c():⋯
 1222            "
 1223            .unindent(),
 1224        );
 1225
 1226        editor.unfold_lines(&UnfoldLines, window, cx);
 1227        assert_eq!(
 1228            editor.display_text(cx),
 1229            editor.buffer.read(cx).read(cx).text()
 1230        );
 1231    });
 1232}
 1233
 1234#[gpui::test]
 1235fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1236    init_test(cx, |_| {});
 1237
 1238    let editor = cx.add_window(|window, cx| {
 1239        let buffer = MultiBuffer::build_simple(
 1240            &"
 1241                class Foo:
 1242                    # Hello!
 1243
 1244                    def a():
 1245                        print(1)
 1246
 1247                    def b():
 1248                        print(2)
 1249
 1250
 1251                    def c():
 1252                        print(3)
 1253
 1254
 1255            "
 1256            .unindent(),
 1257            cx,
 1258        );
 1259        build_editor(buffer, window, cx)
 1260    });
 1261
 1262    _ = editor.update(cx, |editor, window, cx| {
 1263        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1264            s.select_display_ranges([
 1265                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1266            ]);
 1267        });
 1268        editor.fold(&Fold, window, cx);
 1269        assert_eq!(
 1270            editor.display_text(cx),
 1271            "
 1272                class Foo:
 1273                    # Hello!
 1274
 1275                    def a():
 1276                        print(1)
 1277
 1278                    def b():⋯
 1279
 1280
 1281                    def c():⋯
 1282
 1283
 1284            "
 1285            .unindent(),
 1286        );
 1287
 1288        editor.fold(&Fold, window, cx);
 1289        assert_eq!(
 1290            editor.display_text(cx),
 1291            "
 1292                class Foo:⋯
 1293
 1294
 1295            "
 1296            .unindent(),
 1297        );
 1298
 1299        editor.unfold_lines(&UnfoldLines, window, cx);
 1300        assert_eq!(
 1301            editor.display_text(cx),
 1302            "
 1303                class Foo:
 1304                    # Hello!
 1305
 1306                    def a():
 1307                        print(1)
 1308
 1309                    def b():⋯
 1310
 1311
 1312                    def c():⋯
 1313
 1314
 1315            "
 1316            .unindent(),
 1317        );
 1318
 1319        editor.unfold_lines(&UnfoldLines, window, cx);
 1320        assert_eq!(
 1321            editor.display_text(cx),
 1322            editor.buffer.read(cx).read(cx).text()
 1323        );
 1324    });
 1325}
 1326
 1327#[gpui::test]
 1328fn test_fold_at_level(cx: &mut TestAppContext) {
 1329    init_test(cx, |_| {});
 1330
 1331    let editor = cx.add_window(|window, cx| {
 1332        let buffer = MultiBuffer::build_simple(
 1333            &"
 1334                class Foo:
 1335                    # Hello!
 1336
 1337                    def a():
 1338                        print(1)
 1339
 1340                    def b():
 1341                        print(2)
 1342
 1343
 1344                class Bar:
 1345                    # World!
 1346
 1347                    def a():
 1348                        print(1)
 1349
 1350                    def b():
 1351                        print(2)
 1352
 1353
 1354            "
 1355            .unindent(),
 1356            cx,
 1357        );
 1358        build_editor(buffer, window, cx)
 1359    });
 1360
 1361    _ = editor.update(cx, |editor, window, cx| {
 1362        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1363        assert_eq!(
 1364            editor.display_text(cx),
 1365            "
 1366                class Foo:
 1367                    # Hello!
 1368
 1369                    def a():⋯
 1370
 1371                    def b():⋯
 1372
 1373
 1374                class Bar:
 1375                    # World!
 1376
 1377                    def a():⋯
 1378
 1379                    def b():⋯
 1380
 1381
 1382            "
 1383            .unindent(),
 1384        );
 1385
 1386        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1387        assert_eq!(
 1388            editor.display_text(cx),
 1389            "
 1390                class Foo:⋯
 1391
 1392
 1393                class Bar:⋯
 1394
 1395
 1396            "
 1397            .unindent(),
 1398        );
 1399
 1400        editor.unfold_all(&UnfoldAll, window, cx);
 1401        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1402        assert_eq!(
 1403            editor.display_text(cx),
 1404            "
 1405                class Foo:
 1406                    # Hello!
 1407
 1408                    def a():
 1409                        print(1)
 1410
 1411                    def b():
 1412                        print(2)
 1413
 1414
 1415                class Bar:
 1416                    # World!
 1417
 1418                    def a():
 1419                        print(1)
 1420
 1421                    def b():
 1422                        print(2)
 1423
 1424
 1425            "
 1426            .unindent(),
 1427        );
 1428
 1429        assert_eq!(
 1430            editor.display_text(cx),
 1431            editor.buffer.read(cx).read(cx).text()
 1432        );
 1433        let (_, positions) = marked_text_ranges(
 1434            &"
 1435                       class Foo:
 1436                           # Hello!
 1437
 1438                           def a():
 1439                              print(1)
 1440
 1441                           def b():
 1442                               p«riˇ»nt(2)
 1443
 1444
 1445                       class Bar:
 1446                           # World!
 1447
 1448                           def a():
 1449                               «ˇprint(1)
 1450
 1451                           def b():
 1452                               print(2)»
 1453
 1454
 1455                   "
 1456            .unindent(),
 1457            true,
 1458        );
 1459
 1460        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1461            s.select_ranges(
 1462                positions
 1463                    .iter()
 1464                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1465            )
 1466        });
 1467
 1468        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1469        assert_eq!(
 1470            editor.display_text(cx),
 1471            "
 1472                class Foo:
 1473                    # Hello!
 1474
 1475                    def a():⋯
 1476
 1477                    def b():
 1478                        print(2)
 1479
 1480
 1481                class Bar:
 1482                    # World!
 1483
 1484                    def a():
 1485                        print(1)
 1486
 1487                    def b():
 1488                        print(2)
 1489
 1490
 1491            "
 1492            .unindent(),
 1493        );
 1494    });
 1495}
 1496
 1497#[gpui::test]
 1498fn test_move_cursor(cx: &mut TestAppContext) {
 1499    init_test(cx, |_| {});
 1500
 1501    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1502    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1503
 1504    buffer.update(cx, |buffer, cx| {
 1505        buffer.edit(
 1506            vec![
 1507                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1508                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1509            ],
 1510            None,
 1511            cx,
 1512        );
 1513    });
 1514    _ = editor.update(cx, |editor, window, cx| {
 1515        assert_eq!(
 1516            display_ranges(editor, cx),
 1517            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1518        );
 1519
 1520        editor.move_down(&MoveDown, window, cx);
 1521        assert_eq!(
 1522            display_ranges(editor, cx),
 1523            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1524        );
 1525
 1526        editor.move_right(&MoveRight, window, cx);
 1527        assert_eq!(
 1528            display_ranges(editor, cx),
 1529            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1530        );
 1531
 1532        editor.move_left(&MoveLeft, window, cx);
 1533        assert_eq!(
 1534            display_ranges(editor, cx),
 1535            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1536        );
 1537
 1538        editor.move_up(&MoveUp, window, cx);
 1539        assert_eq!(
 1540            display_ranges(editor, cx),
 1541            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1542        );
 1543
 1544        editor.move_to_end(&MoveToEnd, window, cx);
 1545        assert_eq!(
 1546            display_ranges(editor, cx),
 1547            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1548        );
 1549
 1550        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1551        assert_eq!(
 1552            display_ranges(editor, cx),
 1553            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1554        );
 1555
 1556        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1557            s.select_display_ranges([
 1558                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1559            ]);
 1560        });
 1561        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1562        assert_eq!(
 1563            display_ranges(editor, cx),
 1564            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1565        );
 1566
 1567        editor.select_to_end(&SelectToEnd, window, cx);
 1568        assert_eq!(
 1569            display_ranges(editor, cx),
 1570            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1571        );
 1572    });
 1573}
 1574
 1575#[gpui::test]
 1576fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1577    init_test(cx, |_| {});
 1578
 1579    let editor = cx.add_window(|window, cx| {
 1580        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1581        build_editor(buffer, window, cx)
 1582    });
 1583
 1584    assert_eq!('🟥'.len_utf8(), 4);
 1585    assert_eq!('α'.len_utf8(), 2);
 1586
 1587    _ = editor.update(cx, |editor, window, cx| {
 1588        editor.fold_creases(
 1589            vec![
 1590                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1591                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1592                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1593            ],
 1594            true,
 1595            window,
 1596            cx,
 1597        );
 1598        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1599
 1600        editor.move_right(&MoveRight, window, cx);
 1601        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1602        editor.move_right(&MoveRight, window, cx);
 1603        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1604        editor.move_right(&MoveRight, window, cx);
 1605        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1606
 1607        editor.move_down(&MoveDown, window, cx);
 1608        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1609        editor.move_left(&MoveLeft, window, cx);
 1610        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1611        editor.move_left(&MoveLeft, window, cx);
 1612        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1613        editor.move_left(&MoveLeft, window, cx);
 1614        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1615
 1616        editor.move_down(&MoveDown, window, cx);
 1617        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1618        editor.move_right(&MoveRight, window, cx);
 1619        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1620        editor.move_right(&MoveRight, window, cx);
 1621        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1622        editor.move_right(&MoveRight, window, cx);
 1623        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1624
 1625        editor.move_up(&MoveUp, window, cx);
 1626        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1627        editor.move_down(&MoveDown, window, cx);
 1628        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1629        editor.move_up(&MoveUp, window, cx);
 1630        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1631
 1632        editor.move_up(&MoveUp, window, cx);
 1633        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1634        editor.move_left(&MoveLeft, window, cx);
 1635        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1636        editor.move_left(&MoveLeft, window, cx);
 1637        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1638    });
 1639}
 1640
 1641#[gpui::test]
 1642fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1643    init_test(cx, |_| {});
 1644
 1645    let editor = cx.add_window(|window, cx| {
 1646        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1647        build_editor(buffer, window, cx)
 1648    });
 1649    _ = editor.update(cx, |editor, window, cx| {
 1650        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1651            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1652        });
 1653
 1654        // moving above start of document should move selection to start of document,
 1655        // but the next move down should still be at the original goal_x
 1656        editor.move_up(&MoveUp, window, cx);
 1657        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1658
 1659        editor.move_down(&MoveDown, window, cx);
 1660        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1661
 1662        editor.move_down(&MoveDown, window, cx);
 1663        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1664
 1665        editor.move_down(&MoveDown, window, cx);
 1666        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1667
 1668        editor.move_down(&MoveDown, window, cx);
 1669        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1670
 1671        // moving past end of document should not change goal_x
 1672        editor.move_down(&MoveDown, window, cx);
 1673        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1674
 1675        editor.move_down(&MoveDown, window, cx);
 1676        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1677
 1678        editor.move_up(&MoveUp, window, cx);
 1679        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1680
 1681        editor.move_up(&MoveUp, window, cx);
 1682        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1683
 1684        editor.move_up(&MoveUp, window, cx);
 1685        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1686    });
 1687}
 1688
 1689#[gpui::test]
 1690fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1691    init_test(cx, |_| {});
 1692    let move_to_beg = MoveToBeginningOfLine {
 1693        stop_at_soft_wraps: true,
 1694        stop_at_indent: true,
 1695    };
 1696
 1697    let delete_to_beg = DeleteToBeginningOfLine {
 1698        stop_at_indent: false,
 1699    };
 1700
 1701    let move_to_end = MoveToEndOfLine {
 1702        stop_at_soft_wraps: true,
 1703    };
 1704
 1705    let editor = cx.add_window(|window, cx| {
 1706        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1707        build_editor(buffer, window, cx)
 1708    });
 1709    _ = editor.update(cx, |editor, window, cx| {
 1710        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1711            s.select_display_ranges([
 1712                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1713                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1714            ]);
 1715        });
 1716    });
 1717
 1718    _ = editor.update(cx, |editor, window, cx| {
 1719        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1720        assert_eq!(
 1721            display_ranges(editor, cx),
 1722            &[
 1723                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1724                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1725            ]
 1726        );
 1727    });
 1728
 1729    _ = editor.update(cx, |editor, window, cx| {
 1730        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1731        assert_eq!(
 1732            display_ranges(editor, cx),
 1733            &[
 1734                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1735                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1736            ]
 1737        );
 1738    });
 1739
 1740    _ = editor.update(cx, |editor, window, cx| {
 1741        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1742        assert_eq!(
 1743            display_ranges(editor, cx),
 1744            &[
 1745                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1746                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1747            ]
 1748        );
 1749    });
 1750
 1751    _ = editor.update(cx, |editor, window, cx| {
 1752        editor.move_to_end_of_line(&move_to_end, window, cx);
 1753        assert_eq!(
 1754            display_ranges(editor, cx),
 1755            &[
 1756                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1757                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1758            ]
 1759        );
 1760    });
 1761
 1762    // Moving to the end of line again is a no-op.
 1763    _ = editor.update(cx, |editor, window, cx| {
 1764        editor.move_to_end_of_line(&move_to_end, window, cx);
 1765        assert_eq!(
 1766            display_ranges(editor, cx),
 1767            &[
 1768                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1769                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1770            ]
 1771        );
 1772    });
 1773
 1774    _ = editor.update(cx, |editor, window, cx| {
 1775        editor.move_left(&MoveLeft, window, cx);
 1776        editor.select_to_beginning_of_line(
 1777            &SelectToBeginningOfLine {
 1778                stop_at_soft_wraps: true,
 1779                stop_at_indent: true,
 1780            },
 1781            window,
 1782            cx,
 1783        );
 1784        assert_eq!(
 1785            display_ranges(editor, cx),
 1786            &[
 1787                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1788                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1789            ]
 1790        );
 1791    });
 1792
 1793    _ = editor.update(cx, |editor, window, cx| {
 1794        editor.select_to_beginning_of_line(
 1795            &SelectToBeginningOfLine {
 1796                stop_at_soft_wraps: true,
 1797                stop_at_indent: true,
 1798            },
 1799            window,
 1800            cx,
 1801        );
 1802        assert_eq!(
 1803            display_ranges(editor, cx),
 1804            &[
 1805                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1806                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1807            ]
 1808        );
 1809    });
 1810
 1811    _ = editor.update(cx, |editor, window, cx| {
 1812        editor.select_to_beginning_of_line(
 1813            &SelectToBeginningOfLine {
 1814                stop_at_soft_wraps: true,
 1815                stop_at_indent: 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), 0),
 1824                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1825            ]
 1826        );
 1827    });
 1828
 1829    _ = editor.update(cx, |editor, window, cx| {
 1830        editor.select_to_end_of_line(
 1831            &SelectToEndOfLine {
 1832                stop_at_soft_wraps: true,
 1833            },
 1834            window,
 1835            cx,
 1836        );
 1837        assert_eq!(
 1838            display_ranges(editor, cx),
 1839            &[
 1840                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1841                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1842            ]
 1843        );
 1844    });
 1845
 1846    _ = editor.update(cx, |editor, window, cx| {
 1847        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1848        assert_eq!(editor.display_text(cx), "ab\n  de");
 1849        assert_eq!(
 1850            display_ranges(editor, cx),
 1851            &[
 1852                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1853                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1854            ]
 1855        );
 1856    });
 1857
 1858    _ = editor.update(cx, |editor, window, cx| {
 1859        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1860        assert_eq!(editor.display_text(cx), "\n");
 1861        assert_eq!(
 1862            display_ranges(editor, cx),
 1863            &[
 1864                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1865                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1866            ]
 1867        );
 1868    });
 1869}
 1870
 1871#[gpui::test]
 1872fn test_beginning_of_line_single_line_editor(cx: &mut TestAppContext) {
 1873    init_test(cx, |_| {});
 1874
 1875    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
 1876
 1877    _ = editor.update(cx, |editor, window, cx| {
 1878        editor.set_text("  indented text", window, cx);
 1879        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1880            s.select_display_ranges([
 1881                DisplayPoint::new(DisplayRow(0), 10)..DisplayPoint::new(DisplayRow(0), 10)
 1882            ]);
 1883        });
 1884
 1885        editor.move_to_beginning_of_line(
 1886            &MoveToBeginningOfLine {
 1887                stop_at_soft_wraps: true,
 1888                stop_at_indent: true,
 1889            },
 1890            window,
 1891            cx,
 1892        );
 1893        assert_eq!(
 1894            display_ranges(editor, cx),
 1895            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1896        );
 1897    });
 1898
 1899    _ = editor.update(cx, |editor, window, cx| {
 1900        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1901            s.select_display_ranges([
 1902                DisplayPoint::new(DisplayRow(0), 10)..DisplayPoint::new(DisplayRow(0), 10)
 1903            ]);
 1904        });
 1905
 1906        editor.select_to_beginning_of_line(
 1907            &SelectToBeginningOfLine {
 1908                stop_at_soft_wraps: true,
 1909                stop_at_indent: true,
 1910            },
 1911            window,
 1912            cx,
 1913        );
 1914        assert_eq!(
 1915            display_ranges(editor, cx),
 1916            &[DisplayPoint::new(DisplayRow(0), 10)..DisplayPoint::new(DisplayRow(0), 0)]
 1917        );
 1918    });
 1919}
 1920
 1921#[gpui::test]
 1922fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1923    init_test(cx, |_| {});
 1924    let move_to_beg = MoveToBeginningOfLine {
 1925        stop_at_soft_wraps: false,
 1926        stop_at_indent: false,
 1927    };
 1928
 1929    let move_to_end = MoveToEndOfLine {
 1930        stop_at_soft_wraps: false,
 1931    };
 1932
 1933    let editor = cx.add_window(|window, cx| {
 1934        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1935        build_editor(buffer, window, cx)
 1936    });
 1937
 1938    _ = editor.update(cx, |editor, window, cx| {
 1939        editor.set_wrap_width(Some(140.0.into()), cx);
 1940
 1941        // We expect the following lines after wrapping
 1942        // ```
 1943        // thequickbrownfox
 1944        // jumpedoverthelazydo
 1945        // gs
 1946        // ```
 1947        // The final `gs` was soft-wrapped onto a new line.
 1948        assert_eq!(
 1949            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1950            editor.display_text(cx),
 1951        );
 1952
 1953        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1954        // Start the cursor at the `k` on the first line
 1955        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1956            s.select_display_ranges([
 1957                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1958            ]);
 1959        });
 1960
 1961        // Moving to the beginning of the line should put us at the beginning of the line.
 1962        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1963        assert_eq!(
 1964            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1965            display_ranges(editor, cx)
 1966        );
 1967
 1968        // Moving to the end of the line should put us at the end of the line.
 1969        editor.move_to_end_of_line(&move_to_end, window, cx);
 1970        assert_eq!(
 1971            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1972            display_ranges(editor, cx)
 1973        );
 1974
 1975        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1976        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1977        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1978            s.select_display_ranges([
 1979                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1980            ]);
 1981        });
 1982
 1983        // Moving to the beginning of the line should put us at the start of the second line of
 1984        // display text, i.e., the `j`.
 1985        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1986        assert_eq!(
 1987            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1988            display_ranges(editor, cx)
 1989        );
 1990
 1991        // Moving to the beginning of the line again should be a no-op.
 1992        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1993        assert_eq!(
 1994            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1995            display_ranges(editor, cx)
 1996        );
 1997
 1998        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1999        // next display line.
 2000        editor.move_to_end_of_line(&move_to_end, window, cx);
 2001        assert_eq!(
 2002            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 2003            display_ranges(editor, cx)
 2004        );
 2005
 2006        // Moving to the end of the line again should be a no-op.
 2007        editor.move_to_end_of_line(&move_to_end, window, cx);
 2008        assert_eq!(
 2009            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 2010            display_ranges(editor, cx)
 2011        );
 2012    });
 2013}
 2014
 2015#[gpui::test]
 2016fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 2017    init_test(cx, |_| {});
 2018
 2019    let move_to_beg = MoveToBeginningOfLine {
 2020        stop_at_soft_wraps: true,
 2021        stop_at_indent: true,
 2022    };
 2023
 2024    let select_to_beg = SelectToBeginningOfLine {
 2025        stop_at_soft_wraps: true,
 2026        stop_at_indent: true,
 2027    };
 2028
 2029    let delete_to_beg = DeleteToBeginningOfLine {
 2030        stop_at_indent: true,
 2031    };
 2032
 2033    let move_to_end = MoveToEndOfLine {
 2034        stop_at_soft_wraps: false,
 2035    };
 2036
 2037    let editor = cx.add_window(|window, cx| {
 2038        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 2039        build_editor(buffer, window, cx)
 2040    });
 2041
 2042    _ = editor.update(cx, |editor, window, cx| {
 2043        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2044            s.select_display_ranges([
 2045                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 2046                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 2047            ]);
 2048        });
 2049
 2050        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 2051        // and the second cursor at the first non-whitespace character in the line.
 2052        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2053        assert_eq!(
 2054            display_ranges(editor, cx),
 2055            &[
 2056                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2057                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2058            ]
 2059        );
 2060
 2061        // Moving to the beginning of the line again should be a no-op for the first cursor,
 2062        // and should move the second cursor to the beginning of the line.
 2063        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2064        assert_eq!(
 2065            display_ranges(editor, cx),
 2066            &[
 2067                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2068                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2069            ]
 2070        );
 2071
 2072        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2073        // and should move the second cursor back to the first non-whitespace character in the line.
 2074        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2075        assert_eq!(
 2076            display_ranges(editor, cx),
 2077            &[
 2078                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2079                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2080            ]
 2081        );
 2082
 2083        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2084        // and to the first non-whitespace character in the line for the second cursor.
 2085        editor.move_to_end_of_line(&move_to_end, window, cx);
 2086        editor.move_left(&MoveLeft, window, cx);
 2087        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2088        assert_eq!(
 2089            display_ranges(editor, cx),
 2090            &[
 2091                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2092                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2093            ]
 2094        );
 2095
 2096        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2097        // and should select to the beginning of the line for the second cursor.
 2098        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2099        assert_eq!(
 2100            display_ranges(editor, cx),
 2101            &[
 2102                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2103                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2104            ]
 2105        );
 2106
 2107        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2108        // and should delete to the first non-whitespace character in the line for the second cursor.
 2109        editor.move_to_end_of_line(&move_to_end, window, cx);
 2110        editor.move_left(&MoveLeft, window, cx);
 2111        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2112        assert_eq!(editor.text(cx), "c\n  f");
 2113    });
 2114}
 2115
 2116#[gpui::test]
 2117fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2118    init_test(cx, |_| {});
 2119
 2120    let move_to_beg = MoveToBeginningOfLine {
 2121        stop_at_soft_wraps: true,
 2122        stop_at_indent: true,
 2123    };
 2124
 2125    let editor = cx.add_window(|window, cx| {
 2126        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2127        build_editor(buffer, window, cx)
 2128    });
 2129
 2130    _ = editor.update(cx, |editor, window, cx| {
 2131        // test cursor between line_start and indent_start
 2132        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2133            s.select_display_ranges([
 2134                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2135            ]);
 2136        });
 2137
 2138        // cursor should move to line_start
 2139        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2140        assert_eq!(
 2141            display_ranges(editor, cx),
 2142            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2143        );
 2144
 2145        // cursor should move to indent_start
 2146        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2147        assert_eq!(
 2148            display_ranges(editor, cx),
 2149            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2150        );
 2151
 2152        // cursor should move to back to line_start
 2153        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2154        assert_eq!(
 2155            display_ranges(editor, cx),
 2156            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2157        );
 2158    });
 2159}
 2160
 2161#[gpui::test]
 2162fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2163    init_test(cx, |_| {});
 2164
 2165    let editor = cx.add_window(|window, cx| {
 2166        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2167        build_editor(buffer, window, cx)
 2168    });
 2169    _ = editor.update(cx, |editor, window, cx| {
 2170        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2171            s.select_display_ranges([
 2172                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2173                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2174            ])
 2175        });
 2176        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2177        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2178
 2179        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2180        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2181
 2182        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2183        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2184
 2185        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2186        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2187
 2188        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2189        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2190
 2191        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2192        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2193
 2194        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2195        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2196
 2197        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2198        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2199
 2200        editor.move_right(&MoveRight, window, cx);
 2201        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2202        assert_selection_ranges(
 2203            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2204            editor,
 2205            cx,
 2206        );
 2207
 2208        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2209        assert_selection_ranges(
 2210            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2211            editor,
 2212            cx,
 2213        );
 2214
 2215        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2216        assert_selection_ranges(
 2217            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2218            editor,
 2219            cx,
 2220        );
 2221    });
 2222}
 2223
 2224#[gpui::test]
 2225fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2226    init_test(cx, |_| {});
 2227
 2228    let editor = cx.add_window(|window, cx| {
 2229        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2230        build_editor(buffer, window, cx)
 2231    });
 2232
 2233    _ = editor.update(cx, |editor, window, cx| {
 2234        editor.set_wrap_width(Some(140.0.into()), cx);
 2235        assert_eq!(
 2236            editor.display_text(cx),
 2237            "use one::{\n    two::three::\n    four::five\n};"
 2238        );
 2239
 2240        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2241            s.select_display_ranges([
 2242                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2243            ]);
 2244        });
 2245
 2246        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2247        assert_eq!(
 2248            display_ranges(editor, cx),
 2249            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2250        );
 2251
 2252        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2253        assert_eq!(
 2254            display_ranges(editor, cx),
 2255            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2256        );
 2257
 2258        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2259        assert_eq!(
 2260            display_ranges(editor, cx),
 2261            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2262        );
 2263
 2264        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2265        assert_eq!(
 2266            display_ranges(editor, cx),
 2267            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2268        );
 2269
 2270        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2271        assert_eq!(
 2272            display_ranges(editor, cx),
 2273            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2274        );
 2275
 2276        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2277        assert_eq!(
 2278            display_ranges(editor, cx),
 2279            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2280        );
 2281    });
 2282}
 2283
 2284#[gpui::test]
 2285async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2286    init_test(cx, |_| {});
 2287    let mut cx = EditorTestContext::new(cx).await;
 2288
 2289    let line_height = cx.update_editor(|editor, window, cx| {
 2290        editor
 2291            .style(cx)
 2292            .text
 2293            .line_height_in_pixels(window.rem_size())
 2294    });
 2295    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2296
 2297    // The third line only contains a single space so we can later assert that the
 2298    // editor's paragraph movement considers a non-blank line as a paragraph
 2299    // boundary.
 2300    cx.set_state(&"ˇone\ntwo\n \nthree\nfourˇ\nfive\n\nsix");
 2301
 2302    cx.update_editor(|editor, window, cx| {
 2303        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2304    });
 2305    cx.assert_editor_state(&"one\ntwo\nˇ \nthree\nfour\nfive\nˇ\nsix");
 2306
 2307    cx.update_editor(|editor, window, cx| {
 2308        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2309    });
 2310    cx.assert_editor_state(&"one\ntwo\n \nthree\nfour\nfive\nˇ\nsixˇ");
 2311
 2312    cx.update_editor(|editor, window, cx| {
 2313        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2314    });
 2315    cx.assert_editor_state(&"one\ntwo\n \nthree\nfour\nfive\n\nsixˇ");
 2316
 2317    cx.update_editor(|editor, window, cx| {
 2318        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2319    });
 2320    cx.assert_editor_state(&"one\ntwo\n \nthree\nfour\nfive\nˇ\nsix");
 2321
 2322    cx.update_editor(|editor, window, cx| {
 2323        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2324    });
 2325
 2326    cx.assert_editor_state(&"one\ntwo\nˇ \nthree\nfour\nfive\n\nsix");
 2327
 2328    cx.update_editor(|editor, window, cx| {
 2329        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2330    });
 2331    cx.assert_editor_state(&"ˇone\ntwo\n \nthree\nfour\nfive\n\nsix");
 2332}
 2333
 2334#[gpui::test]
 2335async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2336    init_test(cx, |_| {});
 2337    let mut cx = EditorTestContext::new(cx).await;
 2338    let line_height = cx.update_editor(|editor, window, cx| {
 2339        editor
 2340            .style(cx)
 2341            .text
 2342            .line_height_in_pixels(window.rem_size())
 2343    });
 2344    let window = cx.window;
 2345    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2346
 2347    cx.set_state(
 2348        r#"ˇone
 2349        two
 2350        three
 2351        four
 2352        five
 2353        six
 2354        seven
 2355        eight
 2356        nine
 2357        ten
 2358        "#,
 2359    );
 2360
 2361    cx.update_editor(|editor, window, cx| {
 2362        assert_eq!(
 2363            editor.snapshot(window, cx).scroll_position(),
 2364            gpui::Point::new(0., 0.)
 2365        );
 2366        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2367        assert_eq!(
 2368            editor.snapshot(window, cx).scroll_position(),
 2369            gpui::Point::new(0., 3.)
 2370        );
 2371        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2372        assert_eq!(
 2373            editor.snapshot(window, cx).scroll_position(),
 2374            gpui::Point::new(0., 6.)
 2375        );
 2376        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2377        assert_eq!(
 2378            editor.snapshot(window, cx).scroll_position(),
 2379            gpui::Point::new(0., 3.)
 2380        );
 2381
 2382        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2383        assert_eq!(
 2384            editor.snapshot(window, cx).scroll_position(),
 2385            gpui::Point::new(0., 1.)
 2386        );
 2387        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2388        assert_eq!(
 2389            editor.snapshot(window, cx).scroll_position(),
 2390            gpui::Point::new(0., 3.)
 2391        );
 2392    });
 2393}
 2394
 2395#[gpui::test]
 2396async fn test_autoscroll(cx: &mut TestAppContext) {
 2397    init_test(cx, |_| {});
 2398    let mut cx = EditorTestContext::new(cx).await;
 2399
 2400    let line_height = cx.update_editor(|editor, window, cx| {
 2401        editor.set_vertical_scroll_margin(2, cx);
 2402        editor
 2403            .style(cx)
 2404            .text
 2405            .line_height_in_pixels(window.rem_size())
 2406    });
 2407    let window = cx.window;
 2408    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2409
 2410    cx.set_state(
 2411        r#"ˇone
 2412            two
 2413            three
 2414            four
 2415            five
 2416            six
 2417            seven
 2418            eight
 2419            nine
 2420            ten
 2421        "#,
 2422    );
 2423    cx.update_editor(|editor, window, cx| {
 2424        assert_eq!(
 2425            editor.snapshot(window, cx).scroll_position(),
 2426            gpui::Point::new(0., 0.0)
 2427        );
 2428    });
 2429
 2430    // Add a cursor below the visible area. Since both cursors cannot fit
 2431    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2432    // allows the vertical scroll margin below that cursor.
 2433    cx.update_editor(|editor, window, cx| {
 2434        editor.change_selections(Default::default(), window, cx, |selections| {
 2435            selections.select_ranges([
 2436                Point::new(0, 0)..Point::new(0, 0),
 2437                Point::new(6, 0)..Point::new(6, 0),
 2438            ]);
 2439        })
 2440    });
 2441    cx.update_editor(|editor, window, cx| {
 2442        assert_eq!(
 2443            editor.snapshot(window, cx).scroll_position(),
 2444            gpui::Point::new(0., 3.0)
 2445        );
 2446    });
 2447
 2448    // Move down. The editor cursor scrolls down to track the newest cursor.
 2449    cx.update_editor(|editor, window, cx| {
 2450        editor.move_down(&Default::default(), window, cx);
 2451    });
 2452    cx.update_editor(|editor, window, cx| {
 2453        assert_eq!(
 2454            editor.snapshot(window, cx).scroll_position(),
 2455            gpui::Point::new(0., 4.0)
 2456        );
 2457    });
 2458
 2459    // Add a cursor above the visible area. Since both cursors fit on screen,
 2460    // the editor scrolls to show both.
 2461    cx.update_editor(|editor, window, cx| {
 2462        editor.change_selections(Default::default(), window, cx, |selections| {
 2463            selections.select_ranges([
 2464                Point::new(1, 0)..Point::new(1, 0),
 2465                Point::new(6, 0)..Point::new(6, 0),
 2466            ]);
 2467        })
 2468    });
 2469    cx.update_editor(|editor, window, cx| {
 2470        assert_eq!(
 2471            editor.snapshot(window, cx).scroll_position(),
 2472            gpui::Point::new(0., 1.0)
 2473        );
 2474    });
 2475}
 2476
 2477#[gpui::test]
 2478async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2479    init_test(cx, |_| {});
 2480    let mut cx = EditorTestContext::new(cx).await;
 2481
 2482    let line_height = cx.update_editor(|editor, window, cx| {
 2483        editor
 2484            .style(cx)
 2485            .text
 2486            .line_height_in_pixels(window.rem_size())
 2487    });
 2488    let window = cx.window;
 2489    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2490    cx.set_state(
 2491        &r#"
 2492        ˇone
 2493        two
 2494        threeˇ
 2495        four
 2496        five
 2497        six
 2498        seven
 2499        eight
 2500        nine
 2501        ten
 2502        "#
 2503        .unindent(),
 2504    );
 2505
 2506    cx.update_editor(|editor, window, cx| {
 2507        editor.move_page_down(&MovePageDown::default(), window, cx)
 2508    });
 2509    cx.assert_editor_state(
 2510        &r#"
 2511        one
 2512        two
 2513        three
 2514        ˇfour
 2515        five
 2516        sixˇ
 2517        seven
 2518        eight
 2519        nine
 2520        ten
 2521        "#
 2522        .unindent(),
 2523    );
 2524
 2525    cx.update_editor(|editor, window, cx| {
 2526        editor.move_page_down(&MovePageDown::default(), window, cx)
 2527    });
 2528    cx.assert_editor_state(
 2529        &r#"
 2530        one
 2531        two
 2532        three
 2533        four
 2534        five
 2535        six
 2536        ˇseven
 2537        eight
 2538        nineˇ
 2539        ten
 2540        "#
 2541        .unindent(),
 2542    );
 2543
 2544    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2545    cx.assert_editor_state(
 2546        &r#"
 2547        one
 2548        two
 2549        three
 2550        ˇfour
 2551        five
 2552        sixˇ
 2553        seven
 2554        eight
 2555        nine
 2556        ten
 2557        "#
 2558        .unindent(),
 2559    );
 2560
 2561    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2562    cx.assert_editor_state(
 2563        &r#"
 2564        ˇone
 2565        two
 2566        threeˇ
 2567        four
 2568        five
 2569        six
 2570        seven
 2571        eight
 2572        nine
 2573        ten
 2574        "#
 2575        .unindent(),
 2576    );
 2577
 2578    // Test select collapsing
 2579    cx.update_editor(|editor, window, cx| {
 2580        editor.move_page_down(&MovePageDown::default(), window, cx);
 2581        editor.move_page_down(&MovePageDown::default(), window, cx);
 2582        editor.move_page_down(&MovePageDown::default(), window, cx);
 2583    });
 2584    cx.assert_editor_state(
 2585        &r#"
 2586        one
 2587        two
 2588        three
 2589        four
 2590        five
 2591        six
 2592        seven
 2593        eight
 2594        nine
 2595        ˇten
 2596        ˇ"#
 2597        .unindent(),
 2598    );
 2599}
 2600
 2601#[gpui::test]
 2602async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2603    init_test(cx, |_| {});
 2604    let mut cx = EditorTestContext::new(cx).await;
 2605    cx.set_state("one «two threeˇ» four");
 2606    cx.update_editor(|editor, window, cx| {
 2607        editor.delete_to_beginning_of_line(
 2608            &DeleteToBeginningOfLine {
 2609                stop_at_indent: false,
 2610            },
 2611            window,
 2612            cx,
 2613        );
 2614        assert_eq!(editor.text(cx), " four");
 2615    });
 2616}
 2617
 2618#[gpui::test]
 2619async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2620    init_test(cx, |_| {});
 2621
 2622    let mut cx = EditorTestContext::new(cx).await;
 2623
 2624    // For an empty selection, the preceding word fragment is deleted.
 2625    // For non-empty selections, only selected characters are deleted.
 2626    cx.set_state("onˇe two t«hreˇ»e four");
 2627    cx.update_editor(|editor, window, cx| {
 2628        editor.delete_to_previous_word_start(
 2629            &DeleteToPreviousWordStart {
 2630                ignore_newlines: false,
 2631                ignore_brackets: false,
 2632            },
 2633            window,
 2634            cx,
 2635        );
 2636    });
 2637    cx.assert_editor_state("ˇe two tˇe four");
 2638
 2639    cx.set_state("e tˇwo te «fˇ»our");
 2640    cx.update_editor(|editor, window, cx| {
 2641        editor.delete_to_next_word_end(
 2642            &DeleteToNextWordEnd {
 2643                ignore_newlines: false,
 2644                ignore_brackets: false,
 2645            },
 2646            window,
 2647            cx,
 2648        );
 2649    });
 2650    cx.assert_editor_state("e tˇ te ˇour");
 2651}
 2652
 2653#[gpui::test]
 2654async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2655    init_test(cx, |_| {});
 2656
 2657    let mut cx = EditorTestContext::new(cx).await;
 2658
 2659    cx.set_state("here is some text    ˇwith a space");
 2660    cx.update_editor(|editor, window, cx| {
 2661        editor.delete_to_previous_word_start(
 2662            &DeleteToPreviousWordStart {
 2663                ignore_newlines: false,
 2664                ignore_brackets: true,
 2665            },
 2666            window,
 2667            cx,
 2668        );
 2669    });
 2670    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2671    cx.assert_editor_state("here is some textˇwith a space");
 2672
 2673    cx.set_state("here is some text    ˇwith a space");
 2674    cx.update_editor(|editor, window, cx| {
 2675        editor.delete_to_previous_word_start(
 2676            &DeleteToPreviousWordStart {
 2677                ignore_newlines: false,
 2678                ignore_brackets: false,
 2679            },
 2680            window,
 2681            cx,
 2682        );
 2683    });
 2684    cx.assert_editor_state("here is some textˇwith a space");
 2685
 2686    cx.set_state("here is some textˇ    with a space");
 2687    cx.update_editor(|editor, window, cx| {
 2688        editor.delete_to_next_word_end(
 2689            &DeleteToNextWordEnd {
 2690                ignore_newlines: false,
 2691                ignore_brackets: true,
 2692            },
 2693            window,
 2694            cx,
 2695        );
 2696    });
 2697    // Same happens in the other direction.
 2698    cx.assert_editor_state("here is some textˇwith a space");
 2699
 2700    cx.set_state("here is some textˇ    with a space");
 2701    cx.update_editor(|editor, window, cx| {
 2702        editor.delete_to_next_word_end(
 2703            &DeleteToNextWordEnd {
 2704                ignore_newlines: false,
 2705                ignore_brackets: false,
 2706            },
 2707            window,
 2708            cx,
 2709        );
 2710    });
 2711    cx.assert_editor_state("here is some textˇwith a space");
 2712
 2713    cx.set_state("here is some textˇ    with a space");
 2714    cx.update_editor(|editor, window, cx| {
 2715        editor.delete_to_next_word_end(
 2716            &DeleteToNextWordEnd {
 2717                ignore_newlines: true,
 2718                ignore_brackets: false,
 2719            },
 2720            window,
 2721            cx,
 2722        );
 2723    });
 2724    cx.assert_editor_state("here is some textˇwith a space");
 2725    cx.update_editor(|editor, window, cx| {
 2726        editor.delete_to_previous_word_start(
 2727            &DeleteToPreviousWordStart {
 2728                ignore_newlines: true,
 2729                ignore_brackets: false,
 2730            },
 2731            window,
 2732            cx,
 2733        );
 2734    });
 2735    cx.assert_editor_state("here is some ˇwith a space");
 2736    cx.update_editor(|editor, window, cx| {
 2737        editor.delete_to_previous_word_start(
 2738            &DeleteToPreviousWordStart {
 2739                ignore_newlines: true,
 2740                ignore_brackets: false,
 2741            },
 2742            window,
 2743            cx,
 2744        );
 2745    });
 2746    // Single whitespaces are removed with the word behind them.
 2747    cx.assert_editor_state("here is ˇwith a space");
 2748    cx.update_editor(|editor, window, cx| {
 2749        editor.delete_to_previous_word_start(
 2750            &DeleteToPreviousWordStart {
 2751                ignore_newlines: true,
 2752                ignore_brackets: false,
 2753            },
 2754            window,
 2755            cx,
 2756        );
 2757    });
 2758    cx.assert_editor_state("here ˇwith a space");
 2759    cx.update_editor(|editor, window, cx| {
 2760        editor.delete_to_previous_word_start(
 2761            &DeleteToPreviousWordStart {
 2762                ignore_newlines: true,
 2763                ignore_brackets: false,
 2764            },
 2765            window,
 2766            cx,
 2767        );
 2768    });
 2769    cx.assert_editor_state("ˇwith a space");
 2770    cx.update_editor(|editor, window, cx| {
 2771        editor.delete_to_previous_word_start(
 2772            &DeleteToPreviousWordStart {
 2773                ignore_newlines: true,
 2774                ignore_brackets: false,
 2775            },
 2776            window,
 2777            cx,
 2778        );
 2779    });
 2780    cx.assert_editor_state("ˇwith a space");
 2781    cx.update_editor(|editor, window, cx| {
 2782        editor.delete_to_next_word_end(
 2783            &DeleteToNextWordEnd {
 2784                ignore_newlines: true,
 2785                ignore_brackets: false,
 2786            },
 2787            window,
 2788            cx,
 2789        );
 2790    });
 2791    // Same happens in the other direction.
 2792    cx.assert_editor_state("ˇ a space");
 2793    cx.update_editor(|editor, window, cx| {
 2794        editor.delete_to_next_word_end(
 2795            &DeleteToNextWordEnd {
 2796                ignore_newlines: true,
 2797                ignore_brackets: false,
 2798            },
 2799            window,
 2800            cx,
 2801        );
 2802    });
 2803    cx.assert_editor_state("ˇ space");
 2804    cx.update_editor(|editor, window, cx| {
 2805        editor.delete_to_next_word_end(
 2806            &DeleteToNextWordEnd {
 2807                ignore_newlines: true,
 2808                ignore_brackets: false,
 2809            },
 2810            window,
 2811            cx,
 2812        );
 2813    });
 2814    cx.assert_editor_state("ˇ");
 2815    cx.update_editor(|editor, window, cx| {
 2816        editor.delete_to_next_word_end(
 2817            &DeleteToNextWordEnd {
 2818                ignore_newlines: true,
 2819                ignore_brackets: false,
 2820            },
 2821            window,
 2822            cx,
 2823        );
 2824    });
 2825    cx.assert_editor_state("ˇ");
 2826    cx.update_editor(|editor, window, cx| {
 2827        editor.delete_to_previous_word_start(
 2828            &DeleteToPreviousWordStart {
 2829                ignore_newlines: true,
 2830                ignore_brackets: false,
 2831            },
 2832            window,
 2833            cx,
 2834        );
 2835    });
 2836    cx.assert_editor_state("ˇ");
 2837}
 2838
 2839#[gpui::test]
 2840async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2841    init_test(cx, |_| {});
 2842
 2843    let language = Arc::new(
 2844        Language::new(
 2845            LanguageConfig {
 2846                brackets: BracketPairConfig {
 2847                    pairs: vec![
 2848                        BracketPair {
 2849                            start: "\"".to_string(),
 2850                            end: "\"".to_string(),
 2851                            close: true,
 2852                            surround: true,
 2853                            newline: false,
 2854                        },
 2855                        BracketPair {
 2856                            start: "(".to_string(),
 2857                            end: ")".to_string(),
 2858                            close: true,
 2859                            surround: true,
 2860                            newline: true,
 2861                        },
 2862                    ],
 2863                    ..BracketPairConfig::default()
 2864                },
 2865                ..LanguageConfig::default()
 2866            },
 2867            Some(tree_sitter_rust::LANGUAGE.into()),
 2868        )
 2869        .with_brackets_query(
 2870            r#"
 2871                ("(" @open ")" @close)
 2872                ("\"" @open "\"" @close)
 2873            "#,
 2874        )
 2875        .unwrap(),
 2876    );
 2877
 2878    let mut cx = EditorTestContext::new(cx).await;
 2879    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2880
 2881    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2882    cx.update_editor(|editor, window, cx| {
 2883        editor.delete_to_previous_word_start(
 2884            &DeleteToPreviousWordStart {
 2885                ignore_newlines: true,
 2886                ignore_brackets: false,
 2887            },
 2888            window,
 2889            cx,
 2890        );
 2891    });
 2892    // Deletion stops before brackets if asked to not ignore them.
 2893    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2894    cx.update_editor(|editor, window, cx| {
 2895        editor.delete_to_previous_word_start(
 2896            &DeleteToPreviousWordStart {
 2897                ignore_newlines: true,
 2898                ignore_brackets: false,
 2899            },
 2900            window,
 2901            cx,
 2902        );
 2903    });
 2904    // Deletion has to remove a single bracket and then stop again.
 2905    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2906
 2907    cx.update_editor(|editor, window, cx| {
 2908        editor.delete_to_previous_word_start(
 2909            &DeleteToPreviousWordStart {
 2910                ignore_newlines: true,
 2911                ignore_brackets: false,
 2912            },
 2913            window,
 2914            cx,
 2915        );
 2916    });
 2917    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2918
 2919    cx.update_editor(|editor, window, cx| {
 2920        editor.delete_to_previous_word_start(
 2921            &DeleteToPreviousWordStart {
 2922                ignore_newlines: true,
 2923                ignore_brackets: false,
 2924            },
 2925            window,
 2926            cx,
 2927        );
 2928    });
 2929    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2930
 2931    cx.update_editor(|editor, window, cx| {
 2932        editor.delete_to_previous_word_start(
 2933            &DeleteToPreviousWordStart {
 2934                ignore_newlines: true,
 2935                ignore_brackets: false,
 2936            },
 2937            window,
 2938            cx,
 2939        );
 2940    });
 2941    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2942
 2943    cx.update_editor(|editor, window, cx| {
 2944        editor.delete_to_next_word_end(
 2945            &DeleteToNextWordEnd {
 2946                ignore_newlines: true,
 2947                ignore_brackets: false,
 2948            },
 2949            window,
 2950            cx,
 2951        );
 2952    });
 2953    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2954    cx.assert_editor_state(r#"ˇ");"#);
 2955
 2956    cx.update_editor(|editor, window, cx| {
 2957        editor.delete_to_next_word_end(
 2958            &DeleteToNextWordEnd {
 2959                ignore_newlines: true,
 2960                ignore_brackets: false,
 2961            },
 2962            window,
 2963            cx,
 2964        );
 2965    });
 2966    cx.assert_editor_state(r#"ˇ"#);
 2967
 2968    cx.update_editor(|editor, window, cx| {
 2969        editor.delete_to_next_word_end(
 2970            &DeleteToNextWordEnd {
 2971                ignore_newlines: true,
 2972                ignore_brackets: false,
 2973            },
 2974            window,
 2975            cx,
 2976        );
 2977    });
 2978    cx.assert_editor_state(r#"ˇ"#);
 2979
 2980    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2981    cx.update_editor(|editor, window, cx| {
 2982        editor.delete_to_previous_word_start(
 2983            &DeleteToPreviousWordStart {
 2984                ignore_newlines: true,
 2985                ignore_brackets: true,
 2986            },
 2987            window,
 2988            cx,
 2989        );
 2990    });
 2991    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2992}
 2993
 2994#[gpui::test]
 2995fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2996    init_test(cx, |_| {});
 2997
 2998    let editor = cx.add_window(|window, cx| {
 2999        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3000        build_editor(buffer, window, cx)
 3001    });
 3002    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3003        ignore_newlines: false,
 3004        ignore_brackets: false,
 3005    };
 3006    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3007        ignore_newlines: true,
 3008        ignore_brackets: false,
 3009    };
 3010
 3011    _ = editor.update(cx, |editor, window, cx| {
 3012        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3013            s.select_display_ranges([
 3014                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3015            ])
 3016        });
 3017        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3018        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3019        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3020        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3021        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3022        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3023        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3024        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3025        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3026        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3027        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3028        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3029    });
 3030}
 3031
 3032#[gpui::test]
 3033fn test_delete_to_previous_subword_start_or_newline(cx: &mut TestAppContext) {
 3034    init_test(cx, |_| {});
 3035
 3036    let editor = cx.add_window(|window, cx| {
 3037        let buffer = MultiBuffer::build_simple("fooBar\n\nbazQux", cx);
 3038        build_editor(buffer, window, cx)
 3039    });
 3040    let del_to_prev_sub_word_start = DeleteToPreviousSubwordStart {
 3041        ignore_newlines: false,
 3042        ignore_brackets: false,
 3043    };
 3044    let del_to_prev_sub_word_start_ignore_newlines = DeleteToPreviousSubwordStart {
 3045        ignore_newlines: true,
 3046        ignore_brackets: false,
 3047    };
 3048
 3049    _ = editor.update(cx, |editor, window, cx| {
 3050        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3051            s.select_display_ranges([
 3052                DisplayPoint::new(DisplayRow(2), 6)..DisplayPoint::new(DisplayRow(2), 6)
 3053            ])
 3054        });
 3055        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3056        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\nbaz");
 3057        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3058        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n\n");
 3059        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3060        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n");
 3061        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3062        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar");
 3063        editor.delete_to_previous_subword_start(&del_to_prev_sub_word_start, window, cx);
 3064        assert_eq!(editor.buffer.read(cx).read(cx).text(), "foo");
 3065        editor.delete_to_previous_subword_start(
 3066            &del_to_prev_sub_word_start_ignore_newlines,
 3067            window,
 3068            cx,
 3069        );
 3070        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3071    });
 3072}
 3073
 3074#[gpui::test]
 3075fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3076    init_test(cx, |_| {});
 3077
 3078    let editor = cx.add_window(|window, cx| {
 3079        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3080        build_editor(buffer, window, cx)
 3081    });
 3082    let del_to_next_word_end = DeleteToNextWordEnd {
 3083        ignore_newlines: false,
 3084        ignore_brackets: false,
 3085    };
 3086    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3087        ignore_newlines: true,
 3088        ignore_brackets: false,
 3089    };
 3090
 3091    _ = editor.update(cx, |editor, window, cx| {
 3092        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3093            s.select_display_ranges([
 3094                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3095            ])
 3096        });
 3097        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3098        assert_eq!(
 3099            editor.buffer.read(cx).read(cx).text(),
 3100            "one\n   two\nthree\n   four"
 3101        );
 3102        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3103        assert_eq!(
 3104            editor.buffer.read(cx).read(cx).text(),
 3105            "\n   two\nthree\n   four"
 3106        );
 3107        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3108        assert_eq!(
 3109            editor.buffer.read(cx).read(cx).text(),
 3110            "two\nthree\n   four"
 3111        );
 3112        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3113        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3114        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3115        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3116        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3117        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3118        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3119        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3120    });
 3121}
 3122
 3123#[gpui::test]
 3124fn test_delete_to_next_subword_end_or_newline(cx: &mut TestAppContext) {
 3125    init_test(cx, |_| {});
 3126
 3127    let editor = cx.add_window(|window, cx| {
 3128        let buffer = MultiBuffer::build_simple("\nfooBar\n   bazQux", cx);
 3129        build_editor(buffer, window, cx)
 3130    });
 3131    let del_to_next_subword_end = DeleteToNextSubwordEnd {
 3132        ignore_newlines: false,
 3133        ignore_brackets: false,
 3134    };
 3135    let del_to_next_subword_end_ignore_newlines = DeleteToNextSubwordEnd {
 3136        ignore_newlines: true,
 3137        ignore_brackets: false,
 3138    };
 3139
 3140    _ = editor.update(cx, |editor, window, cx| {
 3141        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3142            s.select_display_ranges([
 3143                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3144            ])
 3145        });
 3146        // Delete "\n" (empty line)
 3147        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3148        assert_eq!(editor.buffer.read(cx).read(cx).text(), "fooBar\n   bazQux");
 3149        // Delete "foo" (subword boundary)
 3150        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3151        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Bar\n   bazQux");
 3152        // Delete "Bar"
 3153        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3154        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   bazQux");
 3155        // Delete "\n   " (newline + leading whitespace)
 3156        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3157        assert_eq!(editor.buffer.read(cx).read(cx).text(), "bazQux");
 3158        // Delete "baz" (subword boundary)
 3159        editor.delete_to_next_subword_end(&del_to_next_subword_end, window, cx);
 3160        assert_eq!(editor.buffer.read(cx).read(cx).text(), "Qux");
 3161        // With ignore_newlines, delete "Qux"
 3162        editor.delete_to_next_subword_end(&del_to_next_subword_end_ignore_newlines, window, cx);
 3163        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3164    });
 3165}
 3166
 3167#[gpui::test]
 3168fn test_newline(cx: &mut TestAppContext) {
 3169    init_test(cx, |_| {});
 3170
 3171    let editor = cx.add_window(|window, cx| {
 3172        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3173        build_editor(buffer, window, cx)
 3174    });
 3175
 3176    _ = editor.update(cx, |editor, window, cx| {
 3177        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3178            s.select_display_ranges([
 3179                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3180                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3181                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3182            ])
 3183        });
 3184
 3185        editor.newline(&Newline, window, cx);
 3186        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3187    });
 3188}
 3189
 3190#[gpui::test]
 3191async fn test_newline_yaml(cx: &mut TestAppContext) {
 3192    init_test(cx, |_| {});
 3193
 3194    let mut cx = EditorTestContext::new(cx).await;
 3195    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3196    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3197
 3198    // Object (between 2 fields)
 3199    cx.set_state(indoc! {"
 3200    test:ˇ
 3201    hello: bye"});
 3202    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3203    cx.assert_editor_state(indoc! {"
 3204    test:
 3205        ˇ
 3206    hello: bye"});
 3207
 3208    // Object (first and single line)
 3209    cx.set_state(indoc! {"
 3210    test:ˇ"});
 3211    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3212    cx.assert_editor_state(indoc! {"
 3213    test:
 3214        ˇ"});
 3215
 3216    // Array with objects (after first element)
 3217    cx.set_state(indoc! {"
 3218    test:
 3219        - foo: barˇ"});
 3220    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3221    cx.assert_editor_state(indoc! {"
 3222    test:
 3223        - foo: bar
 3224        ˇ"});
 3225
 3226    // Array with objects and comment
 3227    cx.set_state(indoc! {"
 3228    test:
 3229        - foo: bar
 3230        - bar: # testˇ"});
 3231    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3232    cx.assert_editor_state(indoc! {"
 3233    test:
 3234        - foo: bar
 3235        - bar: # test
 3236            ˇ"});
 3237
 3238    // Array with objects (after second element)
 3239    cx.set_state(indoc! {"
 3240    test:
 3241        - foo: bar
 3242        - bar: fooˇ"});
 3243    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3244    cx.assert_editor_state(indoc! {"
 3245    test:
 3246        - foo: bar
 3247        - bar: foo
 3248        ˇ"});
 3249
 3250    // Array with strings (after first element)
 3251    cx.set_state(indoc! {"
 3252    test:
 3253        - fooˇ"});
 3254    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3255    cx.assert_editor_state(indoc! {"
 3256    test:
 3257        - foo
 3258        ˇ"});
 3259}
 3260
 3261#[gpui::test]
 3262fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3263    init_test(cx, |_| {});
 3264
 3265    let editor = cx.add_window(|window, cx| {
 3266        let buffer = MultiBuffer::build_simple(
 3267            "
 3268                a
 3269                b(
 3270                    X
 3271                )
 3272                c(
 3273                    X
 3274                )
 3275            "
 3276            .unindent()
 3277            .as_str(),
 3278            cx,
 3279        );
 3280        let mut editor = build_editor(buffer, window, cx);
 3281        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3282            s.select_ranges([
 3283                Point::new(2, 4)..Point::new(2, 5),
 3284                Point::new(5, 4)..Point::new(5, 5),
 3285            ])
 3286        });
 3287        editor
 3288    });
 3289
 3290    _ = editor.update(cx, |editor, window, cx| {
 3291        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3292        editor.buffer.update(cx, |buffer, cx| {
 3293            buffer.edit(
 3294                [
 3295                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3296                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3297                ],
 3298                None,
 3299                cx,
 3300            );
 3301            assert_eq!(
 3302                buffer.read(cx).text(),
 3303                "
 3304                    a
 3305                    b()
 3306                    c()
 3307                "
 3308                .unindent()
 3309            );
 3310        });
 3311        assert_eq!(
 3312            editor.selections.ranges(&editor.display_snapshot(cx)),
 3313            &[
 3314                Point::new(1, 2)..Point::new(1, 2),
 3315                Point::new(2, 2)..Point::new(2, 2),
 3316            ],
 3317        );
 3318
 3319        editor.newline(&Newline, window, cx);
 3320        assert_eq!(
 3321            editor.text(cx),
 3322            "
 3323                a
 3324                b(
 3325                )
 3326                c(
 3327                )
 3328            "
 3329            .unindent()
 3330        );
 3331
 3332        // The selections are moved after the inserted newlines
 3333        assert_eq!(
 3334            editor.selections.ranges(&editor.display_snapshot(cx)),
 3335            &[
 3336                Point::new(2, 0)..Point::new(2, 0),
 3337                Point::new(4, 0)..Point::new(4, 0),
 3338            ],
 3339        );
 3340    });
 3341}
 3342
 3343#[gpui::test]
 3344async fn test_newline_above(cx: &mut TestAppContext) {
 3345    init_test(cx, |settings| {
 3346        settings.defaults.tab_size = NonZeroU32::new(4)
 3347    });
 3348
 3349    let language = Arc::new(
 3350        Language::new(
 3351            LanguageConfig::default(),
 3352            Some(tree_sitter_rust::LANGUAGE.into()),
 3353        )
 3354        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3355        .unwrap(),
 3356    );
 3357
 3358    let mut cx = EditorTestContext::new(cx).await;
 3359    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3360    cx.set_state(indoc! {"
 3361        const a: ˇA = (
 3362 3363                «const_functionˇ»(ˇ),
 3364                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3365 3366        ˇ);ˇ
 3367    "});
 3368
 3369    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3370    cx.assert_editor_state(indoc! {"
 3371        ˇ
 3372        const a: A = (
 3373            ˇ
 3374            (
 3375                ˇ
 3376                ˇ
 3377                const_function(),
 3378                ˇ
 3379                ˇ
 3380                ˇ
 3381                ˇ
 3382                something_else,
 3383                ˇ
 3384            )
 3385            ˇ
 3386            ˇ
 3387        );
 3388    "});
 3389}
 3390
 3391#[gpui::test]
 3392async fn test_newline_below(cx: &mut TestAppContext) {
 3393    init_test(cx, |settings| {
 3394        settings.defaults.tab_size = NonZeroU32::new(4)
 3395    });
 3396
 3397    let language = Arc::new(
 3398        Language::new(
 3399            LanguageConfig::default(),
 3400            Some(tree_sitter_rust::LANGUAGE.into()),
 3401        )
 3402        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3403        .unwrap(),
 3404    );
 3405
 3406    let mut cx = EditorTestContext::new(cx).await;
 3407    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3408    cx.set_state(indoc! {"
 3409        const a: ˇA = (
 3410 3411                «const_functionˇ»(ˇ),
 3412                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3413 3414        ˇ);ˇ
 3415    "});
 3416
 3417    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3418    cx.assert_editor_state(indoc! {"
 3419        const a: A = (
 3420            ˇ
 3421            (
 3422                ˇ
 3423                const_function(),
 3424                ˇ
 3425                ˇ
 3426                something_else,
 3427                ˇ
 3428                ˇ
 3429                ˇ
 3430                ˇ
 3431            )
 3432            ˇ
 3433        );
 3434        ˇ
 3435        ˇ
 3436    "});
 3437}
 3438
 3439#[gpui::test]
 3440fn test_newline_respects_read_only(cx: &mut TestAppContext) {
 3441    init_test(cx, |_| {});
 3442
 3443    let editor = cx.add_window(|window, cx| {
 3444        let buffer = MultiBuffer::build_simple("aaaa\nbbbb\n", cx);
 3445        build_editor(buffer, window, cx)
 3446    });
 3447
 3448    _ = editor.update(cx, |editor, window, cx| {
 3449        editor.set_read_only(true);
 3450        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3451            s.select_display_ranges([
 3452                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2)
 3453            ])
 3454        });
 3455
 3456        editor.newline(&Newline, window, cx);
 3457        assert_eq!(
 3458            editor.text(cx),
 3459            "aaaa\nbbbb\n",
 3460            "newline should not modify a read-only editor"
 3461        );
 3462
 3463        editor.newline_above(&NewlineAbove, window, cx);
 3464        assert_eq!(
 3465            editor.text(cx),
 3466            "aaaa\nbbbb\n",
 3467            "newline_above should not modify a read-only editor"
 3468        );
 3469
 3470        editor.newline_below(&NewlineBelow, window, cx);
 3471        assert_eq!(
 3472            editor.text(cx),
 3473            "aaaa\nbbbb\n",
 3474            "newline_below should not modify a read-only editor"
 3475        );
 3476    });
 3477}
 3478
 3479#[gpui::test]
 3480fn test_newline_below_multibuffer(cx: &mut TestAppContext) {
 3481    init_test(cx, |_| {});
 3482
 3483    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
 3484    let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
 3485    let multibuffer = cx.new(|cx| {
 3486        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3487        multibuffer.set_excerpts_for_path(
 3488            PathKey::sorted(0),
 3489            buffer_1.clone(),
 3490            [Point::new(0, 0)..Point::new(2, 3)],
 3491            0,
 3492            cx,
 3493        );
 3494        multibuffer.set_excerpts_for_path(
 3495            PathKey::sorted(1),
 3496            buffer_2.clone(),
 3497            [Point::new(0, 0)..Point::new(2, 3)],
 3498            0,
 3499            cx,
 3500        );
 3501        multibuffer
 3502    });
 3503
 3504    cx.add_window(|window, cx| {
 3505        let mut editor = build_editor(multibuffer, window, cx);
 3506
 3507        assert_eq!(
 3508            editor.text(cx),
 3509            indoc! {"
 3510                aaa
 3511                bbb
 3512                ccc
 3513                ddd
 3514                eee
 3515                fff"}
 3516        );
 3517
 3518        // Cursor on the last line of the first excerpt.
 3519        // The newline should be inserted within the first excerpt (buffer_1),
 3520        // not in the second excerpt (buffer_2).
 3521        select_ranges(
 3522            &mut editor,
 3523            indoc! {"
 3524                aaa
 3525                bbb
 3526                cˇcc
 3527                ddd
 3528                eee
 3529                fff"},
 3530            window,
 3531            cx,
 3532        );
 3533        editor.newline_below(&NewlineBelow, window, cx);
 3534        assert_text_with_selections(
 3535            &mut editor,
 3536            indoc! {"
 3537                aaa
 3538                bbb
 3539                ccc
 3540                ˇ
 3541                ddd
 3542                eee
 3543                fff"},
 3544            cx,
 3545        );
 3546        buffer_1.read_with(cx, |buffer, _| {
 3547            assert_eq!(buffer.text(), "aaa\nbbb\nccc\n");
 3548        });
 3549        buffer_2.read_with(cx, |buffer, _| {
 3550            assert_eq!(buffer.text(), "ddd\neee\nfff");
 3551        });
 3552
 3553        editor
 3554    });
 3555}
 3556
 3557#[gpui::test]
 3558fn test_newline_below_multibuffer_middle_of_excerpt(cx: &mut TestAppContext) {
 3559    init_test(cx, |_| {});
 3560
 3561    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
 3562    let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
 3563    let multibuffer = cx.new(|cx| {
 3564        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3565        multibuffer.set_excerpts_for_path(
 3566            PathKey::sorted(0),
 3567            buffer_1.clone(),
 3568            [Point::new(0, 0)..Point::new(2, 3)],
 3569            0,
 3570            cx,
 3571        );
 3572        multibuffer.set_excerpts_for_path(
 3573            PathKey::sorted(1),
 3574            buffer_2.clone(),
 3575            [Point::new(0, 0)..Point::new(2, 3)],
 3576            0,
 3577            cx,
 3578        );
 3579        multibuffer
 3580    });
 3581
 3582    cx.add_window(|window, cx| {
 3583        let mut editor = build_editor(multibuffer, window, cx);
 3584
 3585        // Cursor in the middle of the first excerpt.
 3586        select_ranges(
 3587            &mut editor,
 3588            indoc! {"
 3589                aˇaa
 3590                bbb
 3591                ccc
 3592                ddd
 3593                eee
 3594                fff"},
 3595            window,
 3596            cx,
 3597        );
 3598        editor.newline_below(&NewlineBelow, window, cx);
 3599        assert_text_with_selections(
 3600            &mut editor,
 3601            indoc! {"
 3602                aaa
 3603                ˇ
 3604                bbb
 3605                ccc
 3606                ddd
 3607                eee
 3608                fff"},
 3609            cx,
 3610        );
 3611        buffer_1.read_with(cx, |buffer, _| {
 3612            assert_eq!(buffer.text(), "aaa\n\nbbb\nccc");
 3613        });
 3614        buffer_2.read_with(cx, |buffer, _| {
 3615            assert_eq!(buffer.text(), "ddd\neee\nfff");
 3616        });
 3617
 3618        editor
 3619    });
 3620}
 3621
 3622#[gpui::test]
 3623fn test_newline_below_multibuffer_last_line_of_last_excerpt(cx: &mut TestAppContext) {
 3624    init_test(cx, |_| {});
 3625
 3626    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
 3627    let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
 3628    let multibuffer = cx.new(|cx| {
 3629        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3630        multibuffer.set_excerpts_for_path(
 3631            PathKey::sorted(0),
 3632            buffer_1.clone(),
 3633            [Point::new(0, 0)..Point::new(2, 3)],
 3634            0,
 3635            cx,
 3636        );
 3637        multibuffer.set_excerpts_for_path(
 3638            PathKey::sorted(1),
 3639            buffer_2.clone(),
 3640            [Point::new(0, 0)..Point::new(2, 3)],
 3641            0,
 3642            cx,
 3643        );
 3644        multibuffer
 3645    });
 3646
 3647    cx.add_window(|window, cx| {
 3648        let mut editor = build_editor(multibuffer, window, cx);
 3649
 3650        // Cursor on the last line of the last excerpt.
 3651        select_ranges(
 3652            &mut editor,
 3653            indoc! {"
 3654                aaa
 3655                bbb
 3656                ccc
 3657                ddd
 3658                eee
 3659                fˇff"},
 3660            window,
 3661            cx,
 3662        );
 3663        editor.newline_below(&NewlineBelow, window, cx);
 3664        assert_text_with_selections(
 3665            &mut editor,
 3666            indoc! {"
 3667                aaa
 3668                bbb
 3669                ccc
 3670                ddd
 3671                eee
 3672                fff
 3673                ˇ"},
 3674            cx,
 3675        );
 3676        buffer_1.read_with(cx, |buffer, _| {
 3677            assert_eq!(buffer.text(), "aaa\nbbb\nccc");
 3678        });
 3679        buffer_2.read_with(cx, |buffer, _| {
 3680            assert_eq!(buffer.text(), "ddd\neee\nfff\n");
 3681        });
 3682
 3683        editor
 3684    });
 3685}
 3686
 3687#[gpui::test]
 3688fn test_newline_below_multibuffer_multiple_cursors(cx: &mut TestAppContext) {
 3689    init_test(cx, |_| {});
 3690
 3691    let buffer_1 = cx.new(|cx| Buffer::local("aaa\nbbb\nccc", cx));
 3692    let buffer_2 = cx.new(|cx| Buffer::local("ddd\neee\nfff", cx));
 3693    let multibuffer = cx.new(|cx| {
 3694        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3695        multibuffer.set_excerpts_for_path(
 3696            PathKey::sorted(0),
 3697            buffer_1.clone(),
 3698            [Point::new(0, 0)..Point::new(2, 3)],
 3699            0,
 3700            cx,
 3701        );
 3702        multibuffer.set_excerpts_for_path(
 3703            PathKey::sorted(1),
 3704            buffer_2.clone(),
 3705            [Point::new(0, 0)..Point::new(2, 3)],
 3706            0,
 3707            cx,
 3708        );
 3709        multibuffer
 3710    });
 3711
 3712    cx.add_window(|window, cx| {
 3713        let mut editor = build_editor(multibuffer, window, cx);
 3714
 3715        // Cursors on the last line of the first excerpt and the first line
 3716        // of the second excerpt. Each newline should go into its respective buffer.
 3717        select_ranges(
 3718            &mut editor,
 3719            indoc! {"
 3720                aaa
 3721                bbb
 3722                cˇcc
 3723                dˇdd
 3724                eee
 3725                fff"},
 3726            window,
 3727            cx,
 3728        );
 3729        editor.newline_below(&NewlineBelow, window, cx);
 3730        assert_text_with_selections(
 3731            &mut editor,
 3732            indoc! {"
 3733                aaa
 3734                bbb
 3735                ccc
 3736                ˇ
 3737                ddd
 3738                ˇ
 3739                eee
 3740                fff"},
 3741            cx,
 3742        );
 3743        buffer_1.read_with(cx, |buffer, _| {
 3744            assert_eq!(buffer.text(), "aaa\nbbb\nccc\n");
 3745        });
 3746        buffer_2.read_with(cx, |buffer, _| {
 3747            assert_eq!(buffer.text(), "ddd\n\neee\nfff");
 3748        });
 3749
 3750        editor
 3751    });
 3752}
 3753
 3754#[gpui::test]
 3755async fn test_newline_comments(cx: &mut TestAppContext) {
 3756    init_test(cx, |settings| {
 3757        settings.defaults.tab_size = NonZeroU32::new(4)
 3758    });
 3759
 3760    let language = Arc::new(Language::new(
 3761        LanguageConfig {
 3762            line_comments: vec!["// ".into()],
 3763            ..LanguageConfig::default()
 3764        },
 3765        None,
 3766    ));
 3767    {
 3768        let mut cx = EditorTestContext::new(cx).await;
 3769        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3770        cx.set_state(indoc! {"
 3771        // Fooˇ
 3772    "});
 3773
 3774        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3775        cx.assert_editor_state(indoc! {"
 3776        // Foo
 3777        // ˇ
 3778    "});
 3779        // Ensure that we add comment prefix when existing line contains space
 3780        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3781        cx.assert_editor_state(
 3782            indoc! {"
 3783        // Foo
 3784        //s
 3785        // ˇ
 3786    "}
 3787            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3788            .as_str(),
 3789        );
 3790        // Ensure that we add comment prefix when existing line does not contain space
 3791        cx.set_state(indoc! {"
 3792        // Foo
 3793        //ˇ
 3794    "});
 3795        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3796        cx.assert_editor_state(indoc! {"
 3797        // Foo
 3798        //
 3799        // ˇ
 3800    "});
 3801        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3802        cx.set_state(indoc! {"
 3803        ˇ// Foo
 3804    "});
 3805        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3806        cx.assert_editor_state(indoc! {"
 3807
 3808        ˇ// Foo
 3809    "});
 3810    }
 3811    // Ensure that comment continuations can be disabled.
 3812    update_test_language_settings(cx, &|settings| {
 3813        settings.defaults.extend_comment_on_newline = Some(false);
 3814    });
 3815    let mut cx = EditorTestContext::new(cx).await;
 3816    cx.set_state(indoc! {"
 3817        // Fooˇ
 3818    "});
 3819    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3820    cx.assert_editor_state(indoc! {"
 3821        // Foo
 3822        ˇ
 3823    "});
 3824}
 3825
 3826#[gpui::test]
 3827async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3828    init_test(cx, |settings| {
 3829        settings.defaults.tab_size = NonZeroU32::new(4)
 3830    });
 3831
 3832    let language = Arc::new(Language::new(
 3833        LanguageConfig {
 3834            line_comments: vec!["// ".into(), "/// ".into()],
 3835            ..LanguageConfig::default()
 3836        },
 3837        None,
 3838    ));
 3839    {
 3840        let mut cx = EditorTestContext::new(cx).await;
 3841        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3842        cx.set_state(indoc! {"
 3843        //ˇ
 3844    "});
 3845        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3846        cx.assert_editor_state(indoc! {"
 3847        //
 3848        // ˇ
 3849    "});
 3850
 3851        cx.set_state(indoc! {"
 3852        ///ˇ
 3853    "});
 3854        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3855        cx.assert_editor_state(indoc! {"
 3856        ///
 3857        /// ˇ
 3858    "});
 3859    }
 3860}
 3861
 3862#[gpui::test]
 3863async fn test_newline_comments_repl_separators(cx: &mut TestAppContext) {
 3864    init_test(cx, |settings| {
 3865        settings.defaults.tab_size = NonZeroU32::new(4)
 3866    });
 3867    let language = Arc::new(Language::new(
 3868        LanguageConfig {
 3869            line_comments: vec!["# ".into()],
 3870            ..LanguageConfig::default()
 3871        },
 3872        None,
 3873    ));
 3874
 3875    {
 3876        let mut cx = EditorTestContext::new(cx).await;
 3877        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3878        cx.set_state(indoc! {"
 3879        # %%ˇ
 3880    "});
 3881        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3882        cx.assert_editor_state(indoc! {"
 3883        # %%
 3884        ˇ
 3885    "});
 3886
 3887        cx.set_state(indoc! {"
 3888            # %%%%%ˇ
 3889    "});
 3890        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3891        cx.assert_editor_state(indoc! {"
 3892            # %%%%%
 3893            ˇ
 3894    "});
 3895
 3896        cx.set_state(indoc! {"
 3897            # %ˇ%
 3898    "});
 3899        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3900        cx.assert_editor_state(indoc! {"
 3901            # %
 3902            # ˇ%
 3903    "});
 3904    }
 3905}
 3906
 3907#[gpui::test]
 3908async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3909    init_test(cx, |settings| {
 3910        settings.defaults.tab_size = NonZeroU32::new(4)
 3911    });
 3912
 3913    let language = Arc::new(
 3914        Language::new(
 3915            LanguageConfig {
 3916                documentation_comment: Some(language::BlockCommentConfig {
 3917                    start: "/**".into(),
 3918                    end: "*/".into(),
 3919                    prefix: "* ".into(),
 3920                    tab_size: 1,
 3921                }),
 3922
 3923                ..LanguageConfig::default()
 3924            },
 3925            Some(tree_sitter_rust::LANGUAGE.into()),
 3926        )
 3927        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3928        .unwrap(),
 3929    );
 3930
 3931    {
 3932        let mut cx = EditorTestContext::new(cx).await;
 3933        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3934        cx.set_state(indoc! {"
 3935        /**ˇ
 3936    "});
 3937
 3938        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3939        cx.assert_editor_state(indoc! {"
 3940        /**
 3941         * ˇ
 3942    "});
 3943        // Ensure that if cursor is before the comment start,
 3944        // we do not actually insert a comment prefix.
 3945        cx.set_state(indoc! {"
 3946        ˇ/**
 3947    "});
 3948        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3949        cx.assert_editor_state(indoc! {"
 3950
 3951        ˇ/**
 3952    "});
 3953        // Ensure that if cursor is between it doesn't add comment prefix.
 3954        cx.set_state(indoc! {"
 3955        /*ˇ*
 3956    "});
 3957        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3958        cx.assert_editor_state(indoc! {"
 3959        /*
 3960        ˇ*
 3961    "});
 3962        // Ensure that if suffix exists on same line after cursor it adds new line.
 3963        cx.set_state(indoc! {"
 3964        /**ˇ*/
 3965    "});
 3966        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3967        cx.assert_editor_state(indoc! {"
 3968        /**
 3969         * ˇ
 3970         */
 3971    "});
 3972        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3973        cx.set_state(indoc! {"
 3974        /**ˇ */
 3975    "});
 3976        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3977        cx.assert_editor_state(indoc! {"
 3978        /**
 3979         * ˇ
 3980         */
 3981    "});
 3982        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3983        cx.set_state(indoc! {"
 3984        /** ˇ*/
 3985    "});
 3986        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3987        cx.assert_editor_state(
 3988            indoc! {"
 3989        /**s
 3990         * ˇ
 3991         */
 3992    "}
 3993            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3994            .as_str(),
 3995        );
 3996        // Ensure that delimiter space is preserved when newline on already
 3997        // spaced delimiter.
 3998        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3999        cx.assert_editor_state(
 4000            indoc! {"
 4001        /**s
 4002         *s
 4003         * ˇ
 4004         */
 4005    "}
 4006            .replace("s", " ") // s is used as space placeholder to prevent format on save
 4007            .as_str(),
 4008        );
 4009        // Ensure that delimiter space is preserved when space is not
 4010        // on existing delimiter.
 4011        cx.set_state(indoc! {"
 4012        /**
 4013 4014         */
 4015    "});
 4016        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4017        cx.assert_editor_state(indoc! {"
 4018        /**
 4019         *
 4020         * ˇ
 4021         */
 4022    "});
 4023        // Ensure that if suffix exists on same line after cursor it
 4024        // doesn't add extra new line if prefix is not on same line.
 4025        cx.set_state(indoc! {"
 4026        /**
 4027        ˇ*/
 4028    "});
 4029        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4030        cx.assert_editor_state(indoc! {"
 4031        /**
 4032
 4033        ˇ*/
 4034    "});
 4035        // Ensure that it detects suffix after existing prefix.
 4036        cx.set_state(indoc! {"
 4037        /**ˇ/
 4038    "});
 4039        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4040        cx.assert_editor_state(indoc! {"
 4041        /**
 4042        ˇ/
 4043    "});
 4044        // Ensure that if suffix exists on same line before
 4045        // cursor it does not add comment prefix.
 4046        cx.set_state(indoc! {"
 4047        /** */ˇ
 4048    "});
 4049        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4050        cx.assert_editor_state(indoc! {"
 4051        /** */
 4052        ˇ
 4053    "});
 4054        // Ensure that if suffix exists on same line before
 4055        // cursor it does not add comment prefix.
 4056        cx.set_state(indoc! {"
 4057        /**
 4058         *
 4059         */ˇ
 4060    "});
 4061        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4062        cx.assert_editor_state(indoc! {"
 4063        /**
 4064         *
 4065         */
 4066         ˇ
 4067    "});
 4068
 4069        // Ensure that inline comment followed by code
 4070        // doesn't add comment prefix on newline
 4071        cx.set_state(indoc! {"
 4072        /** */ textˇ
 4073    "});
 4074        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4075        cx.assert_editor_state(indoc! {"
 4076        /** */ text
 4077        ˇ
 4078    "});
 4079
 4080        // Ensure that text after comment end tag
 4081        // doesn't add comment prefix on newline
 4082        cx.set_state(indoc! {"
 4083        /**
 4084         *
 4085         */ˇtext
 4086    "});
 4087        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4088        cx.assert_editor_state(indoc! {"
 4089        /**
 4090         *
 4091         */
 4092         ˇtext
 4093    "});
 4094
 4095        // Ensure if not comment block it doesn't
 4096        // add comment prefix on newline
 4097        cx.set_state(indoc! {"
 4098        * textˇ
 4099    "});
 4100        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4101        cx.assert_editor_state(indoc! {"
 4102        * text
 4103        ˇ
 4104    "});
 4105    }
 4106    // Ensure that comment continuations can be disabled.
 4107    update_test_language_settings(cx, &|settings| {
 4108        settings.defaults.extend_comment_on_newline = Some(false);
 4109    });
 4110    let mut cx = EditorTestContext::new(cx).await;
 4111    cx.set_state(indoc! {"
 4112        /**ˇ
 4113    "});
 4114    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4115    cx.assert_editor_state(indoc! {"
 4116        /**
 4117        ˇ
 4118    "});
 4119}
 4120
 4121#[gpui::test]
 4122async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 4123    init_test(cx, |settings| {
 4124        settings.defaults.tab_size = NonZeroU32::new(4)
 4125    });
 4126
 4127    let lua_language = Arc::new(Language::new(
 4128        LanguageConfig {
 4129            line_comments: vec!["--".into()],
 4130            block_comment: Some(language::BlockCommentConfig {
 4131                start: "--[[".into(),
 4132                prefix: "".into(),
 4133                end: "]]".into(),
 4134                tab_size: 0,
 4135            }),
 4136            ..LanguageConfig::default()
 4137        },
 4138        None,
 4139    ));
 4140
 4141    let mut cx = EditorTestContext::new(cx).await;
 4142    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 4143
 4144    // Line with line comment should extend
 4145    cx.set_state(indoc! {"
 4146        --ˇ
 4147    "});
 4148    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4149    cx.assert_editor_state(indoc! {"
 4150        --
 4151        --ˇ
 4152    "});
 4153
 4154    // Line with block comment that matches line comment should not extend
 4155    cx.set_state(indoc! {"
 4156        --[[ˇ
 4157    "});
 4158    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 4159    cx.assert_editor_state(indoc! {"
 4160        --[[
 4161        ˇ
 4162    "});
 4163}
 4164
 4165#[gpui::test]
 4166fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 4167    init_test(cx, |_| {});
 4168
 4169    let editor = cx.add_window(|window, cx| {
 4170        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 4171        let mut editor = build_editor(buffer, window, cx);
 4172        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4173            s.select_ranges([
 4174                MultiBufferOffset(3)..MultiBufferOffset(4),
 4175                MultiBufferOffset(11)..MultiBufferOffset(12),
 4176                MultiBufferOffset(19)..MultiBufferOffset(20),
 4177            ])
 4178        });
 4179        editor
 4180    });
 4181
 4182    _ = editor.update(cx, |editor, window, cx| {
 4183        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 4184        editor.buffer.update(cx, |buffer, cx| {
 4185            buffer.edit(
 4186                [
 4187                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 4188                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 4189                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 4190                ],
 4191                None,
 4192                cx,
 4193            );
 4194            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 4195        });
 4196        assert_eq!(
 4197            editor.selections.ranges(&editor.display_snapshot(cx)),
 4198            &[
 4199                MultiBufferOffset(2)..MultiBufferOffset(2),
 4200                MultiBufferOffset(7)..MultiBufferOffset(7),
 4201                MultiBufferOffset(12)..MultiBufferOffset(12)
 4202            ],
 4203        );
 4204
 4205        editor.insert("Z", window, cx);
 4206        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 4207
 4208        // The selections are moved after the inserted characters
 4209        assert_eq!(
 4210            editor.selections.ranges(&editor.display_snapshot(cx)),
 4211            &[
 4212                MultiBufferOffset(3)..MultiBufferOffset(3),
 4213                MultiBufferOffset(9)..MultiBufferOffset(9),
 4214                MultiBufferOffset(15)..MultiBufferOffset(15)
 4215            ],
 4216        );
 4217    });
 4218}
 4219
 4220#[gpui::test]
 4221async fn test_tab(cx: &mut TestAppContext) {
 4222    init_test(cx, |settings| {
 4223        settings.defaults.tab_size = NonZeroU32::new(3)
 4224    });
 4225
 4226    let mut cx = EditorTestContext::new(cx).await;
 4227    cx.set_state(indoc! {"
 4228        ˇabˇc
 4229        ˇ🏀ˇ🏀ˇefg
 4230 4231    "});
 4232    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4233    cx.assert_editor_state(indoc! {"
 4234           ˇab ˇc
 4235           ˇ🏀  ˇ🏀  ˇefg
 4236        d  ˇ
 4237    "});
 4238
 4239    cx.set_state(indoc! {"
 4240        a
 4241        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 4242    "});
 4243    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4244    cx.assert_editor_state(indoc! {"
 4245        a
 4246           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 4247    "});
 4248}
 4249
 4250#[gpui::test]
 4251async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 4252    init_test(cx, |_| {});
 4253
 4254    let mut cx = EditorTestContext::new(cx).await;
 4255    let language = Arc::new(
 4256        Language::new(
 4257            LanguageConfig::default(),
 4258            Some(tree_sitter_rust::LANGUAGE.into()),
 4259        )
 4260        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 4261        .unwrap(),
 4262    );
 4263    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4264
 4265    // test when all cursors are not at suggested indent
 4266    // then simply move to their suggested indent location
 4267    cx.set_state(indoc! {"
 4268        const a: B = (
 4269            c(
 4270        ˇ
 4271        ˇ    )
 4272        );
 4273    "});
 4274    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4275    cx.assert_editor_state(indoc! {"
 4276        const a: B = (
 4277            c(
 4278                ˇ
 4279            ˇ)
 4280        );
 4281    "});
 4282
 4283    // test cursor already at suggested indent not moving when
 4284    // other cursors are yet to reach their suggested indents
 4285    cx.set_state(indoc! {"
 4286        ˇ
 4287        const a: B = (
 4288            c(
 4289                d(
 4290        ˇ
 4291                )
 4292        ˇ
 4293        ˇ    )
 4294        );
 4295    "});
 4296    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4297    cx.assert_editor_state(indoc! {"
 4298        ˇ
 4299        const a: B = (
 4300            c(
 4301                d(
 4302                    ˇ
 4303                )
 4304                ˇ
 4305            ˇ)
 4306        );
 4307    "});
 4308    // test when all cursors are at suggested indent then tab is inserted
 4309    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4310    cx.assert_editor_state(indoc! {"
 4311            ˇ
 4312        const a: B = (
 4313            c(
 4314                d(
 4315                        ˇ
 4316                )
 4317                    ˇ
 4318                ˇ)
 4319        );
 4320    "});
 4321
 4322    // test when current indent is less than suggested indent,
 4323    // we adjust line to match suggested indent and move cursor to it
 4324    //
 4325    // when no other cursor is at word boundary, all of them should move
 4326    cx.set_state(indoc! {"
 4327        const a: B = (
 4328            c(
 4329                d(
 4330        ˇ
 4331        ˇ   )
 4332        ˇ   )
 4333        );
 4334    "});
 4335    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4336    cx.assert_editor_state(indoc! {"
 4337        const a: B = (
 4338            c(
 4339                d(
 4340                    ˇ
 4341                ˇ)
 4342            ˇ)
 4343        );
 4344    "});
 4345
 4346    // test when current indent is less than suggested indent,
 4347    // we adjust line to match suggested indent and move cursor to it
 4348    //
 4349    // when some other cursor is at word boundary, it should not move
 4350    cx.set_state(indoc! {"
 4351        const a: B = (
 4352            c(
 4353                d(
 4354        ˇ
 4355        ˇ   )
 4356           ˇ)
 4357        );
 4358    "});
 4359    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4360    cx.assert_editor_state(indoc! {"
 4361        const a: B = (
 4362            c(
 4363                d(
 4364                    ˇ
 4365                ˇ)
 4366            ˇ)
 4367        );
 4368    "});
 4369
 4370    // test when current indent is more than suggested indent,
 4371    // we just move cursor to current indent instead of suggested indent
 4372    //
 4373    // when no other cursor is at word boundary, all of them should move
 4374    cx.set_state(indoc! {"
 4375        const a: B = (
 4376            c(
 4377                d(
 4378        ˇ
 4379        ˇ                )
 4380        ˇ   )
 4381        );
 4382    "});
 4383    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4384    cx.assert_editor_state(indoc! {"
 4385        const a: B = (
 4386            c(
 4387                d(
 4388                    ˇ
 4389                        ˇ)
 4390            ˇ)
 4391        );
 4392    "});
 4393    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4394    cx.assert_editor_state(indoc! {"
 4395        const a: B = (
 4396            c(
 4397                d(
 4398                        ˇ
 4399                            ˇ)
 4400                ˇ)
 4401        );
 4402    "});
 4403
 4404    // test when current indent is more than suggested indent,
 4405    // we just move cursor to current indent instead of suggested indent
 4406    //
 4407    // when some other cursor is at word boundary, it doesn't move
 4408    cx.set_state(indoc! {"
 4409        const a: B = (
 4410            c(
 4411                d(
 4412        ˇ
 4413        ˇ                )
 4414            ˇ)
 4415        );
 4416    "});
 4417    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4418    cx.assert_editor_state(indoc! {"
 4419        const a: B = (
 4420            c(
 4421                d(
 4422                    ˇ
 4423                        ˇ)
 4424            ˇ)
 4425        );
 4426    "});
 4427
 4428    // handle auto-indent when there are multiple cursors on the same line
 4429    cx.set_state(indoc! {"
 4430        const a: B = (
 4431            c(
 4432        ˇ    ˇ
 4433        ˇ    )
 4434        );
 4435    "});
 4436    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4437    cx.assert_editor_state(indoc! {"
 4438        const a: B = (
 4439            c(
 4440                ˇ
 4441            ˇ)
 4442        );
 4443    "});
 4444}
 4445
 4446#[gpui::test]
 4447async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4448    init_test(cx, |settings| {
 4449        settings.defaults.tab_size = NonZeroU32::new(3)
 4450    });
 4451
 4452    let mut cx = EditorTestContext::new(cx).await;
 4453    cx.set_state(indoc! {"
 4454         ˇ
 4455        \t ˇ
 4456        \t  ˇ
 4457        \t   ˇ
 4458         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4459    "});
 4460
 4461    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4462    cx.assert_editor_state(indoc! {"
 4463           ˇ
 4464        \t   ˇ
 4465        \t   ˇ
 4466        \t      ˇ
 4467         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4468    "});
 4469}
 4470
 4471#[gpui::test]
 4472async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4473    init_test(cx, |settings| {
 4474        settings.defaults.tab_size = NonZeroU32::new(4)
 4475    });
 4476
 4477    let language = Arc::new(
 4478        Language::new(
 4479            LanguageConfig::default(),
 4480            Some(tree_sitter_rust::LANGUAGE.into()),
 4481        )
 4482        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4483        .unwrap(),
 4484    );
 4485
 4486    let mut cx = EditorTestContext::new(cx).await;
 4487    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4488    cx.set_state(indoc! {"
 4489        fn a() {
 4490            if b {
 4491        \t ˇc
 4492            }
 4493        }
 4494    "});
 4495
 4496    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4497    cx.assert_editor_state(indoc! {"
 4498        fn a() {
 4499            if b {
 4500                ˇc
 4501            }
 4502        }
 4503    "});
 4504}
 4505
 4506#[gpui::test]
 4507async fn test_indent_outdent(cx: &mut TestAppContext) {
 4508    init_test(cx, |settings| {
 4509        settings.defaults.tab_size = NonZeroU32::new(4);
 4510    });
 4511
 4512    let mut cx = EditorTestContext::new(cx).await;
 4513
 4514    cx.set_state(indoc! {"
 4515          «oneˇ» «twoˇ»
 4516        three
 4517         four
 4518    "});
 4519    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4520    cx.assert_editor_state(indoc! {"
 4521            «oneˇ» «twoˇ»
 4522        three
 4523         four
 4524    "});
 4525
 4526    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4527    cx.assert_editor_state(indoc! {"
 4528        «oneˇ» «twoˇ»
 4529        three
 4530         four
 4531    "});
 4532
 4533    // select across line ending
 4534    cx.set_state(indoc! {"
 4535        one two
 4536        t«hree
 4537        ˇ» four
 4538    "});
 4539    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4540    cx.assert_editor_state(indoc! {"
 4541        one two
 4542            t«hree
 4543        ˇ» four
 4544    "});
 4545
 4546    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4547    cx.assert_editor_state(indoc! {"
 4548        one two
 4549        t«hree
 4550        ˇ» four
 4551    "});
 4552
 4553    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4554    cx.set_state(indoc! {"
 4555        one two
 4556        ˇthree
 4557            four
 4558    "});
 4559    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4560    cx.assert_editor_state(indoc! {"
 4561        one two
 4562            ˇthree
 4563            four
 4564    "});
 4565
 4566    cx.set_state(indoc! {"
 4567        one two
 4568        ˇ    three
 4569            four
 4570    "});
 4571    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4572    cx.assert_editor_state(indoc! {"
 4573        one two
 4574        ˇthree
 4575            four
 4576    "});
 4577}
 4578
 4579#[gpui::test]
 4580async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4581    // This is a regression test for issue #33761
 4582    init_test(cx, |_| {});
 4583
 4584    let mut cx = EditorTestContext::new(cx).await;
 4585    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4586    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4587
 4588    cx.set_state(
 4589        r#"ˇ#     ingress:
 4590ˇ#         api:
 4591ˇ#             enabled: false
 4592ˇ#             pathType: Prefix
 4593ˇ#           console:
 4594ˇ#               enabled: false
 4595ˇ#               pathType: Prefix
 4596"#,
 4597    );
 4598
 4599    // Press tab to indent all lines
 4600    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4601
 4602    cx.assert_editor_state(
 4603        r#"    ˇ#     ingress:
 4604    ˇ#         api:
 4605    ˇ#             enabled: false
 4606    ˇ#             pathType: Prefix
 4607    ˇ#           console:
 4608    ˇ#               enabled: false
 4609    ˇ#               pathType: Prefix
 4610"#,
 4611    );
 4612}
 4613
 4614#[gpui::test]
 4615async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4616    // This is a test to make sure our fix for issue #33761 didn't break anything
 4617    init_test(cx, |_| {});
 4618
 4619    let mut cx = EditorTestContext::new(cx).await;
 4620    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4621    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4622
 4623    cx.set_state(
 4624        r#"ˇingress:
 4625ˇ  api:
 4626ˇ    enabled: false
 4627ˇ    pathType: Prefix
 4628"#,
 4629    );
 4630
 4631    // Press tab to indent all lines
 4632    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4633
 4634    cx.assert_editor_state(
 4635        r#"ˇingress:
 4636    ˇapi:
 4637        ˇenabled: false
 4638        ˇpathType: Prefix
 4639"#,
 4640    );
 4641}
 4642
 4643#[gpui::test]
 4644async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4645    init_test(cx, |settings| {
 4646        settings.defaults.hard_tabs = Some(true);
 4647    });
 4648
 4649    let mut cx = EditorTestContext::new(cx).await;
 4650
 4651    // select two ranges on one line
 4652    cx.set_state(indoc! {"
 4653        «oneˇ» «twoˇ»
 4654        three
 4655        four
 4656    "});
 4657    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4658    cx.assert_editor_state(indoc! {"
 4659        \t«oneˇ» «twoˇ»
 4660        three
 4661        four
 4662    "});
 4663    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4664    cx.assert_editor_state(indoc! {"
 4665        \t\t«oneˇ» «twoˇ»
 4666        three
 4667        four
 4668    "});
 4669    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4670    cx.assert_editor_state(indoc! {"
 4671        \t«oneˇ» «twoˇ»
 4672        three
 4673        four
 4674    "});
 4675    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4676    cx.assert_editor_state(indoc! {"
 4677        «oneˇ» «twoˇ»
 4678        three
 4679        four
 4680    "});
 4681
 4682    // select across a line ending
 4683    cx.set_state(indoc! {"
 4684        one two
 4685        t«hree
 4686        ˇ»four
 4687    "});
 4688    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4689    cx.assert_editor_state(indoc! {"
 4690        one two
 4691        \tt«hree
 4692        ˇ»four
 4693    "});
 4694    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4695    cx.assert_editor_state(indoc! {"
 4696        one two
 4697        \t\tt«hree
 4698        ˇ»four
 4699    "});
 4700    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4701    cx.assert_editor_state(indoc! {"
 4702        one two
 4703        \tt«hree
 4704        ˇ»four
 4705    "});
 4706    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4707    cx.assert_editor_state(indoc! {"
 4708        one two
 4709        t«hree
 4710        ˇ»four
 4711    "});
 4712
 4713    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4714    cx.set_state(indoc! {"
 4715        one two
 4716        ˇthree
 4717        four
 4718    "});
 4719    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4720    cx.assert_editor_state(indoc! {"
 4721        one two
 4722        ˇthree
 4723        four
 4724    "});
 4725    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4726    cx.assert_editor_state(indoc! {"
 4727        one two
 4728        \tˇthree
 4729        four
 4730    "});
 4731    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4732    cx.assert_editor_state(indoc! {"
 4733        one two
 4734        ˇthree
 4735        four
 4736    "});
 4737}
 4738
 4739#[gpui::test]
 4740fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4741    init_test(cx, |settings| {
 4742        settings.languages.0.extend([
 4743            (
 4744                "TOML".into(),
 4745                LanguageSettingsContent {
 4746                    tab_size: NonZeroU32::new(2),
 4747                    ..Default::default()
 4748                },
 4749            ),
 4750            (
 4751                "Rust".into(),
 4752                LanguageSettingsContent {
 4753                    tab_size: NonZeroU32::new(4),
 4754                    ..Default::default()
 4755                },
 4756            ),
 4757        ]);
 4758    });
 4759
 4760    let toml_language = Arc::new(Language::new(
 4761        LanguageConfig {
 4762            name: "TOML".into(),
 4763            ..Default::default()
 4764        },
 4765        None,
 4766    ));
 4767    let rust_language = Arc::new(Language::new(
 4768        LanguageConfig {
 4769            name: "Rust".into(),
 4770            ..Default::default()
 4771        },
 4772        None,
 4773    ));
 4774
 4775    let toml_buffer =
 4776        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4777    let rust_buffer =
 4778        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4779    let multibuffer = cx.new(|cx| {
 4780        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4781        multibuffer.set_excerpts_for_path(
 4782            PathKey::sorted(0),
 4783            toml_buffer.clone(),
 4784            [Point::new(0, 0)..Point::new(2, 0)],
 4785            0,
 4786            cx,
 4787        );
 4788        multibuffer.set_excerpts_for_path(
 4789            PathKey::sorted(1),
 4790            rust_buffer.clone(),
 4791            [Point::new(0, 0)..Point::new(1, 0)],
 4792            0,
 4793            cx,
 4794        );
 4795        multibuffer
 4796    });
 4797
 4798    cx.add_window(|window, cx| {
 4799        let mut editor = build_editor(multibuffer, window, cx);
 4800
 4801        assert_eq!(
 4802            editor.text(cx),
 4803            indoc! {"
 4804                a = 1
 4805                b = 2
 4806
 4807                const c: usize = 3;
 4808            "}
 4809        );
 4810
 4811        select_ranges(
 4812            &mut editor,
 4813            indoc! {"
 4814                «aˇ» = 1
 4815                b = 2
 4816
 4817                «const c:ˇ» usize = 3;
 4818            "},
 4819            window,
 4820            cx,
 4821        );
 4822
 4823        editor.tab(&Tab, window, cx);
 4824        assert_text_with_selections(
 4825            &mut editor,
 4826            indoc! {"
 4827                  «aˇ» = 1
 4828                b = 2
 4829
 4830                    «const c:ˇ» usize = 3;
 4831            "},
 4832            cx,
 4833        );
 4834        editor.backtab(&Backtab, window, cx);
 4835        assert_text_with_selections(
 4836            &mut editor,
 4837            indoc! {"
 4838                «aˇ» = 1
 4839                b = 2
 4840
 4841                «const c:ˇ» usize = 3;
 4842            "},
 4843            cx,
 4844        );
 4845
 4846        editor
 4847    });
 4848}
 4849
 4850#[gpui::test]
 4851async fn test_backspace(cx: &mut TestAppContext) {
 4852    init_test(cx, |_| {});
 4853
 4854    let mut cx = EditorTestContext::new(cx).await;
 4855
 4856    // Basic backspace
 4857    cx.set_state(indoc! {"
 4858        onˇe two three
 4859        fou«rˇ» five six
 4860        seven «ˇeight nine
 4861        »ten
 4862    "});
 4863    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4864    cx.assert_editor_state(indoc! {"
 4865        oˇe two three
 4866        fouˇ five six
 4867        seven ˇten
 4868    "});
 4869
 4870    // Test backspace inside and around indents
 4871    cx.set_state(indoc! {"
 4872        zero
 4873            ˇone
 4874                ˇtwo
 4875            ˇ ˇ ˇ  three
 4876        ˇ  ˇ  four
 4877    "});
 4878    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4879    cx.assert_editor_state(indoc! {"
 4880        zero
 4881        ˇone
 4882            ˇtwo
 4883        ˇ  threeˇ  four
 4884    "});
 4885}
 4886
 4887#[gpui::test]
 4888async fn test_delete(cx: &mut TestAppContext) {
 4889    init_test(cx, |_| {});
 4890
 4891    let mut cx = EditorTestContext::new(cx).await;
 4892    cx.set_state(indoc! {"
 4893        onˇe two three
 4894        fou«rˇ» five six
 4895        seven «ˇeight nine
 4896        »ten
 4897    "});
 4898    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4899    cx.assert_editor_state(indoc! {"
 4900        onˇ two three
 4901        fouˇ five six
 4902        seven ˇten
 4903    "});
 4904}
 4905
 4906#[gpui::test]
 4907fn test_delete_line(cx: &mut TestAppContext) {
 4908    init_test(cx, |_| {});
 4909
 4910    let editor = cx.add_window(|window, cx| {
 4911        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4912        build_editor(buffer, window, cx)
 4913    });
 4914    _ = editor.update(cx, |editor, window, cx| {
 4915        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4916            s.select_display_ranges([
 4917                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4918                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4919                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4920            ])
 4921        });
 4922        editor.delete_line(&DeleteLine, window, cx);
 4923        assert_eq!(editor.display_text(cx), "ghi");
 4924        assert_eq!(
 4925            display_ranges(editor, cx),
 4926            vec![
 4927                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4928                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4929            ]
 4930        );
 4931    });
 4932
 4933    let editor = cx.add_window(|window, cx| {
 4934        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4935        build_editor(buffer, window, cx)
 4936    });
 4937    _ = editor.update(cx, |editor, window, cx| {
 4938        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4939            s.select_display_ranges([
 4940                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4941            ])
 4942        });
 4943        editor.delete_line(&DeleteLine, window, cx);
 4944        assert_eq!(editor.display_text(cx), "ghi\n");
 4945        assert_eq!(
 4946            display_ranges(editor, cx),
 4947            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4948        );
 4949    });
 4950
 4951    let editor = cx.add_window(|window, cx| {
 4952        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4953        build_editor(buffer, window, cx)
 4954    });
 4955    _ = editor.update(cx, |editor, window, cx| {
 4956        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4957            s.select_display_ranges([
 4958                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4959            ])
 4960        });
 4961        editor.delete_line(&DeleteLine, window, cx);
 4962        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4963        assert_eq!(
 4964            display_ranges(editor, cx),
 4965            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4966        );
 4967    });
 4968}
 4969
 4970#[gpui::test]
 4971fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4972    init_test(cx, |_| {});
 4973
 4974    cx.add_window(|window, cx| {
 4975        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4976        let mut editor = build_editor(buffer.clone(), window, cx);
 4977        let buffer = buffer.read(cx).as_singleton().unwrap();
 4978
 4979        assert_eq!(
 4980            editor
 4981                .selections
 4982                .ranges::<Point>(&editor.display_snapshot(cx)),
 4983            &[Point::new(0, 0)..Point::new(0, 0)]
 4984        );
 4985
 4986        // When on single line, replace newline at end by space
 4987        editor.join_lines(&JoinLines, window, cx);
 4988        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4989        assert_eq!(
 4990            editor
 4991                .selections
 4992                .ranges::<Point>(&editor.display_snapshot(cx)),
 4993            &[Point::new(0, 3)..Point::new(0, 3)]
 4994        );
 4995
 4996        editor.undo(&Undo, window, cx);
 4997        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\nddd\n\n");
 4998
 4999        // Select a full line, i.e. start of the first line to the start of the second line
 5000        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5001            s.select_ranges([Point::new(0, 0)..Point::new(1, 0)])
 5002        });
 5003        editor.join_lines(&JoinLines, window, cx);
 5004        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 5005
 5006        editor.undo(&Undo, window, cx);
 5007        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\nddd\n\n");
 5008
 5009        // Select two full lines
 5010        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5011            s.select_ranges([Point::new(0, 0)..Point::new(2, 0)])
 5012        });
 5013        editor.join_lines(&JoinLines, window, cx);
 5014
 5015        // Only the selected lines should be joined, not the third.
 5016        assert_eq!(
 5017            buffer.read(cx).text(),
 5018            "aaa bbb\nccc\nddd\n\n",
 5019            "only the two selected lines (a and b) should be joined"
 5020        );
 5021
 5022        // When multiple lines are selected, remove newlines that are spanned by the selection
 5023        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5024            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 5025        });
 5026        editor.join_lines(&JoinLines, window, cx);
 5027        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 5028        assert_eq!(
 5029            editor
 5030                .selections
 5031                .ranges::<Point>(&editor.display_snapshot(cx)),
 5032            &[Point::new(0, 11)..Point::new(0, 11)]
 5033        );
 5034
 5035        // Undo should be transactional
 5036        editor.undo(&Undo, window, cx);
 5037        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 5038        assert_eq!(
 5039            editor
 5040                .selections
 5041                .ranges::<Point>(&editor.display_snapshot(cx)),
 5042            &[Point::new(0, 5)..Point::new(2, 2)]
 5043        );
 5044
 5045        // When joining an empty line don't insert a space
 5046        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5047            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 5048        });
 5049        editor.join_lines(&JoinLines, window, cx);
 5050        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 5051        assert_eq!(
 5052            editor
 5053                .selections
 5054                .ranges::<Point>(&editor.display_snapshot(cx)),
 5055            [Point::new(2, 3)..Point::new(2, 3)]
 5056        );
 5057
 5058        // We can remove trailing newlines
 5059        editor.join_lines(&JoinLines, window, cx);
 5060        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 5061        assert_eq!(
 5062            editor
 5063                .selections
 5064                .ranges::<Point>(&editor.display_snapshot(cx)),
 5065            [Point::new(2, 3)..Point::new(2, 3)]
 5066        );
 5067
 5068        // We don't blow up on the last line
 5069        editor.join_lines(&JoinLines, window, cx);
 5070        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 5071        assert_eq!(
 5072            editor
 5073                .selections
 5074                .ranges::<Point>(&editor.display_snapshot(cx)),
 5075            [Point::new(2, 3)..Point::new(2, 3)]
 5076        );
 5077
 5078        // reset to test indentation
 5079        editor.buffer.update(cx, |buffer, cx| {
 5080            buffer.edit(
 5081                [
 5082                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 5083                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 5084                ],
 5085                None,
 5086                cx,
 5087            )
 5088        });
 5089
 5090        // We remove any leading spaces
 5091        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 5092        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5093            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 5094        });
 5095        editor.join_lines(&JoinLines, window, cx);
 5096        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 5097
 5098        // We don't insert a space for a line containing only spaces
 5099        editor.join_lines(&JoinLines, window, cx);
 5100        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 5101
 5102        // We ignore any leading tabs
 5103        editor.join_lines(&JoinLines, window, cx);
 5104        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 5105
 5106        editor
 5107    });
 5108}
 5109
 5110#[gpui::test]
 5111fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 5112    init_test(cx, |_| {});
 5113
 5114    cx.add_window(|window, cx| {
 5115        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 5116        let mut editor = build_editor(buffer.clone(), window, cx);
 5117        let buffer = buffer.read(cx).as_singleton().unwrap();
 5118
 5119        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5120            s.select_ranges([
 5121                Point::new(0, 2)..Point::new(1, 1),
 5122                Point::new(1, 2)..Point::new(1, 2),
 5123                Point::new(3, 1)..Point::new(3, 2),
 5124            ])
 5125        });
 5126
 5127        editor.join_lines(&JoinLines, window, cx);
 5128        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 5129
 5130        assert_eq!(
 5131            editor
 5132                .selections
 5133                .ranges::<Point>(&editor.display_snapshot(cx)),
 5134            [
 5135                Point::new(0, 7)..Point::new(0, 7),
 5136                Point::new(1, 3)..Point::new(1, 3)
 5137            ]
 5138        );
 5139        editor
 5140    });
 5141}
 5142
 5143#[gpui::test]
 5144async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 5145    init_test(cx, |_| {});
 5146
 5147    let mut cx = EditorTestContext::new(cx).await;
 5148
 5149    let diff_base = r#"
 5150        Line 0
 5151        Line 1
 5152        Line 2
 5153        Line 3
 5154        "#
 5155    .unindent();
 5156
 5157    cx.set_state(
 5158        &r#"
 5159        ˇLine 0
 5160        Line 1
 5161        Line 2
 5162        Line 3
 5163        "#
 5164        .unindent(),
 5165    );
 5166
 5167    cx.set_head_text(&diff_base);
 5168    executor.run_until_parked();
 5169
 5170    // Join lines
 5171    cx.update_editor(|editor, window, cx| {
 5172        editor.join_lines(&JoinLines, window, cx);
 5173    });
 5174    executor.run_until_parked();
 5175
 5176    cx.assert_editor_state(
 5177        &r#"
 5178        Line 0ˇ Line 1
 5179        Line 2
 5180        Line 3
 5181        "#
 5182        .unindent(),
 5183    );
 5184    // Join again
 5185    cx.update_editor(|editor, window, cx| {
 5186        editor.join_lines(&JoinLines, window, cx);
 5187    });
 5188    executor.run_until_parked();
 5189
 5190    cx.assert_editor_state(
 5191        &r#"
 5192        Line 0 Line 1ˇ Line 2
 5193        Line 3
 5194        "#
 5195        .unindent(),
 5196    );
 5197}
 5198
 5199#[gpui::test]
 5200async fn test_join_lines_strips_comment_prefix(cx: &mut TestAppContext) {
 5201    init_test(cx, |_| {});
 5202
 5203    {
 5204        let language = Arc::new(Language::new(
 5205            LanguageConfig {
 5206                line_comments: vec!["// ".into(), "/// ".into()],
 5207                documentation_comment: Some(BlockCommentConfig {
 5208                    start: "/*".into(),
 5209                    end: "*/".into(),
 5210                    prefix: "* ".into(),
 5211                    tab_size: 1,
 5212                }),
 5213                ..LanguageConfig::default()
 5214            },
 5215            None,
 5216        ));
 5217
 5218        let mut cx = EditorTestContext::new(cx).await;
 5219        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5220
 5221        // Strips the comment prefix (with trailing space) from the joined-in line.
 5222        cx.set_state(indoc! {"
 5223            // ˇfoo
 5224            // bar
 5225        "});
 5226        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5227        cx.assert_editor_state(indoc! {"
 5228            // fooˇ bar
 5229        "});
 5230
 5231        // Strips the longer doc-comment prefix when both `//` and `///` match.
 5232        cx.set_state(indoc! {"
 5233            /// ˇfoo
 5234            /// bar
 5235        "});
 5236        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5237        cx.assert_editor_state(indoc! {"
 5238            /// fooˇ bar
 5239        "});
 5240
 5241        // Does not strip when the second line is a regular line (no comment prefix).
 5242        cx.set_state(indoc! {"
 5243            // ˇfoo
 5244            bar
 5245        "});
 5246        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5247        cx.assert_editor_state(indoc! {"
 5248            // fooˇ bar
 5249        "});
 5250
 5251        // No-whitespace join also strips the comment prefix.
 5252        cx.set_state(indoc! {"
 5253            // ˇfoo
 5254            // bar
 5255        "});
 5256        cx.update_editor(|e, window, cx| e.join_lines_impl(false, window, cx));
 5257        cx.assert_editor_state(indoc! {"
 5258            // fooˇbar
 5259        "});
 5260
 5261        // Strips even when the joined-in line is just the bare prefix (no trailing space).
 5262        cx.set_state(indoc! {"
 5263            // ˇfoo
 5264            //
 5265        "});
 5266        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5267        cx.assert_editor_state(indoc! {"
 5268            // fooˇ
 5269        "});
 5270
 5271        // Mixed line comment prefix types: the longer matching prefix is stripped.
 5272        cx.set_state(indoc! {"
 5273            // ˇfoo
 5274            /// bar
 5275        "});
 5276        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5277        cx.assert_editor_state(indoc! {"
 5278            // fooˇ bar
 5279        "});
 5280
 5281        // Strips block comment body prefix (`* `) from the joined-in line.
 5282        cx.set_state(indoc! {"
 5283             * ˇfoo
 5284             * bar
 5285        "});
 5286        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5287        cx.assert_editor_state(indoc! {"
 5288             * fooˇ bar
 5289        "});
 5290
 5291        // Strips bare block comment body prefix (`*` without trailing space).
 5292        cx.set_state(indoc! {"
 5293             * ˇfoo
 5294             *
 5295        "});
 5296        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5297        cx.assert_editor_state(indoc! {"
 5298             * fooˇ
 5299        "});
 5300    }
 5301
 5302    {
 5303        let markdown_language = Arc::new(Language::new(
 5304            LanguageConfig {
 5305                unordered_list: vec!["- ".into(), "* ".into(), "+ ".into()],
 5306                ..LanguageConfig::default()
 5307            },
 5308            None,
 5309        ));
 5310
 5311        let mut cx = EditorTestContext::new(cx).await;
 5312        cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
 5313
 5314        // Strips the `- ` list marker from the joined-in line.
 5315        cx.set_state(indoc! {"
 5316            - ˇfoo
 5317            - bar
 5318        "});
 5319        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5320        cx.assert_editor_state(indoc! {"
 5321            - fooˇ bar
 5322        "});
 5323
 5324        // Strips the `* ` list marker from the joined-in line.
 5325        cx.set_state(indoc! {"
 5326            * ˇfoo
 5327            * bar
 5328        "});
 5329        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5330        cx.assert_editor_state(indoc! {"
 5331            * fooˇ bar
 5332        "});
 5333
 5334        // Strips the `+ ` list marker from the joined-in line.
 5335        cx.set_state(indoc! {"
 5336            + ˇfoo
 5337            + bar
 5338        "});
 5339        cx.update_editor(|e, window, cx| e.join_lines(&JoinLines, window, cx));
 5340        cx.assert_editor_state(indoc! {"
 5341            + fooˇ bar
 5342        "});
 5343
 5344        // No-whitespace join also strips the list marker.
 5345        cx.set_state(indoc! {"
 5346            - ˇfoo
 5347            - bar
 5348        "});
 5349        cx.update_editor(|e, window, cx| e.join_lines_impl(false, window, cx));
 5350        cx.assert_editor_state(indoc! {"
 5351            - fooˇbar
 5352        "});
 5353    }
 5354}
 5355
 5356#[gpui::test]
 5357async fn test_custom_newlines_cause_no_false_positive_diffs(
 5358    executor: BackgroundExecutor,
 5359    cx: &mut TestAppContext,
 5360) {
 5361    init_test(cx, |_| {});
 5362    let mut cx = EditorTestContext::new(cx).await;
 5363    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 5364    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 5365    executor.run_until_parked();
 5366
 5367    cx.update_editor(|editor, window, cx| {
 5368        let snapshot = editor.snapshot(window, cx);
 5369        assert_eq!(
 5370            snapshot
 5371                .buffer_snapshot()
 5372                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 5373                .collect::<Vec<_>>(),
 5374            Vec::new(),
 5375            "Should not have any diffs for files with custom newlines"
 5376        );
 5377    });
 5378}
 5379
 5380#[gpui::test]
 5381async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 5382    init_test(cx, |_| {});
 5383
 5384    let mut cx = EditorTestContext::new(cx).await;
 5385
 5386    // Test sort_lines_case_insensitive()
 5387    cx.set_state(indoc! {"
 5388        «z
 5389        y
 5390        x
 5391        Z
 5392        Y
 5393        Xˇ»
 5394    "});
 5395    cx.update_editor(|e, window, cx| {
 5396        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 5397    });
 5398    cx.assert_editor_state(indoc! {"
 5399        «x
 5400        X
 5401        y
 5402        Y
 5403        z
 5404        Zˇ»
 5405    "});
 5406
 5407    // Test sort_lines_by_length()
 5408    //
 5409    // Demonstrates:
 5410    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 5411    // - sort is stable
 5412    cx.set_state(indoc! {"
 5413        «123
 5414        æ
 5415        12
 5416 5417        1
 5418        æˇ»
 5419    "});
 5420    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 5421    cx.assert_editor_state(indoc! {"
 5422        «æ
 5423 5424        1
 5425        æ
 5426        12
 5427        123ˇ»
 5428    "});
 5429
 5430    // Test reverse_lines()
 5431    cx.set_state(indoc! {"
 5432        «5
 5433        4
 5434        3
 5435        2
 5436        1ˇ»
 5437    "});
 5438    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 5439    cx.assert_editor_state(indoc! {"
 5440        «1
 5441        2
 5442        3
 5443        4
 5444        5ˇ»
 5445    "});
 5446
 5447    // Skip testing shuffle_line()
 5448
 5449    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 5450    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 5451
 5452    // Don't manipulate when cursor is on single line, but expand the selection
 5453    cx.set_state(indoc! {"
 5454        ddˇdd
 5455        ccc
 5456        bb
 5457        a
 5458    "});
 5459    cx.update_editor(|e, window, cx| {
 5460        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5461    });
 5462    cx.assert_editor_state(indoc! {"
 5463        «ddddˇ»
 5464        ccc
 5465        bb
 5466        a
 5467    "});
 5468
 5469    // Basic manipulate case
 5470    // Start selection moves to column 0
 5471    // End of selection shrinks to fit shorter line
 5472    cx.set_state(indoc! {"
 5473        dd«d
 5474        ccc
 5475        bb
 5476        aaaaaˇ»
 5477    "});
 5478    cx.update_editor(|e, window, cx| {
 5479        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5480    });
 5481    cx.assert_editor_state(indoc! {"
 5482        «aaaaa
 5483        bb
 5484        ccc
 5485        dddˇ»
 5486    "});
 5487
 5488    // Manipulate case with newlines
 5489    cx.set_state(indoc! {"
 5490        dd«d
 5491        ccc
 5492
 5493        bb
 5494        aaaaa
 5495
 5496        ˇ»
 5497    "});
 5498    cx.update_editor(|e, window, cx| {
 5499        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5500    });
 5501    cx.assert_editor_state(indoc! {"
 5502        «
 5503
 5504        aaaaa
 5505        bb
 5506        ccc
 5507        dddˇ»
 5508
 5509    "});
 5510
 5511    // Adding new line
 5512    cx.set_state(indoc! {"
 5513        aa«a
 5514        bbˇ»b
 5515    "});
 5516    cx.update_editor(|e, window, cx| {
 5517        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 5518    });
 5519    cx.assert_editor_state(indoc! {"
 5520        «aaa
 5521        bbb
 5522        added_lineˇ»
 5523    "});
 5524
 5525    // Removing line
 5526    cx.set_state(indoc! {"
 5527        aa«a
 5528        bbbˇ»
 5529    "});
 5530    cx.update_editor(|e, window, cx| {
 5531        e.manipulate_immutable_lines(window, cx, |lines| {
 5532            lines.pop();
 5533        })
 5534    });
 5535    cx.assert_editor_state(indoc! {"
 5536        «aaaˇ»
 5537    "});
 5538
 5539    // Removing all lines
 5540    cx.set_state(indoc! {"
 5541        aa«a
 5542        bbbˇ»
 5543    "});
 5544    cx.update_editor(|e, window, cx| {
 5545        e.manipulate_immutable_lines(window, cx, |lines| {
 5546            lines.drain(..);
 5547        })
 5548    });
 5549    cx.assert_editor_state(indoc! {"
 5550        ˇ
 5551    "});
 5552}
 5553
 5554#[gpui::test]
 5555async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 5556    init_test(cx, |_| {});
 5557
 5558    let mut cx = EditorTestContext::new(cx).await;
 5559
 5560    // Consider continuous selection as single selection
 5561    cx.set_state(indoc! {"
 5562        Aaa«aa
 5563        cˇ»c«c
 5564        bb
 5565        aaaˇ»aa
 5566    "});
 5567    cx.update_editor(|e, window, cx| {
 5568        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5569    });
 5570    cx.assert_editor_state(indoc! {"
 5571        «Aaaaa
 5572        ccc
 5573        bb
 5574        aaaaaˇ»
 5575    "});
 5576
 5577    cx.set_state(indoc! {"
 5578        Aaa«aa
 5579        cˇ»c«c
 5580        bb
 5581        aaaˇ»aa
 5582    "});
 5583    cx.update_editor(|e, window, cx| {
 5584        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5585    });
 5586    cx.assert_editor_state(indoc! {"
 5587        «Aaaaa
 5588        ccc
 5589        bbˇ»
 5590    "});
 5591
 5592    // Consider non continuous selection as distinct dedup operations
 5593    cx.set_state(indoc! {"
 5594        «aaaaa
 5595        bb
 5596        aaaaa
 5597        aaaaaˇ»
 5598
 5599        aaa«aaˇ»
 5600    "});
 5601    cx.update_editor(|e, window, cx| {
 5602        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5603    });
 5604    cx.assert_editor_state(indoc! {"
 5605        «aaaaa
 5606        bbˇ»
 5607
 5608        «aaaaaˇ»
 5609    "});
 5610}
 5611
 5612#[gpui::test]
 5613async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 5614    init_test(cx, |_| {});
 5615
 5616    let mut cx = EditorTestContext::new(cx).await;
 5617
 5618    cx.set_state(indoc! {"
 5619        «Aaa
 5620        aAa
 5621        Aaaˇ»
 5622    "});
 5623    cx.update_editor(|e, window, cx| {
 5624        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 5625    });
 5626    cx.assert_editor_state(indoc! {"
 5627        «Aaa
 5628        aAaˇ»
 5629    "});
 5630
 5631    cx.set_state(indoc! {"
 5632        «Aaa
 5633        aAa
 5634        aaAˇ»
 5635    "});
 5636    cx.update_editor(|e, window, cx| {
 5637        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5638    });
 5639    cx.assert_editor_state(indoc! {"
 5640        «Aaaˇ»
 5641    "});
 5642}
 5643
 5644#[gpui::test]
 5645async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5646    init_test(cx, |_| {});
 5647
 5648    let mut cx = EditorTestContext::new(cx).await;
 5649
 5650    let js_language = Arc::new(Language::new(
 5651        LanguageConfig {
 5652            name: "JavaScript".into(),
 5653            wrap_characters: Some(language::WrapCharactersConfig {
 5654                start_prefix: "<".into(),
 5655                start_suffix: ">".into(),
 5656                end_prefix: "</".into(),
 5657                end_suffix: ">".into(),
 5658            }),
 5659            ..LanguageConfig::default()
 5660        },
 5661        None,
 5662    ));
 5663
 5664    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5665
 5666    cx.set_state(indoc! {"
 5667        «testˇ»
 5668    "});
 5669    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5670    cx.assert_editor_state(indoc! {"
 5671        <«ˇ»>test</«ˇ»>
 5672    "});
 5673
 5674    cx.set_state(indoc! {"
 5675        «test
 5676         testˇ»
 5677    "});
 5678    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5679    cx.assert_editor_state(indoc! {"
 5680        <«ˇ»>test
 5681         test</«ˇ»>
 5682    "});
 5683
 5684    cx.set_state(indoc! {"
 5685        teˇst
 5686    "});
 5687    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5688    cx.assert_editor_state(indoc! {"
 5689        te<«ˇ»></«ˇ»>st
 5690    "});
 5691}
 5692
 5693#[gpui::test]
 5694async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5695    init_test(cx, |_| {});
 5696
 5697    let mut cx = EditorTestContext::new(cx).await;
 5698
 5699    let js_language = Arc::new(Language::new(
 5700        LanguageConfig {
 5701            name: "JavaScript".into(),
 5702            wrap_characters: Some(language::WrapCharactersConfig {
 5703                start_prefix: "<".into(),
 5704                start_suffix: ">".into(),
 5705                end_prefix: "</".into(),
 5706                end_suffix: ">".into(),
 5707            }),
 5708            ..LanguageConfig::default()
 5709        },
 5710        None,
 5711    ));
 5712
 5713    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5714
 5715    cx.set_state(indoc! {"
 5716        «testˇ»
 5717        «testˇ» «testˇ»
 5718        «testˇ»
 5719    "});
 5720    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5721    cx.assert_editor_state(indoc! {"
 5722        <«ˇ»>test</«ˇ»>
 5723        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5724        <«ˇ»>test</«ˇ»>
 5725    "});
 5726
 5727    cx.set_state(indoc! {"
 5728        «test
 5729         testˇ»
 5730        «test
 5731         testˇ»
 5732    "});
 5733    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5734    cx.assert_editor_state(indoc! {"
 5735        <«ˇ»>test
 5736         test</«ˇ»>
 5737        <«ˇ»>test
 5738         test</«ˇ»>
 5739    "});
 5740}
 5741
 5742#[gpui::test]
 5743async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5744    init_test(cx, |_| {});
 5745
 5746    let mut cx = EditorTestContext::new(cx).await;
 5747
 5748    let plaintext_language = Arc::new(Language::new(
 5749        LanguageConfig {
 5750            name: "Plain Text".into(),
 5751            ..LanguageConfig::default()
 5752        },
 5753        None,
 5754    ));
 5755
 5756    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5757
 5758    cx.set_state(indoc! {"
 5759        «testˇ»
 5760    "});
 5761    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5762    cx.assert_editor_state(indoc! {"
 5763      «testˇ»
 5764    "});
 5765}
 5766
 5767#[gpui::test]
 5768async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5769    init_test(cx, |_| {});
 5770
 5771    let mut cx = EditorTestContext::new(cx).await;
 5772
 5773    // Manipulate with multiple selections on a single line
 5774    cx.set_state(indoc! {"
 5775        dd«dd
 5776        cˇ»c«c
 5777        bb
 5778        aaaˇ»aa
 5779    "});
 5780    cx.update_editor(|e, window, cx| {
 5781        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5782    });
 5783    cx.assert_editor_state(indoc! {"
 5784        «aaaaa
 5785        bb
 5786        ccc
 5787        ddddˇ»
 5788    "});
 5789
 5790    // Manipulate with multiple disjoin selections
 5791    cx.set_state(indoc! {"
 5792 5793        4
 5794        3
 5795        2
 5796        1ˇ»
 5797
 5798        dd«dd
 5799        ccc
 5800        bb
 5801        aaaˇ»aa
 5802    "});
 5803    cx.update_editor(|e, window, cx| {
 5804        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5805    });
 5806    cx.assert_editor_state(indoc! {"
 5807        «1
 5808        2
 5809        3
 5810        4
 5811        5ˇ»
 5812
 5813        «aaaaa
 5814        bb
 5815        ccc
 5816        ddddˇ»
 5817    "});
 5818
 5819    // Adding lines on each selection
 5820    cx.set_state(indoc! {"
 5821 5822        1ˇ»
 5823
 5824        bb«bb
 5825        aaaˇ»aa
 5826    "});
 5827    cx.update_editor(|e, window, cx| {
 5828        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5829    });
 5830    cx.assert_editor_state(indoc! {"
 5831        «2
 5832        1
 5833        added lineˇ»
 5834
 5835        «bbbb
 5836        aaaaa
 5837        added lineˇ»
 5838    "});
 5839
 5840    // Removing lines on each selection
 5841    cx.set_state(indoc! {"
 5842 5843        1ˇ»
 5844
 5845        bb«bb
 5846        aaaˇ»aa
 5847    "});
 5848    cx.update_editor(|e, window, cx| {
 5849        e.manipulate_immutable_lines(window, cx, |lines| {
 5850            lines.pop();
 5851        })
 5852    });
 5853    cx.assert_editor_state(indoc! {"
 5854        «2ˇ»
 5855
 5856        «bbbbˇ»
 5857    "});
 5858}
 5859
 5860#[gpui::test]
 5861async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5862    init_test(cx, |settings| {
 5863        settings.defaults.tab_size = NonZeroU32::new(3)
 5864    });
 5865
 5866    let mut cx = EditorTestContext::new(cx).await;
 5867
 5868    // MULTI SELECTION
 5869    // Ln.1 "«" tests empty lines
 5870    // Ln.9 tests just leading whitespace
 5871    cx.set_state(indoc! {"
 5872        «
 5873        abc                 // No indentationˇ»
 5874        «\tabc              // 1 tabˇ»
 5875        \t\tabc «      ˇ»   // 2 tabs
 5876        \t ab«c             // Tab followed by space
 5877         \tabc              // Space followed by tab (3 spaces should be the result)
 5878        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5879           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5880        \t
 5881        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5882    "});
 5883    cx.update_editor(|e, window, cx| {
 5884        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5885    });
 5886    cx.assert_editor_state(
 5887        indoc! {"
 5888            «
 5889            abc                 // No indentation
 5890               abc              // 1 tab
 5891                  abc          // 2 tabs
 5892                abc             // Tab followed by space
 5893               abc              // Space followed by tab (3 spaces should be the result)
 5894                           abc   // Mixed indentation (tab conversion depends on the column)
 5895               abc         // Already space indented
 5896               ·
 5897               abc\tdef          // Only the leading tab is manipulatedˇ»
 5898        "}
 5899        .replace("·", "")
 5900        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5901    );
 5902
 5903    // Test on just a few lines, the others should remain unchanged
 5904    // Only lines (3, 5, 10, 11) should change
 5905    cx.set_state(
 5906        indoc! {"
 5907            ·
 5908            abc                 // No indentation
 5909            \tabcˇ               // 1 tab
 5910            \t\tabc             // 2 tabs
 5911            \t abcˇ              // Tab followed by space
 5912             \tabc              // Space followed by tab (3 spaces should be the result)
 5913            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5914               abc              // Already space indented
 5915            «\t
 5916            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5917        "}
 5918        .replace("·", "")
 5919        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5920    );
 5921    cx.update_editor(|e, window, cx| {
 5922        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5923    });
 5924    cx.assert_editor_state(
 5925        indoc! {"
 5926            ·
 5927            abc                 // No indentation
 5928            «   abc               // 1 tabˇ»
 5929            \t\tabc             // 2 tabs
 5930            «    abc              // Tab followed by spaceˇ»
 5931             \tabc              // Space followed by tab (3 spaces should be the result)
 5932            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5933               abc              // Already space indented
 5934            «   ·
 5935               abc\tdef          // Only the leading tab is manipulatedˇ»
 5936        "}
 5937        .replace("·", "")
 5938        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5939    );
 5940
 5941    // SINGLE SELECTION
 5942    // Ln.1 "«" tests empty lines
 5943    // Ln.9 tests just leading whitespace
 5944    cx.set_state(indoc! {"
 5945        «
 5946        abc                 // No indentation
 5947        \tabc               // 1 tab
 5948        \t\tabc             // 2 tabs
 5949        \t abc              // Tab followed by space
 5950         \tabc              // Space followed by tab (3 spaces should be the result)
 5951        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5952           abc              // Already space indented
 5953        \t
 5954        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5955    "});
 5956    cx.update_editor(|e, window, cx| {
 5957        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5958    });
 5959    cx.assert_editor_state(
 5960        indoc! {"
 5961            «
 5962            abc                 // No indentation
 5963               abc               // 1 tab
 5964                  abc             // 2 tabs
 5965                abc              // Tab followed by space
 5966               abc              // Space followed by tab (3 spaces should be the result)
 5967                           abc   // Mixed indentation (tab conversion depends on the column)
 5968               abc              // Already space indented
 5969               ·
 5970               abc\tdef          // Only the leading tab is manipulatedˇ»
 5971        "}
 5972        .replace("·", "")
 5973        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5974    );
 5975}
 5976
 5977#[gpui::test]
 5978async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5979    init_test(cx, |settings| {
 5980        settings.defaults.tab_size = NonZeroU32::new(3)
 5981    });
 5982
 5983    let mut cx = EditorTestContext::new(cx).await;
 5984
 5985    // MULTI SELECTION
 5986    // Ln.1 "«" tests empty lines
 5987    // Ln.11 tests just leading whitespace
 5988    cx.set_state(indoc! {"
 5989        «
 5990        abˇ»ˇc                 // No indentation
 5991         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5992          abc  «             // 2 spaces (< 3 so dont convert)
 5993           abc              // 3 spaces (convert)
 5994             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5995        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5996        «\t abc              // Tab followed by space
 5997         \tabc              // Space followed by tab (should be consumed due to tab)
 5998        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5999           \tˇ»  «\t
 6000           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 6001    "});
 6002    cx.update_editor(|e, window, cx| {
 6003        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 6004    });
 6005    cx.assert_editor_state(indoc! {"
 6006        «
 6007        abc                 // No indentation
 6008         abc                // 1 space (< 3 so dont convert)
 6009          abc               // 2 spaces (< 3 so dont convert)
 6010        \tabc              // 3 spaces (convert)
 6011        \t  abc            // 5 spaces (1 tab + 2 spaces)
 6012        \t\t\tabc           // Already tab indented
 6013        \t abc              // Tab followed by space
 6014        \tabc              // Space followed by tab (should be consumed due to tab)
 6015        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 6016        \t\t\t
 6017        \tabc   \t         // Only the leading spaces should be convertedˇ»
 6018    "});
 6019
 6020    // Test on just a few lines, the other should remain unchanged
 6021    // Only lines (4, 8, 11, 12) should change
 6022    cx.set_state(
 6023        indoc! {"
 6024            ·
 6025            abc                 // No indentation
 6026             abc                // 1 space (< 3 so dont convert)
 6027              abc               // 2 spaces (< 3 so dont convert)
 6028            «   abc              // 3 spaces (convert)ˇ»
 6029                 abc            // 5 spaces (1 tab + 2 spaces)
 6030            \t\t\tabc           // Already tab indented
 6031            \t abc              // Tab followed by space
 6032             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 6033               \t\t  \tabc      // Mixed indentation
 6034            \t \t  \t   \tabc   // Mixed indentation
 6035               \t  \tˇ
 6036            «   abc   \t         // Only the leading spaces should be convertedˇ»
 6037        "}
 6038        .replace("·", "")
 6039        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 6040    );
 6041    cx.update_editor(|e, window, cx| {
 6042        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 6043    });
 6044    cx.assert_editor_state(
 6045        indoc! {"
 6046            ·
 6047            abc                 // No indentation
 6048             abc                // 1 space (< 3 so dont convert)
 6049              abc               // 2 spaces (< 3 so dont convert)
 6050            «\tabc              // 3 spaces (convert)ˇ»
 6051                 abc            // 5 spaces (1 tab + 2 spaces)
 6052            \t\t\tabc           // Already tab indented
 6053            \t abc              // Tab followed by space
 6054            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 6055               \t\t  \tabc      // Mixed indentation
 6056            \t \t  \t   \tabc   // Mixed indentation
 6057            «\t\t\t
 6058            \tabc   \t         // Only the leading spaces should be convertedˇ»
 6059        "}
 6060        .replace("·", "")
 6061        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 6062    );
 6063
 6064    // SINGLE SELECTION
 6065    // Ln.1 "«" tests empty lines
 6066    // Ln.11 tests just leading whitespace
 6067    cx.set_state(indoc! {"
 6068        «
 6069        abc                 // No indentation
 6070         abc                // 1 space (< 3 so dont convert)
 6071          abc               // 2 spaces (< 3 so dont convert)
 6072           abc              // 3 spaces (convert)
 6073             abc            // 5 spaces (1 tab + 2 spaces)
 6074        \t\t\tabc           // Already tab indented
 6075        \t abc              // Tab followed by space
 6076         \tabc              // Space followed by tab (should be consumed due to tab)
 6077        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 6078           \t  \t
 6079           abc   \t         // Only the leading spaces should be convertedˇ»
 6080    "});
 6081    cx.update_editor(|e, window, cx| {
 6082        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 6083    });
 6084    cx.assert_editor_state(indoc! {"
 6085        «
 6086        abc                 // No indentation
 6087         abc                // 1 space (< 3 so dont convert)
 6088          abc               // 2 spaces (< 3 so dont convert)
 6089        \tabc              // 3 spaces (convert)
 6090        \t  abc            // 5 spaces (1 tab + 2 spaces)
 6091        \t\t\tabc           // Already tab indented
 6092        \t abc              // Tab followed by space
 6093        \tabc              // Space followed by tab (should be consumed due to tab)
 6094        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 6095        \t\t\t
 6096        \tabc   \t         // Only the leading spaces should be convertedˇ»
 6097    "});
 6098}
 6099
 6100#[gpui::test]
 6101async fn test_toggle_case(cx: &mut TestAppContext) {
 6102    init_test(cx, |_| {});
 6103
 6104    let mut cx = EditorTestContext::new(cx).await;
 6105
 6106    // If all lower case -> upper case
 6107    cx.set_state(indoc! {"
 6108        «hello worldˇ»
 6109    "});
 6110    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 6111    cx.assert_editor_state(indoc! {"
 6112        «HELLO WORLDˇ»
 6113    "});
 6114
 6115    // If all upper case -> lower case
 6116    cx.set_state(indoc! {"
 6117        «HELLO WORLDˇ»
 6118    "});
 6119    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 6120    cx.assert_editor_state(indoc! {"
 6121        «hello worldˇ»
 6122    "});
 6123
 6124    // If any upper case characters are identified -> lower case
 6125    // This matches JetBrains IDEs
 6126    cx.set_state(indoc! {"
 6127        «hEllo worldˇ»
 6128    "});
 6129    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 6130    cx.assert_editor_state(indoc! {"
 6131        «hello worldˇ»
 6132    "});
 6133}
 6134
 6135#[gpui::test]
 6136async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 6137    init_test(cx, |_| {});
 6138
 6139    let mut cx = EditorTestContext::new(cx).await;
 6140
 6141    cx.set_state(indoc! {"
 6142        «implement-windows-supportˇ»
 6143    "});
 6144    cx.update_editor(|e, window, cx| {
 6145        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 6146    });
 6147    cx.assert_editor_state(indoc! {"
 6148        «Implement windows supportˇ»
 6149    "});
 6150}
 6151
 6152#[gpui::test]
 6153async fn test_manipulate_text(cx: &mut TestAppContext) {
 6154    init_test(cx, |_| {});
 6155
 6156    let mut cx = EditorTestContext::new(cx).await;
 6157
 6158    // Test convert_to_upper_case()
 6159    cx.set_state(indoc! {"
 6160        «hello worldˇ»
 6161    "});
 6162    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6163    cx.assert_editor_state(indoc! {"
 6164        «HELLO WORLDˇ»
 6165    "});
 6166
 6167    // Test convert_to_lower_case()
 6168    cx.set_state(indoc! {"
 6169        «HELLO WORLDˇ»
 6170    "});
 6171    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 6172    cx.assert_editor_state(indoc! {"
 6173        «hello worldˇ»
 6174    "});
 6175
 6176    // Test multiple line, single selection case
 6177    cx.set_state(indoc! {"
 6178        «The quick brown
 6179        fox jumps over
 6180        the lazy dogˇ»
 6181    "});
 6182    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 6183    cx.assert_editor_state(indoc! {"
 6184        «The Quick Brown
 6185        Fox Jumps Over
 6186        The Lazy Dogˇ»
 6187    "});
 6188
 6189    // Test multiple line, single selection case
 6190    cx.set_state(indoc! {"
 6191        «The quick brown
 6192        fox jumps over
 6193        the lazy dogˇ»
 6194    "});
 6195    cx.update_editor(|e, window, cx| {
 6196        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 6197    });
 6198    cx.assert_editor_state(indoc! {"
 6199        «TheQuickBrown
 6200        FoxJumpsOver
 6201        TheLazyDogˇ»
 6202    "});
 6203
 6204    // From here on out, test more complex cases of manipulate_text()
 6205
 6206    // Test no selection case - should affect words cursors are in
 6207    // Cursor at beginning, middle, and end of word
 6208    cx.set_state(indoc! {"
 6209        ˇhello big beauˇtiful worldˇ
 6210    "});
 6211    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6212    cx.assert_editor_state(indoc! {"
 6213        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 6214    "});
 6215
 6216    // Test multiple selections on a single line and across multiple lines
 6217    cx.set_state(indoc! {"
 6218        «Theˇ» quick «brown
 6219        foxˇ» jumps «overˇ»
 6220        the «lazyˇ» dog
 6221    "});
 6222    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6223    cx.assert_editor_state(indoc! {"
 6224        «THEˇ» quick «BROWN
 6225        FOXˇ» jumps «OVERˇ»
 6226        the «LAZYˇ» dog
 6227    "});
 6228
 6229    // Test case where text length grows
 6230    cx.set_state(indoc! {"
 6231        «tschüߡ»
 6232    "});
 6233    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6234    cx.assert_editor_state(indoc! {"
 6235        «TSCHÜSSˇ»
 6236    "});
 6237
 6238    // Test to make sure we don't crash when text shrinks
 6239    cx.set_state(indoc! {"
 6240        aaa_bbbˇ
 6241    "});
 6242    cx.update_editor(|e, window, cx| {
 6243        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 6244    });
 6245    cx.assert_editor_state(indoc! {"
 6246        «aaaBbbˇ»
 6247    "});
 6248
 6249    // Test to make sure we all aware of the fact that each word can grow and shrink
 6250    // Final selections should be aware of this fact
 6251    cx.set_state(indoc! {"
 6252        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 6253    "});
 6254    cx.update_editor(|e, window, cx| {
 6255        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 6256    });
 6257    cx.assert_editor_state(indoc! {"
 6258        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 6259    "});
 6260
 6261    cx.set_state(indoc! {"
 6262        «hElLo, WoRld!ˇ»
 6263    "});
 6264    cx.update_editor(|e, window, cx| {
 6265        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 6266    });
 6267    cx.assert_editor_state(indoc! {"
 6268        «HeLlO, wOrLD!ˇ»
 6269    "});
 6270
 6271    // Test that case conversions backed by `to_case` preserve leading/trailing whitespace.
 6272    cx.set_state(indoc! {"
 6273        «    hello worldˇ»
 6274    "});
 6275    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 6276    cx.assert_editor_state(indoc! {"
 6277        «    Hello Worldˇ»
 6278    "});
 6279
 6280    cx.set_state(indoc! {"
 6281        «    hello worldˇ»
 6282    "});
 6283    cx.update_editor(|e, window, cx| {
 6284        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 6285    });
 6286    cx.assert_editor_state(indoc! {"
 6287        «    HelloWorldˇ»
 6288    "});
 6289
 6290    cx.set_state(indoc! {"
 6291        «    hello worldˇ»
 6292    "});
 6293    cx.update_editor(|e, window, cx| {
 6294        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 6295    });
 6296    cx.assert_editor_state(indoc! {"
 6297        «    helloWorldˇ»
 6298    "});
 6299
 6300    cx.set_state(indoc! {"
 6301        «    hello worldˇ»
 6302    "});
 6303    cx.update_editor(|e, window, cx| e.convert_to_snake_case(&ConvertToSnakeCase, window, cx));
 6304    cx.assert_editor_state(indoc! {"
 6305        «    hello_worldˇ»
 6306    "});
 6307
 6308    cx.set_state(indoc! {"
 6309        «    hello worldˇ»
 6310    "});
 6311    cx.update_editor(|e, window, cx| e.convert_to_kebab_case(&ConvertToKebabCase, window, cx));
 6312    cx.assert_editor_state(indoc! {"
 6313        «    hello-worldˇ»
 6314    "});
 6315
 6316    cx.set_state(indoc! {"
 6317        «    hello worldˇ»
 6318    "});
 6319    cx.update_editor(|e, window, cx| {
 6320        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 6321    });
 6322    cx.assert_editor_state(indoc! {"
 6323        «    Hello worldˇ»
 6324    "});
 6325
 6326    cx.set_state(indoc! {"
 6327        «    hello world\t\tˇ»
 6328    "});
 6329    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 6330    cx.assert_editor_state(indoc! {"
 6331        «    Hello World\t\tˇ»
 6332    "});
 6333
 6334    cx.set_state(indoc! {"
 6335        «    hello world\t\tˇ»
 6336    "});
 6337    cx.update_editor(|e, window, cx| e.convert_to_snake_case(&ConvertToSnakeCase, window, cx));
 6338    cx.assert_editor_state(indoc! {"
 6339        «    hello_world\t\tˇ»
 6340    "});
 6341
 6342    // Test selections with `line_mode() = true`.
 6343    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 6344    cx.set_state(indoc! {"
 6345        «The quick brown
 6346        fox jumps over
 6347        tˇ»he lazy dog
 6348    "});
 6349    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 6350    cx.assert_editor_state(indoc! {"
 6351        «THE QUICK BROWN
 6352        FOX JUMPS OVER
 6353        THE LAZY DOGˇ»
 6354    "});
 6355}
 6356
 6357#[gpui::test]
 6358fn test_duplicate_line(cx: &mut TestAppContext) {
 6359    init_test(cx, |_| {});
 6360
 6361    let editor = cx.add_window(|window, cx| {
 6362        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6363        build_editor(buffer, window, cx)
 6364    });
 6365    _ = editor.update(cx, |editor, window, cx| {
 6366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6367            s.select_display_ranges([
 6368                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6369                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6370                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6371                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6372            ])
 6373        });
 6374        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 6375        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 6376        assert_eq!(
 6377            display_ranges(editor, cx),
 6378            vec![
 6379                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 6380                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 6381                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6382                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 6383            ]
 6384        );
 6385    });
 6386
 6387    let editor = cx.add_window(|window, cx| {
 6388        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6389        build_editor(buffer, window, cx)
 6390    });
 6391    _ = editor.update(cx, |editor, window, cx| {
 6392        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6393            s.select_display_ranges([
 6394                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6395                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 6396            ])
 6397        });
 6398        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 6399        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 6400        assert_eq!(
 6401            display_ranges(editor, cx),
 6402            vec![
 6403                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 6404                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 6405            ]
 6406        );
 6407    });
 6408
 6409    // With `duplicate_line_up` the selections move to the duplicated lines,
 6410    // which are inserted above the original lines
 6411    let editor = cx.add_window(|window, cx| {
 6412        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6413        build_editor(buffer, window, cx)
 6414    });
 6415    _ = editor.update(cx, |editor, window, cx| {
 6416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6417            s.select_display_ranges([
 6418                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6419                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6420                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6421                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6422            ])
 6423        });
 6424        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 6425        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 6426        assert_eq!(
 6427            display_ranges(editor, cx),
 6428            vec![
 6429                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6430                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6431                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6432                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6433            ]
 6434        );
 6435    });
 6436
 6437    let editor = cx.add_window(|window, cx| {
 6438        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6439        build_editor(buffer, window, cx)
 6440    });
 6441    _ = editor.update(cx, |editor, window, cx| {
 6442        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6443            s.select_display_ranges([
 6444                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6445                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 6446            ])
 6447        });
 6448        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 6449        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 6450        assert_eq!(
 6451            display_ranges(editor, cx),
 6452            vec![
 6453                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6454                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 6455            ]
 6456        );
 6457    });
 6458
 6459    let editor = cx.add_window(|window, cx| {
 6460        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 6461        build_editor(buffer, window, cx)
 6462    });
 6463    _ = editor.update(cx, |editor, window, cx| {
 6464        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6465            s.select_display_ranges([
 6466                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6467                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 6468            ])
 6469        });
 6470        editor.duplicate_selection(&DuplicateSelection, window, cx);
 6471        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 6472        assert_eq!(
 6473            display_ranges(editor, cx),
 6474            vec![
 6475                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6476                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 6477            ]
 6478        );
 6479    });
 6480}
 6481
 6482#[gpui::test]
 6483async fn test_rotate_selections(cx: &mut TestAppContext) {
 6484    init_test(cx, |_| {});
 6485
 6486    let mut cx = EditorTestContext::new(cx).await;
 6487
 6488    // Rotate text selections (horizontal)
 6489    cx.set_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 6490    cx.update_editor(|e, window, cx| {
 6491        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6492    });
 6493    cx.assert_editor_state("x=«3ˇ», y=«1ˇ», z=«2ˇ»");
 6494    cx.update_editor(|e, window, cx| {
 6495        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6496    });
 6497    cx.assert_editor_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 6498
 6499    // Rotate text selections (vertical)
 6500    cx.set_state(indoc! {"
 6501        x=«1ˇ»
 6502        y=«2ˇ»
 6503        z=«3ˇ»
 6504    "});
 6505    cx.update_editor(|e, window, cx| {
 6506        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6507    });
 6508    cx.assert_editor_state(indoc! {"
 6509        x=«3ˇ»
 6510        y=«1ˇ»
 6511        z=«2ˇ»
 6512    "});
 6513    cx.update_editor(|e, window, cx| {
 6514        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6515    });
 6516    cx.assert_editor_state(indoc! {"
 6517        x=«1ˇ»
 6518        y=«2ˇ»
 6519        z=«3ˇ»
 6520    "});
 6521
 6522    // Rotate text selections (vertical, different lengths)
 6523    cx.set_state(indoc! {"
 6524        x=\"«ˇ»\"
 6525        y=\"«aˇ»\"
 6526        z=\"«aaˇ»\"
 6527    "});
 6528    cx.update_editor(|e, window, cx| {
 6529        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6530    });
 6531    cx.assert_editor_state(indoc! {"
 6532        x=\"«aaˇ»\"
 6533        y=\"«ˇ»\"
 6534        z=\"«aˇ»\"
 6535    "});
 6536    cx.update_editor(|e, window, cx| {
 6537        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6538    });
 6539    cx.assert_editor_state(indoc! {"
 6540        x=\"«ˇ»\"
 6541        y=\"«aˇ»\"
 6542        z=\"«aaˇ»\"
 6543    "});
 6544
 6545    // Rotate whole lines (cursor positions preserved)
 6546    cx.set_state(indoc! {"
 6547        ˇline123
 6548        liˇne23
 6549        line3ˇ
 6550    "});
 6551    cx.update_editor(|e, window, cx| {
 6552        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6553    });
 6554    cx.assert_editor_state(indoc! {"
 6555        line3ˇ
 6556        ˇline123
 6557        liˇne23
 6558    "});
 6559    cx.update_editor(|e, window, cx| {
 6560        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6561    });
 6562    cx.assert_editor_state(indoc! {"
 6563        ˇline123
 6564        liˇne23
 6565        line3ˇ
 6566    "});
 6567
 6568    // Rotate whole lines, multiple cursors per line (positions preserved)
 6569    cx.set_state(indoc! {"
 6570        ˇliˇne123
 6571        ˇline23
 6572        ˇline3
 6573    "});
 6574    cx.update_editor(|e, window, cx| {
 6575        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 6576    });
 6577    cx.assert_editor_state(indoc! {"
 6578        ˇline3
 6579        ˇliˇne123
 6580        ˇline23
 6581    "});
 6582    cx.update_editor(|e, window, cx| {
 6583        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 6584    });
 6585    cx.assert_editor_state(indoc! {"
 6586        ˇliˇne123
 6587        ˇline23
 6588        ˇline3
 6589    "});
 6590}
 6591
 6592#[gpui::test]
 6593fn test_move_line_up_down(cx: &mut TestAppContext) {
 6594    init_test(cx, |_| {});
 6595
 6596    let editor = cx.add_window(|window, cx| {
 6597        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6598        build_editor(buffer, window, cx)
 6599    });
 6600    _ = editor.update(cx, |editor, window, cx| {
 6601        editor.fold_creases(
 6602            vec![
 6603                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6604                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6605                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6606            ],
 6607            true,
 6608            window,
 6609            cx,
 6610        );
 6611        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6612            s.select_display_ranges([
 6613                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 6614                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6615                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6616                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 6617            ])
 6618        });
 6619        assert_eq!(
 6620            editor.display_text(cx),
 6621            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 6622        );
 6623
 6624        editor.move_line_up(&MoveLineUp, window, cx);
 6625        assert_eq!(
 6626            editor.display_text(cx),
 6627            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 6628        );
 6629        assert_eq!(
 6630            display_ranges(editor, cx),
 6631            vec![
 6632                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 6633                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6634                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6635                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6636            ]
 6637        );
 6638    });
 6639
 6640    _ = editor.update(cx, |editor, window, cx| {
 6641        editor.move_line_down(&MoveLineDown, window, cx);
 6642        assert_eq!(
 6643            editor.display_text(cx),
 6644            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 6645        );
 6646        assert_eq!(
 6647            display_ranges(editor, cx),
 6648            vec![
 6649                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6650                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6651                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6652                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6653            ]
 6654        );
 6655    });
 6656
 6657    _ = editor.update(cx, |editor, window, cx| {
 6658        editor.move_line_down(&MoveLineDown, window, cx);
 6659        assert_eq!(
 6660            editor.display_text(cx),
 6661            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 6662        );
 6663        assert_eq!(
 6664            display_ranges(editor, cx),
 6665            vec![
 6666                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6667                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 6668                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 6669                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 6670            ]
 6671        );
 6672    });
 6673
 6674    _ = editor.update(cx, |editor, window, cx| {
 6675        editor.move_line_up(&MoveLineUp, window, cx);
 6676        assert_eq!(
 6677            editor.display_text(cx),
 6678            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 6679        );
 6680        assert_eq!(
 6681            display_ranges(editor, cx),
 6682            vec![
 6683                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 6684                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 6685                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 6686                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 6687            ]
 6688        );
 6689    });
 6690}
 6691
 6692#[gpui::test]
 6693fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 6694    init_test(cx, |_| {});
 6695    let editor = cx.add_window(|window, cx| {
 6696        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 6697        build_editor(buffer, window, cx)
 6698    });
 6699    _ = editor.update(cx, |editor, window, cx| {
 6700        editor.fold_creases(
 6701            vec![Crease::simple(
 6702                Point::new(6, 4)..Point::new(7, 4),
 6703                FoldPlaceholder::test(),
 6704            )],
 6705            true,
 6706            window,
 6707            cx,
 6708        );
 6709        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6710            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 6711        });
 6712        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 6713        editor.move_line_up(&MoveLineUp, window, cx);
 6714        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 6715        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 6716    });
 6717}
 6718
 6719#[gpui::test]
 6720fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 6721    init_test(cx, |_| {});
 6722
 6723    let editor = cx.add_window(|window, cx| {
 6724        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6725        build_editor(buffer, window, cx)
 6726    });
 6727    _ = editor.update(cx, |editor, window, cx| {
 6728        let snapshot = editor.buffer.read(cx).snapshot(cx);
 6729        editor.insert_blocks(
 6730            [BlockProperties {
 6731                style: BlockStyle::Fixed,
 6732                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 6733                height: Some(1),
 6734                render: Arc::new(|_| div().into_any()),
 6735                priority: 0,
 6736            }],
 6737            Some(Autoscroll::fit()),
 6738            cx,
 6739        );
 6740        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6741            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 6742        });
 6743        editor.move_line_down(&MoveLineDown, window, cx);
 6744    });
 6745}
 6746
 6747#[gpui::test]
 6748async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 6749    init_test(cx, |_| {});
 6750
 6751    let mut cx = EditorTestContext::new(cx).await;
 6752    cx.set_state(
 6753        &"
 6754            ˇzero
 6755            one
 6756            two
 6757            three
 6758            four
 6759            five
 6760        "
 6761        .unindent(),
 6762    );
 6763
 6764    // Create a four-line block that replaces three lines of text.
 6765    cx.update_editor(|editor, window, cx| {
 6766        let snapshot = editor.snapshot(window, cx);
 6767        let snapshot = &snapshot.buffer_snapshot();
 6768        let placement = BlockPlacement::Replace(
 6769            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 6770        );
 6771        editor.insert_blocks(
 6772            [BlockProperties {
 6773                placement,
 6774                height: Some(4),
 6775                style: BlockStyle::Sticky,
 6776                render: Arc::new(|_| gpui::div().into_any_element()),
 6777                priority: 0,
 6778            }],
 6779            None,
 6780            cx,
 6781        );
 6782    });
 6783
 6784    // Move down so that the cursor touches the block.
 6785    cx.update_editor(|editor, window, cx| {
 6786        editor.move_down(&Default::default(), window, cx);
 6787    });
 6788    cx.assert_editor_state(
 6789        &"
 6790            zero
 6791            «one
 6792            two
 6793            threeˇ»
 6794            four
 6795            five
 6796        "
 6797        .unindent(),
 6798    );
 6799
 6800    // Move down past the block.
 6801    cx.update_editor(|editor, window, cx| {
 6802        editor.move_down(&Default::default(), window, cx);
 6803    });
 6804    cx.assert_editor_state(
 6805        &"
 6806            zero
 6807            one
 6808            two
 6809            three
 6810            ˇfour
 6811            five
 6812        "
 6813        .unindent(),
 6814    );
 6815}
 6816
 6817#[gpui::test]
 6818fn test_transpose(cx: &mut TestAppContext) {
 6819    init_test(cx, |_| {});
 6820
 6821    _ = cx.add_window(|window, cx| {
 6822        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6823        editor.set_style(EditorStyle::default(), window, cx);
 6824        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6825            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6826        });
 6827        editor.transpose(&Default::default(), window, cx);
 6828        assert_eq!(editor.text(cx), "bac");
 6829        assert_eq!(
 6830            editor.selections.ranges(&editor.display_snapshot(cx)),
 6831            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6832        );
 6833
 6834        editor.transpose(&Default::default(), window, cx);
 6835        assert_eq!(editor.text(cx), "bca");
 6836        assert_eq!(
 6837            editor.selections.ranges(&editor.display_snapshot(cx)),
 6838            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6839        );
 6840
 6841        editor.transpose(&Default::default(), window, cx);
 6842        assert_eq!(editor.text(cx), "bac");
 6843        assert_eq!(
 6844            editor.selections.ranges(&editor.display_snapshot(cx)),
 6845            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6846        );
 6847
 6848        editor
 6849    });
 6850
 6851    _ = cx.add_window(|window, cx| {
 6852        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6853        editor.set_style(EditorStyle::default(), window, cx);
 6854        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6855            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6856        });
 6857        editor.transpose(&Default::default(), window, cx);
 6858        assert_eq!(editor.text(cx), "acb\nde");
 6859        assert_eq!(
 6860            editor.selections.ranges(&editor.display_snapshot(cx)),
 6861            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6862        );
 6863
 6864        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6865            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6866        });
 6867        editor.transpose(&Default::default(), window, cx);
 6868        assert_eq!(editor.text(cx), "acbd\ne");
 6869        assert_eq!(
 6870            editor.selections.ranges(&editor.display_snapshot(cx)),
 6871            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6872        );
 6873
 6874        editor.transpose(&Default::default(), window, cx);
 6875        assert_eq!(editor.text(cx), "acbde\n");
 6876        assert_eq!(
 6877            editor.selections.ranges(&editor.display_snapshot(cx)),
 6878            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6879        );
 6880
 6881        editor.transpose(&Default::default(), window, cx);
 6882        assert_eq!(editor.text(cx), "acbd\ne");
 6883        assert_eq!(
 6884            editor.selections.ranges(&editor.display_snapshot(cx)),
 6885            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6886        );
 6887
 6888        editor
 6889    });
 6890
 6891    _ = cx.add_window(|window, cx| {
 6892        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6893        editor.set_style(EditorStyle::default(), window, cx);
 6894        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6895            s.select_ranges([
 6896                MultiBufferOffset(1)..MultiBufferOffset(1),
 6897                MultiBufferOffset(2)..MultiBufferOffset(2),
 6898                MultiBufferOffset(4)..MultiBufferOffset(4),
 6899            ])
 6900        });
 6901        editor.transpose(&Default::default(), window, cx);
 6902        assert_eq!(editor.text(cx), "bacd\ne");
 6903        assert_eq!(
 6904            editor.selections.ranges(&editor.display_snapshot(cx)),
 6905            [
 6906                MultiBufferOffset(2)..MultiBufferOffset(2),
 6907                MultiBufferOffset(3)..MultiBufferOffset(3),
 6908                MultiBufferOffset(5)..MultiBufferOffset(5)
 6909            ]
 6910        );
 6911
 6912        editor.transpose(&Default::default(), window, cx);
 6913        assert_eq!(editor.text(cx), "bcade\n");
 6914        assert_eq!(
 6915            editor.selections.ranges(&editor.display_snapshot(cx)),
 6916            [
 6917                MultiBufferOffset(3)..MultiBufferOffset(3),
 6918                MultiBufferOffset(4)..MultiBufferOffset(4),
 6919                MultiBufferOffset(6)..MultiBufferOffset(6)
 6920            ]
 6921        );
 6922
 6923        editor.transpose(&Default::default(), window, cx);
 6924        assert_eq!(editor.text(cx), "bcda\ne");
 6925        assert_eq!(
 6926            editor.selections.ranges(&editor.display_snapshot(cx)),
 6927            [
 6928                MultiBufferOffset(4)..MultiBufferOffset(4),
 6929                MultiBufferOffset(6)..MultiBufferOffset(6)
 6930            ]
 6931        );
 6932
 6933        editor.transpose(&Default::default(), window, cx);
 6934        assert_eq!(editor.text(cx), "bcade\n");
 6935        assert_eq!(
 6936            editor.selections.ranges(&editor.display_snapshot(cx)),
 6937            [
 6938                MultiBufferOffset(4)..MultiBufferOffset(4),
 6939                MultiBufferOffset(6)..MultiBufferOffset(6)
 6940            ]
 6941        );
 6942
 6943        editor.transpose(&Default::default(), window, cx);
 6944        assert_eq!(editor.text(cx), "bcaed\n");
 6945        assert_eq!(
 6946            editor.selections.ranges(&editor.display_snapshot(cx)),
 6947            [
 6948                MultiBufferOffset(5)..MultiBufferOffset(5),
 6949                MultiBufferOffset(6)..MultiBufferOffset(6)
 6950            ]
 6951        );
 6952
 6953        editor
 6954    });
 6955
 6956    _ = cx.add_window(|window, cx| {
 6957        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6958        editor.set_style(EditorStyle::default(), window, cx);
 6959        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6960            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6961        });
 6962        editor.transpose(&Default::default(), window, cx);
 6963        assert_eq!(editor.text(cx), "🏀🍐✋");
 6964        assert_eq!(
 6965            editor.selections.ranges(&editor.display_snapshot(cx)),
 6966            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6967        );
 6968
 6969        editor.transpose(&Default::default(), window, cx);
 6970        assert_eq!(editor.text(cx), "🏀✋🍐");
 6971        assert_eq!(
 6972            editor.selections.ranges(&editor.display_snapshot(cx)),
 6973            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6974        );
 6975
 6976        editor.transpose(&Default::default(), window, cx);
 6977        assert_eq!(editor.text(cx), "🏀🍐✋");
 6978        assert_eq!(
 6979            editor.selections.ranges(&editor.display_snapshot(cx)),
 6980            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6981        );
 6982
 6983        editor
 6984    });
 6985}
 6986
 6987#[gpui::test]
 6988async fn test_rewrap(cx: &mut TestAppContext) {
 6989    init_test(cx, |settings| {
 6990        settings.languages.0.extend([
 6991            (
 6992                "Markdown".into(),
 6993                LanguageSettingsContent {
 6994                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6995                    preferred_line_length: Some(40),
 6996                    ..Default::default()
 6997                },
 6998            ),
 6999            (
 7000                "Plain Text".into(),
 7001                LanguageSettingsContent {
 7002                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 7003                    preferred_line_length: Some(40),
 7004                    ..Default::default()
 7005                },
 7006            ),
 7007            (
 7008                "C++".into(),
 7009                LanguageSettingsContent {
 7010                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 7011                    preferred_line_length: Some(40),
 7012                    ..Default::default()
 7013                },
 7014            ),
 7015            (
 7016                "Python".into(),
 7017                LanguageSettingsContent {
 7018                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 7019                    preferred_line_length: Some(40),
 7020                    ..Default::default()
 7021                },
 7022            ),
 7023            (
 7024                "Rust".into(),
 7025                LanguageSettingsContent {
 7026                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 7027                    preferred_line_length: Some(40),
 7028                    ..Default::default()
 7029                },
 7030            ),
 7031        ])
 7032    });
 7033
 7034    let mut cx = EditorTestContext::new(cx).await;
 7035
 7036    let cpp_language = Arc::new(Language::new(
 7037        LanguageConfig {
 7038            name: "C++".into(),
 7039            line_comments: vec!["// ".into()],
 7040            ..LanguageConfig::default()
 7041        },
 7042        None,
 7043    ));
 7044    let python_language = Arc::new(Language::new(
 7045        LanguageConfig {
 7046            name: "Python".into(),
 7047            line_comments: vec!["# ".into()],
 7048            ..LanguageConfig::default()
 7049        },
 7050        None,
 7051    ));
 7052    let markdown_language = Arc::new(Language::new(
 7053        LanguageConfig {
 7054            name: "Markdown".into(),
 7055            rewrap_prefixes: vec![
 7056                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 7057                regex::Regex::new("[-*+]\\s+").unwrap(),
 7058            ],
 7059            ..LanguageConfig::default()
 7060        },
 7061        None,
 7062    ));
 7063    let rust_language = Arc::new(
 7064        Language::new(
 7065            LanguageConfig {
 7066                name: "Rust".into(),
 7067                line_comments: vec!["// ".into(), "/// ".into()],
 7068                ..LanguageConfig::default()
 7069            },
 7070            Some(tree_sitter_rust::LANGUAGE.into()),
 7071        )
 7072        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 7073        .unwrap(),
 7074    );
 7075
 7076    let plaintext_language = Arc::new(Language::new(
 7077        LanguageConfig {
 7078            name: "Plain Text".into(),
 7079            ..LanguageConfig::default()
 7080        },
 7081        None,
 7082    ));
 7083
 7084    // Test basic rewrapping of a long line with a cursor
 7085    assert_rewrap(
 7086        indoc! {"
 7087            // ˇThis is a long comment that needs to be wrapped.
 7088        "},
 7089        indoc! {"
 7090            // ˇThis is a long comment that needs to
 7091            // be wrapped.
 7092        "},
 7093        cpp_language.clone(),
 7094        &mut cx,
 7095    );
 7096
 7097    // Test rewrapping a full selection
 7098    assert_rewrap(
 7099        indoc! {"
 7100            «// This selected long comment needs to be wrapped.ˇ»"
 7101        },
 7102        indoc! {"
 7103            «// This selected long comment needs to
 7104            // be wrapped.ˇ»"
 7105        },
 7106        cpp_language.clone(),
 7107        &mut cx,
 7108    );
 7109
 7110    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 7111    assert_rewrap(
 7112        indoc! {"
 7113            // ˇThis is the first line.
 7114            // Thisˇ is the second line.
 7115            // This is the thirdˇ line, all part of one paragraph.
 7116         "},
 7117        indoc! {"
 7118            // ˇThis is the first line. Thisˇ is the
 7119            // second line. This is the thirdˇ line,
 7120            // all part of one paragraph.
 7121         "},
 7122        cpp_language.clone(),
 7123        &mut cx,
 7124    );
 7125
 7126    // Test multiple cursors in different paragraphs trigger separate rewraps
 7127    assert_rewrap(
 7128        indoc! {"
 7129            // ˇThis is the first paragraph, first line.
 7130            // ˇThis is the first paragraph, second line.
 7131
 7132            // ˇThis is the second paragraph, first line.
 7133            // ˇThis is the second paragraph, second line.
 7134        "},
 7135        indoc! {"
 7136            // ˇThis is the first paragraph, first
 7137            // line. ˇThis is the first paragraph,
 7138            // second line.
 7139
 7140            // ˇThis is the second paragraph, first
 7141            // line. ˇThis is the second paragraph,
 7142            // second line.
 7143        "},
 7144        cpp_language.clone(),
 7145        &mut cx,
 7146    );
 7147
 7148    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 7149    assert_rewrap(
 7150        indoc! {"
 7151            «// A regular long long comment to be wrapped.
 7152            /// A documentation long comment to be wrapped.ˇ»
 7153          "},
 7154        indoc! {"
 7155            «// A regular long long comment to be
 7156            // wrapped.
 7157            /// A documentation long comment to be
 7158            /// wrapped.ˇ»
 7159          "},
 7160        rust_language.clone(),
 7161        &mut cx,
 7162    );
 7163
 7164    // Test that change in indentation level trigger seperate rewraps
 7165    assert_rewrap(
 7166        indoc! {"
 7167            fn foo() {
 7168                «// This is a long comment at the base indent.
 7169                    // This is a long comment at the next indent.ˇ»
 7170            }
 7171        "},
 7172        indoc! {"
 7173            fn foo() {
 7174                «// This is a long comment at the
 7175                // base indent.
 7176                    // This is a long comment at the
 7177                    // next indent.ˇ»
 7178            }
 7179        "},
 7180        rust_language.clone(),
 7181        &mut cx,
 7182    );
 7183
 7184    // Test that different comment prefix characters (e.g., '#') are handled correctly
 7185    assert_rewrap(
 7186        indoc! {"
 7187            # ˇThis is a long comment using a pound sign.
 7188        "},
 7189        indoc! {"
 7190            # ˇThis is a long comment using a pound
 7191            # sign.
 7192        "},
 7193        python_language,
 7194        &mut cx,
 7195    );
 7196
 7197    // Test rewrapping only affects comments, not code even when selected
 7198    assert_rewrap(
 7199        indoc! {"
 7200            «/// This doc comment is long and should be wrapped.
 7201            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 7202        "},
 7203        indoc! {"
 7204            «/// This doc comment is long and should
 7205            /// be wrapped.
 7206            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 7207        "},
 7208        rust_language.clone(),
 7209        &mut cx,
 7210    );
 7211
 7212    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 7213    assert_rewrap(
 7214        indoc! {"
 7215            # Header
 7216
 7217            A long long long line of markdown text to wrap.ˇ
 7218         "},
 7219        indoc! {"
 7220            # Header
 7221
 7222            A long long long line of markdown text
 7223            to wrap.ˇ
 7224         "},
 7225        markdown_language.clone(),
 7226        &mut cx,
 7227    );
 7228
 7229    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 7230    assert_rewrap(
 7231        indoc! {"
 7232            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 7233            2. This is a numbered list item that is very long and needs to be wrapped properly.
 7234            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 7235        "},
 7236        indoc! {"
 7237            «1. This is a numbered list item that is
 7238               very long and needs to be wrapped
 7239               properly.
 7240            2. This is a numbered list item that is
 7241               very long and needs to be wrapped
 7242               properly.
 7243            - This is an unordered list item that is
 7244              also very long and should not merge
 7245              with the numbered item.ˇ»
 7246        "},
 7247        markdown_language.clone(),
 7248        &mut cx,
 7249    );
 7250
 7251    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 7252    assert_rewrap(
 7253        indoc! {"
 7254            «1. This is a numbered list item that is
 7255            very long and needs to be wrapped
 7256            properly.
 7257            2. This is a numbered list item that is
 7258            very long and needs to be wrapped
 7259            properly.
 7260            - This is an unordered list item that is
 7261            also very long and should not merge with
 7262            the numbered item.ˇ»
 7263        "},
 7264        indoc! {"
 7265            «1. This is a numbered list item that is
 7266               very long and needs to be wrapped
 7267               properly.
 7268            2. This is a numbered list item that is
 7269               very long and needs to be wrapped
 7270               properly.
 7271            - This is an unordered list item that is
 7272              also very long and should not merge
 7273              with the numbered item.ˇ»
 7274        "},
 7275        markdown_language.clone(),
 7276        &mut cx,
 7277    );
 7278
 7279    // Test that rewrapping maintain indents even when they already exists.
 7280    assert_rewrap(
 7281        indoc! {"
 7282            «1. This is a numbered list
 7283               item that is very long and needs to be wrapped properly.
 7284            2. This is a numbered list
 7285               item that is very long and needs to be wrapped properly.
 7286            - This is an unordered list item that is also very long and
 7287              should not merge with the numbered item.ˇ»
 7288        "},
 7289        indoc! {"
 7290            «1. This is a numbered list item that is
 7291               very long and needs to be wrapped
 7292               properly.
 7293            2. This is a numbered list item that is
 7294               very long and needs to be wrapped
 7295               properly.
 7296            - This is an unordered list item that is
 7297              also very long and should not merge
 7298              with the numbered item.ˇ»
 7299        "},
 7300        markdown_language,
 7301        &mut cx,
 7302    );
 7303
 7304    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 7305    assert_rewrap(
 7306        indoc! {"
 7307            ˇThis is a very long line of plain text that will be wrapped.
 7308        "},
 7309        indoc! {"
 7310            ˇThis is a very long line of plain text
 7311            that will be wrapped.
 7312        "},
 7313        plaintext_language.clone(),
 7314        &mut cx,
 7315    );
 7316
 7317    // Test that non-commented code acts as a paragraph boundary within a selection
 7318    assert_rewrap(
 7319        indoc! {"
 7320               «// This is the first long comment block to be wrapped.
 7321               fn my_func(a: u32);
 7322               // This is the second long comment block to be wrapped.ˇ»
 7323           "},
 7324        indoc! {"
 7325               «// This is the first long comment block
 7326               // to be wrapped.
 7327               fn my_func(a: u32);
 7328               // This is the second long comment block
 7329               // to be wrapped.ˇ»
 7330           "},
 7331        rust_language,
 7332        &mut cx,
 7333    );
 7334
 7335    // Test rewrapping multiple selections, including ones with blank lines or tabs
 7336    assert_rewrap(
 7337        indoc! {"
 7338            «ˇThis is a very long line that will be wrapped.
 7339
 7340            This is another paragraph in the same selection.»
 7341
 7342            «\tThis is a very long indented line that will be wrapped.ˇ»
 7343         "},
 7344        indoc! {"
 7345            «ˇThis is a very long line that will be
 7346            wrapped.
 7347
 7348            This is another paragraph in the same
 7349            selection.»
 7350
 7351            «\tThis is a very long indented line
 7352            \tthat will be wrapped.ˇ»
 7353         "},
 7354        plaintext_language,
 7355        &mut cx,
 7356    );
 7357
 7358    // Test that an empty comment line acts as a paragraph boundary
 7359    assert_rewrap(
 7360        indoc! {"
 7361            // ˇThis is a long comment that will be wrapped.
 7362            //
 7363            // And this is another long comment that will also be wrapped.ˇ
 7364         "},
 7365        indoc! {"
 7366            // ˇThis is a long comment that will be
 7367            // wrapped.
 7368            //
 7369            // And this is another long comment that
 7370            // will also be wrapped.ˇ
 7371         "},
 7372        cpp_language,
 7373        &mut cx,
 7374    );
 7375
 7376    #[track_caller]
 7377    fn assert_rewrap(
 7378        unwrapped_text: &str,
 7379        wrapped_text: &str,
 7380        language: Arc<Language>,
 7381        cx: &mut EditorTestContext,
 7382    ) {
 7383        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7384        cx.set_state(unwrapped_text);
 7385        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7386        cx.assert_editor_state(wrapped_text);
 7387    }
 7388}
 7389
 7390#[gpui::test]
 7391async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 7392    init_test(cx, |settings| {
 7393        settings.languages.0.extend([(
 7394            "Rust".into(),
 7395            LanguageSettingsContent {
 7396                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 7397                preferred_line_length: Some(40),
 7398                ..Default::default()
 7399            },
 7400        )])
 7401    });
 7402
 7403    let mut cx = EditorTestContext::new(cx).await;
 7404
 7405    let rust_lang = Arc::new(
 7406        Language::new(
 7407            LanguageConfig {
 7408                name: "Rust".into(),
 7409                line_comments: vec!["// ".into()],
 7410                block_comment: Some(BlockCommentConfig {
 7411                    start: "/*".into(),
 7412                    end: "*/".into(),
 7413                    prefix: "* ".into(),
 7414                    tab_size: 1,
 7415                }),
 7416                documentation_comment: Some(BlockCommentConfig {
 7417                    start: "/**".into(),
 7418                    end: "*/".into(),
 7419                    prefix: "* ".into(),
 7420                    tab_size: 1,
 7421                }),
 7422
 7423                ..LanguageConfig::default()
 7424            },
 7425            Some(tree_sitter_rust::LANGUAGE.into()),
 7426        )
 7427        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 7428        .unwrap(),
 7429    );
 7430
 7431    // regular block comment
 7432    assert_rewrap(
 7433        indoc! {"
 7434            /*
 7435             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7436             */
 7437            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7438        "},
 7439        indoc! {"
 7440            /*
 7441             *ˇ Lorem ipsum dolor sit amet,
 7442             * consectetur adipiscing elit.
 7443             */
 7444            /*
 7445             *ˇ Lorem ipsum dolor sit amet,
 7446             * consectetur adipiscing elit.
 7447             */
 7448        "},
 7449        rust_lang.clone(),
 7450        &mut cx,
 7451    );
 7452
 7453    // indent is respected
 7454    assert_rewrap(
 7455        indoc! {"
 7456            {}
 7457                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7458        "},
 7459        indoc! {"
 7460            {}
 7461                /*
 7462                 *ˇ Lorem ipsum dolor sit amet,
 7463                 * consectetur adipiscing elit.
 7464                 */
 7465        "},
 7466        rust_lang.clone(),
 7467        &mut cx,
 7468    );
 7469
 7470    // short block comments with inline delimiters
 7471    assert_rewrap(
 7472        indoc! {"
 7473            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7474            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7475             */
 7476            /*
 7477             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7478        "},
 7479        indoc! {"
 7480            /*
 7481             *ˇ Lorem ipsum dolor sit amet,
 7482             * consectetur adipiscing elit.
 7483             */
 7484            /*
 7485             *ˇ Lorem ipsum dolor sit amet,
 7486             * consectetur adipiscing elit.
 7487             */
 7488            /*
 7489             *ˇ Lorem ipsum dolor sit amet,
 7490             * consectetur adipiscing elit.
 7491             */
 7492        "},
 7493        rust_lang.clone(),
 7494        &mut cx,
 7495    );
 7496
 7497    // multiline block comment with inline start/end delimiters
 7498    assert_rewrap(
 7499        indoc! {"
 7500            /*ˇ Lorem ipsum dolor sit amet,
 7501             * consectetur adipiscing elit. */
 7502        "},
 7503        indoc! {"
 7504            /*
 7505             *ˇ Lorem ipsum dolor sit amet,
 7506             * consectetur adipiscing elit.
 7507             */
 7508        "},
 7509        rust_lang.clone(),
 7510        &mut cx,
 7511    );
 7512
 7513    // block comment rewrap still respects paragraph bounds
 7514    assert_rewrap(
 7515        indoc! {"
 7516            /*
 7517             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7518             *
 7519             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7520             */
 7521        "},
 7522        indoc! {"
 7523            /*
 7524             *ˇ Lorem ipsum dolor sit amet,
 7525             * consectetur adipiscing elit.
 7526             *
 7527             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7528             */
 7529        "},
 7530        rust_lang.clone(),
 7531        &mut cx,
 7532    );
 7533
 7534    // documentation comments
 7535    assert_rewrap(
 7536        indoc! {"
 7537            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7538            /**
 7539             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7540             */
 7541        "},
 7542        indoc! {"
 7543            /**
 7544             *ˇ Lorem ipsum dolor sit amet,
 7545             * consectetur adipiscing elit.
 7546             */
 7547            /**
 7548             *ˇ Lorem ipsum dolor sit amet,
 7549             * consectetur adipiscing elit.
 7550             */
 7551        "},
 7552        rust_lang.clone(),
 7553        &mut cx,
 7554    );
 7555
 7556    // different, adjacent comments
 7557    assert_rewrap(
 7558        indoc! {"
 7559            /**
 7560             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7561             */
 7562            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7563            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7564        "},
 7565        indoc! {"
 7566            /**
 7567             *ˇ Lorem ipsum dolor sit amet,
 7568             * consectetur adipiscing elit.
 7569             */
 7570            /*
 7571             *ˇ Lorem ipsum dolor sit amet,
 7572             * consectetur adipiscing elit.
 7573             */
 7574            //ˇ Lorem ipsum dolor sit amet,
 7575            // consectetur adipiscing elit.
 7576        "},
 7577        rust_lang.clone(),
 7578        &mut cx,
 7579    );
 7580
 7581    // selection w/ single short block comment
 7582    assert_rewrap(
 7583        indoc! {"
 7584            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7585        "},
 7586        indoc! {"
 7587            «/*
 7588             * Lorem ipsum dolor sit amet,
 7589             * consectetur adipiscing elit.
 7590             */ˇ»
 7591        "},
 7592        rust_lang.clone(),
 7593        &mut cx,
 7594    );
 7595
 7596    // rewrapping a single comment w/ abutting comments
 7597    assert_rewrap(
 7598        indoc! {"
 7599            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7600            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7601        "},
 7602        indoc! {"
 7603            /*
 7604             * ˇLorem ipsum dolor sit amet,
 7605             * consectetur adipiscing elit.
 7606             */
 7607            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7608        "},
 7609        rust_lang.clone(),
 7610        &mut cx,
 7611    );
 7612
 7613    // selection w/ non-abutting short block comments
 7614    assert_rewrap(
 7615        indoc! {"
 7616            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7617
 7618            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7619        "},
 7620        indoc! {"
 7621            «/*
 7622             * Lorem ipsum dolor sit amet,
 7623             * consectetur adipiscing elit.
 7624             */
 7625
 7626            /*
 7627             * Lorem ipsum dolor sit amet,
 7628             * consectetur adipiscing elit.
 7629             */ˇ»
 7630        "},
 7631        rust_lang.clone(),
 7632        &mut cx,
 7633    );
 7634
 7635    // selection of multiline block comments
 7636    assert_rewrap(
 7637        indoc! {"
 7638            «/* Lorem ipsum dolor sit amet,
 7639             * consectetur adipiscing elit. */ˇ»
 7640        "},
 7641        indoc! {"
 7642            «/*
 7643             * Lorem ipsum dolor sit amet,
 7644             * consectetur adipiscing elit.
 7645             */ˇ»
 7646        "},
 7647        rust_lang.clone(),
 7648        &mut cx,
 7649    );
 7650
 7651    // partial selection of multiline block comments
 7652    assert_rewrap(
 7653        indoc! {"
 7654            «/* Lorem ipsum dolor sit amet,ˇ»
 7655             * consectetur adipiscing elit. */
 7656            /* Lorem ipsum dolor sit amet,
 7657             «* consectetur adipiscing elit. */ˇ»
 7658        "},
 7659        indoc! {"
 7660            «/*
 7661             * Lorem ipsum dolor sit amet,ˇ»
 7662             * consectetur adipiscing elit. */
 7663            /* Lorem ipsum dolor sit amet,
 7664             «* consectetur adipiscing elit.
 7665             */ˇ»
 7666        "},
 7667        rust_lang.clone(),
 7668        &mut cx,
 7669    );
 7670
 7671    // selection w/ abutting short block comments
 7672    // TODO: should not be combined; should rewrap as 2 comments
 7673    assert_rewrap(
 7674        indoc! {"
 7675            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7676            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7677        "},
 7678        // desired behavior:
 7679        // indoc! {"
 7680        //     «/*
 7681        //      * Lorem ipsum dolor sit amet,
 7682        //      * consectetur adipiscing elit.
 7683        //      */
 7684        //     /*
 7685        //      * Lorem ipsum dolor sit amet,
 7686        //      * consectetur adipiscing elit.
 7687        //      */ˇ»
 7688        // "},
 7689        // actual behaviour:
 7690        indoc! {"
 7691            «/*
 7692             * Lorem ipsum dolor sit amet,
 7693             * consectetur adipiscing elit. Lorem
 7694             * ipsum dolor sit amet, consectetur
 7695             * adipiscing elit.
 7696             */ˇ»
 7697        "},
 7698        rust_lang.clone(),
 7699        &mut cx,
 7700    );
 7701
 7702    // TODO: same as above, but with delimiters on separate line
 7703    // assert_rewrap(
 7704    //     indoc! {"
 7705    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7706    //          */
 7707    //         /*
 7708    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7709    //     "},
 7710    //     // desired:
 7711    //     // indoc! {"
 7712    //     //     «/*
 7713    //     //      * Lorem ipsum dolor sit amet,
 7714    //     //      * consectetur adipiscing elit.
 7715    //     //      */
 7716    //     //     /*
 7717    //     //      * Lorem ipsum dolor sit amet,
 7718    //     //      * consectetur adipiscing elit.
 7719    //     //      */ˇ»
 7720    //     // "},
 7721    //     // actual: (but with trailing w/s on the empty lines)
 7722    //     indoc! {"
 7723    //         «/*
 7724    //          * Lorem ipsum dolor sit amet,
 7725    //          * consectetur adipiscing elit.
 7726    //          *
 7727    //          */
 7728    //         /*
 7729    //          *
 7730    //          * Lorem ipsum dolor sit amet,
 7731    //          * consectetur adipiscing elit.
 7732    //          */ˇ»
 7733    //     "},
 7734    //     rust_lang.clone(),
 7735    //     &mut cx,
 7736    // );
 7737
 7738    // TODO these are unhandled edge cases; not correct, just documenting known issues
 7739    assert_rewrap(
 7740        indoc! {"
 7741            /*
 7742             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7743             */
 7744            /*
 7745             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7746            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 7747        "},
 7748        // desired:
 7749        // indoc! {"
 7750        //     /*
 7751        //      *ˇ Lorem ipsum dolor sit amet,
 7752        //      * consectetur adipiscing elit.
 7753        //      */
 7754        //     /*
 7755        //      *ˇ Lorem ipsum dolor sit amet,
 7756        //      * consectetur adipiscing elit.
 7757        //      */
 7758        //     /*
 7759        //      *ˇ Lorem ipsum dolor sit amet
 7760        //      */ /* consectetur adipiscing elit. */
 7761        // "},
 7762        // actual:
 7763        indoc! {"
 7764            /*
 7765             //ˇ Lorem ipsum dolor sit amet,
 7766             // consectetur adipiscing elit.
 7767             */
 7768            /*
 7769             * //ˇ Lorem ipsum dolor sit amet,
 7770             * consectetur adipiscing elit.
 7771             */
 7772            /*
 7773             *ˇ Lorem ipsum dolor sit amet */ /*
 7774             * consectetur adipiscing elit.
 7775             */
 7776        "},
 7777        rust_lang,
 7778        &mut cx,
 7779    );
 7780
 7781    #[track_caller]
 7782    fn assert_rewrap(
 7783        unwrapped_text: &str,
 7784        wrapped_text: &str,
 7785        language: Arc<Language>,
 7786        cx: &mut EditorTestContext,
 7787    ) {
 7788        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7789        cx.set_state(unwrapped_text);
 7790        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7791        cx.assert_editor_state(wrapped_text);
 7792    }
 7793}
 7794
 7795#[gpui::test]
 7796async fn test_hard_wrap(cx: &mut TestAppContext) {
 7797    init_test(cx, |_| {});
 7798    let mut cx = EditorTestContext::new(cx).await;
 7799
 7800    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 7801    cx.update_editor(|editor, _, cx| {
 7802        editor.set_hard_wrap(Some(14), cx);
 7803    });
 7804
 7805    cx.set_state(indoc!(
 7806        "
 7807        one two three ˇ
 7808        "
 7809    ));
 7810    cx.simulate_input("four");
 7811    cx.run_until_parked();
 7812
 7813    cx.assert_editor_state(indoc!(
 7814        "
 7815        one two three
 7816        fourˇ
 7817        "
 7818    ));
 7819
 7820    cx.update_editor(|editor, window, cx| {
 7821        editor.newline(&Default::default(), window, cx);
 7822    });
 7823    cx.run_until_parked();
 7824    cx.assert_editor_state(indoc!(
 7825        "
 7826        one two three
 7827        four
 7828        ˇ
 7829        "
 7830    ));
 7831
 7832    cx.simulate_input("five");
 7833    cx.run_until_parked();
 7834    cx.assert_editor_state(indoc!(
 7835        "
 7836        one two three
 7837        four
 7838        fiveˇ
 7839        "
 7840    ));
 7841
 7842    cx.update_editor(|editor, window, cx| {
 7843        editor.newline(&Default::default(), window, cx);
 7844    });
 7845    cx.run_until_parked();
 7846    cx.simulate_input("# ");
 7847    cx.run_until_parked();
 7848    cx.assert_editor_state(indoc!(
 7849        "
 7850        one two three
 7851        four
 7852        five
 7853        # ˇ
 7854        "
 7855    ));
 7856
 7857    cx.update_editor(|editor, window, cx| {
 7858        editor.newline(&Default::default(), window, cx);
 7859    });
 7860    cx.run_until_parked();
 7861    cx.assert_editor_state(indoc!(
 7862        "
 7863        one two three
 7864        four
 7865        five
 7866        #\x20
 7867 7868        "
 7869    ));
 7870
 7871    cx.simulate_input(" 6");
 7872    cx.run_until_parked();
 7873    cx.assert_editor_state(indoc!(
 7874        "
 7875        one two three
 7876        four
 7877        five
 7878        #
 7879        # 6ˇ
 7880        "
 7881    ));
 7882}
 7883
 7884#[gpui::test]
 7885async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7886    init_test(cx, |_| {});
 7887
 7888    let mut cx = EditorTestContext::new(cx).await;
 7889
 7890    cx.set_state(indoc! {"The quick brownˇ"});
 7891    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7892    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7893
 7894    cx.set_state(indoc! {"The emacs foxˇ"});
 7895    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7896    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7897
 7898    cx.set_state(indoc! {"
 7899        The quick« brownˇ»
 7900        fox jumps overˇ
 7901        the lazy dog"});
 7902    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7903    cx.assert_editor_state(indoc! {"
 7904        The quickˇ
 7905        ˇthe lazy dog"});
 7906
 7907    cx.set_state(indoc! {"
 7908        The quick« brownˇ»
 7909        fox jumps overˇ
 7910        the lazy dog"});
 7911    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7912    cx.assert_editor_state(indoc! {"
 7913        The quickˇ
 7914        fox jumps overˇthe lazy dog"});
 7915
 7916    cx.set_state(indoc! {"
 7917        The quick« brownˇ»
 7918        fox jumps overˇ
 7919        the lazy dog"});
 7920    cx.update_editor(|e, window, cx| {
 7921        e.cut_to_end_of_line(
 7922            &CutToEndOfLine {
 7923                stop_at_newlines: true,
 7924            },
 7925            window,
 7926            cx,
 7927        )
 7928    });
 7929    cx.assert_editor_state(indoc! {"
 7930        The quickˇ
 7931        fox jumps overˇ
 7932        the lazy dog"});
 7933
 7934    cx.set_state(indoc! {"
 7935        The quick« brownˇ»
 7936        fox jumps overˇ
 7937        the lazy dog"});
 7938    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7939    cx.assert_editor_state(indoc! {"
 7940        The quickˇ
 7941        fox jumps overˇthe lazy dog"});
 7942}
 7943
 7944#[gpui::test]
 7945async fn test_clipboard(cx: &mut TestAppContext) {
 7946    init_test(cx, |_| {});
 7947
 7948    let mut cx = EditorTestContext::new(cx).await;
 7949
 7950    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7951    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7952    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7953
 7954    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7955    cx.set_state("two ˇfour ˇsix ˇ");
 7956    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7957    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7958
 7959    // Paste again but with only two cursors. Since the number of cursors doesn't
 7960    // match the number of slices in the clipboard, the entire clipboard text
 7961    // is pasted at each cursor.
 7962    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7963    cx.update_editor(|e, window, cx| {
 7964        e.handle_input("( ", window, cx);
 7965        e.paste(&Paste, window, cx);
 7966        e.handle_input(") ", window, cx);
 7967    });
 7968    cx.assert_editor_state(
 7969        &([
 7970            "( one✅ ",
 7971            "three ",
 7972            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7973            "three ",
 7974            "five ) ˇ",
 7975        ]
 7976        .join("\n")),
 7977    );
 7978
 7979    // Cut with three selections, one of which is full-line.
 7980    cx.set_state(indoc! {"
 7981        1«2ˇ»3
 7982        4ˇ567
 7983        «8ˇ»9"});
 7984    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7985    cx.assert_editor_state(indoc! {"
 7986        1ˇ3
 7987        ˇ9"});
 7988
 7989    // Paste with three selections, noticing how the copied selection that was full-line
 7990    // gets inserted before the second cursor.
 7991    cx.set_state(indoc! {"
 7992        1ˇ3
 7993 7994        «oˇ»ne"});
 7995    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7996    cx.assert_editor_state(indoc! {"
 7997        12ˇ3
 7998        4567
 7999 8000        8ˇne"});
 8001
 8002    // Copy with a single cursor only, which writes the whole line into the clipboard.
 8003    cx.set_state(indoc! {"
 8004        The quick brown
 8005        fox juˇmps over
 8006        the lazy dog"});
 8007    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 8008    assert_eq!(
 8009        cx.read_from_clipboard()
 8010            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8011        Some("fox jumps over\n".to_string())
 8012    );
 8013
 8014    // Paste with three selections, noticing how the copied full-line selection is inserted
 8015    // before the empty selections but replaces the selection that is non-empty.
 8016    cx.set_state(indoc! {"
 8017        Tˇhe quick brown
 8018        «foˇ»x jumps over
 8019        tˇhe lazy dog"});
 8020    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8021    cx.assert_editor_state(indoc! {"
 8022        fox jumps over
 8023        Tˇhe quick brown
 8024        fox jumps over
 8025        ˇx jumps over
 8026        fox jumps over
 8027        tˇhe lazy dog"});
 8028}
 8029
 8030#[gpui::test]
 8031async fn test_copy_trim(cx: &mut TestAppContext) {
 8032    init_test(cx, |_| {});
 8033
 8034    let mut cx = EditorTestContext::new(cx).await;
 8035    cx.set_state(
 8036        r#"            «for selection in selections.iter() {
 8037            let mut start = selection.start;
 8038            let mut end = selection.end;
 8039            let is_entire_line = selection.is_empty();
 8040            if is_entire_line {
 8041                start = Point::new(start.row, 0);ˇ»
 8042                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 8043            }
 8044        "#,
 8045    );
 8046    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 8047    assert_eq!(
 8048        cx.read_from_clipboard()
 8049            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8050        Some(
 8051            "for selection in selections.iter() {
 8052            let mut start = selection.start;
 8053            let mut end = selection.end;
 8054            let is_entire_line = selection.is_empty();
 8055            if is_entire_line {
 8056                start = Point::new(start.row, 0);"
 8057                .to_string()
 8058        ),
 8059        "Regular copying preserves all indentation selected",
 8060    );
 8061    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 8062    assert_eq!(
 8063        cx.read_from_clipboard()
 8064            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8065        Some(
 8066            "for selection in selections.iter() {
 8067let mut start = selection.start;
 8068let mut end = selection.end;
 8069let is_entire_line = selection.is_empty();
 8070if is_entire_line {
 8071    start = Point::new(start.row, 0);"
 8072                .to_string()
 8073        ),
 8074        "Copying with stripping should strip all leading whitespaces"
 8075    );
 8076
 8077    cx.set_state(
 8078        r#"       «     for selection in selections.iter() {
 8079            let mut start = selection.start;
 8080            let mut end = selection.end;
 8081            let is_entire_line = selection.is_empty();
 8082            if is_entire_line {
 8083                start = Point::new(start.row, 0);ˇ»
 8084                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 8085            }
 8086        "#,
 8087    );
 8088    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 8089    assert_eq!(
 8090        cx.read_from_clipboard()
 8091            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8092        Some(
 8093            "     for selection in selections.iter() {
 8094            let mut start = selection.start;
 8095            let mut end = selection.end;
 8096            let is_entire_line = selection.is_empty();
 8097            if is_entire_line {
 8098                start = Point::new(start.row, 0);"
 8099                .to_string()
 8100        ),
 8101        "Regular copying preserves all indentation selected",
 8102    );
 8103    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 8104    assert_eq!(
 8105        cx.read_from_clipboard()
 8106            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8107        Some(
 8108            "for selection in selections.iter() {
 8109let mut start = selection.start;
 8110let mut end = selection.end;
 8111let is_entire_line = selection.is_empty();
 8112if is_entire_line {
 8113    start = Point::new(start.row, 0);"
 8114                .to_string()
 8115        ),
 8116        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 8117    );
 8118
 8119    cx.set_state(
 8120        r#"       «ˇ     for selection in selections.iter() {
 8121            let mut start = selection.start;
 8122            let mut end = selection.end;
 8123            let is_entire_line = selection.is_empty();
 8124            if is_entire_line {
 8125                start = Point::new(start.row, 0);»
 8126                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 8127            }
 8128        "#,
 8129    );
 8130    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 8131    assert_eq!(
 8132        cx.read_from_clipboard()
 8133            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8134        Some(
 8135            "     for selection in selections.iter() {
 8136            let mut start = selection.start;
 8137            let mut end = selection.end;
 8138            let is_entire_line = selection.is_empty();
 8139            if is_entire_line {
 8140                start = Point::new(start.row, 0);"
 8141                .to_string()
 8142        ),
 8143        "Regular copying for reverse selection works the same",
 8144    );
 8145    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 8146    assert_eq!(
 8147        cx.read_from_clipboard()
 8148            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8149        Some(
 8150            "for selection in selections.iter() {
 8151let mut start = selection.start;
 8152let mut end = selection.end;
 8153let is_entire_line = selection.is_empty();
 8154if is_entire_line {
 8155    start = Point::new(start.row, 0);"
 8156                .to_string()
 8157        ),
 8158        "Copying with stripping for reverse selection works the same"
 8159    );
 8160
 8161    cx.set_state(
 8162        r#"            for selection «in selections.iter() {
 8163            let mut start = selection.start;
 8164            let mut end = selection.end;
 8165            let is_entire_line = selection.is_empty();
 8166            if is_entire_line {
 8167                start = Point::new(start.row, 0);ˇ»
 8168                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 8169            }
 8170        "#,
 8171    );
 8172    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 8173    assert_eq!(
 8174        cx.read_from_clipboard()
 8175            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8176        Some(
 8177            "in selections.iter() {
 8178            let mut start = selection.start;
 8179            let mut end = selection.end;
 8180            let is_entire_line = selection.is_empty();
 8181            if is_entire_line {
 8182                start = Point::new(start.row, 0);"
 8183                .to_string()
 8184        ),
 8185        "When selecting past the indent, the copying works as usual",
 8186    );
 8187    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 8188    assert_eq!(
 8189        cx.read_from_clipboard()
 8190            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8191        Some(
 8192            "in selections.iter() {
 8193            let mut start = selection.start;
 8194            let mut end = selection.end;
 8195            let is_entire_line = selection.is_empty();
 8196            if is_entire_line {
 8197                start = Point::new(start.row, 0);"
 8198                .to_string()
 8199        ),
 8200        "When selecting past the indent, nothing is trimmed"
 8201    );
 8202
 8203    cx.set_state(
 8204        r#"            «for selection in selections.iter() {
 8205            let mut start = selection.start;
 8206
 8207            let mut end = selection.end;
 8208            let is_entire_line = selection.is_empty();
 8209            if is_entire_line {
 8210                start = Point::new(start.row, 0);
 8211ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 8212            }
 8213        "#,
 8214    );
 8215    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 8216    assert_eq!(
 8217        cx.read_from_clipboard()
 8218            .and_then(|item| item.text().as_deref().map(str::to_string)),
 8219        Some(
 8220            "for selection in selections.iter() {
 8221let mut start = selection.start;
 8222
 8223let mut end = selection.end;
 8224let is_entire_line = selection.is_empty();
 8225if is_entire_line {
 8226    start = Point::new(start.row, 0);
 8227"
 8228            .to_string()
 8229        ),
 8230        "Copying with stripping should ignore empty lines"
 8231    );
 8232}
 8233
 8234#[gpui::test]
 8235async fn test_copy_trim_line_mode(cx: &mut TestAppContext) {
 8236    init_test(cx, |_| {});
 8237
 8238    let mut cx = EditorTestContext::new(cx).await;
 8239
 8240    cx.set_state(indoc! {"
 8241        «    fn main() {
 8242                1
 8243            }ˇ»
 8244    "});
 8245    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 8246    cx.update_editor(|editor, window, cx| editor.copy_and_trim(&CopyAndTrim, window, cx));
 8247
 8248    assert_eq!(
 8249        cx.read_from_clipboard().and_then(|item| item.text()),
 8250        Some("fn main() {\n    1\n}\n".to_string())
 8251    );
 8252
 8253    let clipboard_selections: Vec<ClipboardSelection> = cx
 8254        .read_from_clipboard()
 8255        .and_then(|item| item.entries().first().cloned())
 8256        .and_then(|entry| match entry {
 8257            gpui::ClipboardEntry::String(text) => text.metadata_json(),
 8258            _ => None,
 8259        })
 8260        .expect("should have clipboard selections");
 8261
 8262    assert_eq!(clipboard_selections.len(), 1);
 8263    assert!(clipboard_selections[0].is_entire_line);
 8264
 8265    cx.set_state(indoc! {"
 8266        «fn main() {
 8267            1
 8268        }ˇ»
 8269    "});
 8270    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 8271    cx.update_editor(|editor, window, cx| editor.copy_and_trim(&CopyAndTrim, window, cx));
 8272
 8273    assert_eq!(
 8274        cx.read_from_clipboard().and_then(|item| item.text()),
 8275        Some("fn main() {\n    1\n}\n".to_string())
 8276    );
 8277
 8278    let clipboard_selections: Vec<ClipboardSelection> = cx
 8279        .read_from_clipboard()
 8280        .and_then(|item| item.entries().first().cloned())
 8281        .and_then(|entry| match entry {
 8282            gpui::ClipboardEntry::String(text) => text.metadata_json(),
 8283            _ => None,
 8284        })
 8285        .expect("should have clipboard selections");
 8286
 8287    assert_eq!(clipboard_selections.len(), 1);
 8288    assert!(clipboard_selections[0].is_entire_line);
 8289}
 8290
 8291#[gpui::test]
 8292async fn test_clipboard_line_numbers_from_multibuffer(cx: &mut TestAppContext) {
 8293    init_test(cx, |_| {});
 8294
 8295    let fs = FakeFs::new(cx.executor());
 8296    fs.insert_file(
 8297        path!("/file.txt"),
 8298        "first line\nsecond line\nthird line\nfourth line\nfifth line\n".into(),
 8299    )
 8300    .await;
 8301
 8302    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
 8303
 8304    let buffer = project
 8305        .update(cx, |project, cx| {
 8306            project.open_local_buffer(path!("/file.txt"), cx)
 8307        })
 8308        .await
 8309        .unwrap();
 8310
 8311    let multibuffer = cx.new(|cx| {
 8312        let mut multibuffer = MultiBuffer::new(ReadWrite);
 8313        multibuffer.set_excerpts_for_path(
 8314            PathKey::sorted(0),
 8315            buffer.clone(),
 8316            [Point::new(2, 0)..Point::new(5, 0)],
 8317            0,
 8318            cx,
 8319        );
 8320        multibuffer
 8321    });
 8322
 8323    let (editor, cx) = cx.add_window_view(|window, cx| {
 8324        build_editor_with_project(project.clone(), multibuffer, window, cx)
 8325    });
 8326
 8327    editor.update_in(cx, |editor, window, cx| {
 8328        assert_eq!(editor.text(cx), "third line\nfourth line\nfifth line\n");
 8329
 8330        editor.select_all(&SelectAll, window, cx);
 8331        editor.copy(&Copy, window, cx);
 8332    });
 8333
 8334    let clipboard_selections: Option<Vec<ClipboardSelection>> = cx
 8335        .read_from_clipboard()
 8336        .and_then(|item| item.entries().first().cloned())
 8337        .and_then(|entry| match entry {
 8338            gpui::ClipboardEntry::String(text) => text.metadata_json(),
 8339            _ => None,
 8340        });
 8341
 8342    let selections = clipboard_selections.expect("should have clipboard selections");
 8343    assert_eq!(selections.len(), 1);
 8344    let selection = &selections[0];
 8345    assert_eq!(
 8346        selection.line_range,
 8347        Some(2..=5),
 8348        "line range should be from original file (rows 2-5), not multibuffer rows (0-2)"
 8349    );
 8350}
 8351
 8352#[gpui::test]
 8353async fn test_paste_multiline(cx: &mut TestAppContext) {
 8354    init_test(cx, |_| {});
 8355
 8356    let mut cx = EditorTestContext::new(cx).await;
 8357    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8358
 8359    // Cut an indented block, without the leading whitespace.
 8360    cx.set_state(indoc! {"
 8361        const a: B = (
 8362            c(),
 8363            «d(
 8364                e,
 8365                f
 8366            )ˇ»
 8367        );
 8368    "});
 8369    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 8370    cx.assert_editor_state(indoc! {"
 8371        const a: B = (
 8372            c(),
 8373            ˇ
 8374        );
 8375    "});
 8376
 8377    // Paste it at the same position.
 8378    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8379    cx.assert_editor_state(indoc! {"
 8380        const a: B = (
 8381            c(),
 8382            d(
 8383                e,
 8384                f
 8385 8386        );
 8387    "});
 8388
 8389    // Paste it at a line with a lower indent level.
 8390    cx.set_state(indoc! {"
 8391        ˇ
 8392        const a: B = (
 8393            c(),
 8394        );
 8395    "});
 8396    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8397    cx.assert_editor_state(indoc! {"
 8398        d(
 8399            e,
 8400            f
 8401 8402        const a: B = (
 8403            c(),
 8404        );
 8405    "});
 8406
 8407    // Cut an indented block, with the leading whitespace.
 8408    cx.set_state(indoc! {"
 8409        const a: B = (
 8410            c(),
 8411        «    d(
 8412                e,
 8413                f
 8414            )
 8415        ˇ»);
 8416    "});
 8417    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 8418    cx.assert_editor_state(indoc! {"
 8419        const a: B = (
 8420            c(),
 8421        ˇ);
 8422    "});
 8423
 8424    // Paste it at the same position.
 8425    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8426    cx.assert_editor_state(indoc! {"
 8427        const a: B = (
 8428            c(),
 8429            d(
 8430                e,
 8431                f
 8432            )
 8433        ˇ);
 8434    "});
 8435
 8436    // Paste it at a line with a higher indent level.
 8437    cx.set_state(indoc! {"
 8438        const a: B = (
 8439            c(),
 8440            d(
 8441                e,
 8442 8443            )
 8444        );
 8445    "});
 8446    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8447    cx.assert_editor_state(indoc! {"
 8448        const a: B = (
 8449            c(),
 8450            d(
 8451                e,
 8452                f    d(
 8453                    e,
 8454                    f
 8455                )
 8456        ˇ
 8457            )
 8458        );
 8459    "});
 8460
 8461    // Copy an indented block, starting mid-line
 8462    cx.set_state(indoc! {"
 8463        const a: B = (
 8464            c(),
 8465            somethin«g(
 8466                e,
 8467                f
 8468            )ˇ»
 8469        );
 8470    "});
 8471    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 8472
 8473    // Paste it on a line with a lower indent level
 8474    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 8475    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8476    cx.assert_editor_state(indoc! {"
 8477        const a: B = (
 8478            c(),
 8479            something(
 8480                e,
 8481                f
 8482            )
 8483        );
 8484        g(
 8485            e,
 8486            f
 8487"});
 8488}
 8489
 8490#[gpui::test]
 8491async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 8492    init_test(cx, |_| {});
 8493
 8494    cx.write_to_clipboard(ClipboardItem::new_string(
 8495        "    d(\n        e\n    );\n".into(),
 8496    ));
 8497
 8498    let mut cx = EditorTestContext::new(cx).await;
 8499    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8500    cx.run_until_parked();
 8501
 8502    cx.set_state(indoc! {"
 8503        fn a() {
 8504            b();
 8505            if c() {
 8506                ˇ
 8507            }
 8508        }
 8509    "});
 8510
 8511    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8512    cx.assert_editor_state(indoc! {"
 8513        fn a() {
 8514            b();
 8515            if c() {
 8516                d(
 8517                    e
 8518                );
 8519        ˇ
 8520            }
 8521        }
 8522    "});
 8523
 8524    cx.set_state(indoc! {"
 8525        fn a() {
 8526            b();
 8527            ˇ
 8528        }
 8529    "});
 8530
 8531    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8532    cx.assert_editor_state(indoc! {"
 8533        fn a() {
 8534            b();
 8535            d(
 8536                e
 8537            );
 8538        ˇ
 8539        }
 8540    "});
 8541}
 8542
 8543#[gpui::test]
 8544async fn test_paste_multiline_from_other_app_into_matching_cursors(cx: &mut TestAppContext) {
 8545    init_test(cx, |_| {});
 8546
 8547    cx.write_to_clipboard(ClipboardItem::new_string("alpha\nbeta\ngamma".into()));
 8548
 8549    let mut cx = EditorTestContext::new(cx).await;
 8550
 8551    // Paste into 3 cursors: each cursor should receive one line.
 8552    cx.set_state("ˇ one ˇ two ˇ three");
 8553    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8554    cx.assert_editor_state("alphaˇ one betaˇ two gammaˇ three");
 8555
 8556    // Paste into 2 cursors: line count doesn't match, so paste entire text at each cursor.
 8557    cx.write_to_clipboard(ClipboardItem::new_string("alpha\nbeta\ngamma".into()));
 8558    cx.set_state("ˇ one ˇ two");
 8559    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8560    cx.assert_editor_state("alpha\nbeta\ngammaˇ one alpha\nbeta\ngammaˇ two");
 8561
 8562    // Paste into a single cursor: should paste everything as-is.
 8563    cx.write_to_clipboard(ClipboardItem::new_string("alpha\nbeta\ngamma".into()));
 8564    cx.set_state("ˇ one");
 8565    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8566    cx.assert_editor_state("alpha\nbeta\ngammaˇ one");
 8567
 8568    // Paste with selections: each selection is replaced with its corresponding line.
 8569    cx.write_to_clipboard(ClipboardItem::new_string("xx\nyy\nzz".into()));
 8570    cx.set_state("«aˇ» one «bˇ» two «cˇ» three");
 8571    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 8572    cx.assert_editor_state("xxˇ one yyˇ two zzˇ three");
 8573}
 8574
 8575#[gpui::test]
 8576fn test_select_all(cx: &mut TestAppContext) {
 8577    init_test(cx, |_| {});
 8578
 8579    let editor = cx.add_window(|window, cx| {
 8580        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 8581        build_editor(buffer, window, cx)
 8582    });
 8583    _ = editor.update(cx, |editor, window, cx| {
 8584        editor.select_all(&SelectAll, window, cx);
 8585        assert_eq!(
 8586            display_ranges(editor, cx),
 8587            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 8588        );
 8589    });
 8590}
 8591
 8592#[gpui::test]
 8593fn test_select_line(cx: &mut TestAppContext) {
 8594    init_test(cx, |_| {});
 8595
 8596    let editor = cx.add_window(|window, cx| {
 8597        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 8598        build_editor(buffer, window, cx)
 8599    });
 8600    _ = editor.update(cx, |editor, window, cx| {
 8601        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8602            s.select_display_ranges([
 8603                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8604                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 8605                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 8606                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 8607            ])
 8608        });
 8609        editor.select_line(&SelectLine, window, cx);
 8610        // Adjacent line selections should NOT merge (only overlapping ones do)
 8611        assert_eq!(
 8612            display_ranges(editor, cx),
 8613            vec![
 8614                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0),
 8615                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(2), 0),
 8616                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 8617            ]
 8618        );
 8619    });
 8620
 8621    _ = editor.update(cx, |editor, window, cx| {
 8622        editor.select_line(&SelectLine, window, cx);
 8623        assert_eq!(
 8624            display_ranges(editor, cx),
 8625            vec![
 8626                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 8627                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 8628            ]
 8629        );
 8630    });
 8631
 8632    _ = editor.update(cx, |editor, window, cx| {
 8633        editor.select_line(&SelectLine, window, cx);
 8634        // Adjacent but not overlapping, so they stay separate
 8635        assert_eq!(
 8636            display_ranges(editor, cx),
 8637            vec![
 8638                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(4), 0),
 8639                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 8640            ]
 8641        );
 8642    });
 8643}
 8644
 8645#[gpui::test]
 8646async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 8647    init_test(cx, |_| {});
 8648    let mut cx = EditorTestContext::new(cx).await;
 8649
 8650    #[track_caller]
 8651    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 8652        cx.set_state(initial_state);
 8653        cx.update_editor(|e, window, cx| {
 8654            e.split_selection_into_lines(&Default::default(), window, cx)
 8655        });
 8656        cx.assert_editor_state(expected_state);
 8657    }
 8658
 8659    // Selection starts and ends at the middle of lines, left-to-right
 8660    test(
 8661        &mut cx,
 8662        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 8663        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 8664    );
 8665    // Same thing, right-to-left
 8666    test(
 8667        &mut cx,
 8668        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 8669        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 8670    );
 8671
 8672    // Whole buffer, left-to-right, last line *doesn't* end with newline
 8673    test(
 8674        &mut cx,
 8675        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 8676        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 8677    );
 8678    // Same thing, right-to-left
 8679    test(
 8680        &mut cx,
 8681        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 8682        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 8683    );
 8684
 8685    // Whole buffer, left-to-right, last line ends with newline
 8686    test(
 8687        &mut cx,
 8688        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 8689        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 8690    );
 8691    // Same thing, right-to-left
 8692    test(
 8693        &mut cx,
 8694        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 8695        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 8696    );
 8697
 8698    // Starts at the end of a line, ends at the start of another
 8699    test(
 8700        &mut cx,
 8701        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 8702        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 8703    );
 8704}
 8705
 8706#[gpui::test]
 8707async fn test_split_selection_into_lines_does_not_scroll(cx: &mut TestAppContext) {
 8708    init_test(cx, |_| {});
 8709    let mut cx = EditorTestContext::new(cx).await;
 8710
 8711    let large_body = "\nline".repeat(300);
 8712    cx.set_state(&format!("«ˇstart{large_body}\nend»"));
 8713    let initial_scroll_position = cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8714
 8715    cx.update_editor(|editor, window, cx| {
 8716        editor.split_selection_into_lines(&Default::default(), window, cx);
 8717    });
 8718
 8719    let scroll_position_after_split = cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8720    assert_eq!(
 8721        initial_scroll_position, scroll_position_after_split,
 8722        "Scroll position should not change after splitting selection into lines"
 8723    );
 8724}
 8725
 8726#[gpui::test]
 8727async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 8728    init_test(cx, |_| {});
 8729
 8730    let editor = cx.add_window(|window, cx| {
 8731        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 8732        build_editor(buffer, window, cx)
 8733    });
 8734
 8735    // setup
 8736    _ = editor.update(cx, |editor, window, cx| {
 8737        editor.fold_creases(
 8738            vec![
 8739                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 8740                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 8741                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 8742            ],
 8743            true,
 8744            window,
 8745            cx,
 8746        );
 8747        assert_eq!(
 8748            editor.display_text(cx),
 8749            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8750        );
 8751    });
 8752
 8753    _ = editor.update(cx, |editor, window, cx| {
 8754        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8755            s.select_display_ranges([
 8756                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 8757                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 8758                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 8759                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 8760            ])
 8761        });
 8762        editor.split_selection_into_lines(&Default::default(), window, cx);
 8763        assert_eq!(
 8764            editor.display_text(cx),
 8765            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 8766        );
 8767    });
 8768    EditorTestContext::for_editor(editor, cx)
 8769        .await
 8770        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 8771
 8772    _ = editor.update(cx, |editor, window, cx| {
 8773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8774            s.select_display_ranges([
 8775                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 8776            ])
 8777        });
 8778        editor.split_selection_into_lines(&Default::default(), window, cx);
 8779        assert_eq!(
 8780            editor.display_text(cx),
 8781            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 8782        );
 8783        assert_eq!(
 8784            display_ranges(editor, cx),
 8785            [
 8786                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 8787                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 8788                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 8789                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 8790                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 8791                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 8792                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 8793            ]
 8794        );
 8795    });
 8796    EditorTestContext::for_editor(editor, cx)
 8797        .await
 8798        .assert_editor_state(
 8799            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 8800        );
 8801}
 8802
 8803#[gpui::test]
 8804async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 8805    init_test(cx, |_| {});
 8806
 8807    let mut cx = EditorTestContext::new(cx).await;
 8808
 8809    cx.set_state(indoc!(
 8810        r#"abc
 8811           defˇghi
 8812
 8813           jk
 8814           nlmo
 8815           "#
 8816    ));
 8817
 8818    cx.update_editor(|editor, window, cx| {
 8819        editor.add_selection_above(&Default::default(), window, cx);
 8820    });
 8821
 8822    cx.assert_editor_state(indoc!(
 8823        r#"abcˇ
 8824           defˇghi
 8825
 8826           jk
 8827           nlmo
 8828           "#
 8829    ));
 8830
 8831    cx.update_editor(|editor, window, cx| {
 8832        editor.add_selection_above(&Default::default(), window, cx);
 8833    });
 8834
 8835    cx.assert_editor_state(indoc!(
 8836        r#"abcˇ
 8837            defˇghi
 8838
 8839            jk
 8840            nlmo
 8841            "#
 8842    ));
 8843
 8844    cx.update_editor(|editor, window, cx| {
 8845        editor.add_selection_below(&Default::default(), window, cx);
 8846    });
 8847
 8848    cx.assert_editor_state(indoc!(
 8849        r#"abc
 8850           defˇghi
 8851
 8852           jk
 8853           nlmo
 8854           "#
 8855    ));
 8856
 8857    cx.update_editor(|editor, window, cx| {
 8858        editor.undo_selection(&Default::default(), window, cx);
 8859    });
 8860
 8861    cx.assert_editor_state(indoc!(
 8862        r#"abcˇ
 8863           defˇghi
 8864
 8865           jk
 8866           nlmo
 8867           "#
 8868    ));
 8869
 8870    cx.update_editor(|editor, window, cx| {
 8871        editor.redo_selection(&Default::default(), window, cx);
 8872    });
 8873
 8874    cx.assert_editor_state(indoc!(
 8875        r#"abc
 8876           defˇghi
 8877
 8878           jk
 8879           nlmo
 8880           "#
 8881    ));
 8882
 8883    cx.update_editor(|editor, window, cx| {
 8884        editor.add_selection_below(&Default::default(), window, cx);
 8885    });
 8886
 8887    cx.assert_editor_state(indoc!(
 8888        r#"abc
 8889           defˇghi
 8890           ˇ
 8891           jk
 8892           nlmo
 8893           "#
 8894    ));
 8895
 8896    cx.update_editor(|editor, window, cx| {
 8897        editor.add_selection_below(&Default::default(), window, cx);
 8898    });
 8899
 8900    cx.assert_editor_state(indoc!(
 8901        r#"abc
 8902           defˇghi
 8903           ˇ
 8904           jkˇ
 8905           nlmo
 8906           "#
 8907    ));
 8908
 8909    cx.update_editor(|editor, window, cx| {
 8910        editor.add_selection_below(&Default::default(), window, cx);
 8911    });
 8912
 8913    cx.assert_editor_state(indoc!(
 8914        r#"abc
 8915           defˇghi
 8916           ˇ
 8917           jkˇ
 8918           nlmˇo
 8919           "#
 8920    ));
 8921
 8922    cx.update_editor(|editor, window, cx| {
 8923        editor.add_selection_below(&Default::default(), window, cx);
 8924    });
 8925
 8926    cx.assert_editor_state(indoc!(
 8927        r#"abc
 8928           defˇghi
 8929           ˇ
 8930           jkˇ
 8931           nlmˇo
 8932           ˇ"#
 8933    ));
 8934
 8935    // change selections
 8936    cx.set_state(indoc!(
 8937        r#"abc
 8938           def«ˇg»hi
 8939
 8940           jk
 8941           nlmo
 8942           "#
 8943    ));
 8944
 8945    cx.update_editor(|editor, window, cx| {
 8946        editor.add_selection_below(&Default::default(), window, cx);
 8947    });
 8948
 8949    cx.assert_editor_state(indoc!(
 8950        r#"abc
 8951           def«ˇg»hi
 8952
 8953           jk
 8954           nlm«ˇo»
 8955           "#
 8956    ));
 8957
 8958    cx.update_editor(|editor, window, cx| {
 8959        editor.add_selection_below(&Default::default(), window, cx);
 8960    });
 8961
 8962    cx.assert_editor_state(indoc!(
 8963        r#"abc
 8964           def«ˇg»hi
 8965
 8966           jk
 8967           nlm«ˇo»
 8968           "#
 8969    ));
 8970
 8971    cx.update_editor(|editor, window, cx| {
 8972        editor.add_selection_above(&Default::default(), window, cx);
 8973    });
 8974
 8975    cx.assert_editor_state(indoc!(
 8976        r#"abc
 8977           def«ˇg»hi
 8978
 8979           jk
 8980           nlmo
 8981           "#
 8982    ));
 8983
 8984    cx.update_editor(|editor, window, cx| {
 8985        editor.add_selection_above(&Default::default(), window, cx);
 8986    });
 8987
 8988    cx.assert_editor_state(indoc!(
 8989        r#"abc
 8990           def«ˇg»hi
 8991
 8992           jk
 8993           nlmo
 8994           "#
 8995    ));
 8996
 8997    // Change selections again
 8998    cx.set_state(indoc!(
 8999        r#"a«bc
 9000           defgˇ»hi
 9001
 9002           jk
 9003           nlmo
 9004           "#
 9005    ));
 9006
 9007    cx.update_editor(|editor, window, cx| {
 9008        editor.add_selection_below(&Default::default(), window, cx);
 9009    });
 9010
 9011    cx.assert_editor_state(indoc!(
 9012        r#"a«bcˇ»
 9013           d«efgˇ»hi
 9014
 9015           j«kˇ»
 9016           nlmo
 9017           "#
 9018    ));
 9019
 9020    cx.update_editor(|editor, window, cx| {
 9021        editor.add_selection_below(&Default::default(), window, cx);
 9022    });
 9023    cx.assert_editor_state(indoc!(
 9024        r#"a«bcˇ»
 9025           d«efgˇ»hi
 9026
 9027           j«kˇ»
 9028           n«lmoˇ»
 9029           "#
 9030    ));
 9031    cx.update_editor(|editor, window, cx| {
 9032        editor.add_selection_above(&Default::default(), window, cx);
 9033    });
 9034
 9035    cx.assert_editor_state(indoc!(
 9036        r#"a«bcˇ»
 9037           d«efgˇ»hi
 9038
 9039           j«kˇ»
 9040           nlmo
 9041           "#
 9042    ));
 9043
 9044    // Change selections again
 9045    cx.set_state(indoc!(
 9046        r#"abc
 9047           d«ˇefghi
 9048
 9049           jk
 9050           nlm»o
 9051           "#
 9052    ));
 9053
 9054    cx.update_editor(|editor, window, cx| {
 9055        editor.add_selection_above(&Default::default(), window, cx);
 9056    });
 9057
 9058    cx.assert_editor_state(indoc!(
 9059        r#"a«ˇbc»
 9060           d«ˇef»ghi
 9061
 9062           j«ˇk»
 9063           n«ˇlm»o
 9064           "#
 9065    ));
 9066
 9067    cx.update_editor(|editor, window, cx| {
 9068        editor.add_selection_below(&Default::default(), window, cx);
 9069    });
 9070
 9071    cx.assert_editor_state(indoc!(
 9072        r#"abc
 9073           d«ˇef»ghi
 9074
 9075           j«ˇk»
 9076           n«ˇlm»o
 9077           "#
 9078    ));
 9079
 9080    // Assert that the oldest selection's goal column is used when adding more
 9081    // selections, not the most recently added selection's actual column.
 9082    cx.set_state(indoc! {"
 9083        foo bar bazˇ
 9084        foo
 9085        foo bar
 9086    "});
 9087
 9088    cx.update_editor(|editor, window, cx| {
 9089        editor.add_selection_below(
 9090            &AddSelectionBelow {
 9091                skip_soft_wrap: true,
 9092            },
 9093            window,
 9094            cx,
 9095        );
 9096    });
 9097
 9098    cx.assert_editor_state(indoc! {"
 9099        foo bar bazˇ
 9100        fooˇ
 9101        foo bar
 9102    "});
 9103
 9104    cx.update_editor(|editor, window, cx| {
 9105        editor.add_selection_below(
 9106            &AddSelectionBelow {
 9107                skip_soft_wrap: true,
 9108            },
 9109            window,
 9110            cx,
 9111        );
 9112    });
 9113
 9114    cx.assert_editor_state(indoc! {"
 9115        foo bar bazˇ
 9116        fooˇ
 9117        foo barˇ
 9118    "});
 9119
 9120    cx.set_state(indoc! {"
 9121        foo bar baz
 9122        foo
 9123        foo barˇ
 9124    "});
 9125
 9126    cx.update_editor(|editor, window, cx| {
 9127        editor.add_selection_above(
 9128            &AddSelectionAbove {
 9129                skip_soft_wrap: true,
 9130            },
 9131            window,
 9132            cx,
 9133        );
 9134    });
 9135
 9136    cx.assert_editor_state(indoc! {"
 9137        foo bar baz
 9138        fooˇ
 9139        foo barˇ
 9140    "});
 9141
 9142    cx.update_editor(|editor, window, cx| {
 9143        editor.add_selection_above(
 9144            &AddSelectionAbove {
 9145                skip_soft_wrap: true,
 9146            },
 9147            window,
 9148            cx,
 9149        );
 9150    });
 9151
 9152    cx.assert_editor_state(indoc! {"
 9153        foo barˇ baz
 9154        fooˇ
 9155        foo barˇ
 9156    "});
 9157}
 9158
 9159#[gpui::test]
 9160async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 9161    init_test(cx, |_| {});
 9162    let mut cx = EditorTestContext::new(cx).await;
 9163
 9164    cx.set_state(indoc!(
 9165        r#"line onˇe
 9166           liˇne two
 9167           line three
 9168           line four"#
 9169    ));
 9170
 9171    cx.update_editor(|editor, window, cx| {
 9172        editor.add_selection_below(&Default::default(), window, cx);
 9173    });
 9174
 9175    // test multiple cursors expand in the same direction
 9176    cx.assert_editor_state(indoc!(
 9177        r#"line onˇe
 9178           liˇne twˇo
 9179           liˇne three
 9180           line four"#
 9181    ));
 9182
 9183    cx.update_editor(|editor, window, cx| {
 9184        editor.add_selection_below(&Default::default(), window, cx);
 9185    });
 9186
 9187    cx.update_editor(|editor, window, cx| {
 9188        editor.add_selection_below(&Default::default(), window, cx);
 9189    });
 9190
 9191    // test multiple cursors expand below overflow
 9192    cx.assert_editor_state(indoc!(
 9193        r#"line onˇe
 9194           liˇne twˇo
 9195           liˇne thˇree
 9196           liˇne foˇur"#
 9197    ));
 9198
 9199    cx.update_editor(|editor, window, cx| {
 9200        editor.add_selection_above(&Default::default(), window, cx);
 9201    });
 9202
 9203    // test multiple cursors retrieves back correctly
 9204    cx.assert_editor_state(indoc!(
 9205        r#"line onˇe
 9206           liˇne twˇo
 9207           liˇne thˇree
 9208           line four"#
 9209    ));
 9210
 9211    cx.update_editor(|editor, window, cx| {
 9212        editor.add_selection_above(&Default::default(), window, cx);
 9213    });
 9214
 9215    cx.update_editor(|editor, window, cx| {
 9216        editor.add_selection_above(&Default::default(), window, cx);
 9217    });
 9218
 9219    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 9220    cx.assert_editor_state(indoc!(
 9221        r#"liˇne onˇe
 9222           liˇne two
 9223           line three
 9224           line four"#
 9225    ));
 9226
 9227    cx.update_editor(|editor, window, cx| {
 9228        editor.undo_selection(&Default::default(), window, cx);
 9229    });
 9230
 9231    // test undo
 9232    cx.assert_editor_state(indoc!(
 9233        r#"line onˇe
 9234           liˇne twˇo
 9235           line three
 9236           line four"#
 9237    ));
 9238
 9239    cx.update_editor(|editor, window, cx| {
 9240        editor.redo_selection(&Default::default(), window, cx);
 9241    });
 9242
 9243    // test redo
 9244    cx.assert_editor_state(indoc!(
 9245        r#"liˇne onˇe
 9246           liˇne two
 9247           line three
 9248           line four"#
 9249    ));
 9250
 9251    cx.set_state(indoc!(
 9252        r#"abcd
 9253           ef«ghˇ»
 9254           ijkl
 9255           «mˇ»nop"#
 9256    ));
 9257
 9258    cx.update_editor(|editor, window, cx| {
 9259        editor.add_selection_above(&Default::default(), window, cx);
 9260    });
 9261
 9262    // test multiple selections expand in the same direction
 9263    cx.assert_editor_state(indoc!(
 9264        r#"ab«cdˇ»
 9265           ef«ghˇ»
 9266           «iˇ»jkl
 9267           «mˇ»nop"#
 9268    ));
 9269
 9270    cx.update_editor(|editor, window, cx| {
 9271        editor.add_selection_above(&Default::default(), window, cx);
 9272    });
 9273
 9274    // test multiple selection upward overflow
 9275    cx.assert_editor_state(indoc!(
 9276        r#"ab«cdˇ»
 9277           «eˇ»f«ghˇ»
 9278           «iˇ»jkl
 9279           «mˇ»nop"#
 9280    ));
 9281
 9282    cx.update_editor(|editor, window, cx| {
 9283        editor.add_selection_below(&Default::default(), window, cx);
 9284    });
 9285
 9286    // test multiple selection retrieves back correctly
 9287    cx.assert_editor_state(indoc!(
 9288        r#"abcd
 9289           ef«ghˇ»
 9290           «iˇ»jkl
 9291           «mˇ»nop"#
 9292    ));
 9293
 9294    cx.update_editor(|editor, window, cx| {
 9295        editor.add_selection_below(&Default::default(), window, cx);
 9296    });
 9297
 9298    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 9299    cx.assert_editor_state(indoc!(
 9300        r#"abcd
 9301           ef«ghˇ»
 9302           ij«klˇ»
 9303           «mˇ»nop"#
 9304    ));
 9305
 9306    cx.update_editor(|editor, window, cx| {
 9307        editor.undo_selection(&Default::default(), window, cx);
 9308    });
 9309
 9310    // test undo
 9311    cx.assert_editor_state(indoc!(
 9312        r#"abcd
 9313           ef«ghˇ»
 9314           «iˇ»jkl
 9315           «mˇ»nop"#
 9316    ));
 9317
 9318    cx.update_editor(|editor, window, cx| {
 9319        editor.redo_selection(&Default::default(), window, cx);
 9320    });
 9321
 9322    // test redo
 9323    cx.assert_editor_state(indoc!(
 9324        r#"abcd
 9325           ef«ghˇ»
 9326           ij«klˇ»
 9327           «mˇ»nop"#
 9328    ));
 9329}
 9330
 9331#[gpui::test]
 9332async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 9333    init_test(cx, |_| {});
 9334    let mut cx = EditorTestContext::new(cx).await;
 9335
 9336    cx.set_state(indoc!(
 9337        r#"line onˇe
 9338           liˇne two
 9339           line three
 9340           line four"#
 9341    ));
 9342
 9343    cx.update_editor(|editor, window, cx| {
 9344        editor.add_selection_below(&Default::default(), window, cx);
 9345        editor.add_selection_below(&Default::default(), window, cx);
 9346        editor.add_selection_below(&Default::default(), window, cx);
 9347    });
 9348
 9349    // initial state with two multi cursor groups
 9350    cx.assert_editor_state(indoc!(
 9351        r#"line onˇe
 9352           liˇne twˇo
 9353           liˇne thˇree
 9354           liˇne foˇur"#
 9355    ));
 9356
 9357    // add single cursor in middle - simulate opt click
 9358    cx.update_editor(|editor, window, cx| {
 9359        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 9360        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 9361        editor.end_selection(window, cx);
 9362    });
 9363
 9364    cx.assert_editor_state(indoc!(
 9365        r#"line onˇe
 9366           liˇne twˇo
 9367           liˇneˇ thˇree
 9368           liˇne foˇur"#
 9369    ));
 9370
 9371    cx.update_editor(|editor, window, cx| {
 9372        editor.add_selection_above(&Default::default(), window, cx);
 9373    });
 9374
 9375    // test new added selection expands above and existing selection shrinks
 9376    cx.assert_editor_state(indoc!(
 9377        r#"line onˇe
 9378           liˇneˇ twˇo
 9379           liˇneˇ thˇree
 9380           line four"#
 9381    ));
 9382
 9383    cx.update_editor(|editor, window, cx| {
 9384        editor.add_selection_above(&Default::default(), window, cx);
 9385    });
 9386
 9387    // test new added selection expands above and existing selection shrinks
 9388    cx.assert_editor_state(indoc!(
 9389        r#"lineˇ onˇe
 9390           liˇneˇ twˇo
 9391           lineˇ three
 9392           line four"#
 9393    ));
 9394
 9395    // intial state with two selection groups
 9396    cx.set_state(indoc!(
 9397        r#"abcd
 9398           ef«ghˇ»
 9399           ijkl
 9400           «mˇ»nop"#
 9401    ));
 9402
 9403    cx.update_editor(|editor, window, cx| {
 9404        editor.add_selection_above(&Default::default(), window, cx);
 9405        editor.add_selection_above(&Default::default(), window, cx);
 9406    });
 9407
 9408    cx.assert_editor_state(indoc!(
 9409        r#"ab«cdˇ»
 9410           «eˇ»f«ghˇ»
 9411           «iˇ»jkl
 9412           «mˇ»nop"#
 9413    ));
 9414
 9415    // add single selection in middle - simulate opt drag
 9416    cx.update_editor(|editor, window, cx| {
 9417        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 9418        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 9419        editor.update_selection(
 9420            DisplayPoint::new(DisplayRow(2), 4),
 9421            0,
 9422            gpui::Point::<f32>::default(),
 9423            window,
 9424            cx,
 9425        );
 9426        editor.end_selection(window, cx);
 9427    });
 9428
 9429    cx.assert_editor_state(indoc!(
 9430        r#"ab«cdˇ»
 9431           «eˇ»f«ghˇ»
 9432           «iˇ»jk«lˇ»
 9433           «mˇ»nop"#
 9434    ));
 9435
 9436    cx.update_editor(|editor, window, cx| {
 9437        editor.add_selection_below(&Default::default(), window, cx);
 9438    });
 9439
 9440    // test new added selection expands below, others shrinks from above
 9441    cx.assert_editor_state(indoc!(
 9442        r#"abcd
 9443           ef«ghˇ»
 9444           «iˇ»jk«lˇ»
 9445           «mˇ»no«pˇ»"#
 9446    ));
 9447}
 9448
 9449#[gpui::test]
 9450async fn test_select_next(cx: &mut TestAppContext) {
 9451    init_test(cx, |_| {});
 9452    let mut cx = EditorTestContext::new(cx).await;
 9453
 9454    // Enable case sensitive search.
 9455    update_test_editor_settings(&mut cx, &|settings| {
 9456        let mut search_settings = SearchSettingsContent::default();
 9457        search_settings.case_sensitive = Some(true);
 9458        settings.search = Some(search_settings);
 9459    });
 9460
 9461    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 9462
 9463    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9464        .unwrap();
 9465    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9466
 9467    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9468        .unwrap();
 9469    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 9470
 9471    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9472    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9473
 9474    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9475    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 9476
 9477    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9478        .unwrap();
 9479    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9480
 9481    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9482        .unwrap();
 9483    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9484
 9485    // Test selection direction should be preserved
 9486    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9487
 9488    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9489        .unwrap();
 9490    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 9491
 9492    // Test case sensitivity
 9493    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 9494    cx.update_editor(|e, window, cx| {
 9495        e.select_next(&SelectNext::default(), window, cx).unwrap();
 9496    });
 9497    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9498
 9499    // Disable case sensitive search.
 9500    update_test_editor_settings(&mut cx, &|settings| {
 9501        let mut search_settings = SearchSettingsContent::default();
 9502        search_settings.case_sensitive = Some(false);
 9503        settings.search = Some(search_settings);
 9504    });
 9505
 9506    cx.set_state("«ˇfoo»\nFOO\nFoo");
 9507    cx.update_editor(|e, window, cx| {
 9508        e.select_next(&SelectNext::default(), window, cx).unwrap();
 9509        e.select_next(&SelectNext::default(), window, cx).unwrap();
 9510    });
 9511    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9512}
 9513
 9514#[gpui::test]
 9515async fn test_select_all_matches(cx: &mut TestAppContext) {
 9516    init_test(cx, |_| {});
 9517    let mut cx = EditorTestContext::new(cx).await;
 9518
 9519    // Enable case sensitive search.
 9520    update_test_editor_settings(&mut cx, &|settings| {
 9521        let mut search_settings = SearchSettingsContent::default();
 9522        search_settings.case_sensitive = Some(true);
 9523        settings.search = Some(search_settings);
 9524    });
 9525
 9526    // Test caret-only selections
 9527    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 9528    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9529        .unwrap();
 9530    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9531
 9532    // Test left-to-right selections
 9533    cx.set_state("abc\n«abcˇ»\nabc");
 9534    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9535        .unwrap();
 9536    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 9537
 9538    // Test right-to-left selections
 9539    cx.set_state("abc\n«ˇabc»\nabc");
 9540    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9541        .unwrap();
 9542    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 9543
 9544    // Test selecting whitespace with caret selection
 9545    cx.set_state("abc\nˇ   abc\nabc");
 9546    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9547        .unwrap();
 9548    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 9549
 9550    // Test selecting whitespace with left-to-right selection
 9551    cx.set_state("abc\n«ˇ  »abc\nabc");
 9552    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9553        .unwrap();
 9554    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 9555
 9556    // Test no matches with right-to-left selection
 9557    cx.set_state("abc\n«  ˇ»abc\nabc");
 9558    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9559        .unwrap();
 9560    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 9561
 9562    // Test with a single word and clip_at_line_ends=true (#29823)
 9563    cx.set_state("aˇbc");
 9564    cx.update_editor(|e, window, cx| {
 9565        e.set_clip_at_line_ends(true, cx);
 9566        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 9567        e.set_clip_at_line_ends(false, cx);
 9568    });
 9569    cx.assert_editor_state("«abcˇ»");
 9570
 9571    // Test case sensitivity
 9572    cx.set_state("fˇoo\nFOO\nFoo");
 9573    cx.update_editor(|e, window, cx| {
 9574        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 9575    });
 9576    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 9577
 9578    // Disable case sensitive search.
 9579    update_test_editor_settings(&mut cx, &|settings| {
 9580        let mut search_settings = SearchSettingsContent::default();
 9581        search_settings.case_sensitive = Some(false);
 9582        settings.search = Some(search_settings);
 9583    });
 9584
 9585    cx.set_state("fˇoo\nFOO\nFoo");
 9586    cx.update_editor(|e, window, cx| {
 9587        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 9588    });
 9589    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 9590}
 9591
 9592#[gpui::test]
 9593async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 9594    init_test(cx, |_| {});
 9595
 9596    let mut cx = EditorTestContext::new(cx).await;
 9597
 9598    let large_body_1 = "\nd".repeat(200);
 9599    let large_body_2 = "\ne".repeat(200);
 9600
 9601    cx.set_state(&format!(
 9602        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 9603    ));
 9604    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 9605        let scroll_position = editor.scroll_position(cx);
 9606        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 9607        scroll_position
 9608    });
 9609
 9610    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 9611        .unwrap();
 9612    cx.assert_editor_state(&format!(
 9613        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 9614    ));
 9615    let scroll_position_after_selection =
 9616        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 9617    assert_eq!(
 9618        initial_scroll_position, scroll_position_after_selection,
 9619        "Scroll position should not change after selecting all matches"
 9620    );
 9621}
 9622
 9623#[gpui::test]
 9624async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 9625    init_test(cx, |_| {});
 9626
 9627    let mut cx = EditorLspTestContext::new_rust(
 9628        lsp::ServerCapabilities {
 9629            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9630            ..Default::default()
 9631        },
 9632        cx,
 9633    )
 9634    .await;
 9635
 9636    cx.set_state(indoc! {"
 9637        line 1
 9638        line 2
 9639        linˇe 3
 9640        line 4
 9641        line 5
 9642    "});
 9643
 9644    // Make an edit
 9645    cx.update_editor(|editor, window, cx| {
 9646        editor.handle_input("X", window, cx);
 9647    });
 9648
 9649    // Move cursor to a different position
 9650    cx.update_editor(|editor, window, cx| {
 9651        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9652            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 9653        });
 9654    });
 9655
 9656    cx.assert_editor_state(indoc! {"
 9657        line 1
 9658        line 2
 9659        linXe 3
 9660        line 4
 9661        liˇne 5
 9662    "});
 9663
 9664    cx.lsp
 9665        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 9666            Ok(Some(vec![lsp::TextEdit::new(
 9667                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 9668                "PREFIX ".to_string(),
 9669            )]))
 9670        });
 9671
 9672    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 9673        .unwrap()
 9674        .await
 9675        .unwrap();
 9676
 9677    cx.assert_editor_state(indoc! {"
 9678        PREFIX line 1
 9679        line 2
 9680        linXe 3
 9681        line 4
 9682        liˇne 5
 9683    "});
 9684
 9685    // Undo formatting
 9686    cx.update_editor(|editor, window, cx| {
 9687        editor.undo(&Default::default(), window, cx);
 9688    });
 9689
 9690    // Verify cursor moved back to position after edit
 9691    cx.assert_editor_state(indoc! {"
 9692        line 1
 9693        line 2
 9694        linXˇe 3
 9695        line 4
 9696        line 5
 9697    "});
 9698}
 9699
 9700#[gpui::test]
 9701async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 9702    init_test(cx, |_| {});
 9703
 9704    let mut cx = EditorTestContext::new(cx).await;
 9705
 9706    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 9707    cx.update_editor(|editor, window, cx| {
 9708        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 9709    });
 9710
 9711    cx.set_state(indoc! {"
 9712        line 1
 9713        line 2
 9714        linˇe 3
 9715        line 4
 9716        line 5
 9717        line 6
 9718        line 7
 9719        line 8
 9720        line 9
 9721        line 10
 9722    "});
 9723
 9724    let snapshot = cx.buffer_snapshot();
 9725    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 9726
 9727    cx.update(|_, cx| {
 9728        provider.update(cx, |provider, _| {
 9729            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
 9730                id: None,
 9731                edits: vec![(edit_position..edit_position, "X".into())],
 9732                cursor_position: None,
 9733                edit_preview: None,
 9734            }))
 9735        })
 9736    });
 9737
 9738    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 9739    cx.update_editor(|editor, window, cx| {
 9740        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 9741    });
 9742
 9743    cx.assert_editor_state(indoc! {"
 9744        line 1
 9745        line 2
 9746        lineXˇ 3
 9747        line 4
 9748        line 5
 9749        line 6
 9750        line 7
 9751        line 8
 9752        line 9
 9753        line 10
 9754    "});
 9755
 9756    cx.update_editor(|editor, window, cx| {
 9757        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9758            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 9759        });
 9760    });
 9761
 9762    cx.assert_editor_state(indoc! {"
 9763        line 1
 9764        line 2
 9765        lineX 3
 9766        line 4
 9767        line 5
 9768        line 6
 9769        line 7
 9770        line 8
 9771        line 9
 9772        liˇne 10
 9773    "});
 9774
 9775    cx.update_editor(|editor, window, cx| {
 9776        editor.undo(&Default::default(), window, cx);
 9777    });
 9778
 9779    cx.assert_editor_state(indoc! {"
 9780        line 1
 9781        line 2
 9782        lineˇ 3
 9783        line 4
 9784        line 5
 9785        line 6
 9786        line 7
 9787        line 8
 9788        line 9
 9789        line 10
 9790    "});
 9791}
 9792
 9793#[gpui::test]
 9794async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 9795    init_test(cx, |_| {});
 9796
 9797    let mut cx = EditorTestContext::new(cx).await;
 9798    cx.set_state(
 9799        r#"let foo = 2;
 9800lˇet foo = 2;
 9801let fooˇ = 2;
 9802let foo = 2;
 9803let foo = ˇ2;"#,
 9804    );
 9805
 9806    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9807        .unwrap();
 9808    cx.assert_editor_state(
 9809        r#"let foo = 2;
 9810«letˇ» foo = 2;
 9811let «fooˇ» = 2;
 9812let foo = 2;
 9813let foo = «2ˇ»;"#,
 9814    );
 9815
 9816    // noop for multiple selections with different contents
 9817    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9818        .unwrap();
 9819    cx.assert_editor_state(
 9820        r#"let foo = 2;
 9821«letˇ» foo = 2;
 9822let «fooˇ» = 2;
 9823let foo = 2;
 9824let foo = «2ˇ»;"#,
 9825    );
 9826
 9827    // Test last selection direction should be preserved
 9828    cx.set_state(
 9829        r#"let foo = 2;
 9830let foo = 2;
 9831let «fooˇ» = 2;
 9832let «ˇfoo» = 2;
 9833let foo = 2;"#,
 9834    );
 9835
 9836    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 9837        .unwrap();
 9838    cx.assert_editor_state(
 9839        r#"let foo = 2;
 9840let foo = 2;
 9841let «fooˇ» = 2;
 9842let «ˇfoo» = 2;
 9843let «ˇfoo» = 2;"#,
 9844    );
 9845}
 9846
 9847#[gpui::test]
 9848async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 9849    init_test(cx, |_| {});
 9850
 9851    let mut cx =
 9852        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc»\nddd", "aaa\n«bbb\nccc»\nddd"]);
 9853
 9854    cx.assert_editor_state(indoc! {"
 9855        ˇbbb
 9856        ccc
 9857        bbb
 9858        ccc"});
 9859    cx.dispatch_action(SelectPrevious::default());
 9860    cx.assert_editor_state(indoc! {"
 9861                «bbbˇ»
 9862                ccc
 9863                bbb
 9864                ccc"});
 9865    cx.dispatch_action(SelectPrevious::default());
 9866    cx.assert_editor_state(indoc! {"
 9867                «bbbˇ»
 9868                ccc
 9869                «bbbˇ»
 9870                ccc"});
 9871}
 9872
 9873#[gpui::test]
 9874async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 9875    init_test(cx, |_| {});
 9876
 9877    let mut cx = EditorTestContext::new(cx).await;
 9878    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 9879
 9880    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9881        .unwrap();
 9882    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9883
 9884    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9885        .unwrap();
 9886    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9887
 9888    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9889    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 9890
 9891    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9892    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 9893
 9894    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9895        .unwrap();
 9896    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 9897
 9898    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9899        .unwrap();
 9900    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 9901}
 9902
 9903#[gpui::test]
 9904async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 9905    init_test(cx, |_| {});
 9906
 9907    let mut cx = EditorTestContext::new(cx).await;
 9908    cx.set_state("");
 9909
 9910    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9911        .unwrap();
 9912    cx.assert_editor_state("«aˇ»");
 9913    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9914        .unwrap();
 9915    cx.assert_editor_state("«aˇ»");
 9916}
 9917
 9918#[gpui::test]
 9919async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 9920    init_test(cx, |_| {});
 9921
 9922    let mut cx = EditorTestContext::new(cx).await;
 9923    cx.set_state(
 9924        r#"let foo = 2;
 9925lˇet foo = 2;
 9926let fooˇ = 2;
 9927let foo = 2;
 9928let foo = ˇ2;"#,
 9929    );
 9930
 9931    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9932        .unwrap();
 9933    cx.assert_editor_state(
 9934        r#"let foo = 2;
 9935«letˇ» foo = 2;
 9936let «fooˇ» = 2;
 9937let foo = 2;
 9938let foo = «2ˇ»;"#,
 9939    );
 9940
 9941    // noop for multiple selections with different contents
 9942    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9943        .unwrap();
 9944    cx.assert_editor_state(
 9945        r#"let foo = 2;
 9946«letˇ» foo = 2;
 9947let «fooˇ» = 2;
 9948let foo = 2;
 9949let foo = «2ˇ»;"#,
 9950    );
 9951}
 9952
 9953#[gpui::test]
 9954async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 9955    init_test(cx, |_| {});
 9956    let mut cx = EditorTestContext::new(cx).await;
 9957
 9958    // Enable case sensitive search.
 9959    update_test_editor_settings(&mut cx, &|settings| {
 9960        let mut search_settings = SearchSettingsContent::default();
 9961        search_settings.case_sensitive = Some(true);
 9962        settings.search = Some(search_settings);
 9963    });
 9964
 9965    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9966
 9967    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9968        .unwrap();
 9969    // selection direction is preserved
 9970    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9971
 9972    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9973        .unwrap();
 9974    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9975
 9976    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9977    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9978
 9979    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9980    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9981
 9982    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9983        .unwrap();
 9984    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 9985
 9986    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9987        .unwrap();
 9988    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 9989
 9990    // Test case sensitivity
 9991    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 9992    cx.update_editor(|e, window, cx| {
 9993        e.select_previous(&SelectPrevious::default(), window, cx)
 9994            .unwrap();
 9995        e.select_previous(&SelectPrevious::default(), window, cx)
 9996            .unwrap();
 9997    });
 9998    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9999
10000    // Disable case sensitive search.
10001    update_test_editor_settings(&mut cx, &|settings| {
10002        let mut search_settings = SearchSettingsContent::default();
10003        search_settings.case_sensitive = Some(false);
10004        settings.search = Some(search_settings);
10005    });
10006
10007    cx.set_state("foo\nFOO\n«ˇFoo»");
10008    cx.update_editor(|e, window, cx| {
10009        e.select_previous(&SelectPrevious::default(), window, cx)
10010            .unwrap();
10011        e.select_previous(&SelectPrevious::default(), window, cx)
10012            .unwrap();
10013    });
10014    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
10015}
10016
10017#[gpui::test]
10018async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
10019    init_test(cx, |_| {});
10020
10021    let language = Arc::new(Language::new(
10022        LanguageConfig::default(),
10023        Some(tree_sitter_rust::LANGUAGE.into()),
10024    ));
10025
10026    let text = r#"
10027        use mod1::mod2::{mod3, mod4};
10028
10029        fn fn_1(param1: bool, param2: &str) {
10030            let var1 = "text";
10031        }
10032    "#
10033    .unindent();
10034
10035    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10036    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10037    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10038
10039    editor
10040        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10041        .await;
10042
10043    editor.update_in(cx, |editor, window, cx| {
10044        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10045            s.select_display_ranges([
10046                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
10047                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
10048                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
10049            ]);
10050        });
10051        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10052    });
10053    editor.update(cx, |editor, cx| {
10054        assert_text_with_selections(
10055            editor,
10056            indoc! {r#"
10057                use mod1::mod2::{mod3, «mod4ˇ»};
10058
10059                fn fn_1«ˇ(param1: bool, param2: &str)» {
10060                    let var1 = "«ˇtext»";
10061                }
10062            "#},
10063            cx,
10064        );
10065    });
10066
10067    editor.update_in(cx, |editor, window, cx| {
10068        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10069    });
10070    editor.update(cx, |editor, cx| {
10071        assert_text_with_selections(
10072            editor,
10073            indoc! {r#"
10074                use mod1::mod2::«{mod3, mod4}ˇ»;
10075
10076                «ˇfn fn_1(param1: bool, param2: &str) {
10077                    let var1 = "text";
1007810079            "#},
10080            cx,
10081        );
10082    });
10083
10084    editor.update_in(cx, |editor, window, cx| {
10085        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10086    });
10087    assert_eq!(
10088        editor.update(cx, |editor, cx| editor
10089            .selections
10090            .display_ranges(&editor.display_snapshot(cx))),
10091        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
10092    );
10093
10094    // Trying to expand the selected syntax node one more time has no effect.
10095    editor.update_in(cx, |editor, window, cx| {
10096        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10097    });
10098    assert_eq!(
10099        editor.update(cx, |editor, cx| editor
10100            .selections
10101            .display_ranges(&editor.display_snapshot(cx))),
10102        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
10103    );
10104
10105    editor.update_in(cx, |editor, window, cx| {
10106        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
10107    });
10108    editor.update(cx, |editor, cx| {
10109        assert_text_with_selections(
10110            editor,
10111            indoc! {r#"
10112                use mod1::mod2::«{mod3, mod4}ˇ»;
10113
10114                «ˇfn fn_1(param1: bool, param2: &str) {
10115                    let var1 = "text";
1011610117            "#},
10118            cx,
10119        );
10120    });
10121
10122    editor.update_in(cx, |editor, window, cx| {
10123        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
10124    });
10125    editor.update(cx, |editor, cx| {
10126        assert_text_with_selections(
10127            editor,
10128            indoc! {r#"
10129                use mod1::mod2::{mod3, «mod4ˇ»};
10130
10131                fn fn_1«ˇ(param1: bool, param2: &str)» {
10132                    let var1 = "«ˇtext»";
10133                }
10134            "#},
10135            cx,
10136        );
10137    });
10138
10139    editor.update_in(cx, |editor, window, cx| {
10140        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
10141    });
10142    editor.update(cx, |editor, cx| {
10143        assert_text_with_selections(
10144            editor,
10145            indoc! {r#"
10146                use mod1::mod2::{mod3, moˇd4};
10147
10148                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
10149                    let var1 = "teˇxt";
10150                }
10151            "#},
10152            cx,
10153        );
10154    });
10155
10156    // Trying to shrink the selected syntax node one more time has no effect.
10157    editor.update_in(cx, |editor, window, cx| {
10158        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
10159    });
10160    editor.update_in(cx, |editor, _, cx| {
10161        assert_text_with_selections(
10162            editor,
10163            indoc! {r#"
10164                use mod1::mod2::{mod3, moˇd4};
10165
10166                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
10167                    let var1 = "teˇxt";
10168                }
10169            "#},
10170            cx,
10171        );
10172    });
10173
10174    // Ensure that we keep expanding the selection if the larger selection starts or ends within
10175    // a fold.
10176    editor.update_in(cx, |editor, window, cx| {
10177        editor.fold_creases(
10178            vec![
10179                Crease::simple(
10180                    Point::new(0, 21)..Point::new(0, 24),
10181                    FoldPlaceholder::test(),
10182                ),
10183                Crease::simple(
10184                    Point::new(3, 20)..Point::new(3, 22),
10185                    FoldPlaceholder::test(),
10186                ),
10187            ],
10188            true,
10189            window,
10190            cx,
10191        );
10192        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10193    });
10194    editor.update(cx, |editor, cx| {
10195        assert_text_with_selections(
10196            editor,
10197            indoc! {r#"
10198                use mod1::mod2::«{mod3, mod4}ˇ»;
10199
10200                fn fn_1«ˇ(param1: bool, param2: &str)» {
10201                    let var1 = "«ˇtext»";
10202                }
10203            "#},
10204            cx,
10205        );
10206    });
10207}
10208
10209#[gpui::test]
10210async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
10211    init_test(cx, |_| {});
10212
10213    let language = Arc::new(Language::new(
10214        LanguageConfig::default(),
10215        Some(tree_sitter_rust::LANGUAGE.into()),
10216    ));
10217
10218    let text = "let a = 2;";
10219
10220    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10221    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10222    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10223
10224    editor
10225        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10226        .await;
10227
10228    // Test case 1: Cursor at end of word
10229    editor.update_in(cx, |editor, window, cx| {
10230        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10231            s.select_display_ranges([
10232                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
10233            ]);
10234        });
10235    });
10236    editor.update(cx, |editor, cx| {
10237        assert_text_with_selections(editor, "let aˇ = 2;", cx);
10238    });
10239    editor.update_in(cx, |editor, window, cx| {
10240        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10241    });
10242    editor.update(cx, |editor, cx| {
10243        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
10244    });
10245    editor.update_in(cx, |editor, window, cx| {
10246        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10247    });
10248    editor.update(cx, |editor, cx| {
10249        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
10250    });
10251
10252    // Test case 2: Cursor at end of statement
10253    editor.update_in(cx, |editor, window, cx| {
10254        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10255            s.select_display_ranges([
10256                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
10257            ]);
10258        });
10259    });
10260    editor.update(cx, |editor, cx| {
10261        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
10262    });
10263    editor.update_in(cx, |editor, window, cx| {
10264        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10265    });
10266    editor.update(cx, |editor, cx| {
10267        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
10268    });
10269}
10270
10271#[gpui::test]
10272async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
10273    init_test(cx, |_| {});
10274
10275    let language = Arc::new(Language::new(
10276        LanguageConfig {
10277            name: "JavaScript".into(),
10278            ..Default::default()
10279        },
10280        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10281    ));
10282
10283    let text = r#"
10284        let a = {
10285            key: "value",
10286        };
10287    "#
10288    .unindent();
10289
10290    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10291    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10292    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10293
10294    editor
10295        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10296        .await;
10297
10298    // Test case 1: Cursor after '{'
10299    editor.update_in(cx, |editor, window, cx| {
10300        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10301            s.select_display_ranges([
10302                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
10303            ]);
10304        });
10305    });
10306    editor.update(cx, |editor, cx| {
10307        assert_text_with_selections(
10308            editor,
10309            indoc! {r#"
10310                let a = {ˇ
10311                    key: "value",
10312                };
10313            "#},
10314            cx,
10315        );
10316    });
10317    editor.update_in(cx, |editor, window, cx| {
10318        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10319    });
10320    editor.update(cx, |editor, cx| {
10321        assert_text_with_selections(
10322            editor,
10323            indoc! {r#"
10324                let a = «ˇ{
10325                    key: "value",
10326                }»;
10327            "#},
10328            cx,
10329        );
10330    });
10331
10332    // Test case 2: Cursor after ':'
10333    editor.update_in(cx, |editor, window, cx| {
10334        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10335            s.select_display_ranges([
10336                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
10337            ]);
10338        });
10339    });
10340    editor.update(cx, |editor, cx| {
10341        assert_text_with_selections(
10342            editor,
10343            indoc! {r#"
10344                let a = {
10345                    key:ˇ "value",
10346                };
10347            "#},
10348            cx,
10349        );
10350    });
10351    editor.update_in(cx, |editor, window, cx| {
10352        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10353    });
10354    editor.update(cx, |editor, cx| {
10355        assert_text_with_selections(
10356            editor,
10357            indoc! {r#"
10358                let a = {
10359                    «ˇkey: "value"»,
10360                };
10361            "#},
10362            cx,
10363        );
10364    });
10365    editor.update_in(cx, |editor, window, cx| {
10366        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10367    });
10368    editor.update(cx, |editor, cx| {
10369        assert_text_with_selections(
10370            editor,
10371            indoc! {r#"
10372                let a = «ˇ{
10373                    key: "value",
10374                }»;
10375            "#},
10376            cx,
10377        );
10378    });
10379
10380    // Test case 3: Cursor after ','
10381    editor.update_in(cx, |editor, window, cx| {
10382        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10383            s.select_display_ranges([
10384                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
10385            ]);
10386        });
10387    });
10388    editor.update(cx, |editor, cx| {
10389        assert_text_with_selections(
10390            editor,
10391            indoc! {r#"
10392                let a = {
10393                    key: "value",ˇ
10394                };
10395            "#},
10396            cx,
10397        );
10398    });
10399    editor.update_in(cx, |editor, window, cx| {
10400        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10401    });
10402    editor.update(cx, |editor, cx| {
10403        assert_text_with_selections(
10404            editor,
10405            indoc! {r#"
10406                let a = «ˇ{
10407                    key: "value",
10408                }»;
10409            "#},
10410            cx,
10411        );
10412    });
10413
10414    // Test case 4: Cursor after ';'
10415    editor.update_in(cx, |editor, window, cx| {
10416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10417            s.select_display_ranges([
10418                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
10419            ]);
10420        });
10421    });
10422    editor.update(cx, |editor, cx| {
10423        assert_text_with_selections(
10424            editor,
10425            indoc! {r#"
10426                let a = {
10427                    key: "value",
10428                };ˇ
10429            "#},
10430            cx,
10431        );
10432    });
10433    editor.update_in(cx, |editor, window, cx| {
10434        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10435    });
10436    editor.update(cx, |editor, cx| {
10437        assert_text_with_selections(
10438            editor,
10439            indoc! {r#"
10440                «ˇlet a = {
10441                    key: "value",
10442                };
10443                »"#},
10444            cx,
10445        );
10446    });
10447}
10448
10449#[gpui::test]
10450async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
10451    init_test(cx, |_| {});
10452
10453    let language = Arc::new(Language::new(
10454        LanguageConfig::default(),
10455        Some(tree_sitter_rust::LANGUAGE.into()),
10456    ));
10457
10458    let text = r#"
10459        use mod1::mod2::{mod3, mod4};
10460
10461        fn fn_1(param1: bool, param2: &str) {
10462            let var1 = "hello world";
10463        }
10464    "#
10465    .unindent();
10466
10467    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10468    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10469    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10470
10471    editor
10472        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10473        .await;
10474
10475    // Test 1: Cursor on a letter of a string word
10476    editor.update_in(cx, |editor, window, cx| {
10477        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10478            s.select_display_ranges([
10479                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
10480            ]);
10481        });
10482    });
10483    editor.update_in(cx, |editor, window, cx| {
10484        assert_text_with_selections(
10485            editor,
10486            indoc! {r#"
10487                use mod1::mod2::{mod3, mod4};
10488
10489                fn fn_1(param1: bool, param2: &str) {
10490                    let var1 = "hˇello world";
10491                }
10492            "#},
10493            cx,
10494        );
10495        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10496        assert_text_with_selections(
10497            editor,
10498            indoc! {r#"
10499                use mod1::mod2::{mod3, mod4};
10500
10501                fn fn_1(param1: bool, param2: &str) {
10502                    let var1 = "«ˇhello» world";
10503                }
10504            "#},
10505            cx,
10506        );
10507    });
10508
10509    // Test 2: Partial selection within a word
10510    editor.update_in(cx, |editor, window, cx| {
10511        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10512            s.select_display_ranges([
10513                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
10514            ]);
10515        });
10516    });
10517    editor.update_in(cx, |editor, window, cx| {
10518        assert_text_with_selections(
10519            editor,
10520            indoc! {r#"
10521                use mod1::mod2::{mod3, mod4};
10522
10523                fn fn_1(param1: bool, param2: &str) {
10524                    let var1 = "h«elˇ»lo world";
10525                }
10526            "#},
10527            cx,
10528        );
10529        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10530        assert_text_with_selections(
10531            editor,
10532            indoc! {r#"
10533                use mod1::mod2::{mod3, mod4};
10534
10535                fn fn_1(param1: bool, param2: &str) {
10536                    let var1 = "«ˇhello» world";
10537                }
10538            "#},
10539            cx,
10540        );
10541    });
10542
10543    // Test 3: Complete word already selected
10544    editor.update_in(cx, |editor, window, cx| {
10545        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10546            s.select_display_ranges([
10547                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
10548            ]);
10549        });
10550    });
10551    editor.update_in(cx, |editor, window, cx| {
10552        assert_text_with_selections(
10553            editor,
10554            indoc! {r#"
10555                use mod1::mod2::{mod3, mod4};
10556
10557                fn fn_1(param1: bool, param2: &str) {
10558                    let var1 = "«helloˇ» world";
10559                }
10560            "#},
10561            cx,
10562        );
10563        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10564        assert_text_with_selections(
10565            editor,
10566            indoc! {r#"
10567                use mod1::mod2::{mod3, mod4};
10568
10569                fn fn_1(param1: bool, param2: &str) {
10570                    let var1 = "«hello worldˇ»";
10571                }
10572            "#},
10573            cx,
10574        );
10575    });
10576
10577    // Test 4: Selection spanning across words
10578    editor.update_in(cx, |editor, window, cx| {
10579        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10580            s.select_display_ranges([
10581                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
10582            ]);
10583        });
10584    });
10585    editor.update_in(cx, |editor, window, cx| {
10586        assert_text_with_selections(
10587            editor,
10588            indoc! {r#"
10589                use mod1::mod2::{mod3, mod4};
10590
10591                fn fn_1(param1: bool, param2: &str) {
10592                    let var1 = "hel«lo woˇ»rld";
10593                }
10594            "#},
10595            cx,
10596        );
10597        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10598        assert_text_with_selections(
10599            editor,
10600            indoc! {r#"
10601                use mod1::mod2::{mod3, mod4};
10602
10603                fn fn_1(param1: bool, param2: &str) {
10604                    let var1 = "«ˇhello world»";
10605                }
10606            "#},
10607            cx,
10608        );
10609    });
10610
10611    // Test 5: Expansion beyond string
10612    editor.update_in(cx, |editor, window, cx| {
10613        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10614        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10615        assert_text_with_selections(
10616            editor,
10617            indoc! {r#"
10618                use mod1::mod2::{mod3, mod4};
10619
10620                fn fn_1(param1: bool, param2: &str) {
10621                    «ˇlet var1 = "hello world";»
10622                }
10623            "#},
10624            cx,
10625        );
10626    });
10627}
10628
10629#[gpui::test]
10630async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
10631    init_test(cx, |_| {});
10632
10633    let mut cx = EditorTestContext::new(cx).await;
10634
10635    let language = Arc::new(Language::new(
10636        LanguageConfig::default(),
10637        Some(tree_sitter_rust::LANGUAGE.into()),
10638    ));
10639
10640    cx.update_buffer(|buffer, cx| {
10641        buffer.set_language(Some(language), cx);
10642    });
10643
10644    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
10645    cx.update_editor(|editor, window, cx| {
10646        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
10647    });
10648
10649    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
10650
10651    cx.set_state(indoc! { r#"fn a() {
10652          // what
10653          // a
10654          // ˇlong
10655          // method
10656          // I
10657          // sure
10658          // hope
10659          // it
10660          // works
10661    }"# });
10662
10663    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
10664    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
10665    cx.update(|_, cx| {
10666        multi_buffer.update(cx, |multi_buffer, cx| {
10667            multi_buffer.set_excerpts_for_path(
10668                PathKey::for_buffer(&buffer, cx),
10669                buffer,
10670                [Point::new(1, 0)..Point::new(1, 0)],
10671                3,
10672                cx,
10673            );
10674        });
10675    });
10676
10677    let editor2 = cx.new_window_entity(|window, cx| {
10678        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
10679    });
10680
10681    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
10682    cx.update_editor(|editor, window, cx| {
10683        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
10684            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
10685        })
10686    });
10687
10688    cx.assert_editor_state(indoc! { "
10689        fn a() {
10690              // what
10691              // a
10692        ˇ      // long
10693              // method"});
10694
10695    cx.update_editor(|editor, window, cx| {
10696        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
10697    });
10698
10699    // Although we could potentially make the action work when the syntax node
10700    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
10701    // did. Maybe we could also expand the excerpt to contain the range?
10702    cx.assert_editor_state(indoc! { "
10703        fn a() {
10704              // what
10705              // a
10706        ˇ      // long
10707              // method"});
10708}
10709
10710#[gpui::test]
10711async fn test_fold_function_bodies(cx: &mut TestAppContext) {
10712    init_test(cx, |_| {});
10713
10714    let base_text = r#"
10715        impl A {
10716            // this is an uncommitted comment
10717
10718            fn b() {
10719                c();
10720            }
10721
10722            // this is another uncommitted comment
10723
10724            fn d() {
10725                // e
10726                // f
10727            }
10728        }
10729
10730        fn g() {
10731            // h
10732        }
10733    "#
10734    .unindent();
10735
10736    let text = r#"
10737        ˇimpl A {
10738
10739            fn b() {
10740                c();
10741            }
10742
10743            fn d() {
10744                // e
10745                // f
10746            }
10747        }
10748
10749        fn g() {
10750            // h
10751        }
10752    "#
10753    .unindent();
10754
10755    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10756    cx.set_state(&text);
10757    cx.set_head_text(&base_text);
10758    cx.update_editor(|editor, window, cx| {
10759        editor.expand_all_diff_hunks(&Default::default(), window, cx);
10760    });
10761
10762    cx.assert_state_with_diff(
10763        "
10764        ˇimpl A {
10765      -     // this is an uncommitted comment
10766
10767            fn b() {
10768                c();
10769            }
10770
10771      -     // this is another uncommitted comment
10772      -
10773            fn d() {
10774                // e
10775                // f
10776            }
10777        }
10778
10779        fn g() {
10780            // h
10781        }
10782    "
10783        .unindent(),
10784    );
10785
10786    let expected_display_text = "
10787        impl A {
10788            // this is an uncommitted comment
10789
10790            fn b() {
1079110792            }
10793
10794            // this is another uncommitted comment
10795
10796            fn d() {
1079710798            }
10799        }
10800
10801        fn g() {
1080210803        }
10804        "
10805    .unindent();
10806
10807    cx.update_editor(|editor, window, cx| {
10808        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
10809        assert_eq!(editor.display_text(cx), expected_display_text);
10810    });
10811}
10812
10813#[gpui::test]
10814async fn test_autoindent(cx: &mut TestAppContext) {
10815    init_test(cx, |_| {});
10816
10817    let language = Arc::new(
10818        Language::new(
10819            LanguageConfig {
10820                brackets: BracketPairConfig {
10821                    pairs: vec![
10822                        BracketPair {
10823                            start: "{".to_string(),
10824                            end: "}".to_string(),
10825                            close: false,
10826                            surround: false,
10827                            newline: true,
10828                        },
10829                        BracketPair {
10830                            start: "(".to_string(),
10831                            end: ")".to_string(),
10832                            close: false,
10833                            surround: false,
10834                            newline: true,
10835                        },
10836                    ],
10837                    ..Default::default()
10838                },
10839                ..Default::default()
10840            },
10841            Some(tree_sitter_rust::LANGUAGE.into()),
10842        )
10843        .with_indents_query(
10844            r#"
10845                (_ "(" ")" @end) @indent
10846                (_ "{" "}" @end) @indent
10847            "#,
10848        )
10849        .unwrap(),
10850    );
10851
10852    let text = "fn a() {}";
10853
10854    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10855    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10856    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10857    editor
10858        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10859        .await;
10860
10861    editor.update_in(cx, |editor, window, cx| {
10862        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10863            s.select_ranges([
10864                MultiBufferOffset(5)..MultiBufferOffset(5),
10865                MultiBufferOffset(8)..MultiBufferOffset(8),
10866                MultiBufferOffset(9)..MultiBufferOffset(9),
10867            ])
10868        });
10869        editor.newline(&Newline, window, cx);
10870        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
10871        assert_eq!(
10872            editor.selections.ranges(&editor.display_snapshot(cx)),
10873            &[
10874                Point::new(1, 4)..Point::new(1, 4),
10875                Point::new(3, 4)..Point::new(3, 4),
10876                Point::new(5, 0)..Point::new(5, 0)
10877            ]
10878        );
10879    });
10880}
10881
10882#[gpui::test]
10883async fn test_autoindent_disabled(cx: &mut TestAppContext) {
10884    init_test(cx, |settings| {
10885        settings.defaults.auto_indent = Some(settings::AutoIndentMode::None)
10886    });
10887
10888    let language = Arc::new(
10889        Language::new(
10890            LanguageConfig {
10891                brackets: BracketPairConfig {
10892                    pairs: vec![
10893                        BracketPair {
10894                            start: "{".to_string(),
10895                            end: "}".to_string(),
10896                            close: false,
10897                            surround: false,
10898                            newline: true,
10899                        },
10900                        BracketPair {
10901                            start: "(".to_string(),
10902                            end: ")".to_string(),
10903                            close: false,
10904                            surround: false,
10905                            newline: true,
10906                        },
10907                    ],
10908                    ..Default::default()
10909                },
10910                ..Default::default()
10911            },
10912            Some(tree_sitter_rust::LANGUAGE.into()),
10913        )
10914        .with_indents_query(
10915            r#"
10916                (_ "(" ")" @end) @indent
10917                (_ "{" "}" @end) @indent
10918            "#,
10919        )
10920        .unwrap(),
10921    );
10922
10923    let text = "fn a() {}";
10924
10925    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10926    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10927    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10928    editor
10929        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10930        .await;
10931
10932    editor.update_in(cx, |editor, window, cx| {
10933        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10934            s.select_ranges([
10935                MultiBufferOffset(5)..MultiBufferOffset(5),
10936                MultiBufferOffset(8)..MultiBufferOffset(8),
10937                MultiBufferOffset(9)..MultiBufferOffset(9),
10938            ])
10939        });
10940        editor.newline(&Newline, window, cx);
10941        assert_eq!(
10942            editor.text(cx),
10943            indoc!(
10944                "
10945                fn a(
10946
10947                ) {
10948
10949                }
10950                "
10951            )
10952        );
10953        assert_eq!(
10954            editor.selections.ranges(&editor.display_snapshot(cx)),
10955            &[
10956                Point::new(1, 0)..Point::new(1, 0),
10957                Point::new(3, 0)..Point::new(3, 0),
10958                Point::new(5, 0)..Point::new(5, 0)
10959            ]
10960        );
10961    });
10962}
10963
10964#[gpui::test]
10965async fn test_autoindent_none_does_not_preserve_indentation_on_newline(cx: &mut TestAppContext) {
10966    init_test(cx, |settings| {
10967        settings.defaults.auto_indent = Some(settings::AutoIndentMode::None)
10968    });
10969
10970    let mut cx = EditorTestContext::new(cx).await;
10971
10972    cx.set_state(indoc! {"
10973        hello
10974            indented lineˇ
10975        world
10976    "});
10977
10978    cx.update_editor(|editor, window, cx| {
10979        editor.newline(&Newline, window, cx);
10980    });
10981
10982    cx.assert_editor_state(indoc! {"
10983        hello
10984            indented line
10985        ˇ
10986        world
10987    "});
10988}
10989
10990#[gpui::test]
10991async fn test_autoindent_preserve_indent_maintains_indentation_on_newline(cx: &mut TestAppContext) {
10992    // When auto_indent is "preserve_indent", pressing Enter on an indented line
10993    // should preserve the indentation but not adjust based on syntax.
10994    init_test(cx, |settings| {
10995        settings.defaults.auto_indent = Some(settings::AutoIndentMode::PreserveIndent)
10996    });
10997
10998    let mut cx = EditorTestContext::new(cx).await;
10999
11000    cx.set_state(indoc! {"
11001        hello
11002            indented lineˇ
11003        world
11004    "});
11005
11006    cx.update_editor(|editor, window, cx| {
11007        editor.newline(&Newline, window, cx);
11008    });
11009
11010    // The new line SHOULD have the same indentation as the previous line
11011    cx.assert_editor_state(indoc! {"
11012        hello
11013            indented line
11014            ˇ
11015        world
11016    "});
11017}
11018
11019#[gpui::test]
11020async fn test_autoindent_preserve_indent_does_not_apply_syntax_indent(cx: &mut TestAppContext) {
11021    init_test(cx, |settings| {
11022        settings.defaults.auto_indent = Some(settings::AutoIndentMode::PreserveIndent)
11023    });
11024
11025    let language = Arc::new(
11026        Language::new(
11027            LanguageConfig {
11028                brackets: BracketPairConfig {
11029                    pairs: vec![BracketPair {
11030                        start: "{".to_string(),
11031                        end: "}".to_string(),
11032                        close: false,
11033                        surround: false,
11034                        newline: false, // Disable extra newline behavior to isolate syntax indent test
11035                    }],
11036                    ..Default::default()
11037                },
11038                ..Default::default()
11039            },
11040            Some(tree_sitter_rust::LANGUAGE.into()),
11041        )
11042        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
11043        .unwrap(),
11044    );
11045
11046    let buffer =
11047        cx.new(|cx| Buffer::local("fn foo() {\n}", cx).with_language(language.clone(), cx));
11048    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11049    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11050    editor
11051        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11052        .await;
11053
11054    // Position cursor at end of line containing `{`
11055    editor.update_in(cx, |editor, window, cx| {
11056        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11057            s.select_ranges([MultiBufferOffset(10)..MultiBufferOffset(10)]) // After "fn foo() {"
11058        });
11059        editor.newline(&Newline, window, cx);
11060
11061        // With PreserveIndent, the new line should have 0 indentation (same as the fn line)
11062        // NOT 4 spaces (which tree-sitter would add for being inside `{}`)
11063        assert_eq!(editor.text(cx), "fn foo() {\n\n}");
11064    });
11065}
11066
11067#[gpui::test]
11068async fn test_autoindent_syntax_aware_applies_syntax_indent(cx: &mut TestAppContext) {
11069    // Companion test to show that SyntaxAware DOES apply tree-sitter indentation
11070    init_test(cx, |settings| {
11071        settings.defaults.auto_indent = Some(settings::AutoIndentMode::SyntaxAware)
11072    });
11073
11074    let language = Arc::new(
11075        Language::new(
11076            LanguageConfig {
11077                brackets: BracketPairConfig {
11078                    pairs: vec![BracketPair {
11079                        start: "{".to_string(),
11080                        end: "}".to_string(),
11081                        close: false,
11082                        surround: false,
11083                        newline: false, // Disable extra newline behavior to isolate syntax indent test
11084                    }],
11085                    ..Default::default()
11086                },
11087                ..Default::default()
11088            },
11089            Some(tree_sitter_rust::LANGUAGE.into()),
11090        )
11091        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
11092        .unwrap(),
11093    );
11094
11095    let buffer =
11096        cx.new(|cx| Buffer::local("fn foo() {\n}", cx).with_language(language.clone(), cx));
11097    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11098    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11099    editor
11100        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11101        .await;
11102
11103    // Position cursor at end of line containing `{`
11104    editor.update_in(cx, |editor, window, cx| {
11105        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11106            s.select_ranges([MultiBufferOffset(10)..MultiBufferOffset(10)]) // After "fn foo() {"
11107        });
11108        editor.newline(&Newline, window, cx);
11109
11110        // With SyntaxAware, tree-sitter adds indentation for being inside `{}`
11111        assert_eq!(editor.text(cx), "fn foo() {\n    \n}");
11112    });
11113}
11114
11115#[gpui::test]
11116async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
11117    init_test(cx, |settings| {
11118        settings.defaults.auto_indent = Some(settings::AutoIndentMode::SyntaxAware);
11119        settings.languages.0.insert(
11120            "python".into(),
11121            LanguageSettingsContent {
11122                auto_indent: Some(settings::AutoIndentMode::None),
11123                ..Default::default()
11124            },
11125        );
11126    });
11127
11128    let mut cx = EditorTestContext::new(cx).await;
11129
11130    let injected_language = Arc::new(
11131        Language::new(
11132            LanguageConfig {
11133                brackets: BracketPairConfig {
11134                    pairs: vec![
11135                        BracketPair {
11136                            start: "{".to_string(),
11137                            end: "}".to_string(),
11138                            close: false,
11139                            surround: false,
11140                            newline: true,
11141                        },
11142                        BracketPair {
11143                            start: "(".to_string(),
11144                            end: ")".to_string(),
11145                            close: true,
11146                            surround: false,
11147                            newline: true,
11148                        },
11149                    ],
11150                    ..Default::default()
11151                },
11152                name: "python".into(),
11153                ..Default::default()
11154            },
11155            Some(tree_sitter_python::LANGUAGE.into()),
11156        )
11157        .with_indents_query(
11158            r#"
11159                (_ "(" ")" @end) @indent
11160                (_ "{" "}" @end) @indent
11161            "#,
11162        )
11163        .unwrap(),
11164    );
11165
11166    let language = Arc::new(
11167        Language::new(
11168            LanguageConfig {
11169                brackets: BracketPairConfig {
11170                    pairs: vec![
11171                        BracketPair {
11172                            start: "{".to_string(),
11173                            end: "}".to_string(),
11174                            close: false,
11175                            surround: false,
11176                            newline: true,
11177                        },
11178                        BracketPair {
11179                            start: "(".to_string(),
11180                            end: ")".to_string(),
11181                            close: true,
11182                            surround: false,
11183                            newline: true,
11184                        },
11185                    ],
11186                    ..Default::default()
11187                },
11188                name: LanguageName::new_static("rust"),
11189                ..Default::default()
11190            },
11191            Some(tree_sitter_rust::LANGUAGE.into()),
11192        )
11193        .with_indents_query(
11194            r#"
11195                (_ "(" ")" @end) @indent
11196                (_ "{" "}" @end) @indent
11197            "#,
11198        )
11199        .unwrap()
11200        .with_injection_query(
11201            r#"
11202            (macro_invocation
11203                macro: (identifier) @_macro_name
11204                (token_tree) @injection.content
11205                (#set! injection.language "python"))
11206           "#,
11207        )
11208        .unwrap(),
11209    );
11210
11211    cx.language_registry().add(injected_language);
11212    cx.language_registry().add(language.clone());
11213
11214    cx.update_buffer(|buffer, cx| {
11215        buffer.set_language(Some(language), cx);
11216    });
11217
11218    cx.set_state(r#"struct A {ˇ}"#);
11219
11220    cx.update_editor(|editor, window, cx| {
11221        editor.newline(&Default::default(), window, cx);
11222    });
11223
11224    cx.assert_editor_state(indoc!(
11225        "struct A {
11226            ˇ
11227        }"
11228    ));
11229
11230    cx.set_state(r#"select_biased!(ˇ)"#);
11231
11232    cx.update_editor(|editor, window, cx| {
11233        editor.newline(&Default::default(), window, cx);
11234        editor.handle_input("def ", window, cx);
11235        editor.handle_input("(", window, cx);
11236        editor.newline(&Default::default(), window, cx);
11237        editor.handle_input("a", window, cx);
11238    });
11239
11240    cx.assert_editor_state(indoc!(
11241        "select_biased!(
11242        def (
1124311244        )
11245        )"
11246    ));
11247}
11248
11249#[gpui::test]
11250async fn test_autoindent_selections(cx: &mut TestAppContext) {
11251    init_test(cx, |_| {});
11252
11253    {
11254        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
11255        cx.set_state(indoc! {"
11256            impl A {
11257
11258                fn b() {}
11259
11260            «fn c() {
11261
11262            }ˇ»
11263            }
11264        "});
11265
11266        cx.update_editor(|editor, window, cx| {
11267            editor.autoindent(&Default::default(), window, cx);
11268        });
11269        cx.wait_for_autoindent_applied().await;
11270
11271        cx.assert_editor_state(indoc! {"
11272            impl A {
11273
11274                fn b() {}
11275
11276                «fn c() {
11277
11278                }ˇ»
11279            }
11280        "});
11281    }
11282
11283    {
11284        let mut cx = EditorTestContext::new_multibuffer(
11285            cx,
11286            [indoc! { "
11287                impl A {
11288                «
11289                // a
11290                fn b(){}
11291                »
11292                «
11293                    }
11294                    fn c(){}
11295                »
11296            "}],
11297        );
11298
11299        let buffer = cx.update_editor(|editor, _, cx| {
11300            let buffer = editor.buffer().update(cx, |buffer, _| {
11301                buffer.all_buffers().iter().next().unwrap().clone()
11302            });
11303            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
11304            buffer
11305        });
11306
11307        cx.run_until_parked();
11308        cx.update_editor(|editor, window, cx| {
11309            editor.select_all(&Default::default(), window, cx);
11310            editor.autoindent(&Default::default(), window, cx)
11311        });
11312        cx.run_until_parked();
11313
11314        cx.update(|_, cx| {
11315            assert_eq!(
11316                buffer.read(cx).text(),
11317                indoc! { "
11318                    impl A {
11319
11320                        // a
11321                        fn b(){}
11322
11323
11324                    }
11325                    fn c(){}
11326
11327                " }
11328            )
11329        });
11330    }
11331}
11332
11333#[gpui::test]
11334async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
11335    init_test(cx, |_| {});
11336
11337    let mut cx = EditorTestContext::new(cx).await;
11338
11339    let language = Arc::new(Language::new(
11340        LanguageConfig {
11341            brackets: BracketPairConfig {
11342                pairs: vec![
11343                    BracketPair {
11344                        start: "{".to_string(),
11345                        end: "}".to_string(),
11346                        close: true,
11347                        surround: true,
11348                        newline: true,
11349                    },
11350                    BracketPair {
11351                        start: "(".to_string(),
11352                        end: ")".to_string(),
11353                        close: true,
11354                        surround: true,
11355                        newline: true,
11356                    },
11357                    BracketPair {
11358                        start: "/*".to_string(),
11359                        end: " */".to_string(),
11360                        close: true,
11361                        surround: true,
11362                        newline: true,
11363                    },
11364                    BracketPair {
11365                        start: "[".to_string(),
11366                        end: "]".to_string(),
11367                        close: false,
11368                        surround: false,
11369                        newline: true,
11370                    },
11371                    BracketPair {
11372                        start: "\"".to_string(),
11373                        end: "\"".to_string(),
11374                        close: true,
11375                        surround: true,
11376                        newline: false,
11377                    },
11378                    BracketPair {
11379                        start: "<".to_string(),
11380                        end: ">".to_string(),
11381                        close: false,
11382                        surround: true,
11383                        newline: true,
11384                    },
11385                ],
11386                ..Default::default()
11387            },
11388            autoclose_before: "})]".to_string(),
11389            ..Default::default()
11390        },
11391        Some(tree_sitter_rust::LANGUAGE.into()),
11392    ));
11393
11394    cx.language_registry().add(language.clone());
11395    cx.update_buffer(|buffer, cx| {
11396        buffer.set_language(Some(language), cx);
11397    });
11398
11399    cx.set_state(
11400        &r#"
11401            🏀ˇ
11402            εˇ
11403            ❤️ˇ
11404        "#
11405        .unindent(),
11406    );
11407
11408    // autoclose multiple nested brackets at multiple cursors
11409    cx.update_editor(|editor, window, cx| {
11410        editor.handle_input("{", window, cx);
11411        editor.handle_input("{", window, cx);
11412        editor.handle_input("{", window, cx);
11413    });
11414    cx.assert_editor_state(
11415        &"
11416            🏀{{{ˇ}}}
11417            ε{{{ˇ}}}
11418            ❤️{{{ˇ}}}
11419        "
11420        .unindent(),
11421    );
11422
11423    // insert a different closing bracket
11424    cx.update_editor(|editor, window, cx| {
11425        editor.handle_input(")", window, cx);
11426    });
11427    cx.assert_editor_state(
11428        &"
11429            🏀{{{)ˇ}}}
11430            ε{{{)ˇ}}}
11431            ❤️{{{)ˇ}}}
11432        "
11433        .unindent(),
11434    );
11435
11436    // skip over the auto-closed brackets when typing a closing bracket
11437    cx.update_editor(|editor, window, cx| {
11438        editor.move_right(&MoveRight, window, cx);
11439        editor.handle_input("}", window, cx);
11440        editor.handle_input("}", window, cx);
11441        editor.handle_input("}", window, cx);
11442    });
11443    cx.assert_editor_state(
11444        &"
11445            🏀{{{)}}}}ˇ
11446            ε{{{)}}}}ˇ
11447            ❤️{{{)}}}}ˇ
11448        "
11449        .unindent(),
11450    );
11451
11452    // autoclose multi-character pairs
11453    cx.set_state(
11454        &"
11455            ˇ
11456            ˇ
11457        "
11458        .unindent(),
11459    );
11460    cx.update_editor(|editor, window, cx| {
11461        editor.handle_input("/", window, cx);
11462        editor.handle_input("*", window, cx);
11463    });
11464    cx.assert_editor_state(
11465        &"
11466            /*ˇ */
11467            /*ˇ */
11468        "
11469        .unindent(),
11470    );
11471
11472    // one cursor autocloses a multi-character pair, one cursor
11473    // does not autoclose.
11474    cx.set_state(
11475        &"
1147611477            ˇ
11478        "
11479        .unindent(),
11480    );
11481    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
11482    cx.assert_editor_state(
11483        &"
11484            /*ˇ */
1148511486        "
11487        .unindent(),
11488    );
11489
11490    // Don't autoclose if the next character isn't whitespace and isn't
11491    // listed in the language's "autoclose_before" section.
11492    cx.set_state("ˇa b");
11493    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
11494    cx.assert_editor_state("{ˇa b");
11495
11496    // Don't autoclose if `close` is false for the bracket pair
11497    cx.set_state("ˇ");
11498    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
11499    cx.assert_editor_state("");
11500
11501    // Surround with brackets if text is selected
11502    cx.set_state("«aˇ» b");
11503    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
11504    cx.assert_editor_state("{«aˇ»} b");
11505
11506    // Autoclose when not immediately after a word character
11507    cx.set_state("a ˇ");
11508    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
11509    cx.assert_editor_state("a \"ˇ\"");
11510
11511    // Autoclose pair where the start and end characters are the same
11512    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
11513    cx.assert_editor_state("a \"\"ˇ");
11514
11515    // Don't autoclose when immediately after a word character
11516    cx.set_state("");
11517    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
11518    cx.assert_editor_state("a\"ˇ");
11519
11520    // Do autoclose when after a non-word character
11521    cx.set_state("");
11522    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
11523    cx.assert_editor_state("{\"ˇ\"");
11524
11525    // Non identical pairs autoclose regardless of preceding character
11526    cx.set_state("");
11527    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
11528    cx.assert_editor_state("a{ˇ}");
11529
11530    // Don't autoclose pair if autoclose is disabled
11531    cx.set_state("ˇ");
11532    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
11533    cx.assert_editor_state("");
11534
11535    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
11536    cx.set_state("«aˇ» b");
11537    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
11538    cx.assert_editor_state("<«aˇ»> b");
11539}
11540
11541#[gpui::test]
11542async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
11543    init_test(cx, |settings| {
11544        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11545    });
11546
11547    let mut cx = EditorTestContext::new(cx).await;
11548
11549    let language = Arc::new(Language::new(
11550        LanguageConfig {
11551            brackets: BracketPairConfig {
11552                pairs: vec![
11553                    BracketPair {
11554                        start: "{".to_string(),
11555                        end: "}".to_string(),
11556                        close: true,
11557                        surround: true,
11558                        newline: true,
11559                    },
11560                    BracketPair {
11561                        start: "(".to_string(),
11562                        end: ")".to_string(),
11563                        close: true,
11564                        surround: true,
11565                        newline: true,
11566                    },
11567                    BracketPair {
11568                        start: "[".to_string(),
11569                        end: "]".to_string(),
11570                        close: false,
11571                        surround: false,
11572                        newline: true,
11573                    },
11574                ],
11575                ..Default::default()
11576            },
11577            autoclose_before: "})]".to_string(),
11578            ..Default::default()
11579        },
11580        Some(tree_sitter_rust::LANGUAGE.into()),
11581    ));
11582
11583    cx.language_registry().add(language.clone());
11584    cx.update_buffer(|buffer, cx| {
11585        buffer.set_language(Some(language), cx);
11586    });
11587
11588    cx.set_state(
11589        &"
11590            ˇ
11591            ˇ
11592            ˇ
11593        "
11594        .unindent(),
11595    );
11596
11597    // ensure only matching closing brackets are skipped over
11598    cx.update_editor(|editor, window, cx| {
11599        editor.handle_input("}", window, cx);
11600        editor.move_left(&MoveLeft, window, cx);
11601        editor.handle_input(")", window, cx);
11602        editor.move_left(&MoveLeft, window, cx);
11603    });
11604    cx.assert_editor_state(
11605        &"
11606            ˇ)}
11607            ˇ)}
11608            ˇ)}
11609        "
11610        .unindent(),
11611    );
11612
11613    // skip-over closing brackets at multiple cursors
11614    cx.update_editor(|editor, window, cx| {
11615        editor.handle_input(")", window, cx);
11616        editor.handle_input("}", window, cx);
11617    });
11618    cx.assert_editor_state(
11619        &"
11620            )}ˇ
11621            )}ˇ
11622            )}ˇ
11623        "
11624        .unindent(),
11625    );
11626
11627    // ignore non-close brackets
11628    cx.update_editor(|editor, window, cx| {
11629        editor.handle_input("]", window, cx);
11630        editor.move_left(&MoveLeft, window, cx);
11631        editor.handle_input("]", window, cx);
11632    });
11633    cx.assert_editor_state(
11634        &"
11635            )}]ˇ]
11636            )}]ˇ]
11637            )}]ˇ]
11638        "
11639        .unindent(),
11640    );
11641}
11642
11643#[gpui::test]
11644async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
11645    init_test(cx, |_| {});
11646
11647    let mut cx = EditorTestContext::new(cx).await;
11648
11649    let html_language = Arc::new(
11650        Language::new(
11651            LanguageConfig {
11652                name: "HTML".into(),
11653                brackets: BracketPairConfig {
11654                    pairs: vec![
11655                        BracketPair {
11656                            start: "<".into(),
11657                            end: ">".into(),
11658                            close: true,
11659                            ..Default::default()
11660                        },
11661                        BracketPair {
11662                            start: "{".into(),
11663                            end: "}".into(),
11664                            close: true,
11665                            ..Default::default()
11666                        },
11667                        BracketPair {
11668                            start: "(".into(),
11669                            end: ")".into(),
11670                            close: true,
11671                            ..Default::default()
11672                        },
11673                    ],
11674                    ..Default::default()
11675                },
11676                autoclose_before: "})]>".into(),
11677                ..Default::default()
11678            },
11679            Some(tree_sitter_html::LANGUAGE.into()),
11680        )
11681        .with_injection_query(
11682            r#"
11683            (script_element
11684                (raw_text) @injection.content
11685                (#set! injection.language "javascript"))
11686            "#,
11687        )
11688        .unwrap(),
11689    );
11690
11691    let javascript_language = Arc::new(Language::new(
11692        LanguageConfig {
11693            name: "JavaScript".into(),
11694            brackets: BracketPairConfig {
11695                pairs: vec![
11696                    BracketPair {
11697                        start: "/*".into(),
11698                        end: " */".into(),
11699                        close: true,
11700                        ..Default::default()
11701                    },
11702                    BracketPair {
11703                        start: "{".into(),
11704                        end: "}".into(),
11705                        close: true,
11706                        ..Default::default()
11707                    },
11708                    BracketPair {
11709                        start: "(".into(),
11710                        end: ")".into(),
11711                        close: true,
11712                        ..Default::default()
11713                    },
11714                ],
11715                ..Default::default()
11716            },
11717            autoclose_before: "})]>".into(),
11718            ..Default::default()
11719        },
11720        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11721    ));
11722
11723    cx.language_registry().add(html_language.clone());
11724    cx.language_registry().add(javascript_language);
11725    cx.executor().run_until_parked();
11726
11727    cx.update_buffer(|buffer, cx| {
11728        buffer.set_language(Some(html_language), cx);
11729    });
11730
11731    cx.set_state(
11732        &r#"
11733            <body>ˇ
11734                <script>
11735                    var x = 1;ˇ
11736                </script>
11737            </body>ˇ
11738        "#
11739        .unindent(),
11740    );
11741
11742    // Precondition: different languages are active at different locations.
11743    cx.update_editor(|editor, window, cx| {
11744        let snapshot = editor.snapshot(window, cx);
11745        let cursors = editor
11746            .selections
11747            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
11748        let languages = cursors
11749            .iter()
11750            .map(|c| snapshot.language_at(c.start).unwrap().name())
11751            .collect::<Vec<_>>();
11752        assert_eq!(
11753            languages,
11754            &[
11755                LanguageName::from("HTML"),
11756                LanguageName::from("JavaScript"),
11757                LanguageName::from("HTML"),
11758            ]
11759        );
11760    });
11761
11762    // Angle brackets autoclose in HTML, but not JavaScript.
11763    cx.update_editor(|editor, window, cx| {
11764        editor.handle_input("<", window, cx);
11765        editor.handle_input("a", window, cx);
11766    });
11767    cx.assert_editor_state(
11768        &r#"
11769            <body><aˇ>
11770                <script>
11771                    var x = 1;<aˇ
11772                </script>
11773            </body><aˇ>
11774        "#
11775        .unindent(),
11776    );
11777
11778    // Curly braces and parens autoclose in both HTML and JavaScript.
11779    cx.update_editor(|editor, window, cx| {
11780        editor.handle_input(" b=", window, cx);
11781        editor.handle_input("{", window, cx);
11782        editor.handle_input("c", window, cx);
11783        editor.handle_input("(", window, cx);
11784    });
11785    cx.assert_editor_state(
11786        &r#"
11787            <body><a b={c(ˇ)}>
11788                <script>
11789                    var x = 1;<a b={c(ˇ)}
11790                </script>
11791            </body><a b={c(ˇ)}>
11792        "#
11793        .unindent(),
11794    );
11795
11796    // Brackets that were already autoclosed are skipped.
11797    cx.update_editor(|editor, window, cx| {
11798        editor.handle_input(")", window, cx);
11799        editor.handle_input("d", window, cx);
11800        editor.handle_input("}", window, cx);
11801    });
11802    cx.assert_editor_state(
11803        &r#"
11804            <body><a b={c()d}ˇ>
11805                <script>
11806                    var x = 1;<a b={c()d}ˇ
11807                </script>
11808            </body><a b={c()d}ˇ>
11809        "#
11810        .unindent(),
11811    );
11812    cx.update_editor(|editor, window, cx| {
11813        editor.handle_input(">", window, cx);
11814    });
11815    cx.assert_editor_state(
11816        &r#"
11817            <body><a b={c()d}>ˇ
11818                <script>
11819                    var x = 1;<a b={c()d}>ˇ
11820                </script>
11821            </body><a b={c()d}>ˇ
11822        "#
11823        .unindent(),
11824    );
11825
11826    // Reset
11827    cx.set_state(
11828        &r#"
11829            <body>ˇ
11830                <script>
11831                    var x = 1;ˇ
11832                </script>
11833            </body>ˇ
11834        "#
11835        .unindent(),
11836    );
11837
11838    cx.update_editor(|editor, window, cx| {
11839        editor.handle_input("<", window, cx);
11840    });
11841    cx.assert_editor_state(
11842        &r#"
11843            <body><ˇ>
11844                <script>
11845                    var x = 1;<ˇ
11846                </script>
11847            </body><ˇ>
11848        "#
11849        .unindent(),
11850    );
11851
11852    // When backspacing, the closing angle brackets are removed.
11853    cx.update_editor(|editor, window, cx| {
11854        editor.backspace(&Backspace, window, cx);
11855    });
11856    cx.assert_editor_state(
11857        &r#"
11858            <body>ˇ
11859                <script>
11860                    var x = 1;ˇ
11861                </script>
11862            </body>ˇ
11863        "#
11864        .unindent(),
11865    );
11866
11867    // Block comments autoclose in JavaScript, but not HTML.
11868    cx.update_editor(|editor, window, cx| {
11869        editor.handle_input("/", window, cx);
11870        editor.handle_input("*", window, cx);
11871    });
11872    cx.assert_editor_state(
11873        &r#"
11874            <body>/*ˇ
11875                <script>
11876                    var x = 1;/*ˇ */
11877                </script>
11878            </body>/*ˇ
11879        "#
11880        .unindent(),
11881    );
11882}
11883
11884#[gpui::test]
11885async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
11886    init_test(cx, |_| {});
11887
11888    let mut cx = EditorTestContext::new(cx).await;
11889
11890    let rust_language = Arc::new(
11891        Language::new(
11892            LanguageConfig {
11893                name: "Rust".into(),
11894                brackets: serde_json::from_value(json!([
11895                    { "start": "{", "end": "}", "close": true, "newline": true },
11896                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
11897                ]))
11898                .unwrap(),
11899                autoclose_before: "})]>".into(),
11900                ..Default::default()
11901            },
11902            Some(tree_sitter_rust::LANGUAGE.into()),
11903        )
11904        .with_override_query("(string_literal) @string")
11905        .unwrap(),
11906    );
11907
11908    cx.language_registry().add(rust_language.clone());
11909    cx.update_buffer(|buffer, cx| {
11910        buffer.set_language(Some(rust_language), cx);
11911    });
11912
11913    cx.set_state(
11914        &r#"
11915            let x = ˇ
11916        "#
11917        .unindent(),
11918    );
11919
11920    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
11921    cx.update_editor(|editor, window, cx| {
11922        editor.handle_input("\"", window, cx);
11923    });
11924    cx.assert_editor_state(
11925        &r#"
11926            let x = "ˇ"
11927        "#
11928        .unindent(),
11929    );
11930
11931    // Inserting another quotation mark. The cursor moves across the existing
11932    // automatically-inserted quotation mark.
11933    cx.update_editor(|editor, window, cx| {
11934        editor.handle_input("\"", window, cx);
11935    });
11936    cx.assert_editor_state(
11937        &r#"
11938            let x = ""ˇ
11939        "#
11940        .unindent(),
11941    );
11942
11943    // Reset
11944    cx.set_state(
11945        &r#"
11946            let x = ˇ
11947        "#
11948        .unindent(),
11949    );
11950
11951    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
11952    cx.update_editor(|editor, window, cx| {
11953        editor.handle_input("\"", window, cx);
11954        editor.handle_input(" ", window, cx);
11955        editor.move_left(&Default::default(), window, cx);
11956        editor.handle_input("\\", window, cx);
11957        editor.handle_input("\"", window, cx);
11958    });
11959    cx.assert_editor_state(
11960        &r#"
11961            let x = "\"ˇ "
11962        "#
11963        .unindent(),
11964    );
11965
11966    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
11967    // mark. Nothing is inserted.
11968    cx.update_editor(|editor, window, cx| {
11969        editor.move_right(&Default::default(), window, cx);
11970        editor.handle_input("\"", window, cx);
11971    });
11972    cx.assert_editor_state(
11973        &r#"
11974            let x = "\" "ˇ
11975        "#
11976        .unindent(),
11977    );
11978}
11979
11980#[gpui::test]
11981async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
11982    init_test(cx, |_| {});
11983
11984    let mut cx = EditorTestContext::new(cx).await;
11985    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
11986
11987    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
11988
11989    // Double quote inside single-quoted string
11990    cx.set_state(indoc! {r#"
11991        def main():
11992            items = ['"', ˇ]
11993    "#});
11994    cx.update_editor(|editor, window, cx| {
11995        editor.handle_input("\"", window, cx);
11996    });
11997    cx.assert_editor_state(indoc! {r#"
11998        def main():
11999            items = ['"', "ˇ"]
12000    "#});
12001
12002    // Two double quotes inside single-quoted string
12003    cx.set_state(indoc! {r#"
12004        def main():
12005            items = ['""', ˇ]
12006    "#});
12007    cx.update_editor(|editor, window, cx| {
12008        editor.handle_input("\"", window, cx);
12009    });
12010    cx.assert_editor_state(indoc! {r#"
12011        def main():
12012            items = ['""', "ˇ"]
12013    "#});
12014
12015    // Single quote inside double-quoted string
12016    cx.set_state(indoc! {r#"
12017        def main():
12018            items = ["'", ˇ]
12019    "#});
12020    cx.update_editor(|editor, window, cx| {
12021        editor.handle_input("'", window, cx);
12022    });
12023    cx.assert_editor_state(indoc! {r#"
12024        def main():
12025            items = ["'", 'ˇ']
12026    "#});
12027
12028    // Two single quotes inside double-quoted string
12029    cx.set_state(indoc! {r#"
12030        def main():
12031            items = ["''", ˇ]
12032    "#});
12033    cx.update_editor(|editor, window, cx| {
12034        editor.handle_input("'", window, cx);
12035    });
12036    cx.assert_editor_state(indoc! {r#"
12037        def main():
12038            items = ["''", 'ˇ']
12039    "#});
12040
12041    // Mixed quotes on same line
12042    cx.set_state(indoc! {r#"
12043        def main():
12044            items = ['"""', "'''''", ˇ]
12045    "#});
12046    cx.update_editor(|editor, window, cx| {
12047        editor.handle_input("\"", window, cx);
12048    });
12049    cx.assert_editor_state(indoc! {r#"
12050        def main():
12051            items = ['"""', "'''''", "ˇ"]
12052    "#});
12053    cx.update_editor(|editor, window, cx| {
12054        editor.move_right(&MoveRight, window, cx);
12055    });
12056    cx.update_editor(|editor, window, cx| {
12057        editor.handle_input(", ", window, cx);
12058    });
12059    cx.update_editor(|editor, window, cx| {
12060        editor.handle_input("'", window, cx);
12061    });
12062    cx.assert_editor_state(indoc! {r#"
12063        def main():
12064            items = ['"""', "'''''", "", 'ˇ']
12065    "#});
12066}
12067
12068#[gpui::test]
12069async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
12070    init_test(cx, |_| {});
12071
12072    let mut cx = EditorTestContext::new(cx).await;
12073    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
12074    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12075
12076    cx.set_state(indoc! {r#"
12077        def main():
12078            items = ["🎉", ˇ]
12079    "#});
12080    cx.update_editor(|editor, window, cx| {
12081        editor.handle_input("\"", window, cx);
12082    });
12083    cx.assert_editor_state(indoc! {r#"
12084        def main():
12085            items = ["🎉", "ˇ"]
12086    "#});
12087}
12088
12089#[gpui::test]
12090async fn test_surround_with_pair(cx: &mut TestAppContext) {
12091    init_test(cx, |_| {});
12092
12093    let language = Arc::new(Language::new(
12094        LanguageConfig {
12095            brackets: BracketPairConfig {
12096                pairs: vec![
12097                    BracketPair {
12098                        start: "{".to_string(),
12099                        end: "}".to_string(),
12100                        close: true,
12101                        surround: true,
12102                        newline: true,
12103                    },
12104                    BracketPair {
12105                        start: "/* ".to_string(),
12106                        end: "*/".to_string(),
12107                        close: true,
12108                        surround: true,
12109                        ..Default::default()
12110                    },
12111                ],
12112                ..Default::default()
12113            },
12114            ..Default::default()
12115        },
12116        Some(tree_sitter_rust::LANGUAGE.into()),
12117    ));
12118
12119    let text = r#"
12120        a
12121        b
12122        c
12123    "#
12124    .unindent();
12125
12126    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12127    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12128    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12129    editor
12130        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12131        .await;
12132
12133    editor.update_in(cx, |editor, window, cx| {
12134        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12135            s.select_display_ranges([
12136                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
12137                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
12138                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
12139            ])
12140        });
12141
12142        editor.handle_input("{", window, cx);
12143        editor.handle_input("{", window, cx);
12144        editor.handle_input("{", window, cx);
12145        assert_eq!(
12146            editor.text(cx),
12147            "
12148                {{{a}}}
12149                {{{b}}}
12150                {{{c}}}
12151            "
12152            .unindent()
12153        );
12154        assert_eq!(
12155            display_ranges(editor, cx),
12156            [
12157                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
12158                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
12159                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
12160            ]
12161        );
12162
12163        editor.undo(&Undo, window, cx);
12164        editor.undo(&Undo, window, cx);
12165        editor.undo(&Undo, window, cx);
12166        assert_eq!(
12167            editor.text(cx),
12168            "
12169                a
12170                b
12171                c
12172            "
12173            .unindent()
12174        );
12175        assert_eq!(
12176            display_ranges(editor, cx),
12177            [
12178                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
12179                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
12180                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
12181            ]
12182        );
12183
12184        // Ensure inserting the first character of a multi-byte bracket pair
12185        // doesn't surround the selections with the bracket.
12186        editor.handle_input("/", window, cx);
12187        assert_eq!(
12188            editor.text(cx),
12189            "
12190                /
12191                /
12192                /
12193            "
12194            .unindent()
12195        );
12196        assert_eq!(
12197            display_ranges(editor, cx),
12198            [
12199                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
12200                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
12201                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
12202            ]
12203        );
12204
12205        editor.undo(&Undo, window, cx);
12206        assert_eq!(
12207            editor.text(cx),
12208            "
12209                a
12210                b
12211                c
12212            "
12213            .unindent()
12214        );
12215        assert_eq!(
12216            display_ranges(editor, cx),
12217            [
12218                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
12219                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
12220                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
12221            ]
12222        );
12223
12224        // Ensure inserting the last character of a multi-byte bracket pair
12225        // doesn't surround the selections with the bracket.
12226        editor.handle_input("*", window, cx);
12227        assert_eq!(
12228            editor.text(cx),
12229            "
12230                *
12231                *
12232                *
12233            "
12234            .unindent()
12235        );
12236        assert_eq!(
12237            display_ranges(editor, cx),
12238            [
12239                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
12240                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
12241                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
12242            ]
12243        );
12244    });
12245}
12246
12247#[gpui::test]
12248async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
12249    init_test(cx, |_| {});
12250
12251    let language = Arc::new(Language::new(
12252        LanguageConfig {
12253            brackets: BracketPairConfig {
12254                pairs: vec![BracketPair {
12255                    start: "{".to_string(),
12256                    end: "}".to_string(),
12257                    close: true,
12258                    surround: true,
12259                    newline: true,
12260                }],
12261                ..Default::default()
12262            },
12263            autoclose_before: "}".to_string(),
12264            ..Default::default()
12265        },
12266        Some(tree_sitter_rust::LANGUAGE.into()),
12267    ));
12268
12269    let text = r#"
12270        a
12271        b
12272        c
12273    "#
12274    .unindent();
12275
12276    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12277    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12278    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12279    editor
12280        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12281        .await;
12282
12283    editor.update_in(cx, |editor, window, cx| {
12284        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12285            s.select_ranges([
12286                Point::new(0, 1)..Point::new(0, 1),
12287                Point::new(1, 1)..Point::new(1, 1),
12288                Point::new(2, 1)..Point::new(2, 1),
12289            ])
12290        });
12291
12292        editor.handle_input("{", window, cx);
12293        editor.handle_input("{", window, cx);
12294        editor.handle_input("_", window, cx);
12295        assert_eq!(
12296            editor.text(cx),
12297            "
12298                a{{_}}
12299                b{{_}}
12300                c{{_}}
12301            "
12302            .unindent()
12303        );
12304        assert_eq!(
12305            editor
12306                .selections
12307                .ranges::<Point>(&editor.display_snapshot(cx)),
12308            [
12309                Point::new(0, 4)..Point::new(0, 4),
12310                Point::new(1, 4)..Point::new(1, 4),
12311                Point::new(2, 4)..Point::new(2, 4)
12312            ]
12313        );
12314
12315        editor.backspace(&Default::default(), window, cx);
12316        editor.backspace(&Default::default(), window, cx);
12317        assert_eq!(
12318            editor.text(cx),
12319            "
12320                a{}
12321                b{}
12322                c{}
12323            "
12324            .unindent()
12325        );
12326        assert_eq!(
12327            editor
12328                .selections
12329                .ranges::<Point>(&editor.display_snapshot(cx)),
12330            [
12331                Point::new(0, 2)..Point::new(0, 2),
12332                Point::new(1, 2)..Point::new(1, 2),
12333                Point::new(2, 2)..Point::new(2, 2)
12334            ]
12335        );
12336
12337        editor.delete_to_previous_word_start(&Default::default(), window, cx);
12338        assert_eq!(
12339            editor.text(cx),
12340            "
12341                a
12342                b
12343                c
12344            "
12345            .unindent()
12346        );
12347        assert_eq!(
12348            editor
12349                .selections
12350                .ranges::<Point>(&editor.display_snapshot(cx)),
12351            [
12352                Point::new(0, 1)..Point::new(0, 1),
12353                Point::new(1, 1)..Point::new(1, 1),
12354                Point::new(2, 1)..Point::new(2, 1)
12355            ]
12356        );
12357    });
12358}
12359
12360#[gpui::test]
12361async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
12362    init_test(cx, |settings| {
12363        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
12364    });
12365
12366    let mut cx = EditorTestContext::new(cx).await;
12367
12368    let language = Arc::new(Language::new(
12369        LanguageConfig {
12370            brackets: BracketPairConfig {
12371                pairs: vec![
12372                    BracketPair {
12373                        start: "{".to_string(),
12374                        end: "}".to_string(),
12375                        close: true,
12376                        surround: true,
12377                        newline: true,
12378                    },
12379                    BracketPair {
12380                        start: "(".to_string(),
12381                        end: ")".to_string(),
12382                        close: true,
12383                        surround: true,
12384                        newline: true,
12385                    },
12386                    BracketPair {
12387                        start: "[".to_string(),
12388                        end: "]".to_string(),
12389                        close: false,
12390                        surround: true,
12391                        newline: true,
12392                    },
12393                ],
12394                ..Default::default()
12395            },
12396            autoclose_before: "})]".to_string(),
12397            ..Default::default()
12398        },
12399        Some(tree_sitter_rust::LANGUAGE.into()),
12400    ));
12401
12402    cx.language_registry().add(language.clone());
12403    cx.update_buffer(|buffer, cx| {
12404        buffer.set_language(Some(language), cx);
12405    });
12406
12407    cx.set_state(
12408        &"
12409            {(ˇ)}
12410            [[ˇ]]
12411            {(ˇ)}
12412        "
12413        .unindent(),
12414    );
12415
12416    cx.update_editor(|editor, window, cx| {
12417        editor.backspace(&Default::default(), window, cx);
12418        editor.backspace(&Default::default(), window, cx);
12419    });
12420
12421    cx.assert_editor_state(
12422        &"
12423            ˇ
12424            ˇ]]
12425            ˇ
12426        "
12427        .unindent(),
12428    );
12429
12430    cx.update_editor(|editor, window, cx| {
12431        editor.handle_input("{", window, cx);
12432        editor.handle_input("{", window, cx);
12433        editor.move_right(&MoveRight, window, cx);
12434        editor.move_right(&MoveRight, window, cx);
12435        editor.move_left(&MoveLeft, window, cx);
12436        editor.move_left(&MoveLeft, window, cx);
12437        editor.backspace(&Default::default(), window, cx);
12438    });
12439
12440    cx.assert_editor_state(
12441        &"
12442            {ˇ}
12443            {ˇ}]]
12444            {ˇ}
12445        "
12446        .unindent(),
12447    );
12448
12449    cx.update_editor(|editor, window, cx| {
12450        editor.backspace(&Default::default(), window, cx);
12451    });
12452
12453    cx.assert_editor_state(
12454        &"
12455            ˇ
12456            ˇ]]
12457            ˇ
12458        "
12459        .unindent(),
12460    );
12461}
12462
12463#[gpui::test]
12464async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
12465    init_test(cx, |_| {});
12466
12467    let language = Arc::new(Language::new(
12468        LanguageConfig::default(),
12469        Some(tree_sitter_rust::LANGUAGE.into()),
12470    ));
12471
12472    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
12473    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12474    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12475    editor
12476        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12477        .await;
12478
12479    editor.update_in(cx, |editor, window, cx| {
12480        editor.set_auto_replace_emoji_shortcode(true);
12481
12482        editor.handle_input("Hello ", window, cx);
12483        editor.handle_input(":wave", window, cx);
12484        assert_eq!(editor.text(cx), "Hello :wave".unindent());
12485
12486        editor.handle_input(":", window, cx);
12487        assert_eq!(editor.text(cx), "Hello 👋".unindent());
12488
12489        editor.handle_input(" :smile", window, cx);
12490        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
12491
12492        editor.handle_input(":", window, cx);
12493        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
12494
12495        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
12496        editor.handle_input(":wave", window, cx);
12497        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
12498
12499        editor.handle_input(":", window, cx);
12500        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
12501
12502        editor.handle_input(":1", window, cx);
12503        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
12504
12505        editor.handle_input(":", window, cx);
12506        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
12507
12508        // Ensure shortcode does not get replaced when it is part of a word
12509        editor.handle_input(" Test:wave", window, cx);
12510        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
12511
12512        editor.handle_input(":", window, cx);
12513        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
12514
12515        editor.set_auto_replace_emoji_shortcode(false);
12516
12517        // Ensure shortcode does not get replaced when auto replace is off
12518        editor.handle_input(" :wave", window, cx);
12519        assert_eq!(
12520            editor.text(cx),
12521            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
12522        );
12523
12524        editor.handle_input(":", window, cx);
12525        assert_eq!(
12526            editor.text(cx),
12527            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
12528        );
12529    });
12530}
12531
12532#[gpui::test]
12533async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
12534    init_test(cx, |_| {});
12535
12536    let (text, insertion_ranges) = marked_text_ranges(
12537        indoc! {"
12538            ˇ
12539        "},
12540        false,
12541    );
12542
12543    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
12544    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12545
12546    _ = editor.update_in(cx, |editor, window, cx| {
12547        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
12548
12549        editor
12550            .insert_snippet(
12551                &insertion_ranges
12552                    .iter()
12553                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
12554                    .collect::<Vec<_>>(),
12555                snippet,
12556                window,
12557                cx,
12558            )
12559            .unwrap();
12560
12561        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
12562            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
12563            assert_eq!(editor.text(cx), expected_text);
12564            assert_eq!(
12565                editor.selections.ranges(&editor.display_snapshot(cx)),
12566                selection_ranges
12567                    .iter()
12568                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
12569                    .collect::<Vec<_>>()
12570            );
12571        }
12572
12573        assert(
12574            editor,
12575            cx,
12576            indoc! {"
12577            type «» =•
12578            "},
12579        );
12580
12581        assert!(editor.context_menu_visible(), "There should be a matches");
12582    });
12583}
12584
12585#[gpui::test]
12586async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
12587    init_test(cx, |_| {});
12588
12589    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
12590        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
12591        assert_eq!(editor.text(cx), expected_text);
12592        assert_eq!(
12593            editor.selections.ranges(&editor.display_snapshot(cx)),
12594            selection_ranges
12595                .iter()
12596                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
12597                .collect::<Vec<_>>()
12598        );
12599    }
12600
12601    let (text, insertion_ranges) = marked_text_ranges(
12602        indoc! {"
12603            ˇ
12604        "},
12605        false,
12606    );
12607
12608    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
12609    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12610
12611    _ = editor.update_in(cx, |editor, window, cx| {
12612        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
12613
12614        editor
12615            .insert_snippet(
12616                &insertion_ranges
12617                    .iter()
12618                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
12619                    .collect::<Vec<_>>(),
12620                snippet,
12621                window,
12622                cx,
12623            )
12624            .unwrap();
12625
12626        assert_state(
12627            editor,
12628            cx,
12629            indoc! {"
12630            type «» = ;•
12631            "},
12632        );
12633
12634        assert!(
12635            editor.context_menu_visible(),
12636            "Context menu should be visible for placeholder choices"
12637        );
12638
12639        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
12640
12641        assert_state(
12642            editor,
12643            cx,
12644            indoc! {"
12645            type  = «»;•
12646            "},
12647        );
12648
12649        assert!(
12650            !editor.context_menu_visible(),
12651            "Context menu should be hidden after moving to next tabstop"
12652        );
12653
12654        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
12655
12656        assert_state(
12657            editor,
12658            cx,
12659            indoc! {"
12660            type  = ; ˇ
12661            "},
12662        );
12663
12664        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
12665
12666        assert_state(
12667            editor,
12668            cx,
12669            indoc! {"
12670            type  = ; ˇ
12671            "},
12672        );
12673    });
12674
12675    _ = editor.update_in(cx, |editor, window, cx| {
12676        editor.select_all(&SelectAll, window, cx);
12677        editor.backspace(&Backspace, window, cx);
12678
12679        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
12680        let insertion_ranges = editor
12681            .selections
12682            .all(&editor.display_snapshot(cx))
12683            .iter()
12684            .map(|s| s.range())
12685            .collect::<Vec<_>>();
12686
12687        editor
12688            .insert_snippet(&insertion_ranges, snippet, window, cx)
12689            .unwrap();
12690
12691        assert_state(editor, cx, "fn «» = value;•");
12692
12693        assert!(
12694            editor.context_menu_visible(),
12695            "Context menu should be visible for placeholder choices"
12696        );
12697
12698        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
12699
12700        assert_state(editor, cx, "fn  = «valueˇ»;•");
12701
12702        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
12703
12704        assert_state(editor, cx, "fn «» = value;•");
12705
12706        assert!(
12707            editor.context_menu_visible(),
12708            "Context menu should be visible again after returning to first tabstop"
12709        );
12710
12711        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
12712
12713        assert_state(editor, cx, "fn «» = value;•");
12714    });
12715}
12716
12717#[gpui::test]
12718async fn test_snippets(cx: &mut TestAppContext) {
12719    init_test(cx, |_| {});
12720
12721    let mut cx = EditorTestContext::new(cx).await;
12722
12723    cx.set_state(indoc! {"
12724        a.ˇ b
12725        a.ˇ b
12726        a.ˇ b
12727    "});
12728
12729    cx.update_editor(|editor, window, cx| {
12730        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
12731        let insertion_ranges = editor
12732            .selections
12733            .all(&editor.display_snapshot(cx))
12734            .iter()
12735            .map(|s| s.range())
12736            .collect::<Vec<_>>();
12737        editor
12738            .insert_snippet(&insertion_ranges, snippet, window, cx)
12739            .unwrap();
12740    });
12741
12742    cx.assert_editor_state(indoc! {"
12743        a.f(«oneˇ», two, «threeˇ») b
12744        a.f(«oneˇ», two, «threeˇ») b
12745        a.f(«oneˇ», two, «threeˇ») b
12746    "});
12747
12748    // Can't move earlier than the first tab stop
12749    cx.update_editor(|editor, window, cx| {
12750        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
12751    });
12752    cx.assert_editor_state(indoc! {"
12753        a.f(«oneˇ», two, «threeˇ») b
12754        a.f(«oneˇ», two, «threeˇ») b
12755        a.f(«oneˇ», two, «threeˇ») b
12756    "});
12757
12758    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
12759    cx.assert_editor_state(indoc! {"
12760        a.f(one, «twoˇ», three) b
12761        a.f(one, «twoˇ», three) b
12762        a.f(one, «twoˇ», three) b
12763    "});
12764
12765    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
12766    cx.assert_editor_state(indoc! {"
12767        a.f(«oneˇ», two, «threeˇ») b
12768        a.f(«oneˇ», two, «threeˇ») b
12769        a.f(«oneˇ», two, «threeˇ») b
12770    "});
12771
12772    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
12773    cx.assert_editor_state(indoc! {"
12774        a.f(one, «twoˇ», three) b
12775        a.f(one, «twoˇ», three) b
12776        a.f(one, «twoˇ», three) b
12777    "});
12778    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
12779    cx.assert_editor_state(indoc! {"
12780        a.f(one, two, three)ˇ b
12781        a.f(one, two, three)ˇ b
12782        a.f(one, two, three)ˇ b
12783    "});
12784
12785    // As soon as the last tab stop is reached, snippet state is gone
12786    cx.update_editor(|editor, window, cx| {
12787        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
12788    });
12789    cx.assert_editor_state(indoc! {"
12790        a.f(one, two, three)ˇ b
12791        a.f(one, two, three)ˇ b
12792        a.f(one, two, three)ˇ b
12793    "});
12794}
12795
12796#[gpui::test]
12797async fn test_snippet_indentation(cx: &mut TestAppContext) {
12798    init_test(cx, |_| {});
12799
12800    let mut cx = EditorTestContext::new(cx).await;
12801
12802    cx.update_editor(|editor, window, cx| {
12803        let snippet = Snippet::parse(indoc! {"
12804            /*
12805             * Multiline comment with leading indentation
12806             *
12807             * $1
12808             */
12809            $0"})
12810        .unwrap();
12811        let insertion_ranges = editor
12812            .selections
12813            .all(&editor.display_snapshot(cx))
12814            .iter()
12815            .map(|s| s.range())
12816            .collect::<Vec<_>>();
12817        editor
12818            .insert_snippet(&insertion_ranges, snippet, window, cx)
12819            .unwrap();
12820    });
12821
12822    cx.assert_editor_state(indoc! {"
12823        /*
12824         * Multiline comment with leading indentation
12825         *
12826         * ˇ
12827         */
12828    "});
12829
12830    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
12831    cx.assert_editor_state(indoc! {"
12832        /*
12833         * Multiline comment with leading indentation
12834         *
12835         *•
12836         */
12837        ˇ"});
12838}
12839
12840#[gpui::test]
12841async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
12842    init_test(cx, |_| {});
12843
12844    let mut cx = EditorTestContext::new(cx).await;
12845    cx.update_editor(|editor, _, cx| {
12846        editor.project().unwrap().update(cx, |project, cx| {
12847            project.snippets().update(cx, |snippets, _cx| {
12848                let snippet = project::snippet_provider::Snippet {
12849                    prefix: vec!["multi word".to_string()],
12850                    body: "this is many words".to_string(),
12851                    description: Some("description".to_string()),
12852                    name: "multi-word snippet test".to_string(),
12853                };
12854                snippets.add_snippet_for_test(
12855                    None,
12856                    PathBuf::from("test_snippets.json"),
12857                    vec![Arc::new(snippet)],
12858                );
12859            });
12860        })
12861    });
12862
12863    for (input_to_simulate, should_match_snippet) in [
12864        ("m", true),
12865        ("m ", true),
12866        ("m w", true),
12867        ("aa m w", true),
12868        ("aa m g", false),
12869    ] {
12870        cx.set_state("ˇ");
12871        cx.simulate_input(input_to_simulate); // fails correctly
12872
12873        cx.update_editor(|editor, _, _| {
12874            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
12875            else {
12876                assert!(!should_match_snippet); // no completions! don't even show the menu
12877                return;
12878            };
12879            assert!(context_menu.visible());
12880            let completions = context_menu.completions.borrow();
12881
12882            assert_eq!(!completions.is_empty(), should_match_snippet);
12883        });
12884    }
12885}
12886
12887#[gpui::test]
12888async fn test_document_format_during_save(cx: &mut TestAppContext) {
12889    init_test(cx, |_| {});
12890
12891    let fs = FakeFs::new(cx.executor());
12892    fs.insert_file(path!("/file.rs"), Default::default()).await;
12893
12894    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
12895
12896    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12897    language_registry.add(rust_lang());
12898    let mut fake_servers = language_registry.register_fake_lsp(
12899        "Rust",
12900        FakeLspAdapter {
12901            capabilities: lsp::ServerCapabilities {
12902                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12903                ..Default::default()
12904            },
12905            ..Default::default()
12906        },
12907    );
12908
12909    let buffer = project
12910        .update(cx, |project, cx| {
12911            project.open_local_buffer(path!("/file.rs"), cx)
12912        })
12913        .await
12914        .unwrap();
12915
12916    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12917    let (editor, cx) = cx.add_window_view(|window, cx| {
12918        build_editor_with_project(project.clone(), buffer, window, cx)
12919    });
12920    editor.update_in(cx, |editor, window, cx| {
12921        editor.set_text("one\ntwo\nthree\n", window, cx)
12922    });
12923    assert!(cx.read(|cx| editor.is_dirty(cx)));
12924
12925    let fake_server = fake_servers.next().await.unwrap();
12926
12927    {
12928        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12929            move |params, _| async move {
12930                assert_eq!(
12931                    params.text_document.uri,
12932                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12933                );
12934                assert_eq!(params.options.tab_size, 4);
12935                Ok(Some(vec![lsp::TextEdit::new(
12936                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12937                    ", ".to_string(),
12938                )]))
12939            },
12940        );
12941        let save = editor
12942            .update_in(cx, |editor, window, cx| {
12943                editor.save(
12944                    SaveOptions {
12945                        format: true,
12946                        autosave: false,
12947                    },
12948                    project.clone(),
12949                    window,
12950                    cx,
12951                )
12952            })
12953            .unwrap();
12954        save.await;
12955
12956        assert_eq!(
12957            editor.update(cx, |editor, cx| editor.text(cx)),
12958            "one, two\nthree\n"
12959        );
12960        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12961    }
12962
12963    {
12964        editor.update_in(cx, |editor, window, cx| {
12965            editor.set_text("one\ntwo\nthree\n", window, cx)
12966        });
12967        assert!(cx.read(|cx| editor.is_dirty(cx)));
12968
12969        // Ensure we can still save even if formatting hangs.
12970        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12971            move |params, _| async move {
12972                assert_eq!(
12973                    params.text_document.uri,
12974                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12975                );
12976                futures::future::pending::<()>().await;
12977                unreachable!()
12978            },
12979        );
12980        let save = editor
12981            .update_in(cx, |editor, window, cx| {
12982                editor.save(
12983                    SaveOptions {
12984                        format: true,
12985                        autosave: false,
12986                    },
12987                    project.clone(),
12988                    window,
12989                    cx,
12990                )
12991            })
12992            .unwrap();
12993        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12994        save.await;
12995        assert_eq!(
12996            editor.update(cx, |editor, cx| editor.text(cx)),
12997            "one\ntwo\nthree\n"
12998        );
12999    }
13000
13001    // Set rust language override and assert overridden tabsize is sent to language server
13002    update_test_language_settings(cx, &|settings| {
13003        settings.languages.0.insert(
13004            "Rust".into(),
13005            LanguageSettingsContent {
13006                tab_size: NonZeroU32::new(8),
13007                ..Default::default()
13008            },
13009        );
13010    });
13011
13012    {
13013        editor.update_in(cx, |editor, window, cx| {
13014            editor.set_text("somehting_new\n", window, cx)
13015        });
13016        assert!(cx.read(|cx| editor.is_dirty(cx)));
13017        let _formatting_request_signal = fake_server
13018            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
13019                assert_eq!(
13020                    params.text_document.uri,
13021                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13022                );
13023                assert_eq!(params.options.tab_size, 8);
13024                Ok(Some(vec![]))
13025            });
13026        let save = editor
13027            .update_in(cx, |editor, window, cx| {
13028                editor.save(
13029                    SaveOptions {
13030                        format: true,
13031                        autosave: false,
13032                    },
13033                    project.clone(),
13034                    window,
13035                    cx,
13036                )
13037            })
13038            .unwrap();
13039        save.await;
13040    }
13041}
13042
13043#[gpui::test]
13044async fn test_auto_formatter_skips_server_without_formatting(cx: &mut TestAppContext) {
13045    init_test(cx, |_| {});
13046
13047    let fs = FakeFs::new(cx.executor());
13048    fs.insert_file(path!("/file.rs"), Default::default()).await;
13049
13050    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
13051
13052    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13053    language_registry.add(rust_lang());
13054
13055    // First server: no formatting capability
13056    let mut no_format_servers = language_registry.register_fake_lsp(
13057        "Rust",
13058        FakeLspAdapter {
13059            name: "no-format-server",
13060            capabilities: lsp::ServerCapabilities {
13061                completion_provider: Some(lsp::CompletionOptions::default()),
13062                ..Default::default()
13063            },
13064            ..Default::default()
13065        },
13066    );
13067
13068    // Second server: has formatting capability
13069    let mut format_servers = language_registry.register_fake_lsp(
13070        "Rust",
13071        FakeLspAdapter {
13072            name: "format-server",
13073            capabilities: lsp::ServerCapabilities {
13074                document_formatting_provider: Some(lsp::OneOf::Left(true)),
13075                ..Default::default()
13076            },
13077            ..Default::default()
13078        },
13079    );
13080
13081    let buffer = project
13082        .update(cx, |project, cx| {
13083            project.open_local_buffer(path!("/file.rs"), cx)
13084        })
13085        .await
13086        .unwrap();
13087
13088    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13089    let (editor, cx) = cx.add_window_view(|window, cx| {
13090        build_editor_with_project(project.clone(), buffer, window, cx)
13091    });
13092    editor.update_in(cx, |editor, window, cx| {
13093        editor.set_text("one\ntwo\nthree\n", window, cx)
13094    });
13095
13096    let _no_format_server = no_format_servers.next().await.unwrap();
13097    let format_server = format_servers.next().await.unwrap();
13098
13099    format_server.set_request_handler::<lsp::request::Formatting, _, _>(
13100        move |params, _| async move {
13101            assert_eq!(
13102                params.text_document.uri,
13103                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13104            );
13105            Ok(Some(vec![lsp::TextEdit::new(
13106                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
13107                ", ".to_string(),
13108            )]))
13109        },
13110    );
13111
13112    let save = editor
13113        .update_in(cx, |editor, window, cx| {
13114            editor.save(
13115                SaveOptions {
13116                    format: true,
13117                    autosave: false,
13118                },
13119                project.clone(),
13120                window,
13121                cx,
13122            )
13123        })
13124        .unwrap();
13125    save.await;
13126
13127    assert_eq!(
13128        editor.update(cx, |editor, cx| editor.text(cx)),
13129        "one, two\nthree\n"
13130    );
13131}
13132
13133#[gpui::test]
13134async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
13135    init_test(cx, |settings| {
13136        settings.defaults.ensure_final_newline_on_save = Some(false);
13137    });
13138
13139    let fs = FakeFs::new(cx.executor());
13140    fs.insert_file(path!("/file.txt"), "foo".into()).await;
13141
13142    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
13143
13144    let buffer = project
13145        .update(cx, |project, cx| {
13146            project.open_local_buffer(path!("/file.txt"), cx)
13147        })
13148        .await
13149        .unwrap();
13150
13151    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13152    let (editor, cx) = cx.add_window_view(|window, cx| {
13153        build_editor_with_project(project.clone(), buffer, window, cx)
13154    });
13155    editor.update_in(cx, |editor, window, cx| {
13156        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
13157            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13158        });
13159    });
13160    assert!(!cx.read(|cx| editor.is_dirty(cx)));
13161
13162    editor.update_in(cx, |editor, window, cx| {
13163        editor.handle_input("\n", window, cx)
13164    });
13165    cx.run_until_parked();
13166    save(&editor, &project, cx).await;
13167    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
13168
13169    editor.update_in(cx, |editor, window, cx| {
13170        editor.undo(&Default::default(), window, cx);
13171    });
13172    save(&editor, &project, cx).await;
13173    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
13174
13175    editor.update_in(cx, |editor, window, cx| {
13176        editor.redo(&Default::default(), window, cx);
13177    });
13178    cx.run_until_parked();
13179    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
13180
13181    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
13182        let save = editor
13183            .update_in(cx, |editor, window, cx| {
13184                editor.save(
13185                    SaveOptions {
13186                        format: true,
13187                        autosave: false,
13188                    },
13189                    project.clone(),
13190                    window,
13191                    cx,
13192                )
13193            })
13194            .unwrap();
13195        save.await;
13196        assert!(!cx.read(|cx| editor.is_dirty(cx)));
13197    }
13198}
13199
13200#[gpui::test]
13201async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
13202    init_test(cx, |_| {});
13203
13204    let cols = 4;
13205    let rows = 10;
13206    let sample_text_1 = sample_text(rows, cols, 'a');
13207    assert_eq!(
13208        sample_text_1,
13209        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13210    );
13211    let sample_text_2 = sample_text(rows, cols, 'l');
13212    assert_eq!(
13213        sample_text_2,
13214        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13215    );
13216    let sample_text_3 = sample_text(rows, cols, 'v').replace('\u{7f}', ".");
13217    assert_eq!(
13218        sample_text_3,
13219        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n...."
13220    );
13221
13222    let fs = FakeFs::new(cx.executor());
13223    fs.insert_tree(
13224        path!("/a"),
13225        json!({
13226            "main.rs": sample_text_1,
13227            "other.rs": sample_text_2,
13228            "lib.rs": sample_text_3,
13229        }),
13230    )
13231    .await;
13232
13233    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13234    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
13235    let cx = &mut VisualTestContext::from_window(*window, cx);
13236
13237    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13238    language_registry.add(rust_lang());
13239    let mut fake_servers = language_registry.register_fake_lsp(
13240        "Rust",
13241        FakeLspAdapter {
13242            capabilities: lsp::ServerCapabilities {
13243                document_formatting_provider: Some(lsp::OneOf::Left(true)),
13244                ..Default::default()
13245            },
13246            ..Default::default()
13247        },
13248    );
13249
13250    let worktree = project.update(cx, |project, cx| {
13251        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
13252        assert_eq!(worktrees.len(), 1);
13253        worktrees.pop().unwrap()
13254    });
13255    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
13256
13257    let buffer_1 = project
13258        .update(cx, |project, cx| {
13259            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
13260        })
13261        .await
13262        .unwrap();
13263    let buffer_2 = project
13264        .update(cx, |project, cx| {
13265            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
13266        })
13267        .await
13268        .unwrap();
13269    let buffer_3 = project
13270        .update(cx, |project, cx| {
13271            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
13272        })
13273        .await
13274        .unwrap();
13275
13276    let multi_buffer = cx.new(|cx| {
13277        let mut multi_buffer = MultiBuffer::new(ReadWrite);
13278        multi_buffer.set_excerpts_for_path(
13279            PathKey::sorted(0),
13280            buffer_1.clone(),
13281            [
13282                Point::new(0, 0)..Point::new(2, 4),
13283                Point::new(5, 0)..Point::new(6, 4),
13284                Point::new(9, 0)..Point::new(9, 4),
13285            ],
13286            0,
13287            cx,
13288        );
13289        multi_buffer.set_excerpts_for_path(
13290            PathKey::sorted(1),
13291            buffer_2.clone(),
13292            [
13293                Point::new(0, 0)..Point::new(2, 4),
13294                Point::new(5, 0)..Point::new(6, 4),
13295                Point::new(9, 0)..Point::new(9, 4),
13296            ],
13297            0,
13298            cx,
13299        );
13300        multi_buffer.set_excerpts_for_path(
13301            PathKey::sorted(2),
13302            buffer_3.clone(),
13303            [
13304                Point::new(0, 0)..Point::new(2, 4),
13305                Point::new(5, 0)..Point::new(6, 4),
13306                Point::new(9, 0)..Point::new(9, 4),
13307            ],
13308            0,
13309            cx,
13310        );
13311        assert_eq!(multi_buffer.excerpt_ids().len(), 9);
13312        multi_buffer
13313    });
13314    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13315        Editor::new(
13316            EditorMode::full(),
13317            multi_buffer,
13318            Some(project.clone()),
13319            window,
13320            cx,
13321        )
13322    });
13323
13324    multi_buffer_editor.update_in(cx, |editor, window, cx| {
13325        let a = editor.text(cx).find("aaaa").unwrap();
13326        editor.change_selections(
13327            SelectionEffects::scroll(Autoscroll::Next),
13328            window,
13329            cx,
13330            |s| s.select_ranges(Some(MultiBufferOffset(a + 1)..MultiBufferOffset(a + 2))),
13331        );
13332        editor.insert("|one|two|three|", window, cx);
13333    });
13334    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
13335    multi_buffer_editor.update_in(cx, |editor, window, cx| {
13336        let n = editor.text(cx).find("nnnn").unwrap();
13337        editor.change_selections(
13338            SelectionEffects::scroll(Autoscroll::Next),
13339            window,
13340            cx,
13341            |s| s.select_ranges(Some(MultiBufferOffset(n + 4)..MultiBufferOffset(n + 14))),
13342        );
13343        editor.insert("|four|five|six|", window, cx);
13344    });
13345    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
13346
13347    // First two buffers should be edited, but not the third one.
13348    pretty_assertions::assert_eq!(
13349        editor_content_with_blocks(&multi_buffer_editor, cx),
13350        indoc! {"
13351            § main.rs
13352            § -----
13353            a|one|two|three|aa
13354            bbbb
13355            cccc
13356            § -----
13357            ffff
13358            gggg
13359            § -----
13360            jjjj
13361            § other.rs
13362            § -----
13363            llll
13364            mmmm
13365            nnnn|four|five|six|
13366            § -----
13367
13368            § -----
13369            uuuu
13370            § lib.rs
13371            § -----
13372            vvvv
13373            wwww
13374            xxxx
13375            § -----
13376            {{{{
13377            ||||
13378            § -----
13379            ...."}
13380    );
13381    buffer_1.update(cx, |buffer, _| {
13382        assert!(buffer.is_dirty());
13383        assert_eq!(
13384            buffer.text(),
13385            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
13386        )
13387    });
13388    buffer_2.update(cx, |buffer, _| {
13389        assert!(buffer.is_dirty());
13390        assert_eq!(
13391            buffer.text(),
13392            "llll\nmmmm\nnnnn|four|five|six|\noooo\npppp\n\nssss\ntttt\nuuuu",
13393        )
13394    });
13395    buffer_3.update(cx, |buffer, _| {
13396        assert!(!buffer.is_dirty());
13397        assert_eq!(buffer.text(), sample_text_3,)
13398    });
13399    cx.executor().run_until_parked();
13400
13401    let save = multi_buffer_editor
13402        .update_in(cx, |editor, window, cx| {
13403            editor.save(
13404                SaveOptions {
13405                    format: true,
13406                    autosave: false,
13407                },
13408                project.clone(),
13409                window,
13410                cx,
13411            )
13412        })
13413        .unwrap();
13414
13415    let fake_server = fake_servers.next().await.unwrap();
13416    fake_server
13417        .server
13418        .on_request::<lsp::request::Formatting, _, _>(move |_params, _| async move {
13419            Ok(Some(vec![lsp::TextEdit::new(
13420                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
13421                "[formatted]".to_string(),
13422            )]))
13423        })
13424        .detach();
13425    save.await;
13426
13427    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
13428    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
13429    assert_eq!(
13430        editor_content_with_blocks(&multi_buffer_editor, cx),
13431        indoc! {"
13432            § main.rs
13433            § -----
13434            a|o[formatted]bbbb
13435            cccc
13436            § -----
13437            ffff
13438            gggg
13439            § -----
13440            jjjj
13441
13442            § other.rs
13443            § -----
13444            lll[formatted]mmmm
13445            nnnn|four|five|six|
13446            § -----
13447
13448            § -----
13449            uuuu
13450
13451            § lib.rs
13452            § -----
13453            vvvv
13454            wwww
13455            xxxx
13456            § -----
13457            {{{{
13458            ||||
13459            § -----
13460            ...."}
13461    );
13462    buffer_1.update(cx, |buffer, _| {
13463        assert!(!buffer.is_dirty());
13464        assert_eq!(
13465            buffer.text(),
13466            "a|o[formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
13467        )
13468    });
13469    // Diff < left / right > :
13470    //  lll[formatted]mmmm
13471    // <nnnn|four|five|six|
13472    // <oooo
13473    // >nnnn|four|five|six|oooo
13474    //  pppp
13475    // <
13476    //  ssss
13477    //  tttt
13478    //  uuuu
13479
13480    buffer_2.update(cx, |buffer, _| {
13481        assert!(!buffer.is_dirty());
13482        assert_eq!(
13483            buffer.text(),
13484            "lll[formatted]mmmm\nnnnn|four|five|six|\noooo\npppp\n\nssss\ntttt\nuuuu\n",
13485        )
13486    });
13487    buffer_3.update(cx, |buffer, _| {
13488        assert!(!buffer.is_dirty());
13489        assert_eq!(buffer.text(), sample_text_3,)
13490    });
13491}
13492
13493#[gpui::test]
13494async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
13495    init_test(cx, |_| {});
13496
13497    let fs = FakeFs::new(cx.executor());
13498    fs.insert_tree(
13499        path!("/dir"),
13500        json!({
13501            "file1.rs": "fn main() { println!(\"hello\"); }",
13502            "file2.rs": "fn test() { println!(\"test\"); }",
13503            "file3.rs": "fn other() { println!(\"other\"); }\n",
13504        }),
13505    )
13506    .await;
13507
13508    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
13509    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
13510    let cx = &mut VisualTestContext::from_window(*window, cx);
13511
13512    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13513    language_registry.add(rust_lang());
13514
13515    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
13516    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
13517
13518    // Open three buffers
13519    let buffer_1 = project
13520        .update(cx, |project, cx| {
13521            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
13522        })
13523        .await
13524        .unwrap();
13525    let buffer_2 = project
13526        .update(cx, |project, cx| {
13527            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
13528        })
13529        .await
13530        .unwrap();
13531    let buffer_3 = project
13532        .update(cx, |project, cx| {
13533            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
13534        })
13535        .await
13536        .unwrap();
13537
13538    // Create a multi-buffer with all three buffers
13539    let multi_buffer = cx.new(|cx| {
13540        let mut multi_buffer = MultiBuffer::new(ReadWrite);
13541        multi_buffer.set_excerpts_for_path(
13542            PathKey::sorted(0),
13543            buffer_1.clone(),
13544            [Point::new(0, 0)..Point::new(1, 0)],
13545            0,
13546            cx,
13547        );
13548        multi_buffer.set_excerpts_for_path(
13549            PathKey::sorted(1),
13550            buffer_2.clone(),
13551            [Point::new(0, 0)..Point::new(1, 0)],
13552            0,
13553            cx,
13554        );
13555        multi_buffer.set_excerpts_for_path(
13556            PathKey::sorted(2),
13557            buffer_3.clone(),
13558            [Point::new(0, 0)..Point::new(1, 0)],
13559            0,
13560            cx,
13561        );
13562        multi_buffer
13563    });
13564
13565    let editor = cx.new_window_entity(|window, cx| {
13566        Editor::new(
13567            EditorMode::full(),
13568            multi_buffer,
13569            Some(project.clone()),
13570            window,
13571            cx,
13572        )
13573    });
13574
13575    // Edit only the first buffer
13576    editor.update_in(cx, |editor, window, cx| {
13577        editor.change_selections(
13578            SelectionEffects::scroll(Autoscroll::Next),
13579            window,
13580            cx,
13581            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
13582        );
13583        editor.insert("// edited", window, cx);
13584    });
13585
13586    // Verify that only buffer 1 is dirty
13587    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
13588    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
13589    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
13590
13591    // Get write counts after file creation (files were created with initial content)
13592    // We expect each file to have been written once during creation
13593    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
13594    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
13595    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
13596
13597    // Perform autosave
13598    let save_task = editor.update_in(cx, |editor, window, cx| {
13599        editor.save(
13600            SaveOptions {
13601                format: true,
13602                autosave: true,
13603            },
13604            project.clone(),
13605            window,
13606            cx,
13607        )
13608    });
13609    save_task.await.unwrap();
13610
13611    // Only the dirty buffer should have been saved
13612    assert_eq!(
13613        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
13614        1,
13615        "Buffer 1 was dirty, so it should have been written once during autosave"
13616    );
13617    assert_eq!(
13618        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
13619        0,
13620        "Buffer 2 was clean, so it should not have been written during autosave"
13621    );
13622    assert_eq!(
13623        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
13624        0,
13625        "Buffer 3 was clean, so it should not have been written during autosave"
13626    );
13627
13628    // Verify buffer states after autosave
13629    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
13630    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
13631    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
13632
13633    // Now perform a manual save (format = true)
13634    let save_task = editor.update_in(cx, |editor, window, cx| {
13635        editor.save(
13636            SaveOptions {
13637                format: true,
13638                autosave: false,
13639            },
13640            project.clone(),
13641            window,
13642            cx,
13643        )
13644    });
13645    save_task.await.unwrap();
13646
13647    // During manual save, clean buffers don't get written to disk
13648    // They just get did_save called for language server notifications
13649    assert_eq!(
13650        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
13651        1,
13652        "Buffer 1 should only have been written once total (during autosave, not manual save)"
13653    );
13654    assert_eq!(
13655        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
13656        0,
13657        "Buffer 2 should not have been written at all"
13658    );
13659    assert_eq!(
13660        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
13661        0,
13662        "Buffer 3 should not have been written at all"
13663    );
13664}
13665
13666async fn setup_range_format_test(
13667    cx: &mut TestAppContext,
13668) -> (
13669    Entity<Project>,
13670    Entity<Editor>,
13671    &mut gpui::VisualTestContext,
13672    lsp::FakeLanguageServer,
13673) {
13674    init_test(cx, |_| {});
13675
13676    let fs = FakeFs::new(cx.executor());
13677    fs.insert_file(path!("/file.rs"), Default::default()).await;
13678
13679    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13680
13681    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13682    language_registry.add(rust_lang());
13683    let mut fake_servers = language_registry.register_fake_lsp(
13684        "Rust",
13685        FakeLspAdapter {
13686            capabilities: lsp::ServerCapabilities {
13687                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
13688                ..lsp::ServerCapabilities::default()
13689            },
13690            ..FakeLspAdapter::default()
13691        },
13692    );
13693
13694    let buffer = project
13695        .update(cx, |project, cx| {
13696            project.open_local_buffer(path!("/file.rs"), cx)
13697        })
13698        .await
13699        .unwrap();
13700
13701    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13702    let (editor, cx) = cx.add_window_view(|window, cx| {
13703        build_editor_with_project(project.clone(), buffer, window, cx)
13704    });
13705
13706    let fake_server = fake_servers.next().await.unwrap();
13707
13708    (project, editor, cx, fake_server)
13709}
13710
13711#[gpui::test]
13712async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
13713    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
13714
13715    editor.update_in(cx, |editor, window, cx| {
13716        editor.set_text("one\ntwo\nthree\n", window, cx)
13717    });
13718    assert!(cx.read(|cx| editor.is_dirty(cx)));
13719
13720    let save = editor
13721        .update_in(cx, |editor, window, cx| {
13722            editor.save(
13723                SaveOptions {
13724                    format: true,
13725                    autosave: false,
13726                },
13727                project.clone(),
13728                window,
13729                cx,
13730            )
13731        })
13732        .unwrap();
13733    fake_server
13734        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
13735            assert_eq!(
13736                params.text_document.uri,
13737                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13738            );
13739            assert_eq!(params.options.tab_size, 4);
13740            Ok(Some(vec![lsp::TextEdit::new(
13741                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
13742                ", ".to_string(),
13743            )]))
13744        })
13745        .next()
13746        .await;
13747    save.await;
13748    assert_eq!(
13749        editor.update(cx, |editor, cx| editor.text(cx)),
13750        "one, two\nthree\n"
13751    );
13752    assert!(!cx.read(|cx| editor.is_dirty(cx)));
13753}
13754
13755#[gpui::test]
13756async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
13757    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
13758
13759    editor.update_in(cx, |editor, window, cx| {
13760        editor.set_text("one\ntwo\nthree\n", window, cx)
13761    });
13762    assert!(cx.read(|cx| editor.is_dirty(cx)));
13763
13764    // Test that save still works when formatting hangs
13765    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
13766        move |params, _| async move {
13767            assert_eq!(
13768                params.text_document.uri,
13769                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13770            );
13771            futures::future::pending::<()>().await;
13772            unreachable!()
13773        },
13774    );
13775    let save = editor
13776        .update_in(cx, |editor, window, cx| {
13777            editor.save(
13778                SaveOptions {
13779                    format: true,
13780                    autosave: false,
13781                },
13782                project.clone(),
13783                window,
13784                cx,
13785            )
13786        })
13787        .unwrap();
13788    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
13789    save.await;
13790    assert_eq!(
13791        editor.update(cx, |editor, cx| editor.text(cx)),
13792        "one\ntwo\nthree\n"
13793    );
13794    assert!(!cx.read(|cx| editor.is_dirty(cx)));
13795}
13796
13797#[gpui::test]
13798async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
13799    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
13800
13801    // Buffer starts clean, no formatting should be requested
13802    let save = editor
13803        .update_in(cx, |editor, window, cx| {
13804            editor.save(
13805                SaveOptions {
13806                    format: false,
13807                    autosave: false,
13808                },
13809                project.clone(),
13810                window,
13811                cx,
13812            )
13813        })
13814        .unwrap();
13815    let _pending_format_request = fake_server
13816        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
13817            panic!("Should not be invoked");
13818        })
13819        .next();
13820    save.await;
13821    cx.run_until_parked();
13822}
13823
13824#[gpui::test]
13825async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
13826    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
13827
13828    // Set Rust language override and assert overridden tabsize is sent to language server
13829    update_test_language_settings(cx, &|settings| {
13830        settings.languages.0.insert(
13831            "Rust".into(),
13832            LanguageSettingsContent {
13833                tab_size: NonZeroU32::new(8),
13834                ..Default::default()
13835            },
13836        );
13837    });
13838
13839    editor.update_in(cx, |editor, window, cx| {
13840        editor.set_text("something_new\n", window, cx)
13841    });
13842    assert!(cx.read(|cx| editor.is_dirty(cx)));
13843    let save = editor
13844        .update_in(cx, |editor, window, cx| {
13845            editor.save(
13846                SaveOptions {
13847                    format: true,
13848                    autosave: false,
13849                },
13850                project.clone(),
13851                window,
13852                cx,
13853            )
13854        })
13855        .unwrap();
13856    fake_server
13857        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
13858            assert_eq!(
13859                params.text_document.uri,
13860                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13861            );
13862            assert_eq!(params.options.tab_size, 8);
13863            Ok(Some(Vec::new()))
13864        })
13865        .next()
13866        .await;
13867    save.await;
13868}
13869
13870#[gpui::test]
13871async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
13872    init_test(cx, |settings| {
13873        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
13874            settings::LanguageServerFormatterSpecifier::Current,
13875        )))
13876    });
13877
13878    let fs = FakeFs::new(cx.executor());
13879    fs.insert_file(path!("/file.rs"), Default::default()).await;
13880
13881    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13882
13883    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13884    language_registry.add(Arc::new(Language::new(
13885        LanguageConfig {
13886            name: "Rust".into(),
13887            matcher: LanguageMatcher {
13888                path_suffixes: vec!["rs".to_string()],
13889                ..Default::default()
13890            },
13891            ..LanguageConfig::default()
13892        },
13893        Some(tree_sitter_rust::LANGUAGE.into()),
13894    )));
13895    update_test_language_settings(cx, &|settings| {
13896        // Enable Prettier formatting for the same buffer, and ensure
13897        // LSP is called instead of Prettier.
13898        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
13899    });
13900    let mut fake_servers = language_registry.register_fake_lsp(
13901        "Rust",
13902        FakeLspAdapter {
13903            capabilities: lsp::ServerCapabilities {
13904                document_formatting_provider: Some(lsp::OneOf::Left(true)),
13905                ..Default::default()
13906            },
13907            ..Default::default()
13908        },
13909    );
13910
13911    let buffer = project
13912        .update(cx, |project, cx| {
13913            project.open_local_buffer(path!("/file.rs"), cx)
13914        })
13915        .await
13916        .unwrap();
13917
13918    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13919    let (editor, cx) = cx.add_window_view(|window, cx| {
13920        build_editor_with_project(project.clone(), buffer, window, cx)
13921    });
13922    editor.update_in(cx, |editor, window, cx| {
13923        editor.set_text("one\ntwo\nthree\n", window, cx)
13924    });
13925
13926    let fake_server = fake_servers.next().await.unwrap();
13927
13928    let format = editor
13929        .update_in(cx, |editor, window, cx| {
13930            editor.perform_format(
13931                project.clone(),
13932                FormatTrigger::Manual,
13933                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13934                window,
13935                cx,
13936            )
13937        })
13938        .unwrap();
13939    fake_server
13940        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
13941            assert_eq!(
13942                params.text_document.uri,
13943                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13944            );
13945            assert_eq!(params.options.tab_size, 4);
13946            Ok(Some(vec![lsp::TextEdit::new(
13947                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
13948                ", ".to_string(),
13949            )]))
13950        })
13951        .next()
13952        .await;
13953    format.await;
13954    assert_eq!(
13955        editor.update(cx, |editor, cx| editor.text(cx)),
13956        "one, two\nthree\n"
13957    );
13958
13959    editor.update_in(cx, |editor, window, cx| {
13960        editor.set_text("one\ntwo\nthree\n", window, cx)
13961    });
13962    // Ensure we don't lock if formatting hangs.
13963    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
13964        move |params, _| async move {
13965            assert_eq!(
13966                params.text_document.uri,
13967                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13968            );
13969            futures::future::pending::<()>().await;
13970            unreachable!()
13971        },
13972    );
13973    let format = editor
13974        .update_in(cx, |editor, window, cx| {
13975            editor.perform_format(
13976                project,
13977                FormatTrigger::Manual,
13978                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13979                window,
13980                cx,
13981            )
13982        })
13983        .unwrap();
13984    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
13985    format.await;
13986    assert_eq!(
13987        editor.update(cx, |editor, cx| editor.text(cx)),
13988        "one\ntwo\nthree\n"
13989    );
13990}
13991
13992#[gpui::test]
13993async fn test_multiple_formatters(cx: &mut TestAppContext) {
13994    init_test(cx, |settings| {
13995        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
13996        settings.defaults.formatter = Some(FormatterList::Vec(vec![
13997            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
13998            Formatter::CodeAction("code-action-1".into()),
13999            Formatter::CodeAction("code-action-2".into()),
14000        ]))
14001    });
14002
14003    let fs = FakeFs::new(cx.executor());
14004    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
14005        .await;
14006
14007    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14008    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14009    language_registry.add(rust_lang());
14010
14011    let mut fake_servers = language_registry.register_fake_lsp(
14012        "Rust",
14013        FakeLspAdapter {
14014            capabilities: lsp::ServerCapabilities {
14015                document_formatting_provider: Some(lsp::OneOf::Left(true)),
14016                execute_command_provider: Some(lsp::ExecuteCommandOptions {
14017                    commands: vec!["the-command-for-code-action-1".into()],
14018                    ..Default::default()
14019                }),
14020                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14021                ..Default::default()
14022            },
14023            ..Default::default()
14024        },
14025    );
14026
14027    let buffer = project
14028        .update(cx, |project, cx| {
14029            project.open_local_buffer(path!("/file.rs"), cx)
14030        })
14031        .await
14032        .unwrap();
14033
14034    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14035    let (editor, cx) = cx.add_window_view(|window, cx| {
14036        build_editor_with_project(project.clone(), buffer, window, cx)
14037    });
14038
14039    let fake_server = fake_servers.next().await.unwrap();
14040    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
14041        move |_params, _| async move {
14042            Ok(Some(vec![lsp::TextEdit::new(
14043                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
14044                "applied-formatting\n".to_string(),
14045            )]))
14046        },
14047    );
14048    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14049        move |params, _| async move {
14050            let requested_code_actions = params.context.only.expect("Expected code action request");
14051            assert_eq!(requested_code_actions.len(), 1);
14052
14053            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
14054            let code_action = match requested_code_actions[0].as_str() {
14055                "code-action-1" => lsp::CodeAction {
14056                    kind: Some("code-action-1".into()),
14057                    edit: Some(lsp::WorkspaceEdit::new(
14058                        [(
14059                            uri,
14060                            vec![lsp::TextEdit::new(
14061                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
14062                                "applied-code-action-1-edit\n".to_string(),
14063                            )],
14064                        )]
14065                        .into_iter()
14066                        .collect(),
14067                    )),
14068                    command: Some(lsp::Command {
14069                        command: "the-command-for-code-action-1".into(),
14070                        ..Default::default()
14071                    }),
14072                    ..Default::default()
14073                },
14074                "code-action-2" => lsp::CodeAction {
14075                    kind: Some("code-action-2".into()),
14076                    edit: Some(lsp::WorkspaceEdit::new(
14077                        [(
14078                            uri,
14079                            vec![lsp::TextEdit::new(
14080                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
14081                                "applied-code-action-2-edit\n".to_string(),
14082                            )],
14083                        )]
14084                        .into_iter()
14085                        .collect(),
14086                    )),
14087                    ..Default::default()
14088                },
14089                req => panic!("Unexpected code action request: {:?}", req),
14090            };
14091            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14092                code_action,
14093            )]))
14094        },
14095    );
14096
14097    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
14098        move |params, _| async move { Ok(params) }
14099    });
14100
14101    let command_lock = Arc::new(futures::lock::Mutex::new(()));
14102    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
14103        let fake = fake_server.clone();
14104        let lock = command_lock.clone();
14105        move |params, _| {
14106            assert_eq!(params.command, "the-command-for-code-action-1");
14107            let fake = fake.clone();
14108            let lock = lock.clone();
14109            async move {
14110                lock.lock().await;
14111                fake.server
14112                    .request::<lsp::request::ApplyWorkspaceEdit>(
14113                        lsp::ApplyWorkspaceEditParams {
14114                            label: None,
14115                            edit: lsp::WorkspaceEdit {
14116                                changes: Some(
14117                                    [(
14118                                        lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
14119                                        vec![lsp::TextEdit {
14120                                            range: lsp::Range::new(
14121                                                lsp::Position::new(0, 0),
14122                                                lsp::Position::new(0, 0),
14123                                            ),
14124                                            new_text: "applied-code-action-1-command\n".into(),
14125                                        }],
14126                                    )]
14127                                    .into_iter()
14128                                    .collect(),
14129                                ),
14130                                ..Default::default()
14131                            },
14132                        },
14133                        DEFAULT_LSP_REQUEST_TIMEOUT,
14134                    )
14135                    .await
14136                    .into_response()
14137                    .unwrap();
14138                Ok(Some(json!(null)))
14139            }
14140        }
14141    });
14142
14143    editor
14144        .update_in(cx, |editor, window, cx| {
14145            editor.perform_format(
14146                project.clone(),
14147                FormatTrigger::Manual,
14148                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
14149                window,
14150                cx,
14151            )
14152        })
14153        .unwrap()
14154        .await;
14155    editor.update(cx, |editor, cx| {
14156        assert_eq!(
14157            editor.text(cx),
14158            r#"
14159                applied-code-action-2-edit
14160                applied-code-action-1-command
14161                applied-code-action-1-edit
14162                applied-formatting
14163                one
14164                two
14165                three
14166            "#
14167            .unindent()
14168        );
14169    });
14170
14171    editor.update_in(cx, |editor, window, cx| {
14172        editor.undo(&Default::default(), window, cx);
14173        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
14174    });
14175
14176    // Perform a manual edit while waiting for an LSP command
14177    // that's being run as part of a formatting code action.
14178    let lock_guard = command_lock.lock().await;
14179    let format = editor
14180        .update_in(cx, |editor, window, cx| {
14181            editor.perform_format(
14182                project.clone(),
14183                FormatTrigger::Manual,
14184                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
14185                window,
14186                cx,
14187            )
14188        })
14189        .unwrap();
14190    cx.run_until_parked();
14191    editor.update(cx, |editor, cx| {
14192        assert_eq!(
14193            editor.text(cx),
14194            r#"
14195                applied-code-action-1-edit
14196                applied-formatting
14197                one
14198                two
14199                three
14200            "#
14201            .unindent()
14202        );
14203
14204        editor.buffer.update(cx, |buffer, cx| {
14205            let ix = buffer.len(cx);
14206            buffer.edit([(ix..ix, "edited\n")], None, cx);
14207        });
14208    });
14209
14210    // Allow the LSP command to proceed. Because the buffer was edited,
14211    // the second code action will not be run.
14212    drop(lock_guard);
14213    format.await;
14214    editor.update_in(cx, |editor, window, cx| {
14215        assert_eq!(
14216            editor.text(cx),
14217            r#"
14218                applied-code-action-1-command
14219                applied-code-action-1-edit
14220                applied-formatting
14221                one
14222                two
14223                three
14224                edited
14225            "#
14226            .unindent()
14227        );
14228
14229        // The manual edit is undone first, because it is the last thing the user did
14230        // (even though the command completed afterwards).
14231        editor.undo(&Default::default(), window, cx);
14232        assert_eq!(
14233            editor.text(cx),
14234            r#"
14235                applied-code-action-1-command
14236                applied-code-action-1-edit
14237                applied-formatting
14238                one
14239                two
14240                three
14241            "#
14242            .unindent()
14243        );
14244
14245        // All the formatting (including the command, which completed after the manual edit)
14246        // is undone together.
14247        editor.undo(&Default::default(), window, cx);
14248        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
14249    });
14250}
14251
14252#[gpui::test]
14253async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
14254    init_test(cx, |settings| {
14255        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
14256            settings::LanguageServerFormatterSpecifier::Current,
14257        )]))
14258    });
14259
14260    let fs = FakeFs::new(cx.executor());
14261    fs.insert_file(path!("/file.ts"), Default::default()).await;
14262
14263    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14264
14265    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14266    language_registry.add(Arc::new(Language::new(
14267        LanguageConfig {
14268            name: "TypeScript".into(),
14269            matcher: LanguageMatcher {
14270                path_suffixes: vec!["ts".to_string()],
14271                ..Default::default()
14272            },
14273            ..LanguageConfig::default()
14274        },
14275        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14276    )));
14277    update_test_language_settings(cx, &|settings| {
14278        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
14279    });
14280    let mut fake_servers = language_registry.register_fake_lsp(
14281        "TypeScript",
14282        FakeLspAdapter {
14283            capabilities: lsp::ServerCapabilities {
14284                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14285                ..Default::default()
14286            },
14287            ..Default::default()
14288        },
14289    );
14290
14291    let buffer = project
14292        .update(cx, |project, cx| {
14293            project.open_local_buffer(path!("/file.ts"), cx)
14294        })
14295        .await
14296        .unwrap();
14297
14298    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14299    let (editor, cx) = cx.add_window_view(|window, cx| {
14300        build_editor_with_project(project.clone(), buffer, window, cx)
14301    });
14302    editor.update_in(cx, |editor, window, cx| {
14303        editor.set_text(
14304            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
14305            window,
14306            cx,
14307        )
14308    });
14309
14310    let fake_server = fake_servers.next().await.unwrap();
14311
14312    let format = editor
14313        .update_in(cx, |editor, window, cx| {
14314            editor.perform_code_action_kind(
14315                project.clone(),
14316                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14317                window,
14318                cx,
14319            )
14320        })
14321        .unwrap();
14322    fake_server
14323        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
14324            assert_eq!(
14325                params.text_document.uri,
14326                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
14327            );
14328            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14329                lsp::CodeAction {
14330                    title: "Organize Imports".to_string(),
14331                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
14332                    edit: Some(lsp::WorkspaceEdit {
14333                        changes: Some(
14334                            [(
14335                                params.text_document.uri.clone(),
14336                                vec![lsp::TextEdit::new(
14337                                    lsp::Range::new(
14338                                        lsp::Position::new(1, 0),
14339                                        lsp::Position::new(2, 0),
14340                                    ),
14341                                    "".to_string(),
14342                                )],
14343                            )]
14344                            .into_iter()
14345                            .collect(),
14346                        ),
14347                        ..Default::default()
14348                    }),
14349                    ..Default::default()
14350                },
14351            )]))
14352        })
14353        .next()
14354        .await;
14355    format.await;
14356    assert_eq!(
14357        editor.update(cx, |editor, cx| editor.text(cx)),
14358        "import { a } from 'module';\n\nconst x = a;\n"
14359    );
14360
14361    editor.update_in(cx, |editor, window, cx| {
14362        editor.set_text(
14363            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
14364            window,
14365            cx,
14366        )
14367    });
14368    // Ensure we don't lock if code action hangs.
14369    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14370        move |params, _| async move {
14371            assert_eq!(
14372                params.text_document.uri,
14373                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
14374            );
14375            futures::future::pending::<()>().await;
14376            unreachable!()
14377        },
14378    );
14379    let format = editor
14380        .update_in(cx, |editor, window, cx| {
14381            editor.perform_code_action_kind(
14382                project,
14383                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14384                window,
14385                cx,
14386            )
14387        })
14388        .unwrap();
14389    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
14390    format.await;
14391    assert_eq!(
14392        editor.update(cx, |editor, cx| editor.text(cx)),
14393        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
14394    );
14395}
14396
14397#[gpui::test]
14398async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
14399    init_test(cx, |_| {});
14400
14401    let mut cx = EditorLspTestContext::new_rust(
14402        lsp::ServerCapabilities {
14403            document_formatting_provider: Some(lsp::OneOf::Left(true)),
14404            ..Default::default()
14405        },
14406        cx,
14407    )
14408    .await;
14409
14410    cx.set_state(indoc! {"
14411        one.twoˇ
14412    "});
14413
14414    // The format request takes a long time. When it completes, it inserts
14415    // a newline and an indent before the `.`
14416    cx.lsp
14417        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
14418            let executor = cx.background_executor().clone();
14419            async move {
14420                executor.timer(Duration::from_millis(100)).await;
14421                Ok(Some(vec![lsp::TextEdit {
14422                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
14423                    new_text: "\n    ".into(),
14424                }]))
14425            }
14426        });
14427
14428    // Submit a format request.
14429    let format_1 = cx
14430        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
14431        .unwrap();
14432    cx.executor().run_until_parked();
14433
14434    // Submit a second format request.
14435    let format_2 = cx
14436        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
14437        .unwrap();
14438    cx.executor().run_until_parked();
14439
14440    // Wait for both format requests to complete
14441    cx.executor().advance_clock(Duration::from_millis(200));
14442    format_1.await.unwrap();
14443    format_2.await.unwrap();
14444
14445    // The formatting edits only happens once.
14446    cx.assert_editor_state(indoc! {"
14447        one
14448            .twoˇ
14449    "});
14450}
14451
14452#[gpui::test]
14453async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
14454    init_test(cx, |settings| {
14455        settings.defaults.formatter = Some(FormatterList::default())
14456    });
14457
14458    let mut cx = EditorLspTestContext::new_rust(
14459        lsp::ServerCapabilities {
14460            document_formatting_provider: Some(lsp::OneOf::Left(true)),
14461            ..Default::default()
14462        },
14463        cx,
14464    )
14465    .await;
14466
14467    // Record which buffer changes have been sent to the language server
14468    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
14469    cx.lsp
14470        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
14471            let buffer_changes = buffer_changes.clone();
14472            move |params, _| {
14473                buffer_changes.lock().extend(
14474                    params
14475                        .content_changes
14476                        .into_iter()
14477                        .map(|e| (e.range.unwrap(), e.text)),
14478                );
14479            }
14480        });
14481    // Handle formatting requests to the language server.
14482    cx.lsp
14483        .set_request_handler::<lsp::request::Formatting, _, _>({
14484            move |_, _| {
14485                // Insert blank lines between each line of the buffer.
14486                async move {
14487                    // TODO: this assertion is not reliably true. Currently nothing guarantees that we deliver
14488                    // DidChangedTextDocument to the LSP before sending the formatting request.
14489                    // assert_eq!(
14490                    //     &buffer_changes.lock()[1..],
14491                    //     &[
14492                    //         (
14493                    //             lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
14494                    //             "".into()
14495                    //         ),
14496                    //         (
14497                    //             lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
14498                    //             "".into()
14499                    //         ),
14500                    //         (
14501                    //             lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
14502                    //             "\n".into()
14503                    //         ),
14504                    //     ]
14505                    // );
14506
14507                    Ok(Some(vec![
14508                        lsp::TextEdit {
14509                            range: lsp::Range::new(
14510                                lsp::Position::new(1, 0),
14511                                lsp::Position::new(1, 0),
14512                            ),
14513                            new_text: "\n".into(),
14514                        },
14515                        lsp::TextEdit {
14516                            range: lsp::Range::new(
14517                                lsp::Position::new(2, 0),
14518                                lsp::Position::new(2, 0),
14519                            ),
14520                            new_text: "\n".into(),
14521                        },
14522                    ]))
14523                }
14524            }
14525        });
14526
14527    // Set up a buffer white some trailing whitespace and no trailing newline.
14528    cx.set_state(
14529        &[
14530            "one ",   //
14531            "twoˇ",   //
14532            "three ", //
14533            "four",   //
14534        ]
14535        .join("\n"),
14536    );
14537
14538    // Submit a format request.
14539    let format = cx
14540        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
14541        .unwrap();
14542
14543    cx.run_until_parked();
14544    // After formatting the buffer, the trailing whitespace is stripped,
14545    // a newline is appended, and the edits provided by the language server
14546    // have been applied.
14547    format.await.unwrap();
14548
14549    cx.assert_editor_state(
14550        &[
14551            "one",   //
14552            "",      //
14553            "twoˇ",  //
14554            "",      //
14555            "three", //
14556            "four",  //
14557            "",      //
14558        ]
14559        .join("\n"),
14560    );
14561
14562    // Undoing the formatting undoes the trailing whitespace removal, the
14563    // trailing newline, and the LSP edits.
14564    cx.update_buffer(|buffer, cx| buffer.undo(cx));
14565    cx.assert_editor_state(
14566        &[
14567            "one ",   //
14568            "twoˇ",   //
14569            "three ", //
14570            "four",   //
14571        ]
14572        .join("\n"),
14573    );
14574}
14575
14576#[gpui::test]
14577async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
14578    cx: &mut TestAppContext,
14579) {
14580    init_test(cx, |_| {});
14581
14582    cx.update(|cx| {
14583        cx.update_global::<SettingsStore, _>(|settings, cx| {
14584            settings.update_user_settings(cx, |settings| {
14585                settings.editor.auto_signature_help = Some(true);
14586                settings.editor.hover_popover_delay = Some(DelayMs(300));
14587            });
14588        });
14589    });
14590
14591    let mut cx = EditorLspTestContext::new_rust(
14592        lsp::ServerCapabilities {
14593            signature_help_provider: Some(lsp::SignatureHelpOptions {
14594                ..Default::default()
14595            }),
14596            ..Default::default()
14597        },
14598        cx,
14599    )
14600    .await;
14601
14602    let language = Language::new(
14603        LanguageConfig {
14604            name: "Rust".into(),
14605            brackets: BracketPairConfig {
14606                pairs: vec![
14607                    BracketPair {
14608                        start: "{".to_string(),
14609                        end: "}".to_string(),
14610                        close: true,
14611                        surround: true,
14612                        newline: true,
14613                    },
14614                    BracketPair {
14615                        start: "(".to_string(),
14616                        end: ")".to_string(),
14617                        close: true,
14618                        surround: true,
14619                        newline: true,
14620                    },
14621                    BracketPair {
14622                        start: "/*".to_string(),
14623                        end: " */".to_string(),
14624                        close: true,
14625                        surround: true,
14626                        newline: true,
14627                    },
14628                    BracketPair {
14629                        start: "[".to_string(),
14630                        end: "]".to_string(),
14631                        close: false,
14632                        surround: false,
14633                        newline: true,
14634                    },
14635                    BracketPair {
14636                        start: "\"".to_string(),
14637                        end: "\"".to_string(),
14638                        close: true,
14639                        surround: true,
14640                        newline: false,
14641                    },
14642                    BracketPair {
14643                        start: "<".to_string(),
14644                        end: ">".to_string(),
14645                        close: false,
14646                        surround: true,
14647                        newline: true,
14648                    },
14649                ],
14650                ..Default::default()
14651            },
14652            autoclose_before: "})]".to_string(),
14653            ..Default::default()
14654        },
14655        Some(tree_sitter_rust::LANGUAGE.into()),
14656    );
14657    let language = Arc::new(language);
14658
14659    cx.language_registry().add(language.clone());
14660    cx.update_buffer(|buffer, cx| {
14661        buffer.set_language(Some(language), cx);
14662    });
14663
14664    cx.set_state(
14665        &r#"
14666            fn main() {
14667                sampleˇ
14668            }
14669        "#
14670        .unindent(),
14671    );
14672
14673    cx.update_editor(|editor, window, cx| {
14674        editor.handle_input("(", window, cx);
14675    });
14676    cx.assert_editor_state(
14677        &"
14678            fn main() {
14679                sample(ˇ)
14680            }
14681        "
14682        .unindent(),
14683    );
14684
14685    let mocked_response = lsp::SignatureHelp {
14686        signatures: vec![lsp::SignatureInformation {
14687            label: "fn sample(param1: u8, param2: u8)".to_string(),
14688            documentation: None,
14689            parameters: Some(vec![
14690                lsp::ParameterInformation {
14691                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14692                    documentation: None,
14693                },
14694                lsp::ParameterInformation {
14695                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14696                    documentation: None,
14697                },
14698            ]),
14699            active_parameter: None,
14700        }],
14701        active_signature: Some(0),
14702        active_parameter: Some(0),
14703    };
14704    handle_signature_help_request(&mut cx, mocked_response).await;
14705
14706    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14707        .await;
14708
14709    cx.editor(|editor, _, _| {
14710        let signature_help_state = editor.signature_help_state.popover().cloned();
14711        let signature = signature_help_state.unwrap();
14712        assert_eq!(
14713            signature.signatures[signature.current_signature].label,
14714            "fn sample(param1: u8, param2: u8)"
14715        );
14716    });
14717}
14718
14719#[gpui::test]
14720async fn test_signature_help_delay_only_for_auto(cx: &mut TestAppContext) {
14721    init_test(cx, |_| {});
14722
14723    let delay_ms = 500;
14724    cx.update(|cx| {
14725        cx.update_global::<SettingsStore, _>(|settings, cx| {
14726            settings.update_user_settings(cx, |settings| {
14727                settings.editor.auto_signature_help = Some(true);
14728                settings.editor.show_signature_help_after_edits = Some(false);
14729                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
14730            });
14731        });
14732    });
14733
14734    let mut cx = EditorLspTestContext::new_rust(
14735        lsp::ServerCapabilities {
14736            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14737            ..lsp::ServerCapabilities::default()
14738        },
14739        cx,
14740    )
14741    .await;
14742
14743    let mocked_response = lsp::SignatureHelp {
14744        signatures: vec![lsp::SignatureInformation {
14745            label: "fn sample(param1: u8)".to_string(),
14746            documentation: None,
14747            parameters: Some(vec![lsp::ParameterInformation {
14748                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14749                documentation: None,
14750            }]),
14751            active_parameter: None,
14752        }],
14753        active_signature: Some(0),
14754        active_parameter: Some(0),
14755    };
14756
14757    cx.set_state(indoc! {"
14758        fn main() {
14759            sample(ˇ);
14760        }
14761
14762        fn sample(param1: u8) {}
14763    "});
14764
14765    // Manual trigger should show immediately without delay
14766    cx.update_editor(|editor, window, cx| {
14767        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14768    });
14769    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14770    cx.run_until_parked();
14771    cx.editor(|editor, _, _| {
14772        assert!(
14773            editor.signature_help_state.is_shown(),
14774            "Manual trigger should show signature help without delay"
14775        );
14776    });
14777
14778    cx.update_editor(|editor, _, cx| {
14779        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
14780    });
14781    cx.run_until_parked();
14782    cx.editor(|editor, _, _| {
14783        assert!(!editor.signature_help_state.is_shown());
14784    });
14785
14786    // Auto trigger (cursor movement into brackets) should respect delay
14787    cx.set_state(indoc! {"
14788        fn main() {
14789            sampleˇ();
14790        }
14791
14792        fn sample(param1: u8) {}
14793    "});
14794    cx.update_editor(|editor, window, cx| {
14795        editor.move_right(&MoveRight, window, cx);
14796    });
14797    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14798    cx.run_until_parked();
14799    cx.editor(|editor, _, _| {
14800        assert!(
14801            !editor.signature_help_state.is_shown(),
14802            "Auto trigger should wait for delay before showing signature help"
14803        );
14804    });
14805
14806    cx.executor()
14807        .advance_clock(Duration::from_millis(delay_ms + 50));
14808    cx.run_until_parked();
14809    cx.editor(|editor, _, _| {
14810        assert!(
14811            editor.signature_help_state.is_shown(),
14812            "Auto trigger should show signature help after delay elapsed"
14813        );
14814    });
14815}
14816
14817#[gpui::test]
14818async fn test_signature_help_after_edits_no_delay(cx: &mut TestAppContext) {
14819    init_test(cx, |_| {});
14820
14821    let delay_ms = 500;
14822    cx.update(|cx| {
14823        cx.update_global::<SettingsStore, _>(|settings, cx| {
14824            settings.update_user_settings(cx, |settings| {
14825                settings.editor.auto_signature_help = Some(false);
14826                settings.editor.show_signature_help_after_edits = Some(true);
14827                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
14828            });
14829        });
14830    });
14831
14832    let mut cx = EditorLspTestContext::new_rust(
14833        lsp::ServerCapabilities {
14834            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14835            ..lsp::ServerCapabilities::default()
14836        },
14837        cx,
14838    )
14839    .await;
14840
14841    let language = Arc::new(Language::new(
14842        LanguageConfig {
14843            name: "Rust".into(),
14844            brackets: BracketPairConfig {
14845                pairs: vec![BracketPair {
14846                    start: "(".to_string(),
14847                    end: ")".to_string(),
14848                    close: true,
14849                    surround: true,
14850                    newline: true,
14851                }],
14852                ..BracketPairConfig::default()
14853            },
14854            autoclose_before: "})".to_string(),
14855            ..LanguageConfig::default()
14856        },
14857        Some(tree_sitter_rust::LANGUAGE.into()),
14858    ));
14859    cx.language_registry().add(language.clone());
14860    cx.update_buffer(|buffer, cx| {
14861        buffer.set_language(Some(language), cx);
14862    });
14863
14864    let mocked_response = lsp::SignatureHelp {
14865        signatures: vec![lsp::SignatureInformation {
14866            label: "fn sample(param1: u8)".to_string(),
14867            documentation: None,
14868            parameters: Some(vec![lsp::ParameterInformation {
14869                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14870                documentation: None,
14871            }]),
14872            active_parameter: None,
14873        }],
14874        active_signature: Some(0),
14875        active_parameter: Some(0),
14876    };
14877
14878    cx.set_state(indoc! {"
14879        fn main() {
14880            sampleˇ
14881        }
14882    "});
14883
14884    // Typing bracket should show signature help immediately without delay
14885    cx.update_editor(|editor, window, cx| {
14886        editor.handle_input("(", window, cx);
14887    });
14888    handle_signature_help_request(&mut cx, mocked_response).await;
14889    cx.run_until_parked();
14890    cx.editor(|editor, _, _| {
14891        assert!(
14892            editor.signature_help_state.is_shown(),
14893            "show_signature_help_after_edits should show signature help without delay"
14894        );
14895    });
14896}
14897
14898#[gpui::test]
14899async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
14900    init_test(cx, |_| {});
14901
14902    cx.update(|cx| {
14903        cx.update_global::<SettingsStore, _>(|settings, cx| {
14904            settings.update_user_settings(cx, |settings| {
14905                settings.editor.auto_signature_help = Some(false);
14906                settings.editor.show_signature_help_after_edits = Some(false);
14907            });
14908        });
14909    });
14910
14911    let mut cx = EditorLspTestContext::new_rust(
14912        lsp::ServerCapabilities {
14913            signature_help_provider: Some(lsp::SignatureHelpOptions {
14914                ..Default::default()
14915            }),
14916            ..Default::default()
14917        },
14918        cx,
14919    )
14920    .await;
14921
14922    let language = Language::new(
14923        LanguageConfig {
14924            name: "Rust".into(),
14925            brackets: BracketPairConfig {
14926                pairs: vec![
14927                    BracketPair {
14928                        start: "{".to_string(),
14929                        end: "}".to_string(),
14930                        close: true,
14931                        surround: true,
14932                        newline: true,
14933                    },
14934                    BracketPair {
14935                        start: "(".to_string(),
14936                        end: ")".to_string(),
14937                        close: true,
14938                        surround: true,
14939                        newline: true,
14940                    },
14941                    BracketPair {
14942                        start: "/*".to_string(),
14943                        end: " */".to_string(),
14944                        close: true,
14945                        surround: true,
14946                        newline: true,
14947                    },
14948                    BracketPair {
14949                        start: "[".to_string(),
14950                        end: "]".to_string(),
14951                        close: false,
14952                        surround: false,
14953                        newline: true,
14954                    },
14955                    BracketPair {
14956                        start: "\"".to_string(),
14957                        end: "\"".to_string(),
14958                        close: true,
14959                        surround: true,
14960                        newline: false,
14961                    },
14962                    BracketPair {
14963                        start: "<".to_string(),
14964                        end: ">".to_string(),
14965                        close: false,
14966                        surround: true,
14967                        newline: true,
14968                    },
14969                ],
14970                ..Default::default()
14971            },
14972            autoclose_before: "})]".to_string(),
14973            ..Default::default()
14974        },
14975        Some(tree_sitter_rust::LANGUAGE.into()),
14976    );
14977    let language = Arc::new(language);
14978
14979    cx.language_registry().add(language.clone());
14980    cx.update_buffer(|buffer, cx| {
14981        buffer.set_language(Some(language), cx);
14982    });
14983
14984    // Ensure that signature_help is not called when no signature help is enabled.
14985    cx.set_state(
14986        &r#"
14987            fn main() {
14988                sampleˇ
14989            }
14990        "#
14991        .unindent(),
14992    );
14993    cx.update_editor(|editor, window, cx| {
14994        editor.handle_input("(", window, cx);
14995    });
14996    cx.assert_editor_state(
14997        &"
14998            fn main() {
14999                sample(ˇ)
15000            }
15001        "
15002        .unindent(),
15003    );
15004    cx.editor(|editor, _, _| {
15005        assert!(editor.signature_help_state.task().is_none());
15006    });
15007
15008    let mocked_response = lsp::SignatureHelp {
15009        signatures: vec![lsp::SignatureInformation {
15010            label: "fn sample(param1: u8, param2: u8)".to_string(),
15011            documentation: None,
15012            parameters: Some(vec![
15013                lsp::ParameterInformation {
15014                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15015                    documentation: None,
15016                },
15017                lsp::ParameterInformation {
15018                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15019                    documentation: None,
15020                },
15021            ]),
15022            active_parameter: None,
15023        }],
15024        active_signature: Some(0),
15025        active_parameter: Some(0),
15026    };
15027
15028    // Ensure that signature_help is called when enabled afte edits
15029    cx.update(|_, cx| {
15030        cx.update_global::<SettingsStore, _>(|settings, cx| {
15031            settings.update_user_settings(cx, |settings| {
15032                settings.editor.auto_signature_help = Some(false);
15033                settings.editor.show_signature_help_after_edits = Some(true);
15034            });
15035        });
15036    });
15037    cx.set_state(
15038        &r#"
15039            fn main() {
15040                sampleˇ
15041            }
15042        "#
15043        .unindent(),
15044    );
15045    cx.update_editor(|editor, window, cx| {
15046        editor.handle_input("(", window, cx);
15047    });
15048    cx.assert_editor_state(
15049        &"
15050            fn main() {
15051                sample(ˇ)
15052            }
15053        "
15054        .unindent(),
15055    );
15056    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15057    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15058        .await;
15059    cx.update_editor(|editor, _, _| {
15060        let signature_help_state = editor.signature_help_state.popover().cloned();
15061        assert!(signature_help_state.is_some());
15062        let signature = signature_help_state.unwrap();
15063        assert_eq!(
15064            signature.signatures[signature.current_signature].label,
15065            "fn sample(param1: u8, param2: u8)"
15066        );
15067        editor.signature_help_state = SignatureHelpState::default();
15068    });
15069
15070    // Ensure that signature_help is called when auto signature help override is enabled
15071    cx.update(|_, cx| {
15072        cx.update_global::<SettingsStore, _>(|settings, cx| {
15073            settings.update_user_settings(cx, |settings| {
15074                settings.editor.auto_signature_help = Some(true);
15075                settings.editor.show_signature_help_after_edits = Some(false);
15076            });
15077        });
15078    });
15079    cx.set_state(
15080        &r#"
15081            fn main() {
15082                sampleˇ
15083            }
15084        "#
15085        .unindent(),
15086    );
15087    cx.update_editor(|editor, window, cx| {
15088        editor.handle_input("(", window, cx);
15089    });
15090    cx.assert_editor_state(
15091        &"
15092            fn main() {
15093                sample(ˇ)
15094            }
15095        "
15096        .unindent(),
15097    );
15098    handle_signature_help_request(&mut cx, mocked_response).await;
15099    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15100        .await;
15101    cx.editor(|editor, _, _| {
15102        let signature_help_state = editor.signature_help_state.popover().cloned();
15103        assert!(signature_help_state.is_some());
15104        let signature = signature_help_state.unwrap();
15105        assert_eq!(
15106            signature.signatures[signature.current_signature].label,
15107            "fn sample(param1: u8, param2: u8)"
15108        );
15109    });
15110}
15111
15112#[gpui::test]
15113async fn test_signature_help(cx: &mut TestAppContext) {
15114    init_test(cx, |_| {});
15115    cx.update(|cx| {
15116        cx.update_global::<SettingsStore, _>(|settings, cx| {
15117            settings.update_user_settings(cx, |settings| {
15118                settings.editor.auto_signature_help = Some(true);
15119            });
15120        });
15121    });
15122
15123    let mut cx = EditorLspTestContext::new_rust(
15124        lsp::ServerCapabilities {
15125            signature_help_provider: Some(lsp::SignatureHelpOptions {
15126                ..Default::default()
15127            }),
15128            ..Default::default()
15129        },
15130        cx,
15131    )
15132    .await;
15133
15134    // A test that directly calls `show_signature_help`
15135    cx.update_editor(|editor, window, cx| {
15136        editor.show_signature_help(&ShowSignatureHelp, window, cx);
15137    });
15138
15139    let mocked_response = lsp::SignatureHelp {
15140        signatures: vec![lsp::SignatureInformation {
15141            label: "fn sample(param1: u8, param2: u8)".to_string(),
15142            documentation: None,
15143            parameters: Some(vec![
15144                lsp::ParameterInformation {
15145                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15146                    documentation: None,
15147                },
15148                lsp::ParameterInformation {
15149                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15150                    documentation: None,
15151                },
15152            ]),
15153            active_parameter: None,
15154        }],
15155        active_signature: Some(0),
15156        active_parameter: Some(0),
15157    };
15158    handle_signature_help_request(&mut cx, mocked_response).await;
15159
15160    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15161        .await;
15162
15163    cx.editor(|editor, _, _| {
15164        let signature_help_state = editor.signature_help_state.popover().cloned();
15165        assert!(signature_help_state.is_some());
15166        let signature = signature_help_state.unwrap();
15167        assert_eq!(
15168            signature.signatures[signature.current_signature].label,
15169            "fn sample(param1: u8, param2: u8)"
15170        );
15171    });
15172
15173    // When exiting outside from inside the brackets, `signature_help` is closed.
15174    cx.set_state(indoc! {"
15175        fn main() {
15176            sample(ˇ);
15177        }
15178
15179        fn sample(param1: u8, param2: u8) {}
15180    "});
15181
15182    cx.update_editor(|editor, window, cx| {
15183        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15184            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
15185        });
15186    });
15187
15188    let mocked_response = lsp::SignatureHelp {
15189        signatures: Vec::new(),
15190        active_signature: None,
15191        active_parameter: None,
15192    };
15193    handle_signature_help_request(&mut cx, mocked_response).await;
15194
15195    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
15196        .await;
15197
15198    cx.editor(|editor, _, _| {
15199        assert!(!editor.signature_help_state.is_shown());
15200    });
15201
15202    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
15203    cx.set_state(indoc! {"
15204        fn main() {
15205            sample(ˇ);
15206        }
15207
15208        fn sample(param1: u8, param2: u8) {}
15209    "});
15210
15211    let mocked_response = lsp::SignatureHelp {
15212        signatures: vec![lsp::SignatureInformation {
15213            label: "fn sample(param1: u8, param2: u8)".to_string(),
15214            documentation: None,
15215            parameters: Some(vec![
15216                lsp::ParameterInformation {
15217                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15218                    documentation: None,
15219                },
15220                lsp::ParameterInformation {
15221                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15222                    documentation: None,
15223                },
15224            ]),
15225            active_parameter: None,
15226        }],
15227        active_signature: Some(0),
15228        active_parameter: Some(0),
15229    };
15230    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15231    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15232        .await;
15233    cx.editor(|editor, _, _| {
15234        assert!(editor.signature_help_state.is_shown());
15235    });
15236
15237    // Restore the popover with more parameter input
15238    cx.set_state(indoc! {"
15239        fn main() {
15240            sample(param1, param2ˇ);
15241        }
15242
15243        fn sample(param1: u8, param2: u8) {}
15244    "});
15245
15246    let mocked_response = lsp::SignatureHelp {
15247        signatures: vec![lsp::SignatureInformation {
15248            label: "fn sample(param1: u8, param2: u8)".to_string(),
15249            documentation: None,
15250            parameters: Some(vec![
15251                lsp::ParameterInformation {
15252                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15253                    documentation: None,
15254                },
15255                lsp::ParameterInformation {
15256                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15257                    documentation: None,
15258                },
15259            ]),
15260            active_parameter: None,
15261        }],
15262        active_signature: Some(0),
15263        active_parameter: Some(1),
15264    };
15265    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15266    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15267        .await;
15268
15269    // When selecting a range, the popover is gone.
15270    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
15271    cx.update_editor(|editor, window, cx| {
15272        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15273            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
15274        })
15275    });
15276    cx.assert_editor_state(indoc! {"
15277        fn main() {
15278            sample(param1, «ˇparam2»);
15279        }
15280
15281        fn sample(param1: u8, param2: u8) {}
15282    "});
15283    cx.editor(|editor, _, _| {
15284        assert!(!editor.signature_help_state.is_shown());
15285    });
15286
15287    // When unselecting again, the popover is back if within the brackets.
15288    cx.update_editor(|editor, window, cx| {
15289        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15290            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
15291        })
15292    });
15293    cx.assert_editor_state(indoc! {"
15294        fn main() {
15295            sample(param1, ˇparam2);
15296        }
15297
15298        fn sample(param1: u8, param2: u8) {}
15299    "});
15300    handle_signature_help_request(&mut cx, mocked_response).await;
15301    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15302        .await;
15303    cx.editor(|editor, _, _| {
15304        assert!(editor.signature_help_state.is_shown());
15305    });
15306
15307    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
15308    cx.update_editor(|editor, window, cx| {
15309        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15310            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
15311            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
15312        })
15313    });
15314    cx.assert_editor_state(indoc! {"
15315        fn main() {
15316            sample(param1, ˇparam2);
15317        }
15318
15319        fn sample(param1: u8, param2: u8) {}
15320    "});
15321
15322    let mocked_response = lsp::SignatureHelp {
15323        signatures: vec![lsp::SignatureInformation {
15324            label: "fn sample(param1: u8, param2: u8)".to_string(),
15325            documentation: None,
15326            parameters: Some(vec![
15327                lsp::ParameterInformation {
15328                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15329                    documentation: None,
15330                },
15331                lsp::ParameterInformation {
15332                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15333                    documentation: None,
15334                },
15335            ]),
15336            active_parameter: None,
15337        }],
15338        active_signature: Some(0),
15339        active_parameter: Some(1),
15340    };
15341    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15342    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15343        .await;
15344    cx.update_editor(|editor, _, cx| {
15345        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
15346    });
15347    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
15348        .await;
15349    cx.update_editor(|editor, window, cx| {
15350        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15351            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
15352        })
15353    });
15354    cx.assert_editor_state(indoc! {"
15355        fn main() {
15356            sample(param1, «ˇparam2»);
15357        }
15358
15359        fn sample(param1: u8, param2: u8) {}
15360    "});
15361    cx.update_editor(|editor, window, cx| {
15362        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15363            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
15364        })
15365    });
15366    cx.assert_editor_state(indoc! {"
15367        fn main() {
15368            sample(param1, ˇparam2);
15369        }
15370
15371        fn sample(param1: u8, param2: u8) {}
15372    "});
15373    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
15374        .await;
15375}
15376
15377#[gpui::test]
15378async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
15379    init_test(cx, |_| {});
15380
15381    let mut cx = EditorLspTestContext::new_rust(
15382        lsp::ServerCapabilities {
15383            signature_help_provider: Some(lsp::SignatureHelpOptions {
15384                ..Default::default()
15385            }),
15386            ..Default::default()
15387        },
15388        cx,
15389    )
15390    .await;
15391
15392    cx.set_state(indoc! {"
15393        fn main() {
15394            overloadedˇ
15395        }
15396    "});
15397
15398    cx.update_editor(|editor, window, cx| {
15399        editor.handle_input("(", window, cx);
15400        editor.show_signature_help(&ShowSignatureHelp, window, cx);
15401    });
15402
15403    // Mock response with 3 signatures
15404    let mocked_response = lsp::SignatureHelp {
15405        signatures: vec![
15406            lsp::SignatureInformation {
15407                label: "fn overloaded(x: i32)".to_string(),
15408                documentation: None,
15409                parameters: Some(vec![lsp::ParameterInformation {
15410                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
15411                    documentation: None,
15412                }]),
15413                active_parameter: None,
15414            },
15415            lsp::SignatureInformation {
15416                label: "fn overloaded(x: i32, y: i32)".to_string(),
15417                documentation: None,
15418                parameters: Some(vec![
15419                    lsp::ParameterInformation {
15420                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
15421                        documentation: None,
15422                    },
15423                    lsp::ParameterInformation {
15424                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
15425                        documentation: None,
15426                    },
15427                ]),
15428                active_parameter: None,
15429            },
15430            lsp::SignatureInformation {
15431                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
15432                documentation: None,
15433                parameters: Some(vec![
15434                    lsp::ParameterInformation {
15435                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
15436                        documentation: None,
15437                    },
15438                    lsp::ParameterInformation {
15439                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
15440                        documentation: None,
15441                    },
15442                    lsp::ParameterInformation {
15443                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
15444                        documentation: None,
15445                    },
15446                ]),
15447                active_parameter: None,
15448            },
15449        ],
15450        active_signature: Some(1),
15451        active_parameter: Some(0),
15452    };
15453    handle_signature_help_request(&mut cx, mocked_response).await;
15454
15455    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15456        .await;
15457
15458    // Verify we have multiple signatures and the right one is selected
15459    cx.editor(|editor, _, _| {
15460        let popover = editor.signature_help_state.popover().cloned().unwrap();
15461        assert_eq!(popover.signatures.len(), 3);
15462        // active_signature was 1, so that should be the current
15463        assert_eq!(popover.current_signature, 1);
15464        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
15465        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
15466        assert_eq!(
15467            popover.signatures[2].label,
15468            "fn overloaded(x: i32, y: i32, z: i32)"
15469        );
15470    });
15471
15472    // Test navigation functionality
15473    cx.update_editor(|editor, window, cx| {
15474        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
15475    });
15476
15477    cx.editor(|editor, _, _| {
15478        let popover = editor.signature_help_state.popover().cloned().unwrap();
15479        assert_eq!(popover.current_signature, 2);
15480    });
15481
15482    // Test wrap around
15483    cx.update_editor(|editor, window, cx| {
15484        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
15485    });
15486
15487    cx.editor(|editor, _, _| {
15488        let popover = editor.signature_help_state.popover().cloned().unwrap();
15489        assert_eq!(popover.current_signature, 0);
15490    });
15491
15492    // Test previous navigation
15493    cx.update_editor(|editor, window, cx| {
15494        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
15495    });
15496
15497    cx.editor(|editor, _, _| {
15498        let popover = editor.signature_help_state.popover().cloned().unwrap();
15499        assert_eq!(popover.current_signature, 2);
15500    });
15501}
15502
15503#[gpui::test]
15504async fn test_completion_mode(cx: &mut TestAppContext) {
15505    init_test(cx, |_| {});
15506    let mut cx = EditorLspTestContext::new_rust(
15507        lsp::ServerCapabilities {
15508            completion_provider: Some(lsp::CompletionOptions {
15509                resolve_provider: Some(true),
15510                ..Default::default()
15511            }),
15512            ..Default::default()
15513        },
15514        cx,
15515    )
15516    .await;
15517
15518    struct Run {
15519        run_description: &'static str,
15520        initial_state: String,
15521        buffer_marked_text: String,
15522        completion_label: &'static str,
15523        completion_text: &'static str,
15524        expected_with_insert_mode: String,
15525        expected_with_replace_mode: String,
15526        expected_with_replace_subsequence_mode: String,
15527        expected_with_replace_suffix_mode: String,
15528    }
15529
15530    let runs = [
15531        Run {
15532            run_description: "Start of word matches completion text",
15533            initial_state: "before ediˇ after".into(),
15534            buffer_marked_text: "before <edi|> after".into(),
15535            completion_label: "editor",
15536            completion_text: "editor",
15537            expected_with_insert_mode: "before editorˇ after".into(),
15538            expected_with_replace_mode: "before editorˇ after".into(),
15539            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
15540            expected_with_replace_suffix_mode: "before editorˇ after".into(),
15541        },
15542        Run {
15543            run_description: "Accept same text at the middle of the word",
15544            initial_state: "before ediˇtor after".into(),
15545            buffer_marked_text: "before <edi|tor> after".into(),
15546            completion_label: "editor",
15547            completion_text: "editor",
15548            expected_with_insert_mode: "before editorˇtor after".into(),
15549            expected_with_replace_mode: "before editorˇ after".into(),
15550            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
15551            expected_with_replace_suffix_mode: "before editorˇ after".into(),
15552        },
15553        Run {
15554            run_description: "End of word matches completion text -- cursor at end",
15555            initial_state: "before torˇ after".into(),
15556            buffer_marked_text: "before <tor|> after".into(),
15557            completion_label: "editor",
15558            completion_text: "editor",
15559            expected_with_insert_mode: "before editorˇ after".into(),
15560            expected_with_replace_mode: "before editorˇ after".into(),
15561            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
15562            expected_with_replace_suffix_mode: "before editorˇ after".into(),
15563        },
15564        Run {
15565            run_description: "End of word matches completion text -- cursor at start",
15566            initial_state: "before ˇtor after".into(),
15567            buffer_marked_text: "before <|tor> after".into(),
15568            completion_label: "editor",
15569            completion_text: "editor",
15570            expected_with_insert_mode: "before editorˇtor after".into(),
15571            expected_with_replace_mode: "before editorˇ after".into(),
15572            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
15573            expected_with_replace_suffix_mode: "before editorˇ after".into(),
15574        },
15575        Run {
15576            run_description: "Prepend text containing whitespace",
15577            initial_state: "pˇfield: bool".into(),
15578            buffer_marked_text: "<p|field>: bool".into(),
15579            completion_label: "pub ",
15580            completion_text: "pub ",
15581            expected_with_insert_mode: "pub ˇfield: bool".into(),
15582            expected_with_replace_mode: "pub ˇ: bool".into(),
15583            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
15584            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
15585        },
15586        Run {
15587            run_description: "Add element to start of list",
15588            initial_state: "[element_ˇelement_2]".into(),
15589            buffer_marked_text: "[<element_|element_2>]".into(),
15590            completion_label: "element_1",
15591            completion_text: "element_1",
15592            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
15593            expected_with_replace_mode: "[element_1ˇ]".into(),
15594            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
15595            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
15596        },
15597        Run {
15598            run_description: "Add element to start of list -- first and second elements are equal",
15599            initial_state: "[elˇelement]".into(),
15600            buffer_marked_text: "[<el|element>]".into(),
15601            completion_label: "element",
15602            completion_text: "element",
15603            expected_with_insert_mode: "[elementˇelement]".into(),
15604            expected_with_replace_mode: "[elementˇ]".into(),
15605            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
15606            expected_with_replace_suffix_mode: "[elementˇ]".into(),
15607        },
15608        Run {
15609            run_description: "Ends with matching suffix",
15610            initial_state: "SubˇError".into(),
15611            buffer_marked_text: "<Sub|Error>".into(),
15612            completion_label: "SubscriptionError",
15613            completion_text: "SubscriptionError",
15614            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
15615            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
15616            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
15617            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
15618        },
15619        Run {
15620            run_description: "Suffix is a subsequence -- contiguous",
15621            initial_state: "SubˇErr".into(),
15622            buffer_marked_text: "<Sub|Err>".into(),
15623            completion_label: "SubscriptionError",
15624            completion_text: "SubscriptionError",
15625            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
15626            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
15627            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
15628            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
15629        },
15630        Run {
15631            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
15632            initial_state: "Suˇscrirr".into(),
15633            buffer_marked_text: "<Su|scrirr>".into(),
15634            completion_label: "SubscriptionError",
15635            completion_text: "SubscriptionError",
15636            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
15637            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
15638            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
15639            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
15640        },
15641        Run {
15642            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
15643            initial_state: "foo(indˇix)".into(),
15644            buffer_marked_text: "foo(<ind|ix>)".into(),
15645            completion_label: "node_index",
15646            completion_text: "node_index",
15647            expected_with_insert_mode: "foo(node_indexˇix)".into(),
15648            expected_with_replace_mode: "foo(node_indexˇ)".into(),
15649            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
15650            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
15651        },
15652        Run {
15653            run_description: "Replace range ends before cursor - should extend to cursor",
15654            initial_state: "before editˇo after".into(),
15655            buffer_marked_text: "before <{ed}>it|o after".into(),
15656            completion_label: "editor",
15657            completion_text: "editor",
15658            expected_with_insert_mode: "before editorˇo after".into(),
15659            expected_with_replace_mode: "before editorˇo after".into(),
15660            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
15661            expected_with_replace_suffix_mode: "before editorˇo after".into(),
15662        },
15663        Run {
15664            run_description: "Uses label for suffix matching",
15665            initial_state: "before ediˇtor after".into(),
15666            buffer_marked_text: "before <edi|tor> after".into(),
15667            completion_label: "editor",
15668            completion_text: "editor()",
15669            expected_with_insert_mode: "before editor()ˇtor after".into(),
15670            expected_with_replace_mode: "before editor()ˇ after".into(),
15671            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
15672            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
15673        },
15674        Run {
15675            run_description: "Case insensitive subsequence and suffix matching",
15676            initial_state: "before EDiˇtoR after".into(),
15677            buffer_marked_text: "before <EDi|toR> after".into(),
15678            completion_label: "editor",
15679            completion_text: "editor",
15680            expected_with_insert_mode: "before editorˇtoR after".into(),
15681            expected_with_replace_mode: "before editorˇ after".into(),
15682            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
15683            expected_with_replace_suffix_mode: "before editorˇ after".into(),
15684        },
15685    ];
15686
15687    for run in runs {
15688        let run_variations = [
15689            (LspInsertMode::Insert, run.expected_with_insert_mode),
15690            (LspInsertMode::Replace, run.expected_with_replace_mode),
15691            (
15692                LspInsertMode::ReplaceSubsequence,
15693                run.expected_with_replace_subsequence_mode,
15694            ),
15695            (
15696                LspInsertMode::ReplaceSuffix,
15697                run.expected_with_replace_suffix_mode,
15698            ),
15699        ];
15700
15701        for (lsp_insert_mode, expected_text) in run_variations {
15702            eprintln!(
15703                "run = {:?}, mode = {lsp_insert_mode:.?}",
15704                run.run_description,
15705            );
15706
15707            update_test_language_settings(&mut cx, &|settings| {
15708                settings.defaults.completions = Some(CompletionSettingsContent {
15709                    lsp_insert_mode: Some(lsp_insert_mode),
15710                    words: Some(WordsCompletionMode::Disabled),
15711                    words_min_length: Some(0),
15712                    ..Default::default()
15713                });
15714            });
15715
15716            cx.set_state(&run.initial_state);
15717
15718            // Set up resolve handler before showing completions, since resolve may be
15719            // triggered when menu becomes visible (for documentation), not just on confirm.
15720            cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
15721                move |_, _, _| async move {
15722                    Ok(lsp::CompletionItem {
15723                        additional_text_edits: None,
15724                        ..Default::default()
15725                    })
15726                },
15727            );
15728
15729            cx.update_editor(|editor, window, cx| {
15730                editor.show_completions(&ShowCompletions, window, cx);
15731            });
15732
15733            let counter = Arc::new(AtomicUsize::new(0));
15734            handle_completion_request_with_insert_and_replace(
15735                &mut cx,
15736                &run.buffer_marked_text,
15737                vec![(run.completion_label, run.completion_text)],
15738                counter.clone(),
15739            )
15740            .await;
15741            cx.condition(|editor, _| editor.context_menu_visible())
15742                .await;
15743            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15744
15745            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15746                editor
15747                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
15748                    .unwrap()
15749            });
15750            cx.assert_editor_state(&expected_text);
15751            apply_additional_edits.await.unwrap();
15752        }
15753    }
15754}
15755
15756#[gpui::test]
15757async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
15758    init_test(cx, |_| {});
15759    let mut cx = EditorLspTestContext::new_rust(
15760        lsp::ServerCapabilities {
15761            completion_provider: Some(lsp::CompletionOptions {
15762                resolve_provider: Some(true),
15763                ..Default::default()
15764            }),
15765            ..Default::default()
15766        },
15767        cx,
15768    )
15769    .await;
15770
15771    let initial_state = "SubˇError";
15772    let buffer_marked_text = "<Sub|Error>";
15773    let completion_text = "SubscriptionError";
15774    let expected_with_insert_mode = "SubscriptionErrorˇError";
15775    let expected_with_replace_mode = "SubscriptionErrorˇ";
15776
15777    update_test_language_settings(&mut cx, &|settings| {
15778        settings.defaults.completions = Some(CompletionSettingsContent {
15779            words: Some(WordsCompletionMode::Disabled),
15780            words_min_length: Some(0),
15781            // set the opposite here to ensure that the action is overriding the default behavior
15782            lsp_insert_mode: Some(LspInsertMode::Insert),
15783            ..Default::default()
15784        });
15785    });
15786
15787    cx.set_state(initial_state);
15788    cx.update_editor(|editor, window, cx| {
15789        editor.show_completions(&ShowCompletions, window, cx);
15790    });
15791
15792    let counter = Arc::new(AtomicUsize::new(0));
15793    handle_completion_request_with_insert_and_replace(
15794        &mut cx,
15795        buffer_marked_text,
15796        vec![(completion_text, completion_text)],
15797        counter.clone(),
15798    )
15799    .await;
15800    cx.condition(|editor, _| editor.context_menu_visible())
15801        .await;
15802    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15803
15804    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15805        editor
15806            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15807            .unwrap()
15808    });
15809    cx.assert_editor_state(expected_with_replace_mode);
15810    handle_resolve_completion_request(&mut cx, None).await;
15811    apply_additional_edits.await.unwrap();
15812
15813    update_test_language_settings(&mut cx, &|settings| {
15814        settings.defaults.completions = Some(CompletionSettingsContent {
15815            words: Some(WordsCompletionMode::Disabled),
15816            words_min_length: Some(0),
15817            // set the opposite here to ensure that the action is overriding the default behavior
15818            lsp_insert_mode: Some(LspInsertMode::Replace),
15819            ..Default::default()
15820        });
15821    });
15822
15823    cx.set_state(initial_state);
15824    cx.update_editor(|editor, window, cx| {
15825        editor.show_completions(&ShowCompletions, window, cx);
15826    });
15827    handle_completion_request_with_insert_and_replace(
15828        &mut cx,
15829        buffer_marked_text,
15830        vec![(completion_text, completion_text)],
15831        counter.clone(),
15832    )
15833    .await;
15834    cx.condition(|editor, _| editor.context_menu_visible())
15835        .await;
15836    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15837
15838    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15839        editor
15840            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
15841            .unwrap()
15842    });
15843    cx.assert_editor_state(expected_with_insert_mode);
15844    handle_resolve_completion_request(&mut cx, None).await;
15845    apply_additional_edits.await.unwrap();
15846}
15847
15848#[gpui::test]
15849async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
15850    init_test(cx, |_| {});
15851    let mut cx = EditorLspTestContext::new_rust(
15852        lsp::ServerCapabilities {
15853            completion_provider: Some(lsp::CompletionOptions {
15854                resolve_provider: Some(true),
15855                ..Default::default()
15856            }),
15857            ..Default::default()
15858        },
15859        cx,
15860    )
15861    .await;
15862
15863    // scenario: surrounding text matches completion text
15864    let completion_text = "to_offset";
15865    let initial_state = indoc! {"
15866        1. buf.to_offˇsuffix
15867        2. buf.to_offˇsuf
15868        3. buf.to_offˇfix
15869        4. buf.to_offˇ
15870        5. into_offˇensive
15871        6. ˇsuffix
15872        7. let ˇ //
15873        8. aaˇzz
15874        9. buf.to_off«zzzzzˇ»suffix
15875        10. buf.«ˇzzzzz»suffix
15876        11. to_off«ˇzzzzz»
15877
15878        buf.to_offˇsuffix  // newest cursor
15879    "};
15880    let completion_marked_buffer = indoc! {"
15881        1. buf.to_offsuffix
15882        2. buf.to_offsuf
15883        3. buf.to_offfix
15884        4. buf.to_off
15885        5. into_offensive
15886        6. suffix
15887        7. let  //
15888        8. aazz
15889        9. buf.to_offzzzzzsuffix
15890        10. buf.zzzzzsuffix
15891        11. to_offzzzzz
15892
15893        buf.<to_off|suffix>  // newest cursor
15894    "};
15895    let expected = indoc! {"
15896        1. buf.to_offsetˇ
15897        2. buf.to_offsetˇsuf
15898        3. buf.to_offsetˇfix
15899        4. buf.to_offsetˇ
15900        5. into_offsetˇensive
15901        6. to_offsetˇsuffix
15902        7. let to_offsetˇ //
15903        8. aato_offsetˇzz
15904        9. buf.to_offsetˇ
15905        10. buf.to_offsetˇsuffix
15906        11. to_offsetˇ
15907
15908        buf.to_offsetˇ  // newest cursor
15909    "};
15910    cx.set_state(initial_state);
15911    cx.update_editor(|editor, window, cx| {
15912        editor.show_completions(&ShowCompletions, window, cx);
15913    });
15914    handle_completion_request_with_insert_and_replace(
15915        &mut cx,
15916        completion_marked_buffer,
15917        vec![(completion_text, completion_text)],
15918        Arc::new(AtomicUsize::new(0)),
15919    )
15920    .await;
15921    cx.condition(|editor, _| editor.context_menu_visible())
15922        .await;
15923    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15924        editor
15925            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15926            .unwrap()
15927    });
15928    cx.assert_editor_state(expected);
15929    handle_resolve_completion_request(&mut cx, None).await;
15930    apply_additional_edits.await.unwrap();
15931
15932    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
15933    let completion_text = "foo_and_bar";
15934    let initial_state = indoc! {"
15935        1. ooanbˇ
15936        2. zooanbˇ
15937        3. ooanbˇz
15938        4. zooanbˇz
15939        5. ooanˇ
15940        6. oanbˇ
15941
15942        ooanbˇ
15943    "};
15944    let completion_marked_buffer = indoc! {"
15945        1. ooanb
15946        2. zooanb
15947        3. ooanbz
15948        4. zooanbz
15949        5. ooan
15950        6. oanb
15951
15952        <ooanb|>
15953    "};
15954    let expected = indoc! {"
15955        1. foo_and_barˇ
15956        2. zfoo_and_barˇ
15957        3. foo_and_barˇz
15958        4. zfoo_and_barˇz
15959        5. ooanfoo_and_barˇ
15960        6. oanbfoo_and_barˇ
15961
15962        foo_and_barˇ
15963    "};
15964    cx.set_state(initial_state);
15965    cx.update_editor(|editor, window, cx| {
15966        editor.show_completions(&ShowCompletions, window, cx);
15967    });
15968    handle_completion_request_with_insert_and_replace(
15969        &mut cx,
15970        completion_marked_buffer,
15971        vec![(completion_text, completion_text)],
15972        Arc::new(AtomicUsize::new(0)),
15973    )
15974    .await;
15975    cx.condition(|editor, _| editor.context_menu_visible())
15976        .await;
15977    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15978        editor
15979            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15980            .unwrap()
15981    });
15982    cx.assert_editor_state(expected);
15983    handle_resolve_completion_request(&mut cx, None).await;
15984    apply_additional_edits.await.unwrap();
15985
15986    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
15987    // (expects the same as if it was inserted at the end)
15988    let completion_text = "foo_and_bar";
15989    let initial_state = indoc! {"
15990        1. ooˇanb
15991        2. zooˇanb
15992        3. ooˇanbz
15993        4. zooˇanbz
15994
15995        ooˇanb
15996    "};
15997    let completion_marked_buffer = indoc! {"
15998        1. ooanb
15999        2. zooanb
16000        3. ooanbz
16001        4. zooanbz
16002
16003        <oo|anb>
16004    "};
16005    let expected = indoc! {"
16006        1. foo_and_barˇ
16007        2. zfoo_and_barˇ
16008        3. foo_and_barˇz
16009        4. zfoo_and_barˇz
16010
16011        foo_and_barˇ
16012    "};
16013    cx.set_state(initial_state);
16014    cx.update_editor(|editor, window, cx| {
16015        editor.show_completions(&ShowCompletions, window, cx);
16016    });
16017    handle_completion_request_with_insert_and_replace(
16018        &mut cx,
16019        completion_marked_buffer,
16020        vec![(completion_text, completion_text)],
16021        Arc::new(AtomicUsize::new(0)),
16022    )
16023    .await;
16024    cx.condition(|editor, _| editor.context_menu_visible())
16025        .await;
16026    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16027        editor
16028            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
16029            .unwrap()
16030    });
16031    cx.assert_editor_state(expected);
16032    handle_resolve_completion_request(&mut cx, None).await;
16033    apply_additional_edits.await.unwrap();
16034}
16035
16036// This used to crash
16037#[gpui::test]
16038async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
16039    init_test(cx, |_| {});
16040
16041    let buffer_text = indoc! {"
16042        fn main() {
16043            10.satu;
16044
16045            //
16046            // separate1
16047            // separate2
16048            // separate3
16049            //
16050
16051            10.satu20;
16052        }
16053    "};
16054    let multibuffer_text_with_selections = indoc! {"
16055        fn main() {
16056            10.satuˇ;
16057
16058            //
16059
16060            10.satuˇ20;
16061        }
16062    "};
16063    let expected_multibuffer = indoc! {"
16064        fn main() {
16065            10.saturating_sub()ˇ;
16066
16067            //
16068
16069            10.saturating_sub()ˇ;
16070        }
16071    "};
16072
16073    let fs = FakeFs::new(cx.executor());
16074    fs.insert_tree(
16075        path!("/a"),
16076        json!({
16077            "main.rs": buffer_text,
16078        }),
16079    )
16080    .await;
16081
16082    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16083    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16084    language_registry.add(rust_lang());
16085    let mut fake_servers = language_registry.register_fake_lsp(
16086        "Rust",
16087        FakeLspAdapter {
16088            capabilities: lsp::ServerCapabilities {
16089                completion_provider: Some(lsp::CompletionOptions {
16090                    resolve_provider: None,
16091                    ..lsp::CompletionOptions::default()
16092                }),
16093                ..lsp::ServerCapabilities::default()
16094            },
16095            ..FakeLspAdapter::default()
16096        },
16097    );
16098    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
16099    let workspace = window
16100        .read_with(cx, |mw, _| mw.workspace().clone())
16101        .unwrap();
16102    let cx = &mut VisualTestContext::from_window(*window, cx);
16103    let buffer = project
16104        .update(cx, |project, cx| {
16105            project.open_local_buffer(path!("/a/main.rs"), cx)
16106        })
16107        .await
16108        .unwrap();
16109
16110    let multi_buffer = cx.new(|cx| {
16111        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
16112        multi_buffer.set_excerpts_for_path(
16113            PathKey::sorted(0),
16114            buffer.clone(),
16115            [
16116                Point::zero()..Point::new(2, 0),
16117                Point::new(7, 0)..buffer.read(cx).max_point(),
16118            ],
16119            0,
16120            cx,
16121        );
16122        multi_buffer
16123    });
16124
16125    let editor = workspace.update_in(cx, |_, window, cx| {
16126        cx.new(|cx| {
16127            Editor::new(
16128                EditorMode::Full {
16129                    scale_ui_elements_with_buffer_font_size: false,
16130                    show_active_line_background: false,
16131                    sizing_behavior: SizingBehavior::Default,
16132                },
16133                multi_buffer.clone(),
16134                Some(project.clone()),
16135                window,
16136                cx,
16137            )
16138        })
16139    });
16140
16141    let pane = workspace.update_in(cx, |workspace, _, _| workspace.active_pane().clone());
16142    pane.update_in(cx, |pane, window, cx| {
16143        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
16144    });
16145
16146    let fake_server = fake_servers.next().await.unwrap();
16147    cx.run_until_parked();
16148
16149    editor.update_in(cx, |editor, window, cx| {
16150        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16151            s.select_ranges([
16152                Point::new(1, 11)..Point::new(1, 11),
16153                Point::new(5, 11)..Point::new(5, 11),
16154            ])
16155        });
16156
16157        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
16158    });
16159
16160    editor.update_in(cx, |editor, window, cx| {
16161        editor.show_completions(&ShowCompletions, window, cx);
16162    });
16163
16164    fake_server
16165        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16166            let completion_item = lsp::CompletionItem {
16167                label: "saturating_sub()".into(),
16168                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16169                    lsp::InsertReplaceEdit {
16170                        new_text: "saturating_sub()".to_owned(),
16171                        insert: lsp::Range::new(
16172                            lsp::Position::new(9, 7),
16173                            lsp::Position::new(9, 11),
16174                        ),
16175                        replace: lsp::Range::new(
16176                            lsp::Position::new(9, 7),
16177                            lsp::Position::new(9, 13),
16178                        ),
16179                    },
16180                )),
16181                ..lsp::CompletionItem::default()
16182            };
16183
16184            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
16185        })
16186        .next()
16187        .await
16188        .unwrap();
16189
16190    cx.condition(&editor, |editor, _| editor.context_menu_visible())
16191        .await;
16192
16193    editor
16194        .update_in(cx, |editor, window, cx| {
16195            editor
16196                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
16197                .unwrap()
16198        })
16199        .await
16200        .unwrap();
16201
16202    editor.update(cx, |editor, cx| {
16203        assert_text_with_selections(editor, expected_multibuffer, cx);
16204    })
16205}
16206
16207#[gpui::test]
16208async fn test_completion(cx: &mut TestAppContext) {
16209    init_test(cx, |_| {});
16210
16211    let mut cx = EditorLspTestContext::new_rust(
16212        lsp::ServerCapabilities {
16213            completion_provider: Some(lsp::CompletionOptions {
16214                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16215                resolve_provider: Some(true),
16216                ..Default::default()
16217            }),
16218            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16219            ..Default::default()
16220        },
16221        cx,
16222    )
16223    .await;
16224    let counter = Arc::new(AtomicUsize::new(0));
16225
16226    cx.set_state(indoc! {"
16227        oneˇ
16228        two
16229        three
16230    "});
16231    cx.simulate_keystroke(".");
16232    handle_completion_request(
16233        indoc! {"
16234            one.|<>
16235            two
16236            three
16237        "},
16238        vec!["first_completion", "second_completion"],
16239        true,
16240        counter.clone(),
16241        &mut cx,
16242    )
16243    .await;
16244    cx.condition(|editor, _| editor.context_menu_visible())
16245        .await;
16246    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16247
16248    let _handler = handle_signature_help_request(
16249        &mut cx,
16250        lsp::SignatureHelp {
16251            signatures: vec![lsp::SignatureInformation {
16252                label: "test signature".to_string(),
16253                documentation: None,
16254                parameters: Some(vec![lsp::ParameterInformation {
16255                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
16256                    documentation: None,
16257                }]),
16258                active_parameter: None,
16259            }],
16260            active_signature: None,
16261            active_parameter: None,
16262        },
16263    );
16264    cx.update_editor(|editor, window, cx| {
16265        assert!(
16266            !editor.signature_help_state.is_shown(),
16267            "No signature help was called for"
16268        );
16269        editor.show_signature_help(&ShowSignatureHelp, window, cx);
16270    });
16271    cx.run_until_parked();
16272    cx.update_editor(|editor, _, _| {
16273        assert!(
16274            !editor.signature_help_state.is_shown(),
16275            "No signature help should be shown when completions menu is open"
16276        );
16277    });
16278
16279    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16280        editor.context_menu_next(&Default::default(), window, cx);
16281        editor
16282            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16283            .unwrap()
16284    });
16285    cx.assert_editor_state(indoc! {"
16286        one.second_completionˇ
16287        two
16288        three
16289    "});
16290
16291    handle_resolve_completion_request(
16292        &mut cx,
16293        Some(vec![
16294            (
16295                //This overlaps with the primary completion edit which is
16296                //misbehavior from the LSP spec, test that we filter it out
16297                indoc! {"
16298                    one.second_ˇcompletion
16299                    two
16300                    threeˇ
16301                "},
16302                "overlapping additional edit",
16303            ),
16304            (
16305                indoc! {"
16306                    one.second_completion
16307                    two
16308                    threeˇ
16309                "},
16310                "\nadditional edit",
16311            ),
16312        ]),
16313    )
16314    .await;
16315    apply_additional_edits.await.unwrap();
16316    cx.assert_editor_state(indoc! {"
16317        one.second_completionˇ
16318        two
16319        three
16320        additional edit
16321    "});
16322
16323    cx.set_state(indoc! {"
16324        one.second_completion
16325        twoˇ
16326        threeˇ
16327        additional edit
16328    "});
16329    cx.simulate_keystroke(" ");
16330    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
16331    cx.simulate_keystroke("s");
16332    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
16333
16334    cx.assert_editor_state(indoc! {"
16335        one.second_completion
16336        two sˇ
16337        three sˇ
16338        additional edit
16339    "});
16340    handle_completion_request(
16341        indoc! {"
16342            one.second_completion
16343            two s
16344            three <s|>
16345            additional edit
16346        "},
16347        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
16348        true,
16349        counter.clone(),
16350        &mut cx,
16351    )
16352    .await;
16353    cx.condition(|editor, _| editor.context_menu_visible())
16354        .await;
16355    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
16356
16357    cx.simulate_keystroke("i");
16358
16359    handle_completion_request(
16360        indoc! {"
16361            one.second_completion
16362            two si
16363            three <si|>
16364            additional edit
16365        "},
16366        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
16367        true,
16368        counter.clone(),
16369        &mut cx,
16370    )
16371    .await;
16372    cx.condition(|editor, _| editor.context_menu_visible())
16373        .await;
16374    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
16375
16376    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16377        editor
16378            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16379            .unwrap()
16380    });
16381    cx.assert_editor_state(indoc! {"
16382        one.second_completion
16383        two sixth_completionˇ
16384        three sixth_completionˇ
16385        additional edit
16386    "});
16387
16388    apply_additional_edits.await.unwrap();
16389
16390    update_test_language_settings(&mut cx, &|settings| {
16391        settings.defaults.show_completions_on_input = Some(false);
16392    });
16393    cx.set_state("editorˇ");
16394    cx.simulate_keystroke(".");
16395    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
16396    cx.simulate_keystrokes("c l o");
16397    cx.assert_editor_state("editor.cloˇ");
16398    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
16399    cx.update_editor(|editor, window, cx| {
16400        editor.show_completions(&ShowCompletions, window, cx);
16401    });
16402    handle_completion_request(
16403        "editor.<clo|>",
16404        vec!["close", "clobber"],
16405        true,
16406        counter.clone(),
16407        &mut cx,
16408    )
16409    .await;
16410    cx.condition(|editor, _| editor.context_menu_visible())
16411        .await;
16412    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
16413
16414    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16415        editor
16416            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16417            .unwrap()
16418    });
16419    cx.assert_editor_state("editor.clobberˇ");
16420    handle_resolve_completion_request(&mut cx, None).await;
16421    apply_additional_edits.await.unwrap();
16422}
16423
16424#[gpui::test]
16425async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
16426    init_test(cx, |_| {});
16427
16428    let fs = FakeFs::new(cx.executor());
16429    fs.insert_tree(
16430        path!("/a"),
16431        json!({
16432            "main.rs": "",
16433        }),
16434    )
16435    .await;
16436
16437    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16438    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16439    language_registry.add(rust_lang());
16440    let command_calls = Arc::new(AtomicUsize::new(0));
16441    let registered_command = "_the/command";
16442
16443    let closure_command_calls = command_calls.clone();
16444    let mut fake_servers = language_registry.register_fake_lsp(
16445        "Rust",
16446        FakeLspAdapter {
16447            capabilities: lsp::ServerCapabilities {
16448                completion_provider: Some(lsp::CompletionOptions {
16449                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16450                    ..lsp::CompletionOptions::default()
16451                }),
16452                execute_command_provider: Some(lsp::ExecuteCommandOptions {
16453                    commands: vec![registered_command.to_owned()],
16454                    ..lsp::ExecuteCommandOptions::default()
16455                }),
16456                ..lsp::ServerCapabilities::default()
16457            },
16458            initializer: Some(Box::new(move |fake_server| {
16459                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
16460                    move |params, _| async move {
16461                        Ok(Some(lsp::CompletionResponse::Array(vec![
16462                            lsp::CompletionItem {
16463                                label: "registered_command".to_owned(),
16464                                text_edit: gen_text_edit(&params, ""),
16465                                command: Some(lsp::Command {
16466                                    title: registered_command.to_owned(),
16467                                    command: "_the/command".to_owned(),
16468                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
16469                                }),
16470                                ..lsp::CompletionItem::default()
16471                            },
16472                            lsp::CompletionItem {
16473                                label: "unregistered_command".to_owned(),
16474                                text_edit: gen_text_edit(&params, ""),
16475                                command: Some(lsp::Command {
16476                                    title: "????????????".to_owned(),
16477                                    command: "????????????".to_owned(),
16478                                    arguments: Some(vec![serde_json::Value::Null]),
16479                                }),
16480                                ..lsp::CompletionItem::default()
16481                            },
16482                        ])))
16483                    },
16484                );
16485                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
16486                    let command_calls = closure_command_calls.clone();
16487                    move |params, _| {
16488                        assert_eq!(params.command, registered_command);
16489                        let command_calls = command_calls.clone();
16490                        async move {
16491                            command_calls.fetch_add(1, atomic::Ordering::Release);
16492                            Ok(Some(json!(null)))
16493                        }
16494                    }
16495                });
16496            })),
16497            ..FakeLspAdapter::default()
16498        },
16499    );
16500    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
16501    let workspace = window
16502        .read_with(cx, |mw, _| mw.workspace().clone())
16503        .unwrap();
16504    let cx = &mut VisualTestContext::from_window(*window, cx);
16505    let editor = workspace
16506        .update_in(cx, |workspace, window, cx| {
16507            workspace.open_abs_path(
16508                PathBuf::from(path!("/a/main.rs")),
16509                OpenOptions::default(),
16510                window,
16511                cx,
16512            )
16513        })
16514        .await
16515        .unwrap()
16516        .downcast::<Editor>()
16517        .unwrap();
16518    let _fake_server = fake_servers.next().await.unwrap();
16519    cx.run_until_parked();
16520
16521    editor.update_in(cx, |editor, window, cx| {
16522        cx.focus_self(window);
16523        editor.move_to_end(&MoveToEnd, window, cx);
16524        editor.handle_input(".", window, cx);
16525    });
16526    cx.run_until_parked();
16527    editor.update(cx, |editor, _| {
16528        assert!(editor.context_menu_visible());
16529        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16530        {
16531            let completion_labels = menu
16532                .completions
16533                .borrow()
16534                .iter()
16535                .map(|c| c.label.text.clone())
16536                .collect::<Vec<_>>();
16537            assert_eq!(
16538                completion_labels,
16539                &["registered_command", "unregistered_command",],
16540            );
16541        } else {
16542            panic!("expected completion menu to be open");
16543        }
16544    });
16545
16546    editor
16547        .update_in(cx, |editor, window, cx| {
16548            editor
16549                .confirm_completion(&ConfirmCompletion::default(), window, cx)
16550                .unwrap()
16551        })
16552        .await
16553        .unwrap();
16554    cx.run_until_parked();
16555    assert_eq!(
16556        command_calls.load(atomic::Ordering::Acquire),
16557        1,
16558        "For completion with a registered command, Zed should send a command execution request",
16559    );
16560
16561    editor.update_in(cx, |editor, window, cx| {
16562        cx.focus_self(window);
16563        editor.handle_input(".", window, cx);
16564    });
16565    cx.run_until_parked();
16566    editor.update(cx, |editor, _| {
16567        assert!(editor.context_menu_visible());
16568        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16569        {
16570            let completion_labels = menu
16571                .completions
16572                .borrow()
16573                .iter()
16574                .map(|c| c.label.text.clone())
16575                .collect::<Vec<_>>();
16576            assert_eq!(
16577                completion_labels,
16578                &["registered_command", "unregistered_command",],
16579            );
16580        } else {
16581            panic!("expected completion menu to be open");
16582        }
16583    });
16584    editor
16585        .update_in(cx, |editor, window, cx| {
16586            editor.context_menu_next(&Default::default(), window, cx);
16587            editor
16588                .confirm_completion(&ConfirmCompletion::default(), window, cx)
16589                .unwrap()
16590        })
16591        .await
16592        .unwrap();
16593    cx.run_until_parked();
16594    assert_eq!(
16595        command_calls.load(atomic::Ordering::Acquire),
16596        1,
16597        "For completion with an unregistered command, Zed should not send a command execution request",
16598    );
16599}
16600
16601#[gpui::test]
16602async fn test_completion_reuse(cx: &mut TestAppContext) {
16603    init_test(cx, |_| {});
16604
16605    let mut cx = EditorLspTestContext::new_rust(
16606        lsp::ServerCapabilities {
16607            completion_provider: Some(lsp::CompletionOptions {
16608                trigger_characters: Some(vec![".".to_string()]),
16609                ..Default::default()
16610            }),
16611            ..Default::default()
16612        },
16613        cx,
16614    )
16615    .await;
16616
16617    let counter = Arc::new(AtomicUsize::new(0));
16618    cx.set_state("objˇ");
16619    cx.simulate_keystroke(".");
16620
16621    // Initial completion request returns complete results
16622    let is_incomplete = false;
16623    handle_completion_request(
16624        "obj.|<>",
16625        vec!["a", "ab", "abc"],
16626        is_incomplete,
16627        counter.clone(),
16628        &mut cx,
16629    )
16630    .await;
16631    cx.run_until_parked();
16632    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16633    cx.assert_editor_state("obj.ˇ");
16634    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
16635
16636    // Type "a" - filters existing completions
16637    cx.simulate_keystroke("a");
16638    cx.run_until_parked();
16639    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16640    cx.assert_editor_state("obj.aˇ");
16641    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
16642
16643    // Type "b" - filters existing completions
16644    cx.simulate_keystroke("b");
16645    cx.run_until_parked();
16646    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16647    cx.assert_editor_state("obj.abˇ");
16648    check_displayed_completions(vec!["ab", "abc"], &mut cx);
16649
16650    // Type "c" - filters existing completions
16651    cx.simulate_keystroke("c");
16652    cx.run_until_parked();
16653    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16654    cx.assert_editor_state("obj.abcˇ");
16655    check_displayed_completions(vec!["abc"], &mut cx);
16656
16657    // Backspace to delete "c" - filters existing completions
16658    cx.update_editor(|editor, window, cx| {
16659        editor.backspace(&Backspace, window, cx);
16660    });
16661    cx.run_until_parked();
16662    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16663    cx.assert_editor_state("obj.abˇ");
16664    check_displayed_completions(vec!["ab", "abc"], &mut cx);
16665
16666    // Moving cursor to the left dismisses menu.
16667    cx.update_editor(|editor, window, cx| {
16668        editor.move_left(&MoveLeft, window, cx);
16669    });
16670    cx.run_until_parked();
16671    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16672    cx.assert_editor_state("obj.aˇb");
16673    cx.update_editor(|editor, _, _| {
16674        assert_eq!(editor.context_menu_visible(), false);
16675    });
16676
16677    // Type "b" - new request
16678    cx.simulate_keystroke("b");
16679    let is_incomplete = false;
16680    handle_completion_request(
16681        "obj.<ab|>a",
16682        vec!["ab", "abc"],
16683        is_incomplete,
16684        counter.clone(),
16685        &mut cx,
16686    )
16687    .await;
16688    cx.run_until_parked();
16689    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
16690    cx.assert_editor_state("obj.abˇb");
16691    check_displayed_completions(vec!["ab", "abc"], &mut cx);
16692
16693    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
16694    cx.update_editor(|editor, window, cx| {
16695        editor.backspace(&Backspace, window, cx);
16696    });
16697    let is_incomplete = false;
16698    handle_completion_request(
16699        "obj.<a|>b",
16700        vec!["a", "ab", "abc"],
16701        is_incomplete,
16702        counter.clone(),
16703        &mut cx,
16704    )
16705    .await;
16706    cx.run_until_parked();
16707    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
16708    cx.assert_editor_state("obj.aˇb");
16709    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
16710
16711    // Backspace to delete "a" - dismisses menu.
16712    cx.update_editor(|editor, window, cx| {
16713        editor.backspace(&Backspace, window, cx);
16714    });
16715    cx.run_until_parked();
16716    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
16717    cx.assert_editor_state("obj.ˇb");
16718    cx.update_editor(|editor, _, _| {
16719        assert_eq!(editor.context_menu_visible(), false);
16720    });
16721}
16722
16723#[gpui::test]
16724async fn test_word_completion(cx: &mut TestAppContext) {
16725    let lsp_fetch_timeout_ms = 10;
16726    init_test(cx, |language_settings| {
16727        language_settings.defaults.completions = Some(CompletionSettingsContent {
16728            words_min_length: Some(0),
16729            lsp_fetch_timeout_ms: Some(10),
16730            lsp_insert_mode: Some(LspInsertMode::Insert),
16731            ..Default::default()
16732        });
16733    });
16734
16735    let mut cx = EditorLspTestContext::new_rust(
16736        lsp::ServerCapabilities {
16737            completion_provider: Some(lsp::CompletionOptions {
16738                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16739                ..lsp::CompletionOptions::default()
16740            }),
16741            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16742            ..lsp::ServerCapabilities::default()
16743        },
16744        cx,
16745    )
16746    .await;
16747
16748    let throttle_completions = Arc::new(AtomicBool::new(false));
16749
16750    let lsp_throttle_completions = throttle_completions.clone();
16751    let _completion_requests_handler =
16752        cx.lsp
16753            .server
16754            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
16755                let lsp_throttle_completions = lsp_throttle_completions.clone();
16756                let cx = cx.clone();
16757                async move {
16758                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
16759                        cx.background_executor()
16760                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
16761                            .await;
16762                    }
16763                    Ok(Some(lsp::CompletionResponse::Array(vec![
16764                        lsp::CompletionItem {
16765                            label: "first".into(),
16766                            ..lsp::CompletionItem::default()
16767                        },
16768                        lsp::CompletionItem {
16769                            label: "last".into(),
16770                            ..lsp::CompletionItem::default()
16771                        },
16772                    ])))
16773                }
16774            });
16775
16776    cx.set_state(indoc! {"
16777        oneˇ
16778        two
16779        three
16780    "});
16781    cx.simulate_keystroke(".");
16782    cx.executor().run_until_parked();
16783    cx.condition(|editor, _| editor.context_menu_visible())
16784        .await;
16785    cx.update_editor(|editor, window, cx| {
16786        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16787        {
16788            assert_eq!(
16789                completion_menu_entries(menu),
16790                &["first", "last"],
16791                "When LSP server is fast to reply, no fallback word completions are used"
16792            );
16793        } else {
16794            panic!("expected completion menu to be open");
16795        }
16796        editor.cancel(&Cancel, window, cx);
16797    });
16798    cx.executor().run_until_parked();
16799    cx.condition(|editor, _| !editor.context_menu_visible())
16800        .await;
16801
16802    throttle_completions.store(true, atomic::Ordering::Release);
16803    cx.simulate_keystroke(".");
16804    cx.executor()
16805        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
16806    cx.executor().run_until_parked();
16807    cx.condition(|editor, _| editor.context_menu_visible())
16808        .await;
16809    cx.update_editor(|editor, _, _| {
16810        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16811        {
16812            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
16813                "When LSP server is slow, document words can be shown instead, if configured accordingly");
16814        } else {
16815            panic!("expected completion menu to be open");
16816        }
16817    });
16818}
16819
16820#[gpui::test]
16821async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
16822    init_test(cx, |language_settings| {
16823        language_settings.defaults.completions = Some(CompletionSettingsContent {
16824            words: Some(WordsCompletionMode::Enabled),
16825            words_min_length: Some(0),
16826            lsp_insert_mode: Some(LspInsertMode::Insert),
16827            ..Default::default()
16828        });
16829    });
16830
16831    let mut cx = EditorLspTestContext::new_rust(
16832        lsp::ServerCapabilities {
16833            completion_provider: Some(lsp::CompletionOptions {
16834                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16835                ..lsp::CompletionOptions::default()
16836            }),
16837            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16838            ..lsp::ServerCapabilities::default()
16839        },
16840        cx,
16841    )
16842    .await;
16843
16844    let _completion_requests_handler =
16845        cx.lsp
16846            .server
16847            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
16848                Ok(Some(lsp::CompletionResponse::Array(vec![
16849                    lsp::CompletionItem {
16850                        label: "first".into(),
16851                        ..lsp::CompletionItem::default()
16852                    },
16853                    lsp::CompletionItem {
16854                        label: "last".into(),
16855                        ..lsp::CompletionItem::default()
16856                    },
16857                ])))
16858            });
16859
16860    cx.set_state(indoc! {"ˇ
16861        first
16862        last
16863        second
16864    "});
16865    cx.simulate_keystroke(".");
16866    cx.executor().run_until_parked();
16867    cx.condition(|editor, _| editor.context_menu_visible())
16868        .await;
16869    cx.update_editor(|editor, _, _| {
16870        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16871        {
16872            assert_eq!(
16873                completion_menu_entries(menu),
16874                &["first", "last", "second"],
16875                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
16876            );
16877        } else {
16878            panic!("expected completion menu to be open");
16879        }
16880    });
16881}
16882
16883#[gpui::test]
16884async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
16885    init_test(cx, |language_settings| {
16886        language_settings.defaults.completions = Some(CompletionSettingsContent {
16887            words: Some(WordsCompletionMode::Disabled),
16888            words_min_length: Some(0),
16889            lsp_insert_mode: Some(LspInsertMode::Insert),
16890            ..Default::default()
16891        });
16892    });
16893
16894    let mut cx = EditorLspTestContext::new_rust(
16895        lsp::ServerCapabilities {
16896            completion_provider: Some(lsp::CompletionOptions {
16897                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16898                ..lsp::CompletionOptions::default()
16899            }),
16900            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16901            ..lsp::ServerCapabilities::default()
16902        },
16903        cx,
16904    )
16905    .await;
16906
16907    let _completion_requests_handler =
16908        cx.lsp
16909            .server
16910            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
16911                panic!("LSP completions should not be queried when dealing with word completions")
16912            });
16913
16914    cx.set_state(indoc! {"ˇ
16915        first
16916        last
16917        second
16918    "});
16919    cx.update_editor(|editor, window, cx| {
16920        editor.show_word_completions(&ShowWordCompletions, window, cx);
16921    });
16922    cx.executor().run_until_parked();
16923    cx.condition(|editor, _| editor.context_menu_visible())
16924        .await;
16925    cx.update_editor(|editor, _, _| {
16926        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16927        {
16928            assert_eq!(
16929                completion_menu_entries(menu),
16930                &["first", "last", "second"],
16931                "`ShowWordCompletions` action should show word completions"
16932            );
16933        } else {
16934            panic!("expected completion menu to be open");
16935        }
16936    });
16937
16938    cx.simulate_keystroke("l");
16939    cx.executor().run_until_parked();
16940    cx.condition(|editor, _| editor.context_menu_visible())
16941        .await;
16942    cx.update_editor(|editor, _, _| {
16943        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16944        {
16945            assert_eq!(
16946                completion_menu_entries(menu),
16947                &["last"],
16948                "After showing word completions, further editing should filter them and not query the LSP"
16949            );
16950        } else {
16951            panic!("expected completion menu to be open");
16952        }
16953    });
16954}
16955
16956#[gpui::test]
16957async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
16958    init_test(cx, |language_settings| {
16959        language_settings.defaults.completions = Some(CompletionSettingsContent {
16960            words_min_length: Some(0),
16961            lsp: Some(false),
16962            lsp_insert_mode: Some(LspInsertMode::Insert),
16963            ..Default::default()
16964        });
16965    });
16966
16967    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16968
16969    cx.set_state(indoc! {"ˇ
16970        0_usize
16971        let
16972        33
16973        4.5f32
16974    "});
16975    cx.update_editor(|editor, window, cx| {
16976        editor.show_completions(&ShowCompletions, window, cx);
16977    });
16978    cx.executor().run_until_parked();
16979    cx.condition(|editor, _| editor.context_menu_visible())
16980        .await;
16981    cx.update_editor(|editor, window, cx| {
16982        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16983        {
16984            assert_eq!(
16985                completion_menu_entries(menu),
16986                &["let"],
16987                "With no digits in the completion query, no digits should be in the word completions"
16988            );
16989        } else {
16990            panic!("expected completion menu to be open");
16991        }
16992        editor.cancel(&Cancel, window, cx);
16993    });
16994
16995    cx.set_state(indoc! {"16996        0_usize
16997        let
16998        3
16999        33.35f32
17000    "});
17001    cx.update_editor(|editor, window, cx| {
17002        editor.show_completions(&ShowCompletions, window, cx);
17003    });
17004    cx.executor().run_until_parked();
17005    cx.condition(|editor, _| editor.context_menu_visible())
17006        .await;
17007    cx.update_editor(|editor, _, _| {
17008        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17009        {
17010            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
17011                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
17012        } else {
17013            panic!("expected completion menu to be open");
17014        }
17015    });
17016}
17017
17018#[gpui::test]
17019async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
17020    init_test(cx, |language_settings| {
17021        language_settings.defaults.completions = Some(CompletionSettingsContent {
17022            words: Some(WordsCompletionMode::Enabled),
17023            words_min_length: Some(3),
17024            lsp_insert_mode: Some(LspInsertMode::Insert),
17025            ..Default::default()
17026        });
17027    });
17028
17029    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17030    cx.set_state(indoc! {"ˇ
17031        wow
17032        wowen
17033        wowser
17034    "});
17035    cx.simulate_keystroke("w");
17036    cx.executor().run_until_parked();
17037    cx.update_editor(|editor, _, _| {
17038        if editor.context_menu.borrow_mut().is_some() {
17039            panic!(
17040                "expected completion menu to be hidden, as words completion threshold is not met"
17041            );
17042        }
17043    });
17044
17045    cx.update_editor(|editor, window, cx| {
17046        editor.show_word_completions(&ShowWordCompletions, window, cx);
17047    });
17048    cx.executor().run_until_parked();
17049    cx.update_editor(|editor, window, cx| {
17050        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17051        {
17052            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");
17053        } else {
17054            panic!("expected completion menu to be open after the word completions are called with an action");
17055        }
17056
17057        editor.cancel(&Cancel, window, cx);
17058    });
17059    cx.update_editor(|editor, _, _| {
17060        if editor.context_menu.borrow_mut().is_some() {
17061            panic!("expected completion menu to be hidden after canceling");
17062        }
17063    });
17064
17065    cx.simulate_keystroke("o");
17066    cx.executor().run_until_parked();
17067    cx.update_editor(|editor, _, _| {
17068        if editor.context_menu.borrow_mut().is_some() {
17069            panic!(
17070                "expected completion menu to be hidden, as words completion threshold is not met still"
17071            );
17072        }
17073    });
17074
17075    cx.simulate_keystroke("w");
17076    cx.executor().run_until_parked();
17077    cx.update_editor(|editor, _, _| {
17078        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17079        {
17080            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
17081        } else {
17082            panic!("expected completion menu to be open after the word completions threshold is met");
17083        }
17084    });
17085}
17086
17087#[gpui::test]
17088async fn test_word_completions_disabled(cx: &mut TestAppContext) {
17089    init_test(cx, |language_settings| {
17090        language_settings.defaults.completions = Some(CompletionSettingsContent {
17091            words: Some(WordsCompletionMode::Enabled),
17092            words_min_length: Some(0),
17093            lsp_insert_mode: Some(LspInsertMode::Insert),
17094            ..Default::default()
17095        });
17096    });
17097
17098    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17099    cx.update_editor(|editor, _, _| {
17100        editor.disable_word_completions();
17101    });
17102    cx.set_state(indoc! {"ˇ
17103        wow
17104        wowen
17105        wowser
17106    "});
17107    cx.simulate_keystroke("w");
17108    cx.executor().run_until_parked();
17109    cx.update_editor(|editor, _, _| {
17110        if editor.context_menu.borrow_mut().is_some() {
17111            panic!(
17112                "expected completion menu to be hidden, as words completion are disabled for this editor"
17113            );
17114        }
17115    });
17116
17117    cx.update_editor(|editor, window, cx| {
17118        editor.show_word_completions(&ShowWordCompletions, window, cx);
17119    });
17120    cx.executor().run_until_parked();
17121    cx.update_editor(|editor, _, _| {
17122        if editor.context_menu.borrow_mut().is_some() {
17123            panic!(
17124                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
17125            );
17126        }
17127    });
17128}
17129
17130#[gpui::test]
17131async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
17132    init_test(cx, |language_settings| {
17133        language_settings.defaults.completions = Some(CompletionSettingsContent {
17134            words: Some(WordsCompletionMode::Disabled),
17135            words_min_length: Some(0),
17136            lsp_insert_mode: Some(LspInsertMode::Insert),
17137            ..Default::default()
17138        });
17139    });
17140
17141    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17142    cx.update_editor(|editor, _, _| {
17143        editor.set_completion_provider(None);
17144    });
17145    cx.set_state(indoc! {"ˇ
17146        wow
17147        wowen
17148        wowser
17149    "});
17150    cx.simulate_keystroke("w");
17151    cx.executor().run_until_parked();
17152    cx.update_editor(|editor, _, _| {
17153        if editor.context_menu.borrow_mut().is_some() {
17154            panic!("expected completion menu to be hidden, as disabled in settings");
17155        }
17156    });
17157}
17158
17159fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
17160    let position = || lsp::Position {
17161        line: params.text_document_position.position.line,
17162        character: params.text_document_position.position.character,
17163    };
17164    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17165        range: lsp::Range {
17166            start: position(),
17167            end: position(),
17168        },
17169        new_text: text.to_string(),
17170    }))
17171}
17172
17173#[gpui::test]
17174async fn test_multiline_completion(cx: &mut TestAppContext) {
17175    init_test(cx, |_| {});
17176
17177    let fs = FakeFs::new(cx.executor());
17178    fs.insert_tree(
17179        path!("/a"),
17180        json!({
17181            "main.ts": "a",
17182        }),
17183    )
17184    .await;
17185
17186    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17187    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17188    let typescript_language = Arc::new(Language::new(
17189        LanguageConfig {
17190            name: "TypeScript".into(),
17191            matcher: LanguageMatcher {
17192                path_suffixes: vec!["ts".to_string()],
17193                ..LanguageMatcher::default()
17194            },
17195            line_comments: vec!["// ".into()],
17196            ..LanguageConfig::default()
17197        },
17198        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17199    ));
17200    language_registry.add(typescript_language.clone());
17201    let mut fake_servers = language_registry.register_fake_lsp(
17202        "TypeScript",
17203        FakeLspAdapter {
17204            capabilities: lsp::ServerCapabilities {
17205                completion_provider: Some(lsp::CompletionOptions {
17206                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
17207                    ..lsp::CompletionOptions::default()
17208                }),
17209                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
17210                ..lsp::ServerCapabilities::default()
17211            },
17212            // Emulate vtsls label generation
17213            label_for_completion: Some(Box::new(|item, _| {
17214                let text = if let Some(description) = item
17215                    .label_details
17216                    .as_ref()
17217                    .and_then(|label_details| label_details.description.as_ref())
17218                {
17219                    format!("{} {}", item.label, description)
17220                } else if let Some(detail) = &item.detail {
17221                    format!("{} {}", item.label, detail)
17222                } else {
17223                    item.label.clone()
17224                };
17225                Some(language::CodeLabel::plain(text, None))
17226            })),
17227            ..FakeLspAdapter::default()
17228        },
17229    );
17230    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
17231    let workspace = window
17232        .read_with(cx, |mw, _| mw.workspace().clone())
17233        .unwrap();
17234    let cx = &mut VisualTestContext::from_window(*window, cx);
17235    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
17236        workspace.project().update(cx, |project, cx| {
17237            project.worktrees(cx).next().unwrap().read(cx).id()
17238        })
17239    });
17240
17241    let _buffer = project
17242        .update(cx, |project, cx| {
17243            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
17244        })
17245        .await
17246        .unwrap();
17247    let editor = workspace
17248        .update_in(cx, |workspace, window, cx| {
17249            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
17250        })
17251        .await
17252        .unwrap()
17253        .downcast::<Editor>()
17254        .unwrap();
17255    let fake_server = fake_servers.next().await.unwrap();
17256    cx.run_until_parked();
17257
17258    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
17259    let multiline_label_2 = "a\nb\nc\n";
17260    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
17261    let multiline_description = "d\ne\nf\n";
17262    let multiline_detail_2 = "g\nh\ni\n";
17263
17264    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
17265        move |params, _| async move {
17266            Ok(Some(lsp::CompletionResponse::Array(vec![
17267                lsp::CompletionItem {
17268                    label: multiline_label.to_string(),
17269                    text_edit: gen_text_edit(&params, "new_text_1"),
17270                    ..lsp::CompletionItem::default()
17271                },
17272                lsp::CompletionItem {
17273                    label: "single line label 1".to_string(),
17274                    detail: Some(multiline_detail.to_string()),
17275                    text_edit: gen_text_edit(&params, "new_text_2"),
17276                    ..lsp::CompletionItem::default()
17277                },
17278                lsp::CompletionItem {
17279                    label: "single line label 2".to_string(),
17280                    label_details: Some(lsp::CompletionItemLabelDetails {
17281                        description: Some(multiline_description.to_string()),
17282                        detail: None,
17283                    }),
17284                    text_edit: gen_text_edit(&params, "new_text_2"),
17285                    ..lsp::CompletionItem::default()
17286                },
17287                lsp::CompletionItem {
17288                    label: multiline_label_2.to_string(),
17289                    detail: Some(multiline_detail_2.to_string()),
17290                    text_edit: gen_text_edit(&params, "new_text_3"),
17291                    ..lsp::CompletionItem::default()
17292                },
17293                lsp::CompletionItem {
17294                    label: "Label with many     spaces and \t but without newlines".to_string(),
17295                    detail: Some(
17296                        "Details with many     spaces and \t but without newlines".to_string(),
17297                    ),
17298                    text_edit: gen_text_edit(&params, "new_text_4"),
17299                    ..lsp::CompletionItem::default()
17300                },
17301            ])))
17302        },
17303    );
17304
17305    editor.update_in(cx, |editor, window, cx| {
17306        cx.focus_self(window);
17307        editor.move_to_end(&MoveToEnd, window, cx);
17308        editor.handle_input(".", window, cx);
17309    });
17310    cx.run_until_parked();
17311    completion_handle.next().await.unwrap();
17312
17313    editor.update(cx, |editor, _| {
17314        assert!(editor.context_menu_visible());
17315        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17316        {
17317            let completion_labels = menu
17318                .completions
17319                .borrow()
17320                .iter()
17321                .map(|c| c.label.text.clone())
17322                .collect::<Vec<_>>();
17323            assert_eq!(
17324                completion_labels,
17325                &[
17326                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
17327                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
17328                    "single line label 2 d e f ",
17329                    "a b c g h i ",
17330                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
17331                ],
17332                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
17333            );
17334
17335            for completion in menu
17336                .completions
17337                .borrow()
17338                .iter() {
17339                    assert_eq!(
17340                        completion.label.filter_range,
17341                        0..completion.label.text.len(),
17342                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
17343                    );
17344                }
17345        } else {
17346            panic!("expected completion menu to be open");
17347        }
17348    });
17349}
17350
17351#[gpui::test]
17352async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
17353    init_test(cx, |_| {});
17354    let mut cx = EditorLspTestContext::new_rust(
17355        lsp::ServerCapabilities {
17356            completion_provider: Some(lsp::CompletionOptions {
17357                trigger_characters: Some(vec![".".to_string()]),
17358                ..Default::default()
17359            }),
17360            ..Default::default()
17361        },
17362        cx,
17363    )
17364    .await;
17365    cx.lsp
17366        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17367            Ok(Some(lsp::CompletionResponse::Array(vec![
17368                lsp::CompletionItem {
17369                    label: "first".into(),
17370                    ..Default::default()
17371                },
17372                lsp::CompletionItem {
17373                    label: "last".into(),
17374                    ..Default::default()
17375                },
17376            ])))
17377        });
17378    cx.set_state("variableˇ");
17379    cx.simulate_keystroke(".");
17380    cx.executor().run_until_parked();
17381
17382    cx.update_editor(|editor, _, _| {
17383        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17384        {
17385            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
17386        } else {
17387            panic!("expected completion menu to be open");
17388        }
17389    });
17390
17391    cx.update_editor(|editor, window, cx| {
17392        editor.move_page_down(&MovePageDown::default(), window, cx);
17393        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17394        {
17395            assert!(
17396                menu.selected_item == 1,
17397                "expected PageDown to select the last item from the context menu"
17398            );
17399        } else {
17400            panic!("expected completion menu to stay open after PageDown");
17401        }
17402    });
17403
17404    cx.update_editor(|editor, window, cx| {
17405        editor.move_page_up(&MovePageUp::default(), window, cx);
17406        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17407        {
17408            assert!(
17409                menu.selected_item == 0,
17410                "expected PageUp to select the first item from the context menu"
17411            );
17412        } else {
17413            panic!("expected completion menu to stay open after PageUp");
17414        }
17415    });
17416}
17417
17418#[gpui::test]
17419async fn test_as_is_completions(cx: &mut TestAppContext) {
17420    init_test(cx, |_| {});
17421    let mut cx = EditorLspTestContext::new_rust(
17422        lsp::ServerCapabilities {
17423            completion_provider: Some(lsp::CompletionOptions {
17424                ..Default::default()
17425            }),
17426            ..Default::default()
17427        },
17428        cx,
17429    )
17430    .await;
17431    cx.lsp
17432        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17433            Ok(Some(lsp::CompletionResponse::Array(vec![
17434                lsp::CompletionItem {
17435                    label: "unsafe".into(),
17436                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17437                        range: lsp::Range {
17438                            start: lsp::Position {
17439                                line: 1,
17440                                character: 2,
17441                            },
17442                            end: lsp::Position {
17443                                line: 1,
17444                                character: 3,
17445                            },
17446                        },
17447                        new_text: "unsafe".to_string(),
17448                    })),
17449                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
17450                    ..Default::default()
17451                },
17452            ])))
17453        });
17454    cx.set_state("fn a() {}\n");
17455    cx.executor().run_until_parked();
17456    cx.update_editor(|editor, window, cx| {
17457        editor.trigger_completion_on_input("n", true, window, cx)
17458    });
17459    cx.executor().run_until_parked();
17460
17461    cx.update_editor(|editor, window, cx| {
17462        editor.confirm_completion(&Default::default(), window, cx)
17463    });
17464    cx.executor().run_until_parked();
17465    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
17466}
17467
17468#[gpui::test]
17469async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
17470    init_test(cx, |_| {});
17471    let language =
17472        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
17473    let mut cx = EditorLspTestContext::new(
17474        language,
17475        lsp::ServerCapabilities {
17476            completion_provider: Some(lsp::CompletionOptions {
17477                ..lsp::CompletionOptions::default()
17478            }),
17479            ..lsp::ServerCapabilities::default()
17480        },
17481        cx,
17482    )
17483    .await;
17484
17485    cx.set_state(
17486        "#ifndef BAR_H
17487#define BAR_H
17488
17489#include <stdbool.h>
17490
17491int fn_branch(bool do_branch1, bool do_branch2);
17492
17493#endif // BAR_H
17494ˇ",
17495    );
17496    cx.executor().run_until_parked();
17497    cx.update_editor(|editor, window, cx| {
17498        editor.handle_input("#", window, cx);
17499    });
17500    cx.executor().run_until_parked();
17501    cx.update_editor(|editor, window, cx| {
17502        editor.handle_input("i", window, cx);
17503    });
17504    cx.executor().run_until_parked();
17505    cx.update_editor(|editor, window, cx| {
17506        editor.handle_input("n", window, cx);
17507    });
17508    cx.executor().run_until_parked();
17509    cx.assert_editor_state(
17510        "#ifndef BAR_H
17511#define BAR_H
17512
17513#include <stdbool.h>
17514
17515int fn_branch(bool do_branch1, bool do_branch2);
17516
17517#endif // BAR_H
17518#inˇ",
17519    );
17520
17521    cx.lsp
17522        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17523            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17524                is_incomplete: false,
17525                item_defaults: None,
17526                items: vec![lsp::CompletionItem {
17527                    kind: Some(lsp::CompletionItemKind::SNIPPET),
17528                    label_details: Some(lsp::CompletionItemLabelDetails {
17529                        detail: Some("header".to_string()),
17530                        description: None,
17531                    }),
17532                    label: " include".to_string(),
17533                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17534                        range: lsp::Range {
17535                            start: lsp::Position {
17536                                line: 8,
17537                                character: 1,
17538                            },
17539                            end: lsp::Position {
17540                                line: 8,
17541                                character: 1,
17542                            },
17543                        },
17544                        new_text: "include \"$0\"".to_string(),
17545                    })),
17546                    sort_text: Some("40b67681include".to_string()),
17547                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17548                    filter_text: Some("include".to_string()),
17549                    insert_text: Some("include \"$0\"".to_string()),
17550                    ..lsp::CompletionItem::default()
17551                }],
17552            })))
17553        });
17554    cx.update_editor(|editor, window, cx| {
17555        editor.show_completions(&ShowCompletions, window, cx);
17556    });
17557    cx.executor().run_until_parked();
17558    cx.update_editor(|editor, window, cx| {
17559        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
17560    });
17561    cx.executor().run_until_parked();
17562    cx.assert_editor_state(
17563        "#ifndef BAR_H
17564#define BAR_H
17565
17566#include <stdbool.h>
17567
17568int fn_branch(bool do_branch1, bool do_branch2);
17569
17570#endif // BAR_H
17571#include \"ˇ\"",
17572    );
17573
17574    cx.lsp
17575        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17576            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17577                is_incomplete: true,
17578                item_defaults: None,
17579                items: vec![lsp::CompletionItem {
17580                    kind: Some(lsp::CompletionItemKind::FILE),
17581                    label: "AGL/".to_string(),
17582                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17583                        range: lsp::Range {
17584                            start: lsp::Position {
17585                                line: 8,
17586                                character: 10,
17587                            },
17588                            end: lsp::Position {
17589                                line: 8,
17590                                character: 11,
17591                            },
17592                        },
17593                        new_text: "AGL/".to_string(),
17594                    })),
17595                    sort_text: Some("40b67681AGL/".to_string()),
17596                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17597                    filter_text: Some("AGL/".to_string()),
17598                    insert_text: Some("AGL/".to_string()),
17599                    ..lsp::CompletionItem::default()
17600                }],
17601            })))
17602        });
17603    cx.update_editor(|editor, window, cx| {
17604        editor.show_completions(&ShowCompletions, window, cx);
17605    });
17606    cx.executor().run_until_parked();
17607    cx.update_editor(|editor, window, cx| {
17608        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
17609    });
17610    cx.executor().run_until_parked();
17611    cx.assert_editor_state(
17612        r##"#ifndef BAR_H
17613#define BAR_H
17614
17615#include <stdbool.h>
17616
17617int fn_branch(bool do_branch1, bool do_branch2);
17618
17619#endif // BAR_H
17620#include "AGL/ˇ"##,
17621    );
17622
17623    cx.update_editor(|editor, window, cx| {
17624        editor.handle_input("\"", window, cx);
17625    });
17626    cx.executor().run_until_parked();
17627    cx.assert_editor_state(
17628        r##"#ifndef BAR_H
17629#define BAR_H
17630
17631#include <stdbool.h>
17632
17633int fn_branch(bool do_branch1, bool do_branch2);
17634
17635#endif // BAR_H
17636#include "AGL/"ˇ"##,
17637    );
17638}
17639
17640#[gpui::test]
17641async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
17642    init_test(cx, |_| {});
17643
17644    let mut cx = EditorLspTestContext::new_rust(
17645        lsp::ServerCapabilities {
17646            completion_provider: Some(lsp::CompletionOptions {
17647                trigger_characters: Some(vec![".".to_string()]),
17648                resolve_provider: Some(false),
17649                ..lsp::CompletionOptions::default()
17650            }),
17651            ..lsp::ServerCapabilities::default()
17652        },
17653        cx,
17654    )
17655    .await;
17656
17657    cx.set_state("fn main() { let a = 2ˇ; }");
17658    cx.simulate_keystroke(".");
17659    let completion_item = lsp::CompletionItem {
17660        label: "Some".into(),
17661        kind: Some(lsp::CompletionItemKind::SNIPPET),
17662        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17663        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17664            kind: lsp::MarkupKind::Markdown,
17665            value: "```rust\nSome(2)\n```".to_string(),
17666        })),
17667        deprecated: Some(false),
17668        sort_text: Some("Some".to_string()),
17669        filter_text: Some("Some".to_string()),
17670        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17671        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17672            range: lsp::Range {
17673                start: lsp::Position {
17674                    line: 0,
17675                    character: 22,
17676                },
17677                end: lsp::Position {
17678                    line: 0,
17679                    character: 22,
17680                },
17681            },
17682            new_text: "Some(2)".to_string(),
17683        })),
17684        additional_text_edits: Some(vec![lsp::TextEdit {
17685            range: lsp::Range {
17686                start: lsp::Position {
17687                    line: 0,
17688                    character: 20,
17689                },
17690                end: lsp::Position {
17691                    line: 0,
17692                    character: 22,
17693                },
17694            },
17695            new_text: "".to_string(),
17696        }]),
17697        ..Default::default()
17698    };
17699
17700    let closure_completion_item = completion_item.clone();
17701    let counter = Arc::new(AtomicUsize::new(0));
17702    let counter_clone = counter.clone();
17703    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17704        let task_completion_item = closure_completion_item.clone();
17705        counter_clone.fetch_add(1, atomic::Ordering::Release);
17706        async move {
17707            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17708                is_incomplete: true,
17709                item_defaults: None,
17710                items: vec![task_completion_item],
17711            })))
17712        }
17713    });
17714
17715    cx.executor().run_until_parked();
17716    cx.condition(|editor, _| editor.context_menu_visible())
17717        .await;
17718    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
17719    assert!(request.next().await.is_some());
17720    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
17721
17722    cx.simulate_keystrokes("S o m");
17723    cx.condition(|editor, _| editor.context_menu_visible())
17724        .await;
17725    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
17726    assert!(request.next().await.is_some());
17727    assert!(request.next().await.is_some());
17728    assert!(request.next().await.is_some());
17729    request.close();
17730    assert!(request.next().await.is_none());
17731    assert_eq!(
17732        counter.load(atomic::Ordering::Acquire),
17733        4,
17734        "With the completions menu open, only one LSP request should happen per input"
17735    );
17736}
17737
17738#[gpui::test]
17739async fn test_toggle_comment(cx: &mut TestAppContext) {
17740    init_test(cx, |_| {});
17741    let mut cx = EditorTestContext::new(cx).await;
17742    let language = Arc::new(Language::new(
17743        LanguageConfig {
17744            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
17745            ..Default::default()
17746        },
17747        Some(tree_sitter_rust::LANGUAGE.into()),
17748    ));
17749    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17750
17751    // If multiple selections intersect a line, the line is only toggled once.
17752    cx.set_state(indoc! {"
17753        fn a() {
17754            «//b();
17755            ˇ»// «c();
17756            //ˇ»  d();
17757        }
17758    "});
17759
17760    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17761
17762    cx.assert_editor_state(indoc! {"
17763        fn a() {
17764            «b();
17765            ˇ»«c();
17766            ˇ» d();
17767        }
17768    "});
17769
17770    // The comment prefix is inserted at the same column for every line in a
17771    // selection.
17772    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17773
17774    cx.assert_editor_state(indoc! {"
17775        fn a() {
17776            // «b();
17777            ˇ»// «c();
17778            ˇ» // d();
17779        }
17780    "});
17781
17782    // If a selection ends at the beginning of a line, that line is not toggled.
17783    cx.set_selections_state(indoc! {"
17784        fn a() {
17785            // b();
17786            «// c();
17787        ˇ»     // d();
17788        }
17789    "});
17790
17791    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17792
17793    cx.assert_editor_state(indoc! {"
17794        fn a() {
17795            // b();
17796            «c();
17797        ˇ»     // d();
17798        }
17799    "});
17800
17801    // If a selection span a single line and is empty, the line is toggled.
17802    cx.set_state(indoc! {"
17803        fn a() {
17804            a();
17805            b();
17806        ˇ
17807        }
17808    "});
17809
17810    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17811
17812    cx.assert_editor_state(indoc! {"
17813        fn a() {
17814            a();
17815            b();
17816        //•ˇ
17817        }
17818    "});
17819
17820    // If a selection span multiple lines, empty lines are not toggled.
17821    cx.set_state(indoc! {"
17822        fn a() {
17823            «a();
17824
17825            c();ˇ»
17826        }
17827    "});
17828
17829    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17830
17831    cx.assert_editor_state(indoc! {"
17832        fn a() {
17833            // «a();
17834
17835            // c();ˇ»
17836        }
17837    "});
17838
17839    // If a selection includes multiple comment prefixes, all lines are uncommented.
17840    cx.set_state(indoc! {"
17841        fn a() {
17842            «// a();
17843            /// b();
17844            //! c();ˇ»
17845        }
17846    "});
17847
17848    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17849
17850    cx.assert_editor_state(indoc! {"
17851        fn a() {
17852            «a();
17853            b();
17854            c();ˇ»
17855        }
17856    "});
17857}
17858
17859#[gpui::test]
17860async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
17861    init_test(cx, |_| {});
17862    let mut cx = EditorTestContext::new(cx).await;
17863    let language = Arc::new(Language::new(
17864        LanguageConfig {
17865            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
17866            ..Default::default()
17867        },
17868        Some(tree_sitter_rust::LANGUAGE.into()),
17869    ));
17870    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17871
17872    let toggle_comments = &ToggleComments {
17873        advance_downwards: false,
17874        ignore_indent: true,
17875    };
17876
17877    // If multiple selections intersect a line, the line is only toggled once.
17878    cx.set_state(indoc! {"
17879        fn a() {
17880        //    «b();
17881        //    c();
17882        //    ˇ» d();
17883        }
17884    "});
17885
17886    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17887
17888    cx.assert_editor_state(indoc! {"
17889        fn a() {
17890            «b();
17891            c();
17892            ˇ» d();
17893        }
17894    "});
17895
17896    // The comment prefix is inserted at the beginning of each line
17897    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17898
17899    cx.assert_editor_state(indoc! {"
17900        fn a() {
17901        //    «b();
17902        //    c();
17903        //    ˇ» d();
17904        }
17905    "});
17906
17907    // If a selection ends at the beginning of a line, that line is not toggled.
17908    cx.set_selections_state(indoc! {"
17909        fn a() {
17910        //    b();
17911        //    «c();
17912        ˇ»//     d();
17913        }
17914    "});
17915
17916    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17917
17918    cx.assert_editor_state(indoc! {"
17919        fn a() {
17920        //    b();
17921            «c();
17922        ˇ»//     d();
17923        }
17924    "});
17925
17926    // If a selection span a single line and is empty, the line is toggled.
17927    cx.set_state(indoc! {"
17928        fn a() {
17929            a();
17930            b();
17931        ˇ
17932        }
17933    "});
17934
17935    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17936
17937    cx.assert_editor_state(indoc! {"
17938        fn a() {
17939            a();
17940            b();
17941        //ˇ
17942        }
17943    "});
17944
17945    // If a selection span multiple lines, empty lines are not toggled.
17946    cx.set_state(indoc! {"
17947        fn a() {
17948            «a();
17949
17950            c();ˇ»
17951        }
17952    "});
17953
17954    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17955
17956    cx.assert_editor_state(indoc! {"
17957        fn a() {
17958        //    «a();
17959
17960        //    c();ˇ»
17961        }
17962    "});
17963
17964    // If a selection includes multiple comment prefixes, all lines are uncommented.
17965    cx.set_state(indoc! {"
17966        fn a() {
17967        //    «a();
17968        ///    b();
17969        //!    c();ˇ»
17970        }
17971    "});
17972
17973    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17974
17975    cx.assert_editor_state(indoc! {"
17976        fn a() {
17977            «a();
17978            b();
17979            c();ˇ»
17980        }
17981    "});
17982}
17983
17984#[gpui::test]
17985async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
17986    init_test(cx, |_| {});
17987
17988    let language = Arc::new(Language::new(
17989        LanguageConfig {
17990            line_comments: vec!["// ".into()],
17991            ..Default::default()
17992        },
17993        Some(tree_sitter_rust::LANGUAGE.into()),
17994    ));
17995
17996    let mut cx = EditorTestContext::new(cx).await;
17997
17998    cx.language_registry().add(language.clone());
17999    cx.update_buffer(|buffer, cx| {
18000        buffer.set_language(Some(language), cx);
18001    });
18002
18003    let toggle_comments = &ToggleComments {
18004        advance_downwards: true,
18005        ignore_indent: false,
18006    };
18007
18008    // Single cursor on one line -> advance
18009    // Cursor moves horizontally 3 characters as well on non-blank line
18010    cx.set_state(indoc!(
18011        "fn a() {
18012             ˇdog();
18013             cat();
18014        }"
18015    ));
18016    cx.update_editor(|editor, window, cx| {
18017        editor.toggle_comments(toggle_comments, window, cx);
18018    });
18019    cx.assert_editor_state(indoc!(
18020        "fn a() {
18021             // dog();
18022             catˇ();
18023        }"
18024    ));
18025
18026    // Single selection on one line -> don't advance
18027    cx.set_state(indoc!(
18028        "fn a() {
18029             «dog()ˇ»;
18030             cat();
18031        }"
18032    ));
18033    cx.update_editor(|editor, window, cx| {
18034        editor.toggle_comments(toggle_comments, window, cx);
18035    });
18036    cx.assert_editor_state(indoc!(
18037        "fn a() {
18038             // «dog()ˇ»;
18039             cat();
18040        }"
18041    ));
18042
18043    // Multiple cursors on one line -> advance
18044    cx.set_state(indoc!(
18045        "fn a() {
18046             ˇdˇog();
18047             cat();
18048        }"
18049    ));
18050    cx.update_editor(|editor, window, cx| {
18051        editor.toggle_comments(toggle_comments, window, cx);
18052    });
18053    cx.assert_editor_state(indoc!(
18054        "fn a() {
18055             // dog();
18056             catˇ(ˇ);
18057        }"
18058    ));
18059
18060    // Multiple cursors on one line, with selection -> don't advance
18061    cx.set_state(indoc!(
18062        "fn a() {
18063             ˇdˇog«()ˇ»;
18064             cat();
18065        }"
18066    ));
18067    cx.update_editor(|editor, window, cx| {
18068        editor.toggle_comments(toggle_comments, window, cx);
18069    });
18070    cx.assert_editor_state(indoc!(
18071        "fn a() {
18072             // ˇdˇog«()ˇ»;
18073             cat();
18074        }"
18075    ));
18076
18077    // Single cursor on one line -> advance
18078    // Cursor moves to column 0 on blank line
18079    cx.set_state(indoc!(
18080        "fn a() {
18081             ˇdog();
18082
18083             cat();
18084        }"
18085    ));
18086    cx.update_editor(|editor, window, cx| {
18087        editor.toggle_comments(toggle_comments, window, cx);
18088    });
18089    cx.assert_editor_state(indoc!(
18090        "fn a() {
18091             // dog();
18092        ˇ
18093             cat();
18094        }"
18095    ));
18096
18097    // Single cursor on one line -> advance
18098    // Cursor starts and ends at column 0
18099    cx.set_state(indoc!(
18100        "fn a() {
18101         ˇ    dog();
18102             cat();
18103        }"
18104    ));
18105    cx.update_editor(|editor, window, cx| {
18106        editor.toggle_comments(toggle_comments, window, cx);
18107    });
18108    cx.assert_editor_state(indoc!(
18109        "fn a() {
18110             // dog();
18111         ˇ    cat();
18112        }"
18113    ));
18114}
18115
18116#[gpui::test]
18117async fn test_toggle_block_comment(cx: &mut TestAppContext) {
18118    init_test(cx, |_| {});
18119
18120    let mut cx = EditorTestContext::new(cx).await;
18121
18122    let html_language = Arc::new(
18123        Language::new(
18124            LanguageConfig {
18125                name: "HTML".into(),
18126                block_comment: Some(BlockCommentConfig {
18127                    start: "<!-- ".into(),
18128                    prefix: "".into(),
18129                    end: " -->".into(),
18130                    tab_size: 0,
18131                }),
18132                ..Default::default()
18133            },
18134            Some(tree_sitter_html::LANGUAGE.into()),
18135        )
18136        .with_injection_query(
18137            r#"
18138            (script_element
18139                (raw_text) @injection.content
18140                (#set! injection.language "javascript"))
18141            "#,
18142        )
18143        .unwrap(),
18144    );
18145
18146    let javascript_language = Arc::new(Language::new(
18147        LanguageConfig {
18148            name: "JavaScript".into(),
18149            line_comments: vec!["// ".into()],
18150            ..Default::default()
18151        },
18152        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18153    ));
18154
18155    cx.language_registry().add(html_language.clone());
18156    cx.language_registry().add(javascript_language);
18157    cx.update_buffer(|buffer, cx| {
18158        buffer.set_language(Some(html_language), cx);
18159    });
18160
18161    // Toggle comments for empty selections
18162    cx.set_state(
18163        &r#"
18164            <p>A</p>ˇ
18165            <p>B</p>ˇ
18166            <p>C</p>ˇ
18167        "#
18168        .unindent(),
18169    );
18170    cx.update_editor(|editor, window, cx| {
18171        editor.toggle_comments(&ToggleComments::default(), window, cx)
18172    });
18173    cx.assert_editor_state(
18174        &r#"
18175            <!-- <p>A</p>ˇ -->
18176            <!-- <p>B</p>ˇ -->
18177            <!-- <p>C</p>ˇ -->
18178        "#
18179        .unindent(),
18180    );
18181    cx.update_editor(|editor, window, cx| {
18182        editor.toggle_comments(&ToggleComments::default(), window, cx)
18183    });
18184    cx.assert_editor_state(
18185        &r#"
18186            <p>A</p>ˇ
18187            <p>B</p>ˇ
18188            <p>C</p>ˇ
18189        "#
18190        .unindent(),
18191    );
18192
18193    // Toggle comments for mixture of empty and non-empty selections, where
18194    // multiple selections occupy a given line.
18195    cx.set_state(
18196        &r#"
18197            <p>A«</p>
18198            <p>ˇ»B</p>ˇ
18199            <p>C«</p>
18200            <p>ˇ»D</p>ˇ
18201        "#
18202        .unindent(),
18203    );
18204
18205    cx.update_editor(|editor, window, cx| {
18206        editor.toggle_comments(&ToggleComments::default(), window, cx)
18207    });
18208    cx.assert_editor_state(
18209        &r#"
18210            <!-- <p>A«</p>
18211            <p>ˇ»B</p>ˇ -->
18212            <!-- <p>C«</p>
18213            <p>ˇ»D</p>ˇ -->
18214        "#
18215        .unindent(),
18216    );
18217    cx.update_editor(|editor, window, cx| {
18218        editor.toggle_comments(&ToggleComments::default(), window, cx)
18219    });
18220    cx.assert_editor_state(
18221        &r#"
18222            <p>A«</p>
18223            <p>ˇ»B</p>ˇ
18224            <p>C«</p>
18225            <p>ˇ»D</p>ˇ
18226        "#
18227        .unindent(),
18228    );
18229
18230    // Toggle comments when different languages are active for different
18231    // selections.
18232    cx.set_state(
18233        &r#"
18234            ˇ<script>
18235                ˇvar x = new Y();
18236            ˇ</script>
18237        "#
18238        .unindent(),
18239    );
18240    cx.executor().run_until_parked();
18241    cx.update_editor(|editor, window, cx| {
18242        editor.toggle_comments(&ToggleComments::default(), window, cx)
18243    });
18244    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
18245    // Uncommenting and commenting from this position brings in even more wrong artifacts.
18246    cx.assert_editor_state(
18247        &r#"
18248            <!-- ˇ<script> -->
18249                // ˇvar x = new Y();
18250            <!-- ˇ</script> -->
18251        "#
18252        .unindent(),
18253    );
18254}
18255
18256#[gpui::test]
18257fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
18258    init_test(cx, |_| {});
18259
18260    let buffer = cx.new(|cx| Buffer::local(sample_text(6, 4, 'a'), cx));
18261    let multibuffer = cx.new(|cx| {
18262        let mut multibuffer = MultiBuffer::new(ReadWrite);
18263        multibuffer.set_excerpts_for_path(
18264            PathKey::sorted(0),
18265            buffer.clone(),
18266            [
18267                Point::new(0, 0)..Point::new(0, 4),
18268                Point::new(5, 0)..Point::new(5, 4),
18269            ],
18270            0,
18271            cx,
18272        );
18273        assert_eq!(multibuffer.read(cx).text(), "aaaa\nffff");
18274        multibuffer
18275    });
18276
18277    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
18278    editor.update_in(cx, |editor, window, cx| {
18279        assert_eq!(editor.text(cx), "aaaa\nffff");
18280        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18281            s.select_ranges([
18282                Point::new(0, 0)..Point::new(0, 0),
18283                Point::new(1, 0)..Point::new(1, 0),
18284            ])
18285        });
18286
18287        editor.handle_input("X", window, cx);
18288        assert_eq!(editor.text(cx), "Xaaaa\nXffff");
18289        assert_eq!(
18290            editor.selections.ranges(&editor.display_snapshot(cx)),
18291            [
18292                Point::new(0, 1)..Point::new(0, 1),
18293                Point::new(1, 1)..Point::new(1, 1),
18294            ]
18295        );
18296
18297        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
18298        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18299            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
18300        });
18301        editor.backspace(&Default::default(), window, cx);
18302        assert_eq!(editor.text(cx), "Xa\nfff");
18303        assert_eq!(
18304            editor.selections.ranges(&editor.display_snapshot(cx)),
18305            [Point::new(1, 0)..Point::new(1, 0)]
18306        );
18307
18308        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18309            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
18310        });
18311        editor.backspace(&Default::default(), window, cx);
18312        assert_eq!(editor.text(cx), "X\nff");
18313        assert_eq!(
18314            editor.selections.ranges(&editor.display_snapshot(cx)),
18315            [Point::new(0, 1)..Point::new(0, 1)]
18316        );
18317    });
18318}
18319
18320#[gpui::test]
18321fn test_refresh_selections(cx: &mut TestAppContext) {
18322    init_test(cx, |_| {});
18323
18324    let buffer = cx.new(|cx| Buffer::local(sample_text(5, 4, 'a'), cx));
18325    let multibuffer = cx.new(|cx| {
18326        let mut multibuffer = MultiBuffer::new(ReadWrite);
18327        multibuffer.set_excerpts_for_path(
18328            PathKey::sorted(0),
18329            buffer.clone(),
18330            [
18331                Point::new(0, 0)..Point::new(1, 4),
18332                Point::new(3, 0)..Point::new(4, 4),
18333            ],
18334            0,
18335            cx,
18336        );
18337        multibuffer
18338    });
18339
18340    let editor = cx.add_window(|window, cx| {
18341        let mut editor = build_editor(multibuffer.clone(), window, cx);
18342        let snapshot = editor.snapshot(window, cx);
18343        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18344            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
18345        });
18346        editor.begin_selection(
18347            Point::new(2, 1).to_display_point(&snapshot),
18348            true,
18349            1,
18350            window,
18351            cx,
18352        );
18353        assert_eq!(
18354            editor.selections.ranges(&editor.display_snapshot(cx)),
18355            [
18356                Point::new(1, 3)..Point::new(1, 3),
18357                Point::new(2, 1)..Point::new(2, 1),
18358            ]
18359        );
18360        editor
18361    });
18362
18363    // Refreshing selections is a no-op when excerpts haven't changed.
18364    _ = editor.update(cx, |editor, window, cx| {
18365        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
18366        assert_eq!(
18367            editor.selections.ranges(&editor.display_snapshot(cx)),
18368            [
18369                Point::new(1, 3)..Point::new(1, 3),
18370                Point::new(2, 1)..Point::new(2, 1),
18371            ]
18372        );
18373    });
18374
18375    multibuffer.update(cx, |multibuffer, cx| {
18376        multibuffer.set_excerpts_for_path(
18377            PathKey::sorted(0),
18378            buffer.clone(),
18379            [Point::new(3, 0)..Point::new(4, 4)],
18380            0,
18381            cx,
18382        );
18383    });
18384    _ = editor.update(cx, |editor, window, cx| {
18385        // Removing an excerpt causes the first selection to become degenerate.
18386        assert_eq!(
18387            editor.selections.ranges(&editor.display_snapshot(cx)),
18388            [
18389                Point::new(0, 0)..Point::new(0, 0),
18390                Point::new(0, 1)..Point::new(0, 1)
18391            ]
18392        );
18393
18394        // Refreshing selections will relocate the first selection to the original buffer
18395        // location.
18396        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
18397        assert_eq!(
18398            editor.selections.ranges(&editor.display_snapshot(cx)),
18399            [
18400                Point::new(0, 0)..Point::new(0, 0),
18401                Point::new(0, 1)..Point::new(0, 1),
18402            ]
18403        );
18404        assert!(editor.selections.pending_anchor().is_some());
18405    });
18406}
18407
18408#[gpui::test]
18409fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
18410    init_test(cx, |_| {});
18411
18412    let buffer = cx.new(|cx| Buffer::local(sample_text(5, 4, 'a'), cx));
18413    let multibuffer = cx.new(|cx| {
18414        let mut multibuffer = MultiBuffer::new(ReadWrite);
18415        multibuffer.set_excerpts_for_path(
18416            PathKey::sorted(0),
18417            buffer.clone(),
18418            [
18419                Point::new(0, 0)..Point::new(1, 4),
18420                Point::new(3, 0)..Point::new(4, 4),
18421            ],
18422            0,
18423            cx,
18424        );
18425        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\ndddd\neeee");
18426        multibuffer
18427    });
18428
18429    let editor = cx.add_window(|window, cx| {
18430        let mut editor = build_editor(multibuffer.clone(), window, cx);
18431        let snapshot = editor.snapshot(window, cx);
18432        editor.begin_selection(
18433            Point::new(1, 3).to_display_point(&snapshot),
18434            false,
18435            1,
18436            window,
18437            cx,
18438        );
18439        assert_eq!(
18440            editor.selections.ranges(&editor.display_snapshot(cx)),
18441            [Point::new(1, 3)..Point::new(1, 3)]
18442        );
18443        editor
18444    });
18445
18446    multibuffer.update(cx, |multibuffer, cx| {
18447        multibuffer.set_excerpts_for_path(
18448            PathKey::sorted(0),
18449            buffer.clone(),
18450            [Point::new(3, 0)..Point::new(4, 4)],
18451            0,
18452            cx,
18453        );
18454    });
18455    _ = editor.update(cx, |editor, window, cx| {
18456        assert_eq!(
18457            editor.selections.ranges(&editor.display_snapshot(cx)),
18458            [Point::new(0, 0)..Point::new(0, 0)]
18459        );
18460
18461        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
18462        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
18463        assert_eq!(
18464            editor.selections.ranges(&editor.display_snapshot(cx)),
18465            [Point::new(0, 0)..Point::new(0, 0)]
18466        );
18467        assert!(editor.selections.pending_anchor().is_some());
18468    });
18469}
18470
18471#[gpui::test]
18472async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
18473    init_test(cx, |_| {});
18474
18475    let language = Arc::new(
18476        Language::new(
18477            LanguageConfig {
18478                brackets: BracketPairConfig {
18479                    pairs: vec![
18480                        BracketPair {
18481                            start: "{".to_string(),
18482                            end: "}".to_string(),
18483                            close: true,
18484                            surround: true,
18485                            newline: true,
18486                        },
18487                        BracketPair {
18488                            start: "/* ".to_string(),
18489                            end: " */".to_string(),
18490                            close: true,
18491                            surround: true,
18492                            newline: true,
18493                        },
18494                    ],
18495                    ..Default::default()
18496                },
18497                ..Default::default()
18498            },
18499            Some(tree_sitter_rust::LANGUAGE.into()),
18500        )
18501        .with_indents_query("")
18502        .unwrap(),
18503    );
18504
18505    let text = concat!(
18506        "{   }\n",     //
18507        "  x\n",       //
18508        "  /*   */\n", //
18509        "x\n",         //
18510        "{{} }\n",     //
18511    );
18512
18513    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
18514    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18515    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18516    editor
18517        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
18518        .await;
18519
18520    editor.update_in(cx, |editor, window, cx| {
18521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18522            s.select_display_ranges([
18523                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
18524                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
18525                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
18526            ])
18527        });
18528        editor.newline(&Newline, window, cx);
18529
18530        assert_eq!(
18531            editor.buffer().read(cx).read(cx).text(),
18532            concat!(
18533                "{ \n",    // Suppress rustfmt
18534                "\n",      //
18535                "}\n",     //
18536                "  x\n",   //
18537                "  /* \n", //
18538                "  \n",    //
18539                "  */\n",  //
18540                "x\n",     //
18541                "{{} \n",  //
18542                "}\n",     //
18543            )
18544        );
18545    });
18546}
18547
18548#[gpui::test]
18549fn test_highlighted_ranges(cx: &mut TestAppContext) {
18550    init_test(cx, |_| {});
18551
18552    let editor = cx.add_window(|window, cx| {
18553        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
18554        build_editor(buffer, window, cx)
18555    });
18556
18557    _ = editor.update(cx, |editor, window, cx| {
18558        let buffer = editor.buffer.read(cx).snapshot(cx);
18559
18560        let anchor_range =
18561            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
18562
18563        editor.highlight_background(
18564            HighlightKey::ColorizeBracket(0),
18565            &[
18566                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
18567                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
18568                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
18569                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
18570            ],
18571            |_, _| Hsla::red(),
18572            cx,
18573        );
18574        editor.highlight_background(
18575            HighlightKey::ColorizeBracket(1),
18576            &[
18577                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
18578                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
18579                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
18580                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
18581            ],
18582            |_, _| Hsla::green(),
18583            cx,
18584        );
18585
18586        let snapshot = editor.snapshot(window, cx);
18587        let highlighted_ranges = editor.sorted_background_highlights_in_range(
18588            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
18589            &snapshot,
18590            cx.theme(),
18591        );
18592        assert_eq!(
18593            highlighted_ranges,
18594            &[
18595                (
18596                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
18597                    Hsla::green(),
18598                ),
18599                (
18600                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
18601                    Hsla::red(),
18602                ),
18603                (
18604                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
18605                    Hsla::green(),
18606                ),
18607                (
18608                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
18609                    Hsla::red(),
18610                ),
18611            ]
18612        );
18613        assert_eq!(
18614            editor.sorted_background_highlights_in_range(
18615                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
18616                &snapshot,
18617                cx.theme(),
18618            ),
18619            &[(
18620                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
18621                Hsla::red(),
18622            )]
18623        );
18624    });
18625}
18626
18627#[gpui::test]
18628async fn test_following(cx: &mut TestAppContext) {
18629    init_test(cx, |_| {});
18630
18631    let fs = FakeFs::new(cx.executor());
18632    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
18633
18634    let buffer = project.update(cx, |project, cx| {
18635        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
18636        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
18637    });
18638    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18639    let follower = cx.update(|cx| {
18640        cx.open_window(
18641            WindowOptions {
18642                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
18643                    gpui::Point::new(px(0.), px(0.)),
18644                    gpui::Point::new(px(10.), px(80.)),
18645                ))),
18646                ..Default::default()
18647            },
18648            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
18649        )
18650        .unwrap()
18651    });
18652
18653    let is_still_following = Rc::new(RefCell::new(true));
18654    let follower_edit_event_count = Rc::new(RefCell::new(0));
18655    let pending_update = Rc::new(RefCell::new(None));
18656    let leader_entity = leader.root(cx).unwrap();
18657    let follower_entity = follower.root(cx).unwrap();
18658    _ = follower.update(cx, {
18659        let update = pending_update.clone();
18660        let is_still_following = is_still_following.clone();
18661        let follower_edit_event_count = follower_edit_event_count.clone();
18662        |_, window, cx| {
18663            cx.subscribe_in(
18664                &leader_entity,
18665                window,
18666                move |_, leader, event, window, cx| {
18667                    leader.update(cx, |leader, cx| {
18668                        leader.add_event_to_update_proto(
18669                            event,
18670                            &mut update.borrow_mut(),
18671                            window,
18672                            cx,
18673                        );
18674                    });
18675                },
18676            )
18677            .detach();
18678
18679            cx.subscribe_in(
18680                &follower_entity,
18681                window,
18682                move |_, _, event: &EditorEvent, _window, _cx| {
18683                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
18684                        *is_still_following.borrow_mut() = false;
18685                    }
18686
18687                    if let EditorEvent::BufferEdited = event {
18688                        *follower_edit_event_count.borrow_mut() += 1;
18689                    }
18690                },
18691            )
18692            .detach();
18693        }
18694    });
18695
18696    // Update the selections only
18697    _ = leader.update(cx, |leader, window, cx| {
18698        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18699            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
18700        });
18701    });
18702    follower
18703        .update(cx, |follower, window, cx| {
18704            follower.apply_update_proto(
18705                &project,
18706                pending_update.borrow_mut().take().unwrap(),
18707                window,
18708                cx,
18709            )
18710        })
18711        .unwrap()
18712        .await
18713        .unwrap();
18714    _ = follower.update(cx, |follower, _, cx| {
18715        assert_eq!(
18716            follower.selections.ranges(&follower.display_snapshot(cx)),
18717            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
18718        );
18719    });
18720    assert!(*is_still_following.borrow());
18721    assert_eq!(*follower_edit_event_count.borrow(), 0);
18722
18723    // Update the scroll position only
18724    _ = leader.update(cx, |leader, window, cx| {
18725        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
18726    });
18727    follower
18728        .update(cx, |follower, window, cx| {
18729            follower.apply_update_proto(
18730                &project,
18731                pending_update.borrow_mut().take().unwrap(),
18732                window,
18733                cx,
18734            )
18735        })
18736        .unwrap()
18737        .await
18738        .unwrap();
18739    assert_eq!(
18740        follower
18741            .update(cx, |follower, _, cx| follower.scroll_position(cx))
18742            .unwrap(),
18743        gpui::Point::new(1.5, 3.5)
18744    );
18745    assert!(*is_still_following.borrow());
18746    assert_eq!(*follower_edit_event_count.borrow(), 0);
18747
18748    // Update the selections and scroll position. The follower's scroll position is updated
18749    // via autoscroll, not via the leader's exact scroll position.
18750    _ = leader.update(cx, |leader, window, cx| {
18751        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18752            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
18753        });
18754        leader.request_autoscroll(Autoscroll::newest(), cx);
18755        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
18756    });
18757    follower
18758        .update(cx, |follower, window, cx| {
18759            follower.apply_update_proto(
18760                &project,
18761                pending_update.borrow_mut().take().unwrap(),
18762                window,
18763                cx,
18764            )
18765        })
18766        .unwrap()
18767        .await
18768        .unwrap();
18769    _ = follower.update(cx, |follower, _, cx| {
18770        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
18771        assert_eq!(
18772            follower.selections.ranges(&follower.display_snapshot(cx)),
18773            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
18774        );
18775    });
18776    assert!(*is_still_following.borrow());
18777
18778    // Creating a pending selection that precedes another selection
18779    _ = leader.update(cx, |leader, window, cx| {
18780        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18781            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
18782        });
18783        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
18784    });
18785    follower
18786        .update(cx, |follower, window, cx| {
18787            follower.apply_update_proto(
18788                &project,
18789                pending_update.borrow_mut().take().unwrap(),
18790                window,
18791                cx,
18792            )
18793        })
18794        .unwrap()
18795        .await
18796        .unwrap();
18797    _ = follower.update(cx, |follower, _, cx| {
18798        assert_eq!(
18799            follower.selections.ranges(&follower.display_snapshot(cx)),
18800            vec![
18801                MultiBufferOffset(0)..MultiBufferOffset(0),
18802                MultiBufferOffset(1)..MultiBufferOffset(1)
18803            ]
18804        );
18805    });
18806    assert!(*is_still_following.borrow());
18807
18808    // Extend the pending selection so that it surrounds another selection
18809    _ = leader.update(cx, |leader, window, cx| {
18810        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
18811    });
18812    follower
18813        .update(cx, |follower, window, cx| {
18814            follower.apply_update_proto(
18815                &project,
18816                pending_update.borrow_mut().take().unwrap(),
18817                window,
18818                cx,
18819            )
18820        })
18821        .unwrap()
18822        .await
18823        .unwrap();
18824    _ = follower.update(cx, |follower, _, cx| {
18825        assert_eq!(
18826            follower.selections.ranges(&follower.display_snapshot(cx)),
18827            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
18828        );
18829    });
18830
18831    // Scrolling locally breaks the follow
18832    _ = follower.update(cx, |follower, window, cx| {
18833        let top_anchor = follower
18834            .buffer()
18835            .read(cx)
18836            .read(cx)
18837            .anchor_after(MultiBufferOffset(0));
18838        follower.set_scroll_anchor(
18839            ScrollAnchor {
18840                anchor: top_anchor,
18841                offset: gpui::Point::new(0.0, 0.5),
18842            },
18843            window,
18844            cx,
18845        );
18846    });
18847    assert!(!(*is_still_following.borrow()));
18848}
18849
18850#[gpui::test]
18851async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
18852    init_test(cx, |_| {});
18853
18854    let fs = FakeFs::new(cx.executor());
18855    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
18856    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
18857    let workspace = window
18858        .read_with(cx, |mw, _| mw.workspace().clone())
18859        .unwrap();
18860    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
18861
18862    let cx = &mut VisualTestContext::from_window(*window, cx);
18863
18864    let leader = pane.update_in(cx, |_, window, cx| {
18865        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
18866        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
18867    });
18868
18869    // Start following the editor when it has no excerpts.
18870    let mut state_message =
18871        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
18872    let workspace_entity = workspace.clone();
18873    let follower_1 = cx
18874        .update_window(*window, |_, window, cx| {
18875            Editor::from_state_proto(
18876                workspace_entity,
18877                ViewId {
18878                    creator: CollaboratorId::PeerId(PeerId::default()),
18879                    id: 0,
18880                },
18881                &mut state_message,
18882                window,
18883                cx,
18884            )
18885        })
18886        .unwrap()
18887        .unwrap()
18888        .await
18889        .unwrap();
18890
18891    let update_message = Rc::new(RefCell::new(None));
18892    follower_1.update_in(cx, {
18893        let update = update_message.clone();
18894        |_, window, cx| {
18895            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
18896                leader.update(cx, |leader, cx| {
18897                    leader.add_event_to_update_proto(event, &mut update.borrow_mut(), window, cx);
18898                });
18899            })
18900            .detach();
18901        }
18902    });
18903
18904    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
18905        (
18906            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
18907            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
18908        )
18909    });
18910
18911    // Insert some excerpts.
18912    leader.update(cx, |leader, cx| {
18913        leader.buffer.update(cx, |multibuffer, cx| {
18914            multibuffer.set_excerpts_for_path(
18915                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
18916                buffer_1.clone(),
18917                vec![
18918                    Point::row_range(0..3),
18919                    Point::row_range(1..6),
18920                    Point::row_range(12..15),
18921                ],
18922                0,
18923                cx,
18924            );
18925            multibuffer.set_excerpts_for_path(
18926                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
18927                buffer_2.clone(),
18928                vec![Point::row_range(0..6), Point::row_range(8..12)],
18929                0,
18930                cx,
18931            );
18932        });
18933    });
18934
18935    // Apply the update of adding the excerpts.
18936    follower_1
18937        .update_in(cx, |follower, window, cx| {
18938            follower.apply_update_proto(
18939                &project,
18940                update_message.borrow().clone().unwrap(),
18941                window,
18942                cx,
18943            )
18944        })
18945        .await
18946        .unwrap();
18947    assert_eq!(
18948        follower_1.update(cx, |editor, cx| editor.text(cx)),
18949        leader.update(cx, |editor, cx| editor.text(cx))
18950    );
18951    update_message.borrow_mut().take();
18952
18953    // Start following separately after it already has excerpts.
18954    let mut state_message =
18955        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
18956    let workspace_entity = workspace.clone();
18957    let follower_2 = cx
18958        .update_window(*window, |_, window, cx| {
18959            Editor::from_state_proto(
18960                workspace_entity,
18961                ViewId {
18962                    creator: CollaboratorId::PeerId(PeerId::default()),
18963                    id: 0,
18964                },
18965                &mut state_message,
18966                window,
18967                cx,
18968            )
18969        })
18970        .unwrap()
18971        .unwrap()
18972        .await
18973        .unwrap();
18974    assert_eq!(
18975        follower_2.update(cx, |editor, cx| editor.text(cx)),
18976        leader.update(cx, |editor, cx| editor.text(cx))
18977    );
18978
18979    // Remove some excerpts.
18980    leader.update(cx, |leader, cx| {
18981        leader.buffer.update(cx, |multibuffer, cx| {
18982            multibuffer.remove_excerpts_for_path(
18983                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
18984                cx,
18985            );
18986        });
18987    });
18988
18989    // Apply the update of removing the excerpts.
18990    follower_1
18991        .update_in(cx, |follower, window, cx| {
18992            follower.apply_update_proto(
18993                &project,
18994                update_message.borrow().clone().unwrap(),
18995                window,
18996                cx,
18997            )
18998        })
18999        .await
19000        .unwrap();
19001    follower_2
19002        .update_in(cx, |follower, window, cx| {
19003            follower.apply_update_proto(
19004                &project,
19005                update_message.borrow().clone().unwrap(),
19006                window,
19007                cx,
19008            )
19009        })
19010        .await
19011        .unwrap();
19012    update_message.borrow_mut().take();
19013    assert_eq!(
19014        follower_1.update(cx, |editor, cx| editor.text(cx)),
19015        leader.update(cx, |editor, cx| editor.text(cx))
19016    );
19017}
19018
19019#[gpui::test]
19020async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19021    init_test(cx, |_| {});
19022
19023    let mut cx = EditorTestContext::new(cx).await;
19024    let lsp_store =
19025        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
19026
19027    cx.set_state(indoc! {"
19028        ˇfn func(abc def: i32) -> u32 {
19029        }
19030    "});
19031
19032    cx.update(|_, cx| {
19033        lsp_store.update(cx, |lsp_store, cx| {
19034            lsp_store
19035                .update_diagnostics(
19036                    LanguageServerId(0),
19037                    lsp::PublishDiagnosticsParams {
19038                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
19039                        version: None,
19040                        diagnostics: vec![
19041                            lsp::Diagnostic {
19042                                range: lsp::Range::new(
19043                                    lsp::Position::new(0, 11),
19044                                    lsp::Position::new(0, 12),
19045                                ),
19046                                severity: Some(lsp::DiagnosticSeverity::ERROR),
19047                                ..Default::default()
19048                            },
19049                            lsp::Diagnostic {
19050                                range: lsp::Range::new(
19051                                    lsp::Position::new(0, 12),
19052                                    lsp::Position::new(0, 15),
19053                                ),
19054                                severity: Some(lsp::DiagnosticSeverity::ERROR),
19055                                ..Default::default()
19056                            },
19057                            lsp::Diagnostic {
19058                                range: lsp::Range::new(
19059                                    lsp::Position::new(0, 25),
19060                                    lsp::Position::new(0, 28),
19061                                ),
19062                                severity: Some(lsp::DiagnosticSeverity::ERROR),
19063                                ..Default::default()
19064                            },
19065                        ],
19066                    },
19067                    None,
19068                    DiagnosticSourceKind::Pushed,
19069                    &[],
19070                    cx,
19071                )
19072                .unwrap()
19073        });
19074    });
19075
19076    executor.run_until_parked();
19077
19078    cx.update_editor(|editor, window, cx| {
19079        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19080    });
19081
19082    cx.assert_editor_state(indoc! {"
19083        fn func(abc def: i32) -> ˇu32 {
19084        }
19085    "});
19086
19087    cx.update_editor(|editor, window, cx| {
19088        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19089    });
19090
19091    cx.assert_editor_state(indoc! {"
19092        fn func(abc ˇdef: i32) -> u32 {
19093        }
19094    "});
19095
19096    cx.update_editor(|editor, window, cx| {
19097        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19098    });
19099
19100    cx.assert_editor_state(indoc! {"
19101        fn func(abcˇ def: i32) -> u32 {
19102        }
19103    "});
19104
19105    cx.update_editor(|editor, window, cx| {
19106        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19107    });
19108
19109    cx.assert_editor_state(indoc! {"
19110        fn func(abc def: i32) -> ˇu32 {
19111        }
19112    "});
19113}
19114
19115#[gpui::test]
19116async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19117    init_test(cx, |_| {});
19118
19119    let mut cx = EditorTestContext::new(cx).await;
19120
19121    let diff_base = r#"
19122        use some::mod;
19123
19124        const A: u32 = 42;
19125
19126        fn main() {
19127            println!("hello");
19128
19129            println!("world");
19130        }
19131        "#
19132    .unindent();
19133
19134    // Edits are modified, removed, modified, added
19135    cx.set_state(
19136        &r#"
19137        use some::modified;
19138
19139        ˇ
19140        fn main() {
19141            println!("hello there");
19142
19143            println!("around the");
19144            println!("world");
19145        }
19146        "#
19147        .unindent(),
19148    );
19149
19150    cx.set_head_text(&diff_base);
19151    executor.run_until_parked();
19152
19153    cx.update_editor(|editor, window, cx| {
19154        //Wrap around the bottom of the buffer
19155        for _ in 0..3 {
19156            editor.go_to_next_hunk(&GoToHunk, window, cx);
19157        }
19158    });
19159
19160    cx.assert_editor_state(
19161        &r#"
19162        ˇuse some::modified;
19163
19164
19165        fn main() {
19166            println!("hello there");
19167
19168            println!("around the");
19169            println!("world");
19170        }
19171        "#
19172        .unindent(),
19173    );
19174
19175    cx.update_editor(|editor, window, cx| {
19176        //Wrap around the top of the buffer
19177        for _ in 0..2 {
19178            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19179        }
19180    });
19181
19182    cx.assert_editor_state(
19183        &r#"
19184        use some::modified;
19185
19186
19187        fn main() {
19188        ˇ    println!("hello there");
19189
19190            println!("around the");
19191            println!("world");
19192        }
19193        "#
19194        .unindent(),
19195    );
19196
19197    cx.update_editor(|editor, window, cx| {
19198        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19199    });
19200
19201    cx.assert_editor_state(
19202        &r#"
19203        use some::modified;
19204
19205        ˇ
19206        fn main() {
19207            println!("hello there");
19208
19209            println!("around the");
19210            println!("world");
19211        }
19212        "#
19213        .unindent(),
19214    );
19215
19216    cx.update_editor(|editor, window, cx| {
19217        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19218    });
19219
19220    cx.assert_editor_state(
19221        &r#"
19222        ˇuse some::modified;
19223
19224
19225        fn main() {
19226            println!("hello there");
19227
19228            println!("around the");
19229            println!("world");
19230        }
19231        "#
19232        .unindent(),
19233    );
19234
19235    cx.update_editor(|editor, window, cx| {
19236        for _ in 0..2 {
19237            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19238        }
19239    });
19240
19241    cx.assert_editor_state(
19242        &r#"
19243        use some::modified;
19244
19245
19246        fn main() {
19247        ˇ    println!("hello there");
19248
19249            println!("around the");
19250            println!("world");
19251        }
19252        "#
19253        .unindent(),
19254    );
19255
19256    cx.update_editor(|editor, window, cx| {
19257        editor.fold(&Fold, window, cx);
19258    });
19259
19260    cx.update_editor(|editor, window, cx| {
19261        editor.go_to_next_hunk(&GoToHunk, window, cx);
19262    });
19263
19264    cx.assert_editor_state(
19265        &r#"
19266        ˇuse some::modified;
19267
19268
19269        fn main() {
19270            println!("hello there");
19271
19272            println!("around the");
19273            println!("world");
19274        }
19275        "#
19276        .unindent(),
19277    );
19278}
19279
19280#[test]
19281fn test_split_words() {
19282    fn split(text: &str) -> Vec<&str> {
19283        split_words(text).collect()
19284    }
19285
19286    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
19287    assert_eq!(split("hello_world"), &["hello_", "world"]);
19288    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
19289    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
19290    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
19291    assert_eq!(split("helloworld"), &["helloworld"]);
19292
19293    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
19294}
19295
19296#[test]
19297fn test_split_words_for_snippet_prefix() {
19298    fn split(text: &str) -> Vec<&str> {
19299        snippet_candidate_suffixes(text, &|c| c.is_alphanumeric() || c == '_').collect()
19300    }
19301
19302    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
19303    assert_eq!(split("hello_world"), &["hello_world"]);
19304    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
19305    assert_eq!(split("Hello_World"), &["Hello_World"]);
19306    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
19307    assert_eq!(split("helloworld"), &["helloworld"]);
19308    assert_eq!(
19309        split("this@is!@#$^many   . symbols"),
19310        &[
19311            "symbols",
19312            " symbols",
19313            ". symbols",
19314            " . symbols",
19315            "  . symbols",
19316            "   . symbols",
19317            "many   . symbols",
19318            "^many   . symbols",
19319            "$^many   . symbols",
19320            "#$^many   . symbols",
19321            "@#$^many   . symbols",
19322            "!@#$^many   . symbols",
19323            "is!@#$^many   . symbols",
19324            "@is!@#$^many   . symbols",
19325            "this@is!@#$^many   . symbols",
19326        ],
19327    );
19328    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
19329}
19330
19331#[gpui::test]
19332async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
19333    init_test(cx, |_| {});
19334
19335    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
19336
19337    #[track_caller]
19338    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
19339        let _state_context = cx.set_state(before);
19340        cx.run_until_parked();
19341        cx.update_editor(|editor, window, cx| {
19342            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
19343        });
19344        cx.run_until_parked();
19345        cx.assert_editor_state(after);
19346    }
19347
19348    // Outside bracket jumps to outside of matching bracket
19349    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
19350    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
19351
19352    // Inside bracket jumps to inside of matching bracket
19353    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
19354    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
19355
19356    // When outside a bracket and inside, favor jumping to the inside bracket
19357    assert(
19358        "console.log('foo', [1, 2, 3]ˇ);",
19359        "console.log('foo', ˇ[1, 2, 3]);",
19360        &mut cx,
19361    );
19362    assert(
19363        "console.log(ˇ'foo', [1, 2, 3]);",
19364        "console.log('foo'ˇ, [1, 2, 3]);",
19365        &mut cx,
19366    );
19367
19368    // Bias forward if two options are equally likely
19369    assert(
19370        "let result = curried_fun()ˇ();",
19371        "let result = curried_fun()()ˇ;",
19372        &mut cx,
19373    );
19374
19375    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
19376    assert(
19377        indoc! {"
19378            function test() {
19379                console.log('test')ˇ
19380            }"},
19381        indoc! {"
19382            function test() {
19383                console.logˇ('test')
19384            }"},
19385        &mut cx,
19386    );
19387}
19388
19389#[gpui::test]
19390async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
19391    init_test(cx, |_| {});
19392    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
19393    language_registry.add(markdown_lang());
19394    language_registry.add(rust_lang());
19395    let buffer = cx.new(|cx| {
19396        let mut buffer = language::Buffer::local(
19397            indoc! {"
19398            ```rs
19399            impl Worktree {
19400                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
19401                }
19402            }
19403            ```
19404        "},
19405            cx,
19406        );
19407        buffer.set_language_registry(language_registry.clone());
19408        buffer.set_language(Some(markdown_lang()), cx);
19409        buffer
19410    });
19411    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19412    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
19413    cx.executor().run_until_parked();
19414    _ = editor.update(cx, |editor, window, cx| {
19415        // Case 1: Test outer enclosing brackets
19416        select_ranges(
19417            editor,
19418            &indoc! {"
19419                ```rs
19420                impl Worktree {
19421                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
19422                    }
1942319424                ```
19425            "},
19426            window,
19427            cx,
19428        );
19429        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
19430        assert_text_with_selections(
19431            editor,
19432            &indoc! {"
19433                ```rs
19434                impl Worktree ˇ{
19435                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
19436                    }
19437                }
19438                ```
19439            "},
19440            cx,
19441        );
19442        // Case 2: Test inner enclosing brackets
19443        select_ranges(
19444            editor,
19445            &indoc! {"
19446                ```rs
19447                impl Worktree {
19448                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1944919450                }
19451                ```
19452            "},
19453            window,
19454            cx,
19455        );
19456        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
19457        assert_text_with_selections(
19458            editor,
19459            &indoc! {"
19460                ```rs
19461                impl Worktree {
19462                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
19463                    }
19464                }
19465                ```
19466            "},
19467            cx,
19468        );
19469    });
19470}
19471
19472#[gpui::test]
19473async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
19474    init_test(cx, |_| {});
19475
19476    let fs = FakeFs::new(cx.executor());
19477    fs.insert_tree(
19478        path!("/a"),
19479        json!({
19480            "main.rs": "fn main() { let a = 5; }",
19481            "other.rs": "// Test file",
19482        }),
19483    )
19484    .await;
19485    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19486
19487    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19488    language_registry.add(Arc::new(Language::new(
19489        LanguageConfig {
19490            name: "Rust".into(),
19491            matcher: LanguageMatcher {
19492                path_suffixes: vec!["rs".to_string()],
19493                ..Default::default()
19494            },
19495            brackets: BracketPairConfig {
19496                pairs: vec![BracketPair {
19497                    start: "{".to_string(),
19498                    end: "}".to_string(),
19499                    close: true,
19500                    surround: true,
19501                    newline: true,
19502                }],
19503                disabled_scopes_by_bracket_ix: Vec::new(),
19504            },
19505            ..Default::default()
19506        },
19507        Some(tree_sitter_rust::LANGUAGE.into()),
19508    )));
19509    let mut fake_servers = language_registry.register_fake_lsp(
19510        "Rust",
19511        FakeLspAdapter {
19512            capabilities: lsp::ServerCapabilities {
19513                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
19514                    first_trigger_character: "{".to_string(),
19515                    more_trigger_character: None,
19516                }),
19517                ..Default::default()
19518            },
19519            ..Default::default()
19520        },
19521    );
19522
19523    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
19524    let workspace = window
19525        .read_with(cx, |mw, _| mw.workspace().clone())
19526        .unwrap();
19527
19528    let cx = &mut VisualTestContext::from_window(*window, cx);
19529
19530    let worktree_id = workspace.update_in(cx, |workspace, _, cx| {
19531        workspace.project().update(cx, |project, cx| {
19532            project.worktrees(cx).next().unwrap().read(cx).id()
19533        })
19534    });
19535
19536    let buffer = project
19537        .update(cx, |project, cx| {
19538            project.open_local_buffer(path!("/a/main.rs"), cx)
19539        })
19540        .await
19541        .unwrap();
19542    let editor_handle = workspace
19543        .update_in(cx, |workspace, window, cx| {
19544            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
19545        })
19546        .await
19547        .unwrap()
19548        .downcast::<Editor>()
19549        .unwrap();
19550
19551    let fake_server = fake_servers.next().await.unwrap();
19552
19553    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
19554        |params, _| async move {
19555            assert_eq!(
19556                params.text_document_position.text_document.uri,
19557                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
19558            );
19559            assert_eq!(
19560                params.text_document_position.position,
19561                lsp::Position::new(0, 21),
19562            );
19563
19564            Ok(Some(vec![lsp::TextEdit {
19565                new_text: "]".to_string(),
19566                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19567            }]))
19568        },
19569    );
19570
19571    editor_handle.update_in(cx, |editor, window, cx| {
19572        window.focus(&editor.focus_handle(cx), cx);
19573        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19574            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
19575        });
19576        editor.handle_input("{", window, cx);
19577    });
19578
19579    cx.executor().run_until_parked();
19580
19581    buffer.update(cx, |buffer, _| {
19582        assert_eq!(
19583            buffer.text(),
19584            "fn main() { let a = {5}; }",
19585            "No extra braces from on type formatting should appear in the buffer"
19586        )
19587    });
19588}
19589
19590#[gpui::test(iterations = 20, seeds(31))]
19591async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
19592    init_test(cx, |_| {});
19593
19594    let mut cx = EditorLspTestContext::new_rust(
19595        lsp::ServerCapabilities {
19596            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
19597                first_trigger_character: ".".to_string(),
19598                more_trigger_character: None,
19599            }),
19600            ..Default::default()
19601        },
19602        cx,
19603    )
19604    .await;
19605
19606    cx.update_buffer(|buffer, _| {
19607        // This causes autoindent to be async.
19608        buffer.set_sync_parse_timeout(None)
19609    });
19610
19611    cx.set_state("fn c() {\n    d()ˇ\n}\n");
19612    cx.simulate_keystroke("\n");
19613    cx.run_until_parked();
19614
19615    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
19616    let mut request =
19617        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
19618            let buffer_cloned = buffer_cloned.clone();
19619            async move {
19620                buffer_cloned.update(&mut cx, |buffer, _| {
19621                    assert_eq!(
19622                        buffer.text(),
19623                        "fn c() {\n    d()\n        .\n}\n",
19624                        "OnTypeFormatting should triggered after autoindent applied"
19625                    )
19626                });
19627
19628                Ok(Some(vec![]))
19629            }
19630        });
19631
19632    cx.simulate_keystroke(".");
19633    cx.run_until_parked();
19634
19635    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
19636    assert!(request.next().await.is_some());
19637    request.close();
19638    assert!(request.next().await.is_none());
19639}
19640
19641#[gpui::test]
19642async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
19643    init_test(cx, |_| {});
19644
19645    let fs = FakeFs::new(cx.executor());
19646    fs.insert_tree(
19647        path!("/a"),
19648        json!({
19649            "main.rs": "fn main() { let a = 5; }",
19650            "other.rs": "// Test file",
19651        }),
19652    )
19653    .await;
19654
19655    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19656
19657    let server_restarts = Arc::new(AtomicUsize::new(0));
19658    let closure_restarts = Arc::clone(&server_restarts);
19659    let language_server_name = "test language server";
19660    let language_name: LanguageName = "Rust".into();
19661
19662    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19663    language_registry.add(Arc::new(Language::new(
19664        LanguageConfig {
19665            name: language_name.clone(),
19666            matcher: LanguageMatcher {
19667                path_suffixes: vec!["rs".to_string()],
19668                ..Default::default()
19669            },
19670            ..Default::default()
19671        },
19672        Some(tree_sitter_rust::LANGUAGE.into()),
19673    )));
19674    let mut fake_servers = language_registry.register_fake_lsp(
19675        "Rust",
19676        FakeLspAdapter {
19677            name: language_server_name,
19678            initialization_options: Some(json!({
19679                "testOptionValue": true
19680            })),
19681            initializer: Some(Box::new(move |fake_server| {
19682                let task_restarts = Arc::clone(&closure_restarts);
19683                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
19684                    task_restarts.fetch_add(1, atomic::Ordering::Release);
19685                    futures::future::ready(Ok(()))
19686                });
19687            })),
19688            ..Default::default()
19689        },
19690    );
19691
19692    let _window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
19693    let _buffer = project
19694        .update(cx, |project, cx| {
19695            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
19696        })
19697        .await
19698        .unwrap();
19699    let _fake_server = fake_servers.next().await.unwrap();
19700    update_test_language_settings(cx, &|language_settings| {
19701        language_settings.languages.0.insert(
19702            language_name.clone().0.to_string(),
19703            LanguageSettingsContent {
19704                tab_size: NonZeroU32::new(8),
19705                ..Default::default()
19706            },
19707        );
19708    });
19709    cx.executor().run_until_parked();
19710    assert_eq!(
19711        server_restarts.load(atomic::Ordering::Acquire),
19712        0,
19713        "Should not restart LSP server on an unrelated change"
19714    );
19715
19716    update_test_project_settings(cx, &|project_settings| {
19717        project_settings.lsp.0.insert(
19718            "Some other server name".into(),
19719            LspSettings {
19720                binary: None,
19721                settings: None,
19722                initialization_options: Some(json!({
19723                    "some other init value": false
19724                })),
19725                enable_lsp_tasks: false,
19726                fetch: None,
19727            },
19728        );
19729    });
19730    cx.executor().run_until_parked();
19731    assert_eq!(
19732        server_restarts.load(atomic::Ordering::Acquire),
19733        0,
19734        "Should not restart LSP server on an unrelated LSP settings change"
19735    );
19736
19737    update_test_project_settings(cx, &|project_settings| {
19738        project_settings.lsp.0.insert(
19739            language_server_name.into(),
19740            LspSettings {
19741                binary: None,
19742                settings: None,
19743                initialization_options: Some(json!({
19744                    "anotherInitValue": false
19745                })),
19746                enable_lsp_tasks: false,
19747                fetch: None,
19748            },
19749        );
19750    });
19751    cx.executor().run_until_parked();
19752    assert_eq!(
19753        server_restarts.load(atomic::Ordering::Acquire),
19754        1,
19755        "Should restart LSP server on a related LSP settings change"
19756    );
19757
19758    update_test_project_settings(cx, &|project_settings| {
19759        project_settings.lsp.0.insert(
19760            language_server_name.into(),
19761            LspSettings {
19762                binary: None,
19763                settings: None,
19764                initialization_options: Some(json!({
19765                    "anotherInitValue": false
19766                })),
19767                enable_lsp_tasks: false,
19768                fetch: None,
19769            },
19770        );
19771    });
19772    cx.executor().run_until_parked();
19773    assert_eq!(
19774        server_restarts.load(atomic::Ordering::Acquire),
19775        1,
19776        "Should not restart LSP server on a related LSP settings change that is the same"
19777    );
19778
19779    update_test_project_settings(cx, &|project_settings| {
19780        project_settings.lsp.0.insert(
19781            language_server_name.into(),
19782            LspSettings {
19783                binary: None,
19784                settings: None,
19785                initialization_options: None,
19786                enable_lsp_tasks: false,
19787                fetch: None,
19788            },
19789        );
19790    });
19791    cx.executor().run_until_parked();
19792    assert_eq!(
19793        server_restarts.load(atomic::Ordering::Acquire),
19794        2,
19795        "Should restart LSP server on another related LSP settings change"
19796    );
19797}
19798
19799#[gpui::test]
19800async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
19801    init_test(cx, |_| {});
19802
19803    let mut cx = EditorLspTestContext::new_rust(
19804        lsp::ServerCapabilities {
19805            completion_provider: Some(lsp::CompletionOptions {
19806                trigger_characters: Some(vec![".".to_string()]),
19807                resolve_provider: Some(true),
19808                ..Default::default()
19809            }),
19810            ..Default::default()
19811        },
19812        cx,
19813    )
19814    .await;
19815
19816    cx.set_state("fn main() { let a = 2ˇ; }");
19817    cx.simulate_keystroke(".");
19818    let completion_item = lsp::CompletionItem {
19819        label: "some".into(),
19820        kind: Some(lsp::CompletionItemKind::SNIPPET),
19821        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
19822        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
19823            kind: lsp::MarkupKind::Markdown,
19824            value: "```rust\nSome(2)\n```".to_string(),
19825        })),
19826        deprecated: Some(false),
19827        sort_text: Some("fffffff2".to_string()),
19828        filter_text: Some("some".to_string()),
19829        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
19830        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19831            range: lsp::Range {
19832                start: lsp::Position {
19833                    line: 0,
19834                    character: 22,
19835                },
19836                end: lsp::Position {
19837                    line: 0,
19838                    character: 22,
19839                },
19840            },
19841            new_text: "Some(2)".to_string(),
19842        })),
19843        additional_text_edits: Some(vec![lsp::TextEdit {
19844            range: lsp::Range {
19845                start: lsp::Position {
19846                    line: 0,
19847                    character: 20,
19848                },
19849                end: lsp::Position {
19850                    line: 0,
19851                    character: 22,
19852                },
19853            },
19854            new_text: "".to_string(),
19855        }]),
19856        ..Default::default()
19857    };
19858
19859    let closure_completion_item = completion_item.clone();
19860    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19861        let task_completion_item = closure_completion_item.clone();
19862        async move {
19863            Ok(Some(lsp::CompletionResponse::Array(vec![
19864                task_completion_item,
19865            ])))
19866        }
19867    });
19868
19869    request.next().await;
19870
19871    cx.condition(|editor, _| editor.context_menu_visible())
19872        .await;
19873    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
19874        editor
19875            .confirm_completion(&ConfirmCompletion::default(), window, cx)
19876            .unwrap()
19877    });
19878    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
19879
19880    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
19881        let task_completion_item = completion_item.clone();
19882        async move { Ok(task_completion_item) }
19883    })
19884    .next()
19885    .await
19886    .unwrap();
19887    apply_additional_edits.await.unwrap();
19888    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
19889}
19890
19891#[gpui::test]
19892async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
19893    init_test(cx, |_| {});
19894
19895    let mut cx = EditorLspTestContext::new_rust(
19896        lsp::ServerCapabilities {
19897            completion_provider: Some(lsp::CompletionOptions {
19898                trigger_characters: Some(vec![".".to_string()]),
19899                resolve_provider: Some(true),
19900                ..Default::default()
19901            }),
19902            ..Default::default()
19903        },
19904        cx,
19905    )
19906    .await;
19907
19908    cx.set_state("fn main() { let a = 2ˇ; }");
19909    cx.simulate_keystroke(".");
19910
19911    let item1 = lsp::CompletionItem {
19912        label: "method id()".to_string(),
19913        filter_text: Some("id".to_string()),
19914        detail: None,
19915        documentation: None,
19916        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19917            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19918            new_text: ".id".to_string(),
19919        })),
19920        ..lsp::CompletionItem::default()
19921    };
19922
19923    let item2 = lsp::CompletionItem {
19924        label: "other".to_string(),
19925        filter_text: Some("other".to_string()),
19926        detail: None,
19927        documentation: None,
19928        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19929            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19930            new_text: ".other".to_string(),
19931        })),
19932        ..lsp::CompletionItem::default()
19933    };
19934
19935    let item1 = item1.clone();
19936    cx.set_request_handler::<lsp::request::Completion, _, _>({
19937        let item1 = item1.clone();
19938        move |_, _, _| {
19939            let item1 = item1.clone();
19940            let item2 = item2.clone();
19941            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
19942        }
19943    })
19944    .next()
19945    .await;
19946
19947    cx.condition(|editor, _| editor.context_menu_visible())
19948        .await;
19949    cx.update_editor(|editor, _, _| {
19950        let context_menu = editor.context_menu.borrow_mut();
19951        let context_menu = context_menu
19952            .as_ref()
19953            .expect("Should have the context menu deployed");
19954        match context_menu {
19955            CodeContextMenu::Completions(completions_menu) => {
19956                let completions = completions_menu.completions.borrow_mut();
19957                assert_eq!(
19958                    completions
19959                        .iter()
19960                        .map(|completion| &completion.label.text)
19961                        .collect::<Vec<_>>(),
19962                    vec!["method id()", "other"]
19963                )
19964            }
19965            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
19966        }
19967    });
19968
19969    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
19970        let item1 = item1.clone();
19971        move |_, item_to_resolve, _| {
19972            let item1 = item1.clone();
19973            async move {
19974                if item1 == item_to_resolve {
19975                    Ok(lsp::CompletionItem {
19976                        label: "method id()".to_string(),
19977                        filter_text: Some("id".to_string()),
19978                        detail: Some("Now resolved!".to_string()),
19979                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
19980                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
19981                            range: lsp::Range::new(
19982                                lsp::Position::new(0, 22),
19983                                lsp::Position::new(0, 22),
19984                            ),
19985                            new_text: ".id".to_string(),
19986                        })),
19987                        ..lsp::CompletionItem::default()
19988                    })
19989                } else {
19990                    Ok(item_to_resolve)
19991                }
19992            }
19993        }
19994    })
19995    .next()
19996    .await
19997    .unwrap();
19998    cx.run_until_parked();
19999
20000    cx.update_editor(|editor, window, cx| {
20001        editor.context_menu_next(&Default::default(), window, cx);
20002    });
20003    cx.run_until_parked();
20004
20005    cx.update_editor(|editor, _, _| {
20006        let context_menu = editor.context_menu.borrow_mut();
20007        let context_menu = context_menu
20008            .as_ref()
20009            .expect("Should have the context menu deployed");
20010        match context_menu {
20011            CodeContextMenu::Completions(completions_menu) => {
20012                let completions = completions_menu.completions.borrow_mut();
20013                assert_eq!(
20014                    completions
20015                        .iter()
20016                        .map(|completion| &completion.label.text)
20017                        .collect::<Vec<_>>(),
20018                    vec!["method id() Now resolved!", "other"],
20019                    "Should update first completion label, but not second as the filter text did not match."
20020                );
20021            }
20022            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
20023        }
20024    });
20025}
20026
20027#[gpui::test]
20028async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
20029    init_test(cx, |_| {});
20030    let mut cx = EditorLspTestContext::new_rust(
20031        lsp::ServerCapabilities {
20032            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
20033            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
20034            completion_provider: Some(lsp::CompletionOptions {
20035                resolve_provider: Some(true),
20036                ..Default::default()
20037            }),
20038            ..Default::default()
20039        },
20040        cx,
20041    )
20042    .await;
20043    cx.set_state(indoc! {"
20044        struct TestStruct {
20045            field: i32
20046        }
20047
20048        fn mainˇ() {
20049            let unused_var = 42;
20050            let test_struct = TestStruct { field: 42 };
20051        }
20052    "});
20053    let symbol_range = cx.lsp_range(indoc! {"
20054        struct TestStruct {
20055            field: i32
20056        }
20057
20058        «fn main»() {
20059            let unused_var = 42;
20060            let test_struct = TestStruct { field: 42 };
20061        }
20062    "});
20063    let mut hover_requests =
20064        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
20065            Ok(Some(lsp::Hover {
20066                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
20067                    kind: lsp::MarkupKind::Markdown,
20068                    value: "Function documentation".to_string(),
20069                }),
20070                range: Some(symbol_range),
20071            }))
20072        });
20073
20074    // Case 1: Test that code action menu hide hover popover
20075    cx.dispatch_action(Hover);
20076    hover_requests.next().await;
20077    cx.condition(|editor, _| editor.hover_state.visible()).await;
20078    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
20079        move |_, _, _| async move {
20080            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
20081                lsp::CodeAction {
20082                    title: "Remove unused variable".to_string(),
20083                    kind: Some(CodeActionKind::QUICKFIX),
20084                    edit: Some(lsp::WorkspaceEdit {
20085                        changes: Some(
20086                            [(
20087                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
20088                                vec![lsp::TextEdit {
20089                                    range: lsp::Range::new(
20090                                        lsp::Position::new(5, 4),
20091                                        lsp::Position::new(5, 27),
20092                                    ),
20093                                    new_text: "".to_string(),
20094                                }],
20095                            )]
20096                            .into_iter()
20097                            .collect(),
20098                        ),
20099                        ..Default::default()
20100                    }),
20101                    ..Default::default()
20102                },
20103            )]))
20104        },
20105    );
20106    cx.update_editor(|editor, window, cx| {
20107        editor.toggle_code_actions(
20108            &ToggleCodeActions {
20109                deployed_from: None,
20110                quick_launch: false,
20111            },
20112            window,
20113            cx,
20114        );
20115    });
20116    code_action_requests.next().await;
20117    cx.run_until_parked();
20118    cx.condition(|editor, _| editor.context_menu_visible())
20119        .await;
20120    cx.update_editor(|editor, _, _| {
20121        assert!(
20122            !editor.hover_state.visible(),
20123            "Hover popover should be hidden when code action menu is shown"
20124        );
20125        // Hide code actions
20126        editor.context_menu.take();
20127    });
20128
20129    // Case 2: Test that code completions hide hover popover
20130    cx.dispatch_action(Hover);
20131    hover_requests.next().await;
20132    cx.condition(|editor, _| editor.hover_state.visible()).await;
20133    let counter = Arc::new(AtomicUsize::new(0));
20134    let mut completion_requests =
20135        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20136            let counter = counter.clone();
20137            async move {
20138                counter.fetch_add(1, atomic::Ordering::Release);
20139                Ok(Some(lsp::CompletionResponse::Array(vec![
20140                    lsp::CompletionItem {
20141                        label: "main".into(),
20142                        kind: Some(lsp::CompletionItemKind::FUNCTION),
20143                        detail: Some("() -> ()".to_string()),
20144                        ..Default::default()
20145                    },
20146                    lsp::CompletionItem {
20147                        label: "TestStruct".into(),
20148                        kind: Some(lsp::CompletionItemKind::STRUCT),
20149                        detail: Some("struct TestStruct".to_string()),
20150                        ..Default::default()
20151                    },
20152                ])))
20153            }
20154        });
20155    cx.update_editor(|editor, window, cx| {
20156        editor.show_completions(&ShowCompletions, window, cx);
20157    });
20158    completion_requests.next().await;
20159    cx.condition(|editor, _| editor.context_menu_visible())
20160        .await;
20161    cx.update_editor(|editor, _, _| {
20162        assert!(
20163            !editor.hover_state.visible(),
20164            "Hover popover should be hidden when completion menu is shown"
20165        );
20166    });
20167}
20168
20169#[gpui::test]
20170async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
20171    init_test(cx, |_| {});
20172
20173    let mut cx = EditorLspTestContext::new_rust(
20174        lsp::ServerCapabilities {
20175            completion_provider: Some(lsp::CompletionOptions {
20176                trigger_characters: Some(vec![".".to_string()]),
20177                resolve_provider: Some(true),
20178                ..Default::default()
20179            }),
20180            ..Default::default()
20181        },
20182        cx,
20183    )
20184    .await;
20185
20186    cx.set_state("fn main() { let a = 2ˇ; }");
20187    cx.simulate_keystroke(".");
20188
20189    let unresolved_item_1 = lsp::CompletionItem {
20190        label: "id".to_string(),
20191        filter_text: Some("id".to_string()),
20192        detail: None,
20193        documentation: None,
20194        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20195            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
20196            new_text: ".id".to_string(),
20197        })),
20198        ..lsp::CompletionItem::default()
20199    };
20200    let resolved_item_1 = lsp::CompletionItem {
20201        additional_text_edits: Some(vec![lsp::TextEdit {
20202            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
20203            new_text: "!!".to_string(),
20204        }]),
20205        ..unresolved_item_1.clone()
20206    };
20207    let unresolved_item_2 = lsp::CompletionItem {
20208        label: "other".to_string(),
20209        filter_text: Some("other".to_string()),
20210        detail: None,
20211        documentation: None,
20212        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20213            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
20214            new_text: ".other".to_string(),
20215        })),
20216        ..lsp::CompletionItem::default()
20217    };
20218    let resolved_item_2 = lsp::CompletionItem {
20219        additional_text_edits: Some(vec![lsp::TextEdit {
20220            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
20221            new_text: "??".to_string(),
20222        }]),
20223        ..unresolved_item_2.clone()
20224    };
20225
20226    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
20227    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
20228    cx.lsp
20229        .server
20230        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
20231            let unresolved_item_1 = unresolved_item_1.clone();
20232            let resolved_item_1 = resolved_item_1.clone();
20233            let unresolved_item_2 = unresolved_item_2.clone();
20234            let resolved_item_2 = resolved_item_2.clone();
20235            let resolve_requests_1 = resolve_requests_1.clone();
20236            let resolve_requests_2 = resolve_requests_2.clone();
20237            move |unresolved_request, _| {
20238                let unresolved_item_1 = unresolved_item_1.clone();
20239                let resolved_item_1 = resolved_item_1.clone();
20240                let unresolved_item_2 = unresolved_item_2.clone();
20241                let resolved_item_2 = resolved_item_2.clone();
20242                let resolve_requests_1 = resolve_requests_1.clone();
20243                let resolve_requests_2 = resolve_requests_2.clone();
20244                async move {
20245                    if unresolved_request == unresolved_item_1 {
20246                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
20247                        Ok(resolved_item_1.clone())
20248                    } else if unresolved_request == unresolved_item_2 {
20249                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
20250                        Ok(resolved_item_2.clone())
20251                    } else {
20252                        panic!("Unexpected completion item {unresolved_request:?}")
20253                    }
20254                }
20255            }
20256        })
20257        .detach();
20258
20259    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20260        let unresolved_item_1 = unresolved_item_1.clone();
20261        let unresolved_item_2 = unresolved_item_2.clone();
20262        async move {
20263            Ok(Some(lsp::CompletionResponse::Array(vec![
20264                unresolved_item_1,
20265                unresolved_item_2,
20266            ])))
20267        }
20268    })
20269    .next()
20270    .await;
20271
20272    cx.condition(|editor, _| editor.context_menu_visible())
20273        .await;
20274    cx.update_editor(|editor, _, _| {
20275        let context_menu = editor.context_menu.borrow_mut();
20276        let context_menu = context_menu
20277            .as_ref()
20278            .expect("Should have the context menu deployed");
20279        match context_menu {
20280            CodeContextMenu::Completions(completions_menu) => {
20281                let completions = completions_menu.completions.borrow_mut();
20282                assert_eq!(
20283                    completions
20284                        .iter()
20285                        .map(|completion| &completion.label.text)
20286                        .collect::<Vec<_>>(),
20287                    vec!["id", "other"]
20288                )
20289            }
20290            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
20291        }
20292    });
20293    cx.run_until_parked();
20294
20295    cx.update_editor(|editor, window, cx| {
20296        editor.context_menu_next(&ContextMenuNext, window, cx);
20297    });
20298    cx.run_until_parked();
20299    cx.update_editor(|editor, window, cx| {
20300        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
20301    });
20302    cx.run_until_parked();
20303    cx.update_editor(|editor, window, cx| {
20304        editor.context_menu_next(&ContextMenuNext, window, cx);
20305    });
20306    cx.run_until_parked();
20307    cx.update_editor(|editor, window, cx| {
20308        editor
20309            .compose_completion(&ComposeCompletion::default(), window, cx)
20310            .expect("No task returned")
20311    })
20312    .await
20313    .expect("Completion failed");
20314    cx.run_until_parked();
20315
20316    cx.update_editor(|editor, _, cx| {
20317        assert_eq!(
20318            resolve_requests_1.load(atomic::Ordering::Acquire),
20319            1,
20320            "Should always resolve once despite multiple selections"
20321        );
20322        assert_eq!(
20323            resolve_requests_2.load(atomic::Ordering::Acquire),
20324            1,
20325            "Should always resolve once after multiple selections and applying the completion"
20326        );
20327        assert_eq!(
20328            editor.text(cx),
20329            "fn main() { let a = ??.other; }",
20330            "Should use resolved data when applying the completion"
20331        );
20332    });
20333}
20334
20335#[gpui::test]
20336async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
20337    init_test(cx, |_| {});
20338
20339    let item_0 = lsp::CompletionItem {
20340        label: "abs".into(),
20341        insert_text: Some("abs".into()),
20342        data: Some(json!({ "very": "special"})),
20343        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
20344        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20345            lsp::InsertReplaceEdit {
20346                new_text: "abs".to_string(),
20347                insert: lsp::Range::default(),
20348                replace: lsp::Range::default(),
20349            },
20350        )),
20351        ..lsp::CompletionItem::default()
20352    };
20353    let items = iter::once(item_0.clone())
20354        .chain((11..51).map(|i| lsp::CompletionItem {
20355            label: format!("item_{}", i),
20356            insert_text: Some(format!("item_{}", i)),
20357            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
20358            ..lsp::CompletionItem::default()
20359        }))
20360        .collect::<Vec<_>>();
20361
20362    let default_commit_characters = vec!["?".to_string()];
20363    let default_data = json!({ "default": "data"});
20364    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
20365    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
20366    let default_edit_range = lsp::Range {
20367        start: lsp::Position {
20368            line: 0,
20369            character: 5,
20370        },
20371        end: lsp::Position {
20372            line: 0,
20373            character: 5,
20374        },
20375    };
20376
20377    let mut cx = EditorLspTestContext::new_rust(
20378        lsp::ServerCapabilities {
20379            completion_provider: Some(lsp::CompletionOptions {
20380                trigger_characters: Some(vec![".".to_string()]),
20381                resolve_provider: Some(true),
20382                ..Default::default()
20383            }),
20384            ..Default::default()
20385        },
20386        cx,
20387    )
20388    .await;
20389
20390    cx.set_state("fn main() { let a = 2ˇ; }");
20391    cx.simulate_keystroke(".");
20392
20393    let completion_data = default_data.clone();
20394    let completion_characters = default_commit_characters.clone();
20395    let completion_items = items.clone();
20396    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20397        let default_data = completion_data.clone();
20398        let default_commit_characters = completion_characters.clone();
20399        let items = completion_items.clone();
20400        async move {
20401            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
20402                items,
20403                item_defaults: Some(lsp::CompletionListItemDefaults {
20404                    data: Some(default_data.clone()),
20405                    commit_characters: Some(default_commit_characters.clone()),
20406                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
20407                        default_edit_range,
20408                    )),
20409                    insert_text_format: Some(default_insert_text_format),
20410                    insert_text_mode: Some(default_insert_text_mode),
20411                }),
20412                ..lsp::CompletionList::default()
20413            })))
20414        }
20415    })
20416    .next()
20417    .await;
20418
20419    let resolved_items = Arc::new(Mutex::new(Vec::new()));
20420    cx.lsp
20421        .server
20422        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
20423            let closure_resolved_items = resolved_items.clone();
20424            move |item_to_resolve, _| {
20425                let closure_resolved_items = closure_resolved_items.clone();
20426                async move {
20427                    closure_resolved_items.lock().push(item_to_resolve.clone());
20428                    Ok(item_to_resolve)
20429                }
20430            }
20431        })
20432        .detach();
20433
20434    cx.condition(|editor, _| editor.context_menu_visible())
20435        .await;
20436    cx.run_until_parked();
20437    cx.update_editor(|editor, _, _| {
20438        let menu = editor.context_menu.borrow_mut();
20439        match menu.as_ref().expect("should have the completions menu") {
20440            CodeContextMenu::Completions(completions_menu) => {
20441                assert_eq!(
20442                    completions_menu
20443                        .entries
20444                        .borrow()
20445                        .iter()
20446                        .map(|mat| mat.string.clone())
20447                        .collect::<Vec<String>>(),
20448                    items
20449                        .iter()
20450                        .map(|completion| completion.label.clone())
20451                        .collect::<Vec<String>>()
20452                );
20453            }
20454            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
20455        }
20456    });
20457    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
20458    // with 4 from the end.
20459    assert_eq!(
20460        *resolved_items.lock(),
20461        [&items[0..16], &items[items.len() - 4..items.len()]]
20462            .concat()
20463            .iter()
20464            .cloned()
20465            .map(|mut item| {
20466                if item.data.is_none() {
20467                    item.data = Some(default_data.clone());
20468                }
20469                item
20470            })
20471            .collect::<Vec<lsp::CompletionItem>>(),
20472        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
20473    );
20474    resolved_items.lock().clear();
20475
20476    cx.update_editor(|editor, window, cx| {
20477        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
20478    });
20479    cx.run_until_parked();
20480    // Completions that have already been resolved are skipped.
20481    assert_eq!(
20482        *resolved_items.lock(),
20483        items[items.len() - 17..items.len() - 4]
20484            .iter()
20485            .cloned()
20486            .map(|mut item| {
20487                if item.data.is_none() {
20488                    item.data = Some(default_data.clone());
20489                }
20490                item
20491            })
20492            .collect::<Vec<lsp::CompletionItem>>()
20493    );
20494    resolved_items.lock().clear();
20495}
20496
20497#[gpui::test]
20498async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
20499    init_test(cx, |_| {});
20500
20501    let mut cx = EditorLspTestContext::new(
20502        Language::new(
20503            LanguageConfig {
20504                matcher: LanguageMatcher {
20505                    path_suffixes: vec!["jsx".into()],
20506                    ..Default::default()
20507                },
20508                overrides: [(
20509                    "element".into(),
20510                    LanguageConfigOverride {
20511                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
20512                        ..Default::default()
20513                    },
20514                )]
20515                .into_iter()
20516                .collect(),
20517                ..Default::default()
20518            },
20519            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
20520        )
20521        .with_override_query("(jsx_self_closing_element) @element")
20522        .unwrap(),
20523        lsp::ServerCapabilities {
20524            completion_provider: Some(lsp::CompletionOptions {
20525                trigger_characters: Some(vec![":".to_string()]),
20526                ..Default::default()
20527            }),
20528            ..Default::default()
20529        },
20530        cx,
20531    )
20532    .await;
20533
20534    cx.lsp
20535        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
20536            Ok(Some(lsp::CompletionResponse::Array(vec![
20537                lsp::CompletionItem {
20538                    label: "bg-blue".into(),
20539                    ..Default::default()
20540                },
20541                lsp::CompletionItem {
20542                    label: "bg-red".into(),
20543                    ..Default::default()
20544                },
20545                lsp::CompletionItem {
20546                    label: "bg-yellow".into(),
20547                    ..Default::default()
20548                },
20549            ])))
20550        });
20551
20552    cx.set_state(r#"<p class="bgˇ" />"#);
20553
20554    // Trigger completion when typing a dash, because the dash is an extra
20555    // word character in the 'element' scope, which contains the cursor.
20556    cx.simulate_keystroke("-");
20557    cx.executor().run_until_parked();
20558    cx.update_editor(|editor, _, _| {
20559        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
20560        {
20561            assert_eq!(
20562                completion_menu_entries(menu),
20563                &["bg-blue", "bg-red", "bg-yellow"]
20564            );
20565        } else {
20566            panic!("expected completion menu to be open");
20567        }
20568    });
20569
20570    cx.simulate_keystroke("l");
20571    cx.executor().run_until_parked();
20572    cx.update_editor(|editor, _, _| {
20573        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
20574        {
20575            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
20576        } else {
20577            panic!("expected completion menu to be open");
20578        }
20579    });
20580
20581    // When filtering completions, consider the character after the '-' to
20582    // be the start of a subword.
20583    cx.set_state(r#"<p class="yelˇ" />"#);
20584    cx.simulate_keystroke("l");
20585    cx.executor().run_until_parked();
20586    cx.update_editor(|editor, _, _| {
20587        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
20588        {
20589            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
20590        } else {
20591            panic!("expected completion menu to be open");
20592        }
20593    });
20594}
20595
20596fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
20597    let entries = menu.entries.borrow();
20598    entries.iter().map(|mat| mat.string.clone()).collect()
20599}
20600
20601#[gpui::test]
20602async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
20603    init_test(cx, |settings| {
20604        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
20605    });
20606
20607    let fs = FakeFs::new(cx.executor());
20608    fs.insert_file(path!("/file.ts"), Default::default()).await;
20609
20610    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
20611    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20612
20613    language_registry.add(Arc::new(Language::new(
20614        LanguageConfig {
20615            name: "TypeScript".into(),
20616            matcher: LanguageMatcher {
20617                path_suffixes: vec!["ts".to_string()],
20618                ..Default::default()
20619            },
20620            ..Default::default()
20621        },
20622        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20623    )));
20624    update_test_language_settings(cx, &|settings| {
20625        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
20626    });
20627
20628    let test_plugin = "test_plugin";
20629    let _ = language_registry.register_fake_lsp(
20630        "TypeScript",
20631        FakeLspAdapter {
20632            prettier_plugins: vec![test_plugin],
20633            ..Default::default()
20634        },
20635    );
20636
20637    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
20638    let buffer = project
20639        .update(cx, |project, cx| {
20640            project.open_local_buffer(path!("/file.ts"), cx)
20641        })
20642        .await
20643        .unwrap();
20644
20645    let buffer_text = "one\ntwo\nthree\n";
20646    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20647    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
20648    editor.update_in(cx, |editor, window, cx| {
20649        editor.set_text(buffer_text, window, cx)
20650    });
20651
20652    editor
20653        .update_in(cx, |editor, window, cx| {
20654            editor.perform_format(
20655                project.clone(),
20656                FormatTrigger::Manual,
20657                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
20658                window,
20659                cx,
20660            )
20661        })
20662        .unwrap()
20663        .await;
20664    assert_eq!(
20665        editor.update(cx, |editor, cx| editor.text(cx)),
20666        buffer_text.to_string() + prettier_format_suffix,
20667        "Test prettier formatting was not applied to the original buffer text",
20668    );
20669
20670    update_test_language_settings(cx, &|settings| {
20671        settings.defaults.formatter = Some(FormatterList::default())
20672    });
20673    let format = editor.update_in(cx, |editor, window, cx| {
20674        editor.perform_format(
20675            project.clone(),
20676            FormatTrigger::Manual,
20677            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
20678            window,
20679            cx,
20680        )
20681    });
20682    format.await.unwrap();
20683    assert_eq!(
20684        editor.update(cx, |editor, cx| editor.text(cx)),
20685        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
20686        "Autoformatting (via test prettier) was not applied to the original buffer text",
20687    );
20688}
20689
20690#[gpui::test]
20691async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
20692    init_test(cx, |settings| {
20693        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
20694    });
20695
20696    let fs = FakeFs::new(cx.executor());
20697    fs.insert_file(path!("/file.settings"), Default::default())
20698        .await;
20699
20700    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
20701    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20702
20703    let ts_lang = Arc::new(Language::new(
20704        LanguageConfig {
20705            name: "TypeScript".into(),
20706            matcher: LanguageMatcher {
20707                path_suffixes: vec!["ts".to_string()],
20708                ..LanguageMatcher::default()
20709            },
20710            prettier_parser_name: Some("typescript".to_string()),
20711            ..LanguageConfig::default()
20712        },
20713        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20714    ));
20715
20716    language_registry.add(ts_lang.clone());
20717
20718    update_test_language_settings(cx, &|settings| {
20719        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
20720    });
20721
20722    let test_plugin = "test_plugin";
20723    let _ = language_registry.register_fake_lsp(
20724        "TypeScript",
20725        FakeLspAdapter {
20726            prettier_plugins: vec![test_plugin],
20727            ..Default::default()
20728        },
20729    );
20730
20731    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
20732    let buffer = project
20733        .update(cx, |project, cx| {
20734            project.open_local_buffer(path!("/file.settings"), cx)
20735        })
20736        .await
20737        .unwrap();
20738
20739    project.update(cx, |project, cx| {
20740        project.set_language_for_buffer(&buffer, ts_lang, cx)
20741    });
20742
20743    let buffer_text = "one\ntwo\nthree\n";
20744    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
20745    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
20746    editor.update_in(cx, |editor, window, cx| {
20747        editor.set_text(buffer_text, window, cx)
20748    });
20749
20750    editor
20751        .update_in(cx, |editor, window, cx| {
20752            editor.perform_format(
20753                project.clone(),
20754                FormatTrigger::Manual,
20755                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
20756                window,
20757                cx,
20758            )
20759        })
20760        .unwrap()
20761        .await;
20762    assert_eq!(
20763        editor.update(cx, |editor, cx| editor.text(cx)),
20764        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
20765        "Test prettier formatting was not applied to the original buffer text",
20766    );
20767
20768    update_test_language_settings(cx, &|settings| {
20769        settings.defaults.formatter = Some(FormatterList::default())
20770    });
20771    let format = editor.update_in(cx, |editor, window, cx| {
20772        editor.perform_format(
20773            project.clone(),
20774            FormatTrigger::Manual,
20775            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
20776            window,
20777            cx,
20778        )
20779    });
20780    format.await.unwrap();
20781
20782    assert_eq!(
20783        editor.update(cx, |editor, cx| editor.text(cx)),
20784        buffer_text.to_string()
20785            + prettier_format_suffix
20786            + "\ntypescript\n"
20787            + prettier_format_suffix
20788            + "\ntypescript",
20789        "Autoformatting (via test prettier) was not applied to the original buffer text",
20790    );
20791}
20792
20793#[gpui::test]
20794async fn test_addition_reverts(cx: &mut TestAppContext) {
20795    init_test(cx, |_| {});
20796    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
20797    let base_text = indoc! {r#"
20798        struct Row;
20799        struct Row1;
20800        struct Row2;
20801
20802        struct Row4;
20803        struct Row5;
20804        struct Row6;
20805
20806        struct Row8;
20807        struct Row9;
20808        struct Row10;"#};
20809
20810    // When addition hunks are not adjacent to carets, no hunk revert is performed
20811    assert_hunk_revert(
20812        indoc! {r#"struct Row;
20813                   struct Row1;
20814                   struct Row1.1;
20815                   struct Row1.2;
20816                   struct Row2;ˇ
20817
20818                   struct Row4;
20819                   struct Row5;
20820                   struct Row6;
20821
20822                   struct Row8;
20823                   ˇstruct Row9;
20824                   struct Row9.1;
20825                   struct Row9.2;
20826                   struct Row9.3;
20827                   struct Row10;"#},
20828        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
20829        indoc! {r#"struct Row;
20830                   struct Row1;
20831                   struct Row1.1;
20832                   struct Row1.2;
20833                   struct Row2;ˇ
20834
20835                   struct Row4;
20836                   struct Row5;
20837                   struct Row6;
20838
20839                   struct Row8;
20840                   ˇstruct Row9;
20841                   struct Row9.1;
20842                   struct Row9.2;
20843                   struct Row9.3;
20844                   struct Row10;"#},
20845        base_text,
20846        &mut cx,
20847    );
20848    // Same for selections
20849    assert_hunk_revert(
20850        indoc! {r#"struct Row;
20851                   struct Row1;
20852                   struct Row2;
20853                   struct Row2.1;
20854                   struct Row2.2;
20855                   «ˇ
20856                   struct Row4;
20857                   struct» Row5;
20858                   «struct Row6;
20859                   ˇ»
20860                   struct Row9.1;
20861                   struct Row9.2;
20862                   struct Row9.3;
20863                   struct Row8;
20864                   struct Row9;
20865                   struct Row10;"#},
20866        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
20867        indoc! {r#"struct Row;
20868                   struct Row1;
20869                   struct Row2;
20870                   struct Row2.1;
20871                   struct Row2.2;
20872                   «ˇ
20873                   struct Row4;
20874                   struct» Row5;
20875                   «struct Row6;
20876                   ˇ»
20877                   struct Row9.1;
20878                   struct Row9.2;
20879                   struct Row9.3;
20880                   struct Row8;
20881                   struct Row9;
20882                   struct Row10;"#},
20883        base_text,
20884        &mut cx,
20885    );
20886
20887    // When carets and selections intersect the addition hunks, those are reverted.
20888    // Adjacent carets got merged.
20889    assert_hunk_revert(
20890        indoc! {r#"struct Row;
20891                   ˇ// something on the top
20892                   struct Row1;
20893                   struct Row2;
20894                   struct Roˇw3.1;
20895                   struct Row2.2;
20896                   struct Row2.3;ˇ
20897
20898                   struct Row4;
20899                   struct ˇRow5.1;
20900                   struct Row5.2;
20901                   struct «Rowˇ»5.3;
20902                   struct Row5;
20903                   struct Row6;
20904                   ˇ
20905                   struct Row9.1;
20906                   struct «Rowˇ»9.2;
20907                   struct «ˇRow»9.3;
20908                   struct Row8;
20909                   struct Row9;
20910                   «ˇ// something on bottom»
20911                   struct Row10;"#},
20912        vec![
20913            DiffHunkStatusKind::Added,
20914            DiffHunkStatusKind::Added,
20915            DiffHunkStatusKind::Added,
20916            DiffHunkStatusKind::Added,
20917            DiffHunkStatusKind::Added,
20918        ],
20919        indoc! {r#"struct Row;
20920                   ˇstruct Row1;
20921                   struct Row2;
20922                   ˇ
20923                   struct Row4;
20924                   ˇstruct Row5;
20925                   struct Row6;
20926                   ˇ
20927                   ˇstruct Row8;
20928                   struct Row9;
20929                   ˇstruct Row10;"#},
20930        base_text,
20931        &mut cx,
20932    );
20933}
20934
20935#[gpui::test]
20936async fn test_modification_reverts(cx: &mut TestAppContext) {
20937    init_test(cx, |_| {});
20938    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
20939    let base_text = indoc! {r#"
20940        struct Row;
20941        struct Row1;
20942        struct Row2;
20943
20944        struct Row4;
20945        struct Row5;
20946        struct Row6;
20947
20948        struct Row8;
20949        struct Row9;
20950        struct Row10;"#};
20951
20952    // Modification hunks behave the same as the addition ones.
20953    assert_hunk_revert(
20954        indoc! {r#"struct Row;
20955                   struct Row1;
20956                   struct Row33;
20957                   ˇ
20958                   struct Row4;
20959                   struct Row5;
20960                   struct Row6;
20961                   ˇ
20962                   struct Row99;
20963                   struct Row9;
20964                   struct Row10;"#},
20965        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
20966        indoc! {r#"struct Row;
20967                   struct Row1;
20968                   struct Row33;
20969                   ˇ
20970                   struct Row4;
20971                   struct Row5;
20972                   struct Row6;
20973                   ˇ
20974                   struct Row99;
20975                   struct Row9;
20976                   struct Row10;"#},
20977        base_text,
20978        &mut cx,
20979    );
20980    assert_hunk_revert(
20981        indoc! {r#"struct Row;
20982                   struct Row1;
20983                   struct Row33;
20984                   «ˇ
20985                   struct Row4;
20986                   struct» Row5;
20987                   «struct Row6;
20988                   ˇ»
20989                   struct Row99;
20990                   struct Row9;
20991                   struct Row10;"#},
20992        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
20993        indoc! {r#"struct Row;
20994                   struct Row1;
20995                   struct Row33;
20996                   «ˇ
20997                   struct Row4;
20998                   struct» Row5;
20999                   «struct Row6;
21000                   ˇ»
21001                   struct Row99;
21002                   struct Row9;
21003                   struct Row10;"#},
21004        base_text,
21005        &mut cx,
21006    );
21007
21008    assert_hunk_revert(
21009        indoc! {r#"ˇstruct Row1.1;
21010                   struct Row1;
21011                   «ˇstr»uct Row22;
21012
21013                   struct ˇRow44;
21014                   struct Row5;
21015                   struct «Rˇ»ow66;ˇ
21016
21017                   «struˇ»ct Row88;
21018                   struct Row9;
21019                   struct Row1011;ˇ"#},
21020        vec![
21021            DiffHunkStatusKind::Modified,
21022            DiffHunkStatusKind::Modified,
21023            DiffHunkStatusKind::Modified,
21024            DiffHunkStatusKind::Modified,
21025            DiffHunkStatusKind::Modified,
21026            DiffHunkStatusKind::Modified,
21027        ],
21028        indoc! {r#"struct Row;
21029                   ˇstruct Row1;
21030                   struct Row2;
21031                   ˇ
21032                   struct Row4;
21033                   ˇstruct Row5;
21034                   struct Row6;
21035                   ˇ
21036                   struct Row8;
21037                   ˇstruct Row9;
21038                   struct Row10;ˇ"#},
21039        base_text,
21040        &mut cx,
21041    );
21042}
21043
21044#[gpui::test]
21045async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
21046    init_test(cx, |_| {});
21047    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
21048    let base_text = indoc! {r#"
21049        one
21050
21051        two
21052        three
21053        "#};
21054
21055    cx.set_head_text(base_text);
21056    cx.set_state("\nˇ\n");
21057    cx.executor().run_until_parked();
21058    cx.update_editor(|editor, _window, cx| {
21059        editor.expand_selected_diff_hunks(cx);
21060    });
21061    cx.executor().run_until_parked();
21062    cx.update_editor(|editor, window, cx| {
21063        editor.backspace(&Default::default(), window, cx);
21064    });
21065    cx.run_until_parked();
21066    cx.assert_state_with_diff(
21067        indoc! {r#"
21068
21069        - two
21070        - threeˇ
21071        +
21072        "#}
21073        .to_string(),
21074    );
21075}
21076
21077#[gpui::test]
21078async fn test_deletion_reverts(cx: &mut TestAppContext) {
21079    init_test(cx, |_| {});
21080    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
21081    let base_text = indoc! {r#"struct Row;
21082struct Row1;
21083struct Row2;
21084
21085struct Row4;
21086struct Row5;
21087struct Row6;
21088
21089struct Row8;
21090struct Row9;
21091struct Row10;"#};
21092
21093    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
21094    assert_hunk_revert(
21095        indoc! {r#"struct Row;
21096                   struct Row2;
21097
21098                   ˇstruct Row4;
21099                   struct Row5;
21100                   struct Row6;
21101                   ˇ
21102                   struct Row8;
21103                   struct Row10;"#},
21104        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
21105        indoc! {r#"struct Row;
21106                   struct Row2;
21107
21108                   ˇstruct Row4;
21109                   struct Row5;
21110                   struct Row6;
21111                   ˇ
21112                   struct Row8;
21113                   struct Row10;"#},
21114        base_text,
21115        &mut cx,
21116    );
21117    assert_hunk_revert(
21118        indoc! {r#"struct Row;
21119                   struct Row2;
21120
21121                   «ˇstruct Row4;
21122                   struct» Row5;
21123                   «struct Row6;
21124                   ˇ»
21125                   struct Row8;
21126                   struct Row10;"#},
21127        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
21128        indoc! {r#"struct Row;
21129                   struct Row2;
21130
21131                   «ˇstruct Row4;
21132                   struct» Row5;
21133                   «struct Row6;
21134                   ˇ»
21135                   struct Row8;
21136                   struct Row10;"#},
21137        base_text,
21138        &mut cx,
21139    );
21140
21141    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
21142    assert_hunk_revert(
21143        indoc! {r#"struct Row;
21144                   ˇstruct Row2;
21145
21146                   struct Row4;
21147                   struct Row5;
21148                   struct Row6;
21149
21150                   struct Row8;ˇ
21151                   struct Row10;"#},
21152        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
21153        indoc! {r#"struct Row;
21154                   struct Row1;
21155                   ˇstruct Row2;
21156
21157                   struct Row4;
21158                   struct Row5;
21159                   struct Row6;
21160
21161                   struct Row8;ˇ
21162                   struct Row9;
21163                   struct Row10;"#},
21164        base_text,
21165        &mut cx,
21166    );
21167    assert_hunk_revert(
21168        indoc! {r#"struct Row;
21169                   struct Row2«ˇ;
21170                   struct Row4;
21171                   struct» Row5;
21172                   «struct Row6;
21173
21174                   struct Row8;ˇ»
21175                   struct Row10;"#},
21176        vec![
21177            DiffHunkStatusKind::Deleted,
21178            DiffHunkStatusKind::Deleted,
21179            DiffHunkStatusKind::Deleted,
21180        ],
21181        indoc! {r#"struct Row;
21182                   struct Row1;
21183                   struct Row2«ˇ;
21184
21185                   struct Row4;
21186                   struct» Row5;
21187                   «struct Row6;
21188
21189                   struct Row8;ˇ»
21190                   struct Row9;
21191                   struct Row10;"#},
21192        base_text,
21193        &mut cx,
21194    );
21195}
21196
21197#[gpui::test]
21198async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
21199    init_test(cx, |_| {});
21200
21201    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
21202    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
21203    let base_text_3 =
21204        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
21205
21206    let text_1 = edit_first_char_of_every_line(base_text_1);
21207    let text_2 = edit_first_char_of_every_line(base_text_2);
21208    let text_3 = edit_first_char_of_every_line(base_text_3);
21209
21210    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
21211    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
21212    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
21213
21214    let multibuffer = cx.new(|cx| {
21215        let mut multibuffer = MultiBuffer::new(ReadWrite);
21216        multibuffer.set_excerpts_for_path(
21217            PathKey::sorted(0),
21218            buffer_1.clone(),
21219            [
21220                Point::new(0, 0)..Point::new(2, 0),
21221                Point::new(5, 0)..Point::new(6, 0),
21222                Point::new(9, 0)..Point::new(9, 4),
21223            ],
21224            0,
21225            cx,
21226        );
21227        multibuffer.set_excerpts_for_path(
21228            PathKey::sorted(1),
21229            buffer_2.clone(),
21230            [
21231                Point::new(0, 0)..Point::new(2, 0),
21232                Point::new(5, 0)..Point::new(6, 0),
21233                Point::new(9, 0)..Point::new(9, 4),
21234            ],
21235            0,
21236            cx,
21237        );
21238        multibuffer.set_excerpts_for_path(
21239            PathKey::sorted(2),
21240            buffer_3.clone(),
21241            [
21242                Point::new(0, 0)..Point::new(2, 0),
21243                Point::new(5, 0)..Point::new(6, 0),
21244                Point::new(9, 0)..Point::new(9, 4),
21245            ],
21246            0,
21247            cx,
21248        );
21249        multibuffer
21250    });
21251
21252    let fs = FakeFs::new(cx.executor());
21253    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21254    let (editor, cx) = cx
21255        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
21256    editor.update_in(cx, |editor, _window, cx| {
21257        for (buffer, diff_base) in [
21258            (buffer_1.clone(), base_text_1),
21259            (buffer_2.clone(), base_text_2),
21260            (buffer_3.clone(), base_text_3),
21261        ] {
21262            let diff = cx.new(|cx| {
21263                BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
21264            });
21265            editor
21266                .buffer
21267                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
21268        }
21269    });
21270    cx.executor().run_until_parked();
21271
21272    editor.update_in(cx, |editor, window, cx| {
21273        assert_eq!(editor.display_text(cx), "\n\nXaaa\nXbbb\nXccc\n\nXfff\nXggg\n\nXjjj\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\n\n\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}");
21274        editor.select_all(&SelectAll, window, cx);
21275        editor.git_restore(&Default::default(), window, cx);
21276    });
21277    cx.executor().run_until_parked();
21278
21279    // When all ranges are selected, all buffer hunks are reverted.
21280    editor.update(cx, |editor, cx| {
21281        assert_eq!(editor.display_text(cx), "\n\naaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\n\n\n\n\nllll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu\n\n\n\n\n\n\nvvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}\n\n\n\n");
21282    });
21283    buffer_1.update(cx, |buffer, _| {
21284        assert_eq!(buffer.text(), base_text_1);
21285    });
21286    buffer_2.update(cx, |buffer, _| {
21287        assert_eq!(buffer.text(), base_text_2);
21288    });
21289    buffer_3.update(cx, |buffer, _| {
21290        assert_eq!(buffer.text(), base_text_3);
21291    });
21292
21293    editor.update_in(cx, |editor, window, cx| {
21294        editor.undo(&Default::default(), window, cx);
21295    });
21296
21297    editor.update_in(cx, |editor, window, cx| {
21298        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21299            s.select_ranges(Some(Point::new(0, 0)..Point::new(5, 0)));
21300        });
21301        editor.git_restore(&Default::default(), window, cx);
21302    });
21303
21304    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
21305    // but not affect buffer_2 and its related excerpts.
21306    editor.update(cx, |editor, cx| {
21307        assert_eq!(
21308            editor.display_text(cx),
21309            "\n\naaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\n\n\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\n\n\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}"
21310        );
21311    });
21312    buffer_1.update(cx, |buffer, _| {
21313        assert_eq!(buffer.text(), base_text_1);
21314    });
21315    buffer_2.update(cx, |buffer, _| {
21316        assert_eq!(
21317            buffer.text(),
21318            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
21319        );
21320    });
21321    buffer_3.update(cx, |buffer, _| {
21322        assert_eq!(
21323            buffer.text(),
21324            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
21325        );
21326    });
21327
21328    fn edit_first_char_of_every_line(text: &str) -> String {
21329        text.split('\n')
21330            .map(|line| format!("X{}", &line[1..]))
21331            .collect::<Vec<_>>()
21332            .join("\n")
21333    }
21334}
21335
21336#[gpui::test]
21337async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
21338    init_test(cx, |_| {});
21339
21340    let cols = 4;
21341    let rows = 10;
21342    let sample_text_1 = sample_text(rows, cols, 'a');
21343    assert_eq!(
21344        sample_text_1,
21345        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
21346    );
21347    let sample_text_2 = sample_text(rows, cols, 'l');
21348    assert_eq!(
21349        sample_text_2,
21350        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
21351    );
21352    let sample_text_3 = sample_text(rows, cols, 'v');
21353    assert_eq!(
21354        sample_text_3,
21355        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
21356    );
21357
21358    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
21359    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
21360    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
21361
21362    let multi_buffer = cx.new(|cx| {
21363        let mut multibuffer = MultiBuffer::new(ReadWrite);
21364        multibuffer.set_excerpts_for_path(
21365            PathKey::sorted(0),
21366            buffer_1.clone(),
21367            [
21368                Point::new(0, 0)..Point::new(2, 0),
21369                Point::new(5, 0)..Point::new(6, 0),
21370                Point::new(9, 0)..Point::new(9, 4),
21371            ],
21372            0,
21373            cx,
21374        );
21375        multibuffer.set_excerpts_for_path(
21376            PathKey::sorted(1),
21377            buffer_2.clone(),
21378            [
21379                Point::new(0, 0)..Point::new(2, 0),
21380                Point::new(5, 0)..Point::new(6, 0),
21381                Point::new(9, 0)..Point::new(9, 4),
21382            ],
21383            0,
21384            cx,
21385        );
21386        multibuffer.set_excerpts_for_path(
21387            PathKey::sorted(2),
21388            buffer_3.clone(),
21389            [
21390                Point::new(0, 0)..Point::new(2, 0),
21391                Point::new(5, 0)..Point::new(6, 0),
21392                Point::new(9, 0)..Point::new(9, 4),
21393            ],
21394            0,
21395            cx,
21396        );
21397        multibuffer
21398    });
21399
21400    let fs = FakeFs::new(cx.executor());
21401    fs.insert_tree(
21402        "/a",
21403        json!({
21404            "main.rs": sample_text_1,
21405            "other.rs": sample_text_2,
21406            "lib.rs": sample_text_3,
21407        }),
21408    )
21409    .await;
21410    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21411    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
21412    let workspace = window
21413        .read_with(cx, |mw, _| mw.workspace().clone())
21414        .unwrap();
21415    let cx = &mut VisualTestContext::from_window(*window, cx);
21416    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21417        Editor::new(
21418            EditorMode::full(),
21419            multi_buffer,
21420            Some(project.clone()),
21421            window,
21422            cx,
21423        )
21424    });
21425    let multibuffer_item_id = workspace.update_in(cx, |workspace, window, cx| {
21426        assert!(
21427            workspace.active_item(cx).is_none(),
21428            "active item should be None before the first item is added"
21429        );
21430        workspace.add_item_to_active_pane(
21431            Box::new(multi_buffer_editor.clone()),
21432            None,
21433            true,
21434            window,
21435            cx,
21436        );
21437        let active_item = workspace
21438            .active_item(cx)
21439            .expect("should have an active item after adding the multi buffer");
21440        assert_eq!(
21441            active_item.buffer_kind(cx),
21442            ItemBufferKind::Multibuffer,
21443            "A multi buffer was expected to active after adding"
21444        );
21445        active_item.item_id()
21446    });
21447
21448    cx.executor().run_until_parked();
21449
21450    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21451        editor.change_selections(
21452            SelectionEffects::scroll(Autoscroll::Next),
21453            window,
21454            cx,
21455            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
21456        );
21457        editor.open_excerpts(&OpenExcerpts, window, cx);
21458    });
21459    cx.executor().run_until_parked();
21460    let first_item_id = workspace.update_in(cx, |workspace, window, cx| {
21461        let active_item = workspace
21462            .active_item(cx)
21463            .expect("should have an active item after navigating into the 1st buffer");
21464        let first_item_id = active_item.item_id();
21465        assert_ne!(
21466            first_item_id, multibuffer_item_id,
21467            "Should navigate into the 1st buffer and activate it"
21468        );
21469        assert_eq!(
21470            active_item.buffer_kind(cx),
21471            ItemBufferKind::Singleton,
21472            "New active item should be a singleton buffer"
21473        );
21474        assert_eq!(
21475            active_item
21476                .act_as::<Editor>(cx)
21477                .expect("should have navigated into an editor for the 1st buffer")
21478                .read(cx)
21479                .text(cx),
21480            sample_text_1
21481        );
21482
21483        workspace
21484            .go_back(workspace.active_pane().downgrade(), window, cx)
21485            .detach_and_log_err(cx);
21486
21487        first_item_id
21488    });
21489
21490    cx.executor().run_until_parked();
21491    workspace.update_in(cx, |workspace, _, cx| {
21492        let active_item = workspace
21493            .active_item(cx)
21494            .expect("should have an active item after navigating back");
21495        assert_eq!(
21496            active_item.item_id(),
21497            multibuffer_item_id,
21498            "Should navigate back to the multi buffer"
21499        );
21500        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
21501    });
21502
21503    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21504        editor.change_selections(
21505            SelectionEffects::scroll(Autoscroll::Next),
21506            window,
21507            cx,
21508            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
21509        );
21510        editor.open_excerpts(&OpenExcerpts, window, cx);
21511    });
21512    cx.executor().run_until_parked();
21513    let second_item_id = workspace.update_in(cx, |workspace, window, cx| {
21514        let active_item = workspace
21515            .active_item(cx)
21516            .expect("should have an active item after navigating into the 2nd buffer");
21517        let second_item_id = active_item.item_id();
21518        assert_ne!(
21519            second_item_id, multibuffer_item_id,
21520            "Should navigate away from the multibuffer"
21521        );
21522        assert_ne!(
21523            second_item_id, first_item_id,
21524            "Should navigate into the 2nd buffer and activate it"
21525        );
21526        assert_eq!(
21527            active_item.buffer_kind(cx),
21528            ItemBufferKind::Singleton,
21529            "New active item should be a singleton buffer"
21530        );
21531        assert_eq!(
21532            active_item
21533                .act_as::<Editor>(cx)
21534                .expect("should have navigated into an editor")
21535                .read(cx)
21536                .text(cx),
21537            sample_text_2
21538        );
21539
21540        workspace
21541            .go_back(workspace.active_pane().downgrade(), window, cx)
21542            .detach_and_log_err(cx);
21543
21544        second_item_id
21545    });
21546
21547    cx.executor().run_until_parked();
21548    workspace.update_in(cx, |workspace, _, cx| {
21549        let active_item = workspace
21550            .active_item(cx)
21551            .expect("should have an active item after navigating back from the 2nd buffer");
21552        assert_eq!(
21553            active_item.item_id(),
21554            multibuffer_item_id,
21555            "Should navigate back from the 2nd buffer to the multi buffer"
21556        );
21557        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
21558    });
21559
21560    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21561        editor.change_selections(
21562            SelectionEffects::scroll(Autoscroll::Next),
21563            window,
21564            cx,
21565            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
21566        );
21567        editor.open_excerpts(&OpenExcerpts, window, cx);
21568    });
21569    cx.executor().run_until_parked();
21570    workspace.update_in(cx, |workspace, window, cx| {
21571        let active_item = workspace
21572            .active_item(cx)
21573            .expect("should have an active item after navigating into the 3rd buffer");
21574        let third_item_id = active_item.item_id();
21575        assert_ne!(
21576            third_item_id, multibuffer_item_id,
21577            "Should navigate into the 3rd buffer and activate it"
21578        );
21579        assert_ne!(third_item_id, first_item_id);
21580        assert_ne!(third_item_id, second_item_id);
21581        assert_eq!(
21582            active_item.buffer_kind(cx),
21583            ItemBufferKind::Singleton,
21584            "New active item should be a singleton buffer"
21585        );
21586        assert_eq!(
21587            active_item
21588                .act_as::<Editor>(cx)
21589                .expect("should have navigated into an editor")
21590                .read(cx)
21591                .text(cx),
21592            sample_text_3
21593        );
21594
21595        workspace
21596            .go_back(workspace.active_pane().downgrade(), window, cx)
21597            .detach_and_log_err(cx);
21598    });
21599
21600    cx.executor().run_until_parked();
21601    workspace.update_in(cx, |workspace, _, cx| {
21602        let active_item = workspace
21603            .active_item(cx)
21604            .expect("should have an active item after navigating back from the 3rd buffer");
21605        assert_eq!(
21606            active_item.item_id(),
21607            multibuffer_item_id,
21608            "Should navigate back from the 3rd buffer to the multi buffer"
21609        );
21610        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
21611    });
21612}
21613
21614#[gpui::test]
21615async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21616    init_test(cx, |_| {});
21617
21618    let mut cx = EditorTestContext::new(cx).await;
21619
21620    let diff_base = r#"
21621        use some::mod;
21622
21623        const A: u32 = 42;
21624
21625        fn main() {
21626            println!("hello");
21627
21628            println!("world");
21629        }
21630        "#
21631    .unindent();
21632
21633    cx.set_state(
21634        &r#"
21635        use some::modified;
21636
21637        ˇ
21638        fn main() {
21639            println!("hello there");
21640
21641            println!("around the");
21642            println!("world");
21643        }
21644        "#
21645        .unindent(),
21646    );
21647
21648    cx.set_head_text(&diff_base);
21649    executor.run_until_parked();
21650
21651    cx.update_editor(|editor, window, cx| {
21652        editor.go_to_next_hunk(&GoToHunk, window, cx);
21653        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21654    });
21655    executor.run_until_parked();
21656    cx.assert_state_with_diff(
21657        r#"
21658          use some::modified;
21659
21660
21661          fn main() {
21662        -     println!("hello");
21663        + ˇ    println!("hello there");
21664
21665              println!("around the");
21666              println!("world");
21667          }
21668        "#
21669        .unindent(),
21670    );
21671
21672    cx.update_editor(|editor, window, cx| {
21673        for _ in 0..2 {
21674            editor.go_to_next_hunk(&GoToHunk, window, cx);
21675            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21676        }
21677    });
21678    executor.run_until_parked();
21679    cx.assert_state_with_diff(
21680        r#"
21681        - use some::mod;
21682        + ˇuse some::modified;
21683
21684
21685          fn main() {
21686        -     println!("hello");
21687        +     println!("hello there");
21688
21689        +     println!("around the");
21690              println!("world");
21691          }
21692        "#
21693        .unindent(),
21694    );
21695
21696    cx.update_editor(|editor, window, cx| {
21697        editor.go_to_next_hunk(&GoToHunk, window, cx);
21698        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21699    });
21700    executor.run_until_parked();
21701    cx.assert_state_with_diff(
21702        r#"
21703        - use some::mod;
21704        + use some::modified;
21705
21706        - const A: u32 = 42;
21707          ˇ
21708          fn main() {
21709        -     println!("hello");
21710        +     println!("hello there");
21711
21712        +     println!("around the");
21713              println!("world");
21714          }
21715        "#
21716        .unindent(),
21717    );
21718
21719    cx.update_editor(|editor, window, cx| {
21720        editor.cancel(&Cancel, window, cx);
21721    });
21722
21723    cx.assert_state_with_diff(
21724        r#"
21725          use some::modified;
21726
21727          ˇ
21728          fn main() {
21729              println!("hello there");
21730
21731              println!("around the");
21732              println!("world");
21733          }
21734        "#
21735        .unindent(),
21736    );
21737}
21738
21739#[gpui::test]
21740async fn test_diff_base_change_with_expanded_diff_hunks(
21741    executor: BackgroundExecutor,
21742    cx: &mut TestAppContext,
21743) {
21744    init_test(cx, |_| {});
21745
21746    let mut cx = EditorTestContext::new(cx).await;
21747
21748    let diff_base = r#"
21749        use some::mod1;
21750        use some::mod2;
21751
21752        const A: u32 = 42;
21753        const B: u32 = 42;
21754        const C: u32 = 42;
21755
21756        fn main() {
21757            println!("hello");
21758
21759            println!("world");
21760        }
21761        "#
21762    .unindent();
21763
21764    cx.set_state(
21765        &r#"
21766        use some::mod2;
21767
21768        const A: u32 = 42;
21769        const C: u32 = 42;
21770
21771        fn main(ˇ) {
21772            //println!("hello");
21773
21774            println!("world");
21775            //
21776            //
21777        }
21778        "#
21779        .unindent(),
21780    );
21781
21782    cx.set_head_text(&diff_base);
21783    executor.run_until_parked();
21784
21785    cx.update_editor(|editor, window, cx| {
21786        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21787    });
21788    executor.run_until_parked();
21789    cx.assert_state_with_diff(
21790        r#"
21791        - use some::mod1;
21792          use some::mod2;
21793
21794          const A: u32 = 42;
21795        - const B: u32 = 42;
21796          const C: u32 = 42;
21797
21798          fn main(ˇ) {
21799        -     println!("hello");
21800        +     //println!("hello");
21801
21802              println!("world");
21803        +     //
21804        +     //
21805          }
21806        "#
21807        .unindent(),
21808    );
21809
21810    cx.set_head_text("new diff base!");
21811    executor.run_until_parked();
21812    cx.assert_state_with_diff(
21813        r#"
21814        - new diff base!
21815        + use some::mod2;
21816        +
21817        + const A: u32 = 42;
21818        + const C: u32 = 42;
21819        +
21820        + fn main(ˇ) {
21821        +     //println!("hello");
21822        +
21823        +     println!("world");
21824        +     //
21825        +     //
21826        + }
21827        "#
21828        .unindent(),
21829    );
21830}
21831
21832#[gpui::test]
21833async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
21834    init_test(cx, |_| {});
21835
21836    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
21837    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
21838    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
21839    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
21840    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
21841    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
21842
21843    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
21844    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
21845    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
21846
21847    let multi_buffer = cx.new(|cx| {
21848        let mut multibuffer = MultiBuffer::new(ReadWrite);
21849        multibuffer.set_excerpts_for_path(
21850            PathKey::sorted(0),
21851            buffer_1.clone(),
21852            [
21853                Point::new(0, 0)..Point::new(2, 3),
21854                Point::new(5, 0)..Point::new(6, 3),
21855                Point::new(9, 0)..Point::new(10, 3),
21856            ],
21857            0,
21858            cx,
21859        );
21860        multibuffer.set_excerpts_for_path(
21861            PathKey::sorted(1),
21862            buffer_2.clone(),
21863            [
21864                Point::new(0, 0)..Point::new(2, 3),
21865                Point::new(5, 0)..Point::new(6, 3),
21866                Point::new(9, 0)..Point::new(10, 3),
21867            ],
21868            0,
21869            cx,
21870        );
21871        multibuffer.set_excerpts_for_path(
21872            PathKey::sorted(2),
21873            buffer_3.clone(),
21874            [
21875                Point::new(0, 0)..Point::new(2, 3),
21876                Point::new(5, 0)..Point::new(6, 3),
21877                Point::new(9, 0)..Point::new(10, 3),
21878            ],
21879            0,
21880            cx,
21881        );
21882        assert_eq!(multibuffer.excerpt_ids().len(), 9);
21883        multibuffer
21884    });
21885
21886    let editor =
21887        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
21888    editor
21889        .update(cx, |editor, _window, cx| {
21890            for (buffer, diff_base) in [
21891                (buffer_1.clone(), file_1_old),
21892                (buffer_2.clone(), file_2_old),
21893                (buffer_3.clone(), file_3_old),
21894            ] {
21895                let diff = cx.new(|cx| {
21896                    BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
21897                });
21898                editor
21899                    .buffer
21900                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
21901            }
21902        })
21903        .unwrap();
21904
21905    let mut cx = EditorTestContext::for_editor(editor, cx).await;
21906    cx.run_until_parked();
21907
21908    cx.assert_editor_state(
21909        &"
21910            ˇaaa
21911            ccc
21912            ddd
21913            ggg
21914            hhh
21915
21916            lll
21917            mmm
21918            NNN
21919            qqq
21920            rrr
21921            uuu
21922            111
21923            222
21924            333
21925            666
21926            777
21927            000
21928            !!!"
21929        .unindent(),
21930    );
21931
21932    cx.update_editor(|editor, window, cx| {
21933        editor.select_all(&SelectAll, window, cx);
21934        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
21935    });
21936    cx.executor().run_until_parked();
21937
21938    cx.assert_state_with_diff(
21939        "
21940            «aaa
21941          - bbb
21942            ccc
21943            ddd
21944            ggg
21945            hhh
21946
21947            lll
21948            mmm
21949          - nnn
21950          + NNN
21951            qqq
21952            rrr
21953            uuu
21954            111
21955            222
21956            333
21957          + 666
21958            777
21959            000
21960            !!!ˇ»"
21961            .unindent(),
21962    );
21963}
21964
21965#[gpui::test]
21966async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
21967    init_test(cx, |_| {});
21968
21969    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
21970    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
21971
21972    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
21973    let multi_buffer = cx.new(|cx| {
21974        let mut multibuffer = MultiBuffer::new(ReadWrite);
21975        multibuffer.set_excerpts_for_path(
21976            PathKey::sorted(0),
21977            buffer.clone(),
21978            [
21979                Point::new(0, 0)..Point::new(1, 3),
21980                Point::new(4, 0)..Point::new(6, 3),
21981                Point::new(9, 0)..Point::new(9, 3),
21982            ],
21983            0,
21984            cx,
21985        );
21986        assert_eq!(multibuffer.excerpt_ids().len(), 3);
21987        multibuffer
21988    });
21989
21990    let editor =
21991        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
21992    editor
21993        .update(cx, |editor, _window, cx| {
21994            let diff = cx.new(|cx| {
21995                BufferDiff::new_with_base_text(base, &buffer.read(cx).text_snapshot(), cx)
21996            });
21997            editor
21998                .buffer
21999                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
22000        })
22001        .unwrap();
22002
22003    let mut cx = EditorTestContext::for_editor(editor, cx).await;
22004    cx.run_until_parked();
22005
22006    cx.update_editor(|editor, window, cx| {
22007        editor.expand_all_diff_hunks(&Default::default(), window, cx)
22008    });
22009    cx.executor().run_until_parked();
22010
22011    // When the start of a hunk coincides with the start of its excerpt,
22012    // the hunk is expanded. When the start of a hunk is earlier than
22013    // the start of its excerpt, the hunk is not expanded.
22014    cx.assert_state_with_diff(
22015        "
22016            ˇaaa
22017          - bbb
22018          + BBB
22019          - ddd
22020          - eee
22021          + DDD
22022          + EEE
22023            fff
22024            iii"
22025        .unindent(),
22026    );
22027}
22028
22029#[gpui::test]
22030async fn test_edits_around_expanded_insertion_hunks(
22031    executor: BackgroundExecutor,
22032    cx: &mut TestAppContext,
22033) {
22034    init_test(cx, |_| {});
22035
22036    let mut cx = EditorTestContext::new(cx).await;
22037
22038    let diff_base = r#"
22039        use some::mod1;
22040        use some::mod2;
22041
22042        const A: u32 = 42;
22043
22044        fn main() {
22045            println!("hello");
22046
22047            println!("world");
22048        }
22049        "#
22050    .unindent();
22051    executor.run_until_parked();
22052    cx.set_state(
22053        &r#"
22054        use some::mod1;
22055        use some::mod2;
22056
22057        const A: u32 = 42;
22058        const B: u32 = 42;
22059        const C: u32 = 42;
22060        ˇ
22061
22062        fn main() {
22063            println!("hello");
22064
22065            println!("world");
22066        }
22067        "#
22068        .unindent(),
22069    );
22070
22071    cx.set_head_text(&diff_base);
22072    executor.run_until_parked();
22073
22074    cx.update_editor(|editor, window, cx| {
22075        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22076    });
22077    executor.run_until_parked();
22078
22079    cx.assert_state_with_diff(
22080        r#"
22081        use some::mod1;
22082        use some::mod2;
22083
22084        const A: u32 = 42;
22085      + const B: u32 = 42;
22086      + const C: u32 = 42;
22087      + ˇ
22088
22089        fn main() {
22090            println!("hello");
22091
22092            println!("world");
22093        }
22094      "#
22095        .unindent(),
22096    );
22097
22098    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
22099    executor.run_until_parked();
22100
22101    cx.assert_state_with_diff(
22102        r#"
22103        use some::mod1;
22104        use some::mod2;
22105
22106        const A: u32 = 42;
22107      + const B: u32 = 42;
22108      + const C: u32 = 42;
22109      + const D: u32 = 42;
22110      + ˇ
22111
22112        fn main() {
22113            println!("hello");
22114
22115            println!("world");
22116        }
22117      "#
22118        .unindent(),
22119    );
22120
22121    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
22122    executor.run_until_parked();
22123
22124    cx.assert_state_with_diff(
22125        r#"
22126        use some::mod1;
22127        use some::mod2;
22128
22129        const A: u32 = 42;
22130      + const B: u32 = 42;
22131      + const C: u32 = 42;
22132      + const D: u32 = 42;
22133      + const E: u32 = 42;
22134      + ˇ
22135
22136        fn main() {
22137            println!("hello");
22138
22139            println!("world");
22140        }
22141      "#
22142        .unindent(),
22143    );
22144
22145    cx.update_editor(|editor, window, cx| {
22146        editor.delete_line(&DeleteLine, window, cx);
22147    });
22148    executor.run_until_parked();
22149
22150    cx.assert_state_with_diff(
22151        r#"
22152        use some::mod1;
22153        use some::mod2;
22154
22155        const A: u32 = 42;
22156      + const B: u32 = 42;
22157      + const C: u32 = 42;
22158      + const D: u32 = 42;
22159      + const E: u32 = 42;
22160        ˇ
22161        fn main() {
22162            println!("hello");
22163
22164            println!("world");
22165        }
22166      "#
22167        .unindent(),
22168    );
22169
22170    cx.update_editor(|editor, window, cx| {
22171        editor.move_up(&MoveUp, window, cx);
22172        editor.delete_line(&DeleteLine, window, cx);
22173        editor.move_up(&MoveUp, window, cx);
22174        editor.delete_line(&DeleteLine, window, cx);
22175        editor.move_up(&MoveUp, window, cx);
22176        editor.delete_line(&DeleteLine, window, cx);
22177    });
22178    executor.run_until_parked();
22179    cx.assert_state_with_diff(
22180        r#"
22181        use some::mod1;
22182        use some::mod2;
22183
22184        const A: u32 = 42;
22185      + const B: u32 = 42;
22186        ˇ
22187        fn main() {
22188            println!("hello");
22189
22190            println!("world");
22191        }
22192      "#
22193        .unindent(),
22194    );
22195
22196    cx.update_editor(|editor, window, cx| {
22197        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
22198        editor.delete_line(&DeleteLine, window, cx);
22199    });
22200    executor.run_until_parked();
22201    cx.assert_state_with_diff(
22202        r#"
22203        ˇ
22204        fn main() {
22205            println!("hello");
22206
22207            println!("world");
22208        }
22209      "#
22210        .unindent(),
22211    );
22212}
22213
22214#[gpui::test]
22215async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
22216    init_test(cx, |_| {});
22217
22218    let mut cx = EditorTestContext::new(cx).await;
22219    cx.set_head_text(indoc! { "
22220        one
22221        two
22222        three
22223        four
22224        five
22225        "
22226    });
22227    cx.set_state(indoc! { "
22228        one
22229        ˇthree
22230        five
22231    "});
22232    cx.run_until_parked();
22233    cx.update_editor(|editor, window, cx| {
22234        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
22235    });
22236    cx.assert_state_with_diff(
22237        indoc! { "
22238        one
22239      - two
22240        ˇthree
22241      - four
22242        five
22243    "}
22244        .to_string(),
22245    );
22246    cx.update_editor(|editor, window, cx| {
22247        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
22248    });
22249
22250    cx.assert_state_with_diff(
22251        indoc! { "
22252        one
22253        ˇthree
22254        five
22255    "}
22256        .to_string(),
22257    );
22258
22259    cx.update_editor(|editor, window, cx| {
22260        editor.move_up(&MoveUp, window, cx);
22261        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
22262    });
22263    cx.assert_state_with_diff(
22264        indoc! { "
22265        ˇone
22266      - two
22267        three
22268        five
22269    "}
22270        .to_string(),
22271    );
22272
22273    cx.update_editor(|editor, window, cx| {
22274        editor.move_down(&MoveDown, window, cx);
22275        editor.move_down(&MoveDown, window, cx);
22276        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
22277    });
22278    cx.assert_state_with_diff(
22279        indoc! { "
22280        one
22281      - two
22282        ˇthree
22283      - four
22284        five
22285    "}
22286        .to_string(),
22287    );
22288
22289    cx.set_state(indoc! { "
22290        one
22291        ˇTWO
22292        three
22293        four
22294        five
22295    "});
22296    cx.run_until_parked();
22297    cx.update_editor(|editor, window, cx| {
22298        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
22299    });
22300
22301    cx.assert_state_with_diff(
22302        indoc! { "
22303            one
22304          - two
22305          + ˇTWO
22306            three
22307            four
22308            five
22309        "}
22310        .to_string(),
22311    );
22312    cx.update_editor(|editor, window, cx| {
22313        editor.move_up(&Default::default(), window, cx);
22314        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
22315    });
22316    cx.assert_state_with_diff(
22317        indoc! { "
22318            one
22319            ˇTWO
22320            three
22321            four
22322            five
22323        "}
22324        .to_string(),
22325    );
22326}
22327
22328#[gpui::test]
22329async fn test_toggling_adjacent_diff_hunks_2(
22330    executor: BackgroundExecutor,
22331    cx: &mut TestAppContext,
22332) {
22333    init_test(cx, |_| {});
22334
22335    let mut cx = EditorTestContext::new(cx).await;
22336
22337    let diff_base = r#"
22338        lineA
22339        lineB
22340        lineC
22341        lineD
22342        "#
22343    .unindent();
22344
22345    cx.set_state(
22346        &r#"
22347        ˇlineA1
22348        lineB
22349        lineD
22350        "#
22351        .unindent(),
22352    );
22353    cx.set_head_text(&diff_base);
22354    executor.run_until_parked();
22355
22356    cx.update_editor(|editor, window, cx| {
22357        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22358    });
22359    executor.run_until_parked();
22360    cx.assert_state_with_diff(
22361        r#"
22362        - lineA
22363        + ˇlineA1
22364          lineB
22365          lineD
22366        "#
22367        .unindent(),
22368    );
22369
22370    cx.update_editor(|editor, window, cx| {
22371        editor.move_down(&MoveDown, window, cx);
22372        editor.move_right(&MoveRight, window, cx);
22373        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22374    });
22375    executor.run_until_parked();
22376    cx.assert_state_with_diff(
22377        r#"
22378        - lineA
22379        + lineA1
22380          lˇineB
22381        - lineC
22382          lineD
22383        "#
22384        .unindent(),
22385    );
22386}
22387
22388#[gpui::test]
22389async fn test_edits_around_expanded_deletion_hunks(
22390    executor: BackgroundExecutor,
22391    cx: &mut TestAppContext,
22392) {
22393    init_test(cx, |_| {});
22394
22395    let mut cx = EditorTestContext::new(cx).await;
22396
22397    let diff_base = r#"
22398        use some::mod1;
22399        use some::mod2;
22400
22401        const A: u32 = 42;
22402        const B: u32 = 42;
22403        const C: u32 = 42;
22404
22405
22406        fn main() {
22407            println!("hello");
22408
22409            println!("world");
22410        }
22411    "#
22412    .unindent();
22413    executor.run_until_parked();
22414    cx.set_state(
22415        &r#"
22416        use some::mod1;
22417        use some::mod2;
22418
22419        ˇconst B: u32 = 42;
22420        const C: u32 = 42;
22421
22422
22423        fn main() {
22424            println!("hello");
22425
22426            println!("world");
22427        }
22428        "#
22429        .unindent(),
22430    );
22431
22432    cx.set_head_text(&diff_base);
22433    executor.run_until_parked();
22434
22435    cx.update_editor(|editor, window, cx| {
22436        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22437    });
22438    executor.run_until_parked();
22439
22440    cx.assert_state_with_diff(
22441        r#"
22442        use some::mod1;
22443        use some::mod2;
22444
22445      - const A: u32 = 42;
22446        ˇconst B: u32 = 42;
22447        const C: u32 = 42;
22448
22449
22450        fn main() {
22451            println!("hello");
22452
22453            println!("world");
22454        }
22455      "#
22456        .unindent(),
22457    );
22458
22459    cx.update_editor(|editor, window, cx| {
22460        editor.delete_line(&DeleteLine, window, cx);
22461    });
22462    executor.run_until_parked();
22463    cx.assert_state_with_diff(
22464        r#"
22465        use some::mod1;
22466        use some::mod2;
22467
22468      - const A: u32 = 42;
22469      - const B: u32 = 42;
22470        ˇconst C: u32 = 42;
22471
22472
22473        fn main() {
22474            println!("hello");
22475
22476            println!("world");
22477        }
22478      "#
22479        .unindent(),
22480    );
22481
22482    cx.update_editor(|editor, window, cx| {
22483        editor.delete_line(&DeleteLine, window, cx);
22484    });
22485    executor.run_until_parked();
22486    cx.assert_state_with_diff(
22487        r#"
22488        use some::mod1;
22489        use some::mod2;
22490
22491      - const A: u32 = 42;
22492      - const B: u32 = 42;
22493      - const C: u32 = 42;
22494        ˇ
22495
22496        fn main() {
22497            println!("hello");
22498
22499            println!("world");
22500        }
22501      "#
22502        .unindent(),
22503    );
22504
22505    cx.update_editor(|editor, window, cx| {
22506        editor.handle_input("replacement", window, cx);
22507    });
22508    executor.run_until_parked();
22509    cx.assert_state_with_diff(
22510        r#"
22511        use some::mod1;
22512        use some::mod2;
22513
22514      - const A: u32 = 42;
22515      - const B: u32 = 42;
22516      - const C: u32 = 42;
22517      -
22518      + replacementˇ
22519
22520        fn main() {
22521            println!("hello");
22522
22523            println!("world");
22524        }
22525      "#
22526        .unindent(),
22527    );
22528}
22529
22530#[gpui::test]
22531async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22532    init_test(cx, |_| {});
22533
22534    let mut cx = EditorTestContext::new(cx).await;
22535
22536    let base_text = r#"
22537        one
22538        two
22539        three
22540        four
22541        five
22542    "#
22543    .unindent();
22544    executor.run_until_parked();
22545    cx.set_state(
22546        &r#"
22547        one
22548        two
22549        fˇour
22550        five
22551        "#
22552        .unindent(),
22553    );
22554
22555    cx.set_head_text(&base_text);
22556    executor.run_until_parked();
22557
22558    cx.update_editor(|editor, window, cx| {
22559        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22560    });
22561    executor.run_until_parked();
22562
22563    cx.assert_state_with_diff(
22564        r#"
22565          one
22566          two
22567        - three
22568          fˇour
22569          five
22570        "#
22571        .unindent(),
22572    );
22573
22574    cx.update_editor(|editor, window, cx| {
22575        editor.backspace(&Backspace, window, cx);
22576        editor.backspace(&Backspace, window, cx);
22577    });
22578    executor.run_until_parked();
22579    cx.assert_state_with_diff(
22580        r#"
22581          one
22582          two
22583        - threeˇ
22584        - four
22585        + our
22586          five
22587        "#
22588        .unindent(),
22589    );
22590}
22591
22592#[gpui::test]
22593async fn test_edit_after_expanded_modification_hunk(
22594    executor: BackgroundExecutor,
22595    cx: &mut TestAppContext,
22596) {
22597    init_test(cx, |_| {});
22598
22599    let mut cx = EditorTestContext::new(cx).await;
22600
22601    let diff_base = r#"
22602        use some::mod1;
22603        use some::mod2;
22604
22605        const A: u32 = 42;
22606        const B: u32 = 42;
22607        const C: u32 = 42;
22608        const D: u32 = 42;
22609
22610
22611        fn main() {
22612            println!("hello");
22613
22614            println!("world");
22615        }"#
22616    .unindent();
22617
22618    cx.set_state(
22619        &r#"
22620        use some::mod1;
22621        use some::mod2;
22622
22623        const A: u32 = 42;
22624        const B: u32 = 42;
22625        const C: u32 = 43ˇ
22626        const D: u32 = 42;
22627
22628
22629        fn main() {
22630            println!("hello");
22631
22632            println!("world");
22633        }"#
22634        .unindent(),
22635    );
22636
22637    cx.set_head_text(&diff_base);
22638    executor.run_until_parked();
22639    cx.update_editor(|editor, window, cx| {
22640        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22641    });
22642    executor.run_until_parked();
22643
22644    cx.assert_state_with_diff(
22645        r#"
22646        use some::mod1;
22647        use some::mod2;
22648
22649        const A: u32 = 42;
22650        const B: u32 = 42;
22651      - const C: u32 = 42;
22652      + const C: u32 = 43ˇ
22653        const D: u32 = 42;
22654
22655
22656        fn main() {
22657            println!("hello");
22658
22659            println!("world");
22660        }"#
22661        .unindent(),
22662    );
22663
22664    cx.update_editor(|editor, window, cx| {
22665        editor.handle_input("\nnew_line\n", window, cx);
22666    });
22667    executor.run_until_parked();
22668
22669    cx.assert_state_with_diff(
22670        r#"
22671        use some::mod1;
22672        use some::mod2;
22673
22674        const A: u32 = 42;
22675        const B: u32 = 42;
22676      - const C: u32 = 42;
22677      + const C: u32 = 43
22678      + new_line
22679      + ˇ
22680        const D: u32 = 42;
22681
22682
22683        fn main() {
22684            println!("hello");
22685
22686            println!("world");
22687        }"#
22688        .unindent(),
22689    );
22690}
22691
22692#[gpui::test]
22693async fn test_stage_and_unstage_added_file_hunk(
22694    executor: BackgroundExecutor,
22695    cx: &mut TestAppContext,
22696) {
22697    init_test(cx, |_| {});
22698
22699    let mut cx = EditorTestContext::new(cx).await;
22700    cx.update_editor(|editor, _, cx| {
22701        editor.set_expand_all_diff_hunks(cx);
22702    });
22703
22704    let working_copy = r#"
22705            ˇfn main() {
22706                println!("hello, world!");
22707            }
22708        "#
22709    .unindent();
22710
22711    cx.set_state(&working_copy);
22712    executor.run_until_parked();
22713
22714    cx.assert_state_with_diff(
22715        r#"
22716            + ˇfn main() {
22717            +     println!("hello, world!");
22718            + }
22719        "#
22720        .unindent(),
22721    );
22722    cx.assert_index_text(None);
22723
22724    cx.update_editor(|editor, window, cx| {
22725        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22726    });
22727    executor.run_until_parked();
22728    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
22729    cx.assert_state_with_diff(
22730        r#"
22731            + ˇfn main() {
22732            +     println!("hello, world!");
22733            + }
22734        "#
22735        .unindent(),
22736    );
22737
22738    cx.update_editor(|editor, window, cx| {
22739        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22740    });
22741    executor.run_until_parked();
22742    cx.assert_index_text(None);
22743}
22744
22745async fn setup_indent_guides_editor(
22746    text: &str,
22747    cx: &mut TestAppContext,
22748) -> (BufferId, EditorTestContext) {
22749    init_test(cx, |_| {});
22750
22751    let mut cx = EditorTestContext::new(cx).await;
22752
22753    let buffer_id = cx.update_editor(|editor, window, cx| {
22754        editor.set_text(text, window, cx);
22755        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
22756
22757        buffer_ids[0]
22758    });
22759
22760    (buffer_id, cx)
22761}
22762
22763fn assert_indent_guides(
22764    range: Range<u32>,
22765    expected: Vec<IndentGuide>,
22766    active_indices: Option<Vec<usize>>,
22767    cx: &mut EditorTestContext,
22768) {
22769    let indent_guides = cx.update_editor(|editor, window, cx| {
22770        let snapshot = editor.snapshot(window, cx).display_snapshot;
22771        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
22772            editor,
22773            MultiBufferRow(range.start)..MultiBufferRow(range.end),
22774            true,
22775            &snapshot,
22776            cx,
22777        );
22778
22779        indent_guides.sort_by(|a, b| {
22780            a.depth.cmp(&b.depth).then(
22781                a.start_row
22782                    .cmp(&b.start_row)
22783                    .then(a.end_row.cmp(&b.end_row)),
22784            )
22785        });
22786        indent_guides
22787    });
22788
22789    if let Some(expected) = active_indices {
22790        let active_indices = cx.update_editor(|editor, window, cx| {
22791            let snapshot = editor.snapshot(window, cx).display_snapshot;
22792            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
22793        });
22794
22795        assert_eq!(
22796            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
22797            expected,
22798            "Active indent guide indices do not match"
22799        );
22800    }
22801
22802    assert_eq!(indent_guides, expected, "Indent guides do not match");
22803}
22804
22805fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
22806    IndentGuide {
22807        buffer_id,
22808        start_row: MultiBufferRow(start_row),
22809        end_row: MultiBufferRow(end_row),
22810        depth,
22811        tab_size: 4,
22812        settings: IndentGuideSettings {
22813            enabled: true,
22814            line_width: 1,
22815            active_line_width: 1,
22816            coloring: IndentGuideColoring::default(),
22817            background_coloring: IndentGuideBackgroundColoring::default(),
22818        },
22819    }
22820}
22821
22822#[gpui::test]
22823async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
22824    let (buffer_id, mut cx) = setup_indent_guides_editor(
22825        &"
22826        fn main() {
22827            let a = 1;
22828        }"
22829        .unindent(),
22830        cx,
22831    )
22832    .await;
22833
22834    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
22835}
22836
22837#[gpui::test]
22838async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
22839    let (buffer_id, mut cx) = setup_indent_guides_editor(
22840        &"
22841        fn main() {
22842            let a = 1;
22843            let b = 2;
22844        }"
22845        .unindent(),
22846        cx,
22847    )
22848    .await;
22849
22850    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
22851}
22852
22853#[gpui::test]
22854async fn test_indent_guide_nested(cx: &mut TestAppContext) {
22855    let (buffer_id, mut cx) = setup_indent_guides_editor(
22856        &"
22857        fn main() {
22858            let a = 1;
22859            if a == 3 {
22860                let b = 2;
22861            } else {
22862                let c = 3;
22863            }
22864        }"
22865        .unindent(),
22866        cx,
22867    )
22868    .await;
22869
22870    assert_indent_guides(
22871        0..8,
22872        vec![
22873            indent_guide(buffer_id, 1, 6, 0),
22874            indent_guide(buffer_id, 3, 3, 1),
22875            indent_guide(buffer_id, 5, 5, 1),
22876        ],
22877        None,
22878        &mut cx,
22879    );
22880}
22881
22882#[gpui::test]
22883async fn test_indent_guide_tab(cx: &mut TestAppContext) {
22884    let (buffer_id, mut cx) = setup_indent_guides_editor(
22885        &"
22886        fn main() {
22887            let a = 1;
22888                let b = 2;
22889            let c = 3;
22890        }"
22891        .unindent(),
22892        cx,
22893    )
22894    .await;
22895
22896    assert_indent_guides(
22897        0..5,
22898        vec![
22899            indent_guide(buffer_id, 1, 3, 0),
22900            indent_guide(buffer_id, 2, 2, 1),
22901        ],
22902        None,
22903        &mut cx,
22904    );
22905}
22906
22907#[gpui::test]
22908async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
22909    let (buffer_id, mut cx) = setup_indent_guides_editor(
22910        &"
22911        fn main() {
22912            let a = 1;
22913
22914            let c = 3;
22915        }"
22916        .unindent(),
22917        cx,
22918    )
22919    .await;
22920
22921    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
22922}
22923
22924#[gpui::test]
22925async fn test_indent_guide_complex(cx: &mut TestAppContext) {
22926    let (buffer_id, mut cx) = setup_indent_guides_editor(
22927        &"
22928        fn main() {
22929            let a = 1;
22930
22931            let c = 3;
22932
22933            if a == 3 {
22934                let b = 2;
22935            } else {
22936                let c = 3;
22937            }
22938        }"
22939        .unindent(),
22940        cx,
22941    )
22942    .await;
22943
22944    assert_indent_guides(
22945        0..11,
22946        vec![
22947            indent_guide(buffer_id, 1, 9, 0),
22948            indent_guide(buffer_id, 6, 6, 1),
22949            indent_guide(buffer_id, 8, 8, 1),
22950        ],
22951        None,
22952        &mut cx,
22953    );
22954}
22955
22956#[gpui::test]
22957async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
22958    let (buffer_id, mut cx) = setup_indent_guides_editor(
22959        &"
22960        fn main() {
22961            let a = 1;
22962
22963            let c = 3;
22964
22965            if a == 3 {
22966                let b = 2;
22967            } else {
22968                let c = 3;
22969            }
22970        }"
22971        .unindent(),
22972        cx,
22973    )
22974    .await;
22975
22976    assert_indent_guides(
22977        1..11,
22978        vec![
22979            indent_guide(buffer_id, 1, 9, 0),
22980            indent_guide(buffer_id, 6, 6, 1),
22981            indent_guide(buffer_id, 8, 8, 1),
22982        ],
22983        None,
22984        &mut cx,
22985    );
22986}
22987
22988#[gpui::test]
22989async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
22990    let (buffer_id, mut cx) = setup_indent_guides_editor(
22991        &"
22992        fn main() {
22993            let a = 1;
22994
22995            let c = 3;
22996
22997            if a == 3 {
22998                let b = 2;
22999            } else {
23000                let c = 3;
23001            }
23002        }"
23003        .unindent(),
23004        cx,
23005    )
23006    .await;
23007
23008    assert_indent_guides(
23009        1..10,
23010        vec![
23011            indent_guide(buffer_id, 1, 9, 0),
23012            indent_guide(buffer_id, 6, 6, 1),
23013            indent_guide(buffer_id, 8, 8, 1),
23014        ],
23015        None,
23016        &mut cx,
23017    );
23018}
23019
23020#[gpui::test]
23021async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
23022    let (buffer_id, mut cx) = setup_indent_guides_editor(
23023        &"
23024        fn main() {
23025            if a {
23026                b(
23027                    c,
23028                    d,
23029                )
23030            } else {
23031                e(
23032                    f
23033                )
23034            }
23035        }"
23036        .unindent(),
23037        cx,
23038    )
23039    .await;
23040
23041    assert_indent_guides(
23042        0..11,
23043        vec![
23044            indent_guide(buffer_id, 1, 10, 0),
23045            indent_guide(buffer_id, 2, 5, 1),
23046            indent_guide(buffer_id, 7, 9, 1),
23047            indent_guide(buffer_id, 3, 4, 2),
23048            indent_guide(buffer_id, 8, 8, 2),
23049        ],
23050        None,
23051        &mut cx,
23052    );
23053
23054    cx.update_editor(|editor, window, cx| {
23055        editor.fold_at(MultiBufferRow(2), window, cx);
23056        assert_eq!(
23057            editor.display_text(cx),
23058            "
23059            fn main() {
23060                if a {
23061                    b(⋯
23062                    )
23063                } else {
23064                    e(
23065                        f
23066                    )
23067                }
23068            }"
23069            .unindent()
23070        );
23071    });
23072
23073    assert_indent_guides(
23074        0..11,
23075        vec![
23076            indent_guide(buffer_id, 1, 10, 0),
23077            indent_guide(buffer_id, 2, 5, 1),
23078            indent_guide(buffer_id, 7, 9, 1),
23079            indent_guide(buffer_id, 8, 8, 2),
23080        ],
23081        None,
23082        &mut cx,
23083    );
23084}
23085
23086#[gpui::test]
23087async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
23088    let (buffer_id, mut cx) = setup_indent_guides_editor(
23089        &"
23090        block1
23091            block2
23092                block3
23093                    block4
23094            block2
23095        block1
23096        block1"
23097            .unindent(),
23098        cx,
23099    )
23100    .await;
23101
23102    assert_indent_guides(
23103        1..10,
23104        vec![
23105            indent_guide(buffer_id, 1, 4, 0),
23106            indent_guide(buffer_id, 2, 3, 1),
23107            indent_guide(buffer_id, 3, 3, 2),
23108        ],
23109        None,
23110        &mut cx,
23111    );
23112}
23113
23114#[gpui::test]
23115async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
23116    let (buffer_id, mut cx) = setup_indent_guides_editor(
23117        &"
23118        block1
23119            block2
23120                block3
23121
23122        block1
23123        block1"
23124            .unindent(),
23125        cx,
23126    )
23127    .await;
23128
23129    assert_indent_guides(
23130        0..6,
23131        vec![
23132            indent_guide(buffer_id, 1, 2, 0),
23133            indent_guide(buffer_id, 2, 2, 1),
23134        ],
23135        None,
23136        &mut cx,
23137    );
23138}
23139
23140#[gpui::test]
23141async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
23142    let (buffer_id, mut cx) = setup_indent_guides_editor(
23143        &"
23144        function component() {
23145        \treturn (
23146        \t\t\t
23147        \t\t<div>
23148        \t\t\t<abc></abc>
23149        \t\t</div>
23150        \t)
23151        }"
23152        .unindent(),
23153        cx,
23154    )
23155    .await;
23156
23157    assert_indent_guides(
23158        0..8,
23159        vec![
23160            indent_guide(buffer_id, 1, 6, 0),
23161            indent_guide(buffer_id, 2, 5, 1),
23162            indent_guide(buffer_id, 4, 4, 2),
23163        ],
23164        None,
23165        &mut cx,
23166    );
23167}
23168
23169#[gpui::test]
23170async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
23171    let (buffer_id, mut cx) = setup_indent_guides_editor(
23172        &"
23173        function component() {
23174        \treturn (
23175        \t
23176        \t\t<div>
23177        \t\t\t<abc></abc>
23178        \t\t</div>
23179        \t)
23180        }"
23181        .unindent(),
23182        cx,
23183    )
23184    .await;
23185
23186    assert_indent_guides(
23187        0..8,
23188        vec![
23189            indent_guide(buffer_id, 1, 6, 0),
23190            indent_guide(buffer_id, 2, 5, 1),
23191            indent_guide(buffer_id, 4, 4, 2),
23192        ],
23193        None,
23194        &mut cx,
23195    );
23196}
23197
23198#[gpui::test]
23199async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
23200    let (buffer_id, mut cx) = setup_indent_guides_editor(
23201        &"
23202        block1
23203
23204
23205
23206            block2
23207        "
23208        .unindent(),
23209        cx,
23210    )
23211    .await;
23212
23213    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
23214}
23215
23216#[gpui::test]
23217async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
23218    let (buffer_id, mut cx) = setup_indent_guides_editor(
23219        &"
23220        def a:
23221        \tb = 3
23222        \tif True:
23223        \t\tc = 4
23224        \t\td = 5
23225        \tprint(b)
23226        "
23227        .unindent(),
23228        cx,
23229    )
23230    .await;
23231
23232    assert_indent_guides(
23233        0..6,
23234        vec![
23235            indent_guide(buffer_id, 1, 5, 0),
23236            indent_guide(buffer_id, 3, 4, 1),
23237        ],
23238        None,
23239        &mut cx,
23240    );
23241}
23242
23243#[gpui::test]
23244async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
23245    let (buffer_id, mut cx) = setup_indent_guides_editor(
23246        &"
23247    fn main() {
23248        let a = 1;
23249    }"
23250        .unindent(),
23251        cx,
23252    )
23253    .await;
23254
23255    cx.update_editor(|editor, window, cx| {
23256        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23257            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
23258        });
23259    });
23260
23261    assert_indent_guides(
23262        0..3,
23263        vec![indent_guide(buffer_id, 1, 1, 0)],
23264        Some(vec![0]),
23265        &mut cx,
23266    );
23267}
23268
23269#[gpui::test]
23270async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
23271    let (buffer_id, mut cx) = setup_indent_guides_editor(
23272        &"
23273    fn main() {
23274        if 1 == 2 {
23275            let a = 1;
23276        }
23277    }"
23278        .unindent(),
23279        cx,
23280    )
23281    .await;
23282
23283    cx.update_editor(|editor, window, cx| {
23284        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23285            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
23286        });
23287    });
23288    cx.run_until_parked();
23289
23290    assert_indent_guides(
23291        0..4,
23292        vec![
23293            indent_guide(buffer_id, 1, 3, 0),
23294            indent_guide(buffer_id, 2, 2, 1),
23295        ],
23296        Some(vec![1]),
23297        &mut cx,
23298    );
23299
23300    cx.update_editor(|editor, window, cx| {
23301        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23302            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
23303        });
23304    });
23305    cx.run_until_parked();
23306
23307    assert_indent_guides(
23308        0..4,
23309        vec![
23310            indent_guide(buffer_id, 1, 3, 0),
23311            indent_guide(buffer_id, 2, 2, 1),
23312        ],
23313        Some(vec![1]),
23314        &mut cx,
23315    );
23316
23317    cx.update_editor(|editor, window, cx| {
23318        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23319            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
23320        });
23321    });
23322    cx.run_until_parked();
23323
23324    assert_indent_guides(
23325        0..4,
23326        vec![
23327            indent_guide(buffer_id, 1, 3, 0),
23328            indent_guide(buffer_id, 2, 2, 1),
23329        ],
23330        Some(vec![0]),
23331        &mut cx,
23332    );
23333}
23334
23335#[gpui::test]
23336async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
23337    let (buffer_id, mut cx) = setup_indent_guides_editor(
23338        &"
23339    fn main() {
23340        let a = 1;
23341
23342        let b = 2;
23343    }"
23344        .unindent(),
23345        cx,
23346    )
23347    .await;
23348
23349    cx.update_editor(|editor, window, cx| {
23350        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23351            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
23352        });
23353    });
23354
23355    assert_indent_guides(
23356        0..5,
23357        vec![indent_guide(buffer_id, 1, 3, 0)],
23358        Some(vec![0]),
23359        &mut cx,
23360    );
23361}
23362
23363#[gpui::test]
23364async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
23365    let (buffer_id, mut cx) = setup_indent_guides_editor(
23366        &"
23367    def m:
23368        a = 1
23369        pass"
23370            .unindent(),
23371        cx,
23372    )
23373    .await;
23374
23375    cx.update_editor(|editor, window, cx| {
23376        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23377            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
23378        });
23379    });
23380
23381    assert_indent_guides(
23382        0..3,
23383        vec![indent_guide(buffer_id, 1, 2, 0)],
23384        Some(vec![0]),
23385        &mut cx,
23386    );
23387}
23388
23389#[gpui::test]
23390async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
23391    init_test(cx, |_| {});
23392    let mut cx = EditorTestContext::new(cx).await;
23393    let text = indoc! {
23394        "
23395        impl A {
23396            fn b() {
23397                0;
23398                3;
23399                5;
23400                6;
23401                7;
23402            }
23403        }
23404        "
23405    };
23406    let base_text = indoc! {
23407        "
23408        impl A {
23409            fn b() {
23410                0;
23411                1;
23412                2;
23413                3;
23414                4;
23415            }
23416            fn c() {
23417                5;
23418                6;
23419                7;
23420            }
23421        }
23422        "
23423    };
23424
23425    cx.update_editor(|editor, window, cx| {
23426        editor.set_text(text, window, cx);
23427
23428        editor.buffer().update(cx, |multibuffer, cx| {
23429            let buffer = multibuffer.as_singleton().unwrap();
23430            let diff = cx.new(|cx| {
23431                BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
23432            });
23433
23434            multibuffer.set_all_diff_hunks_expanded(cx);
23435            multibuffer.add_diff(diff, cx);
23436
23437            buffer.read(cx).remote_id()
23438        })
23439    });
23440    cx.run_until_parked();
23441
23442    cx.assert_state_with_diff(
23443        indoc! { "
23444          impl A {
23445              fn b() {
23446                  0;
23447        -         1;
23448        -         2;
23449                  3;
23450        -         4;
23451        -     }
23452        -     fn c() {
23453                  5;
23454                  6;
23455                  7;
23456              }
23457          }
23458          ˇ"
23459        }
23460        .to_string(),
23461    );
23462
23463    let mut actual_guides = cx.update_editor(|editor, window, cx| {
23464        editor
23465            .snapshot(window, cx)
23466            .buffer_snapshot()
23467            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
23468            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
23469            .collect::<Vec<_>>()
23470    });
23471    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
23472    assert_eq!(
23473        actual_guides,
23474        vec![
23475            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
23476            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
23477            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
23478        ]
23479    );
23480}
23481
23482#[gpui::test]
23483async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
23484    init_test(cx, |_| {});
23485    let mut cx = EditorTestContext::new(cx).await;
23486
23487    let diff_base = r#"
23488        a
23489        b
23490        c
23491        "#
23492    .unindent();
23493
23494    cx.set_state(
23495        &r#"
23496        ˇA
23497        b
23498        C
23499        "#
23500        .unindent(),
23501    );
23502    cx.set_head_text(&diff_base);
23503    cx.update_editor(|editor, window, cx| {
23504        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23505    });
23506    executor.run_until_parked();
23507
23508    let both_hunks_expanded = r#"
23509        - a
23510        + ˇA
23511          b
23512        - c
23513        + C
23514        "#
23515    .unindent();
23516
23517    cx.assert_state_with_diff(both_hunks_expanded.clone());
23518
23519    let hunk_ranges = cx.update_editor(|editor, window, cx| {
23520        let snapshot = editor.snapshot(window, cx);
23521        let hunks = editor
23522            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
23523            .collect::<Vec<_>>();
23524        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
23525        hunks
23526            .into_iter()
23527            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
23528            .collect::<Vec<_>>()
23529    });
23530    assert_eq!(hunk_ranges.len(), 2);
23531
23532    cx.update_editor(|editor, _, cx| {
23533        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
23534    });
23535    executor.run_until_parked();
23536
23537    let second_hunk_expanded = r#"
23538          ˇA
23539          b
23540        - c
23541        + C
23542        "#
23543    .unindent();
23544
23545    cx.assert_state_with_diff(second_hunk_expanded);
23546
23547    cx.update_editor(|editor, _, cx| {
23548        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
23549    });
23550    executor.run_until_parked();
23551
23552    cx.assert_state_with_diff(both_hunks_expanded.clone());
23553
23554    cx.update_editor(|editor, _, cx| {
23555        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
23556    });
23557    executor.run_until_parked();
23558
23559    let first_hunk_expanded = r#"
23560        - a
23561        + ˇA
23562          b
23563          C
23564        "#
23565    .unindent();
23566
23567    cx.assert_state_with_diff(first_hunk_expanded);
23568
23569    cx.update_editor(|editor, _, cx| {
23570        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
23571    });
23572    executor.run_until_parked();
23573
23574    cx.assert_state_with_diff(both_hunks_expanded);
23575
23576    cx.set_state(
23577        &r#"
23578        ˇA
23579        b
23580        "#
23581        .unindent(),
23582    );
23583    cx.run_until_parked();
23584
23585    // TODO this cursor position seems bad
23586    cx.assert_state_with_diff(
23587        r#"
23588        - ˇa
23589        + A
23590          b
23591        "#
23592        .unindent(),
23593    );
23594
23595    cx.update_editor(|editor, window, cx| {
23596        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23597    });
23598
23599    cx.assert_state_with_diff(
23600        r#"
23601            - ˇa
23602            + A
23603              b
23604            - c
23605            "#
23606        .unindent(),
23607    );
23608
23609    let hunk_ranges = cx.update_editor(|editor, window, cx| {
23610        let snapshot = editor.snapshot(window, cx);
23611        let hunks = editor
23612            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
23613            .collect::<Vec<_>>();
23614        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
23615        hunks
23616            .into_iter()
23617            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
23618            .collect::<Vec<_>>()
23619    });
23620    assert_eq!(hunk_ranges.len(), 2);
23621
23622    cx.update_editor(|editor, _, cx| {
23623        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
23624    });
23625    executor.run_until_parked();
23626
23627    cx.assert_state_with_diff(
23628        r#"
23629        - ˇa
23630        + A
23631          b
23632        "#
23633        .unindent(),
23634    );
23635}
23636
23637#[gpui::test]
23638async fn test_toggle_deletion_hunk_at_start_of_file(
23639    executor: BackgroundExecutor,
23640    cx: &mut TestAppContext,
23641) {
23642    init_test(cx, |_| {});
23643    let mut cx = EditorTestContext::new(cx).await;
23644
23645    let diff_base = r#"
23646        a
23647        b
23648        c
23649        "#
23650    .unindent();
23651
23652    cx.set_state(
23653        &r#"
23654        ˇb
23655        c
23656        "#
23657        .unindent(),
23658    );
23659    cx.set_head_text(&diff_base);
23660    cx.update_editor(|editor, window, cx| {
23661        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23662    });
23663    executor.run_until_parked();
23664
23665    let hunk_expanded = r#"
23666        - a
23667          ˇb
23668          c
23669        "#
23670    .unindent();
23671
23672    cx.assert_state_with_diff(hunk_expanded.clone());
23673
23674    let hunk_ranges = cx.update_editor(|editor, window, cx| {
23675        let snapshot = editor.snapshot(window, cx);
23676        let hunks = editor
23677            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
23678            .collect::<Vec<_>>();
23679        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
23680        hunks
23681            .into_iter()
23682            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
23683            .collect::<Vec<_>>()
23684    });
23685    assert_eq!(hunk_ranges.len(), 1);
23686
23687    cx.update_editor(|editor, _, cx| {
23688        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
23689    });
23690    executor.run_until_parked();
23691
23692    let hunk_collapsed = r#"
23693          ˇb
23694          c
23695        "#
23696    .unindent();
23697
23698    cx.assert_state_with_diff(hunk_collapsed);
23699
23700    cx.update_editor(|editor, _, cx| {
23701        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
23702    });
23703    executor.run_until_parked();
23704
23705    cx.assert_state_with_diff(hunk_expanded);
23706}
23707
23708#[gpui::test]
23709async fn test_select_smaller_syntax_node_after_diff_hunk_collapse(
23710    executor: BackgroundExecutor,
23711    cx: &mut TestAppContext,
23712) {
23713    init_test(cx, |_| {});
23714
23715    let mut cx = EditorTestContext::new(cx).await;
23716    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
23717
23718    cx.set_state(
23719        &r#"
23720        fn main() {
23721            let x = ˇ1;
23722        }
23723        "#
23724        .unindent(),
23725    );
23726
23727    let diff_base = r#"
23728        fn removed_one() {
23729            println!("this function was deleted");
23730        }
23731
23732        fn removed_two() {
23733            println!("this function was also deleted");
23734        }
23735
23736        fn main() {
23737            let x = 1;
23738        }
23739        "#
23740    .unindent();
23741    cx.set_head_text(&diff_base);
23742    executor.run_until_parked();
23743
23744    cx.update_editor(|editor, window, cx| {
23745        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23746    });
23747    executor.run_until_parked();
23748
23749    cx.update_editor(|editor, window, cx| {
23750        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
23751    });
23752
23753    cx.update_editor(|editor, window, cx| {
23754        editor.collapse_all_diff_hunks(&CollapseAllDiffHunks, window, cx);
23755    });
23756    executor.run_until_parked();
23757
23758    cx.update_editor(|editor, window, cx| {
23759        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
23760    });
23761}
23762
23763#[gpui::test]
23764async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
23765    executor: BackgroundExecutor,
23766    cx: &mut TestAppContext,
23767) {
23768    init_test(cx, |_| {});
23769    let mut cx = EditorTestContext::new(cx).await;
23770
23771    cx.set_state("ˇnew\nsecond\nthird\n");
23772    cx.set_head_text("old\nsecond\nthird\n");
23773    cx.update_editor(|editor, window, cx| {
23774        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
23775    });
23776    executor.run_until_parked();
23777    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
23778
23779    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
23780    cx.update_editor(|editor, window, cx| {
23781        let snapshot = editor.snapshot(window, cx);
23782        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
23783        let hunks = editor
23784            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
23785            .collect::<Vec<_>>();
23786        assert_eq!(hunks.len(), 1);
23787        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
23788        editor.toggle_single_diff_hunk(hunk_range, cx)
23789    });
23790    executor.run_until_parked();
23791    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
23792
23793    // Keep the editor scrolled to the top so the full hunk remains visible.
23794    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
23795}
23796
23797#[gpui::test]
23798async fn test_display_diff_hunks(cx: &mut TestAppContext) {
23799    init_test(cx, |_| {});
23800
23801    let fs = FakeFs::new(cx.executor());
23802    fs.insert_tree(
23803        path!("/test"),
23804        json!({
23805            ".git": {},
23806            "file-1": "ONE\n",
23807            "file-2": "TWO\n",
23808            "file-3": "THREE\n",
23809        }),
23810    )
23811    .await;
23812
23813    fs.set_head_for_repo(
23814        path!("/test/.git").as_ref(),
23815        &[
23816            ("file-1", "one\n".into()),
23817            ("file-2", "two\n".into()),
23818            ("file-3", "three\n".into()),
23819        ],
23820        "deadbeef",
23821    );
23822
23823    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
23824    let mut buffers = vec![];
23825    for i in 1..=3 {
23826        let buffer = project
23827            .update(cx, |project, cx| {
23828                let path = format!(path!("/test/file-{}"), i);
23829                project.open_local_buffer(path, cx)
23830            })
23831            .await
23832            .unwrap();
23833        buffers.push(buffer);
23834    }
23835
23836    let multibuffer = cx.new(|cx| {
23837        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
23838        multibuffer.set_all_diff_hunks_expanded(cx);
23839        for buffer in &buffers {
23840            let snapshot = buffer.read(cx).snapshot();
23841            multibuffer.set_excerpts_for_path(
23842                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
23843                buffer.clone(),
23844                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
23845                2,
23846                cx,
23847            );
23848        }
23849        multibuffer
23850    });
23851
23852    let editor = cx.add_window(|window, cx| {
23853        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
23854    });
23855    cx.run_until_parked();
23856
23857    let snapshot = editor
23858        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
23859        .unwrap();
23860    let hunks = snapshot
23861        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
23862        .map(|hunk| match hunk {
23863            DisplayDiffHunk::Unfolded {
23864                display_row_range, ..
23865            } => display_row_range,
23866            DisplayDiffHunk::Folded { .. } => unreachable!(),
23867        })
23868        .collect::<Vec<_>>();
23869    assert_eq!(
23870        hunks,
23871        [
23872            DisplayRow(2)..DisplayRow(4),
23873            DisplayRow(7)..DisplayRow(9),
23874            DisplayRow(12)..DisplayRow(14),
23875        ]
23876    );
23877}
23878
23879#[gpui::test]
23880async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
23881    init_test(cx, |_| {});
23882
23883    let mut cx = EditorTestContext::new(cx).await;
23884    cx.set_head_text(indoc! { "
23885        one
23886        two
23887        three
23888        four
23889        five
23890        "
23891    });
23892    cx.set_index_text(indoc! { "
23893        one
23894        two
23895        three
23896        four
23897        five
23898        "
23899    });
23900    cx.set_state(indoc! {"
23901        one
23902        TWO
23903        ˇTHREE
23904        FOUR
23905        five
23906    "});
23907    cx.run_until_parked();
23908    cx.update_editor(|editor, window, cx| {
23909        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
23910    });
23911    cx.run_until_parked();
23912    cx.assert_index_text(Some(indoc! {"
23913        one
23914        TWO
23915        THREE
23916        FOUR
23917        five
23918    "}));
23919    cx.set_state(indoc! { "
23920        one
23921        TWO
23922        ˇTHREE-HUNDRED
23923        FOUR
23924        five
23925    "});
23926    cx.run_until_parked();
23927    cx.update_editor(|editor, window, cx| {
23928        let snapshot = editor.snapshot(window, cx);
23929        let hunks = editor
23930            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
23931            .collect::<Vec<_>>();
23932        assert_eq!(hunks.len(), 1);
23933        assert_eq!(
23934            hunks[0].status(),
23935            DiffHunkStatus {
23936                kind: DiffHunkStatusKind::Modified,
23937                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
23938            }
23939        );
23940
23941        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
23942    });
23943    cx.run_until_parked();
23944    cx.assert_index_text(Some(indoc! {"
23945        one
23946        TWO
23947        THREE-HUNDRED
23948        FOUR
23949        five
23950    "}));
23951}
23952
23953#[gpui::test]
23954fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
23955    init_test(cx, |_| {});
23956
23957    let editor = cx.add_window(|window, cx| {
23958        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
23959        build_editor(buffer, window, cx)
23960    });
23961
23962    let render_args = Arc::new(Mutex::new(None));
23963    let snapshot = editor
23964        .update(cx, |editor, window, cx| {
23965            let snapshot = editor.buffer().read(cx).snapshot(cx);
23966            let range =
23967                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
23968
23969            struct RenderArgs {
23970                row: MultiBufferRow,
23971                folded: bool,
23972                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
23973            }
23974
23975            let crease = Crease::inline(
23976                range,
23977                FoldPlaceholder::test(),
23978                {
23979                    let toggle_callback = render_args.clone();
23980                    move |row, folded, callback, _window, _cx| {
23981                        *toggle_callback.lock() = Some(RenderArgs {
23982                            row,
23983                            folded,
23984                            callback,
23985                        });
23986                        div()
23987                    }
23988                },
23989                |_row, _folded, _window, _cx| div(),
23990            );
23991
23992            editor.insert_creases(Some(crease), cx);
23993            let snapshot = editor.snapshot(window, cx);
23994            let _div =
23995                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
23996            snapshot
23997        })
23998        .unwrap();
23999
24000    let render_args = render_args.lock().take().unwrap();
24001    assert_eq!(render_args.row, MultiBufferRow(1));
24002    assert!(!render_args.folded);
24003    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
24004
24005    cx.update_window(*editor, |_, window, cx| {
24006        (render_args.callback)(true, window, cx)
24007    })
24008    .unwrap();
24009    let snapshot = editor
24010        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
24011        .unwrap();
24012    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
24013
24014    cx.update_window(*editor, |_, window, cx| {
24015        (render_args.callback)(false, window, cx)
24016    })
24017    .unwrap();
24018    let snapshot = editor
24019        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
24020        .unwrap();
24021    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
24022}
24023
24024#[gpui::test]
24025async fn test_input_text(cx: &mut TestAppContext) {
24026    init_test(cx, |_| {});
24027    let mut cx = EditorTestContext::new(cx).await;
24028
24029    cx.set_state(
24030        &r#"ˇone
24031        two
24032
24033        three
24034        fourˇ
24035        five
24036
24037        siˇx"#
24038            .unindent(),
24039    );
24040
24041    cx.dispatch_action(HandleInput(String::new()));
24042    cx.assert_editor_state(
24043        &r#"ˇone
24044        two
24045
24046        three
24047        fourˇ
24048        five
24049
24050        siˇx"#
24051            .unindent(),
24052    );
24053
24054    cx.dispatch_action(HandleInput("AAAA".to_string()));
24055    cx.assert_editor_state(
24056        &r#"AAAAˇone
24057        two
24058
24059        three
24060        fourAAAAˇ
24061        five
24062
24063        siAAAAˇx"#
24064            .unindent(),
24065    );
24066}
24067
24068#[gpui::test]
24069async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
24070    init_test(cx, |_| {});
24071
24072    let mut cx = EditorTestContext::new(cx).await;
24073    cx.set_state(
24074        r#"let foo = 1;
24075let foo = 2;
24076let foo = 3;
24077let fooˇ = 4;
24078let foo = 5;
24079let foo = 6;
24080let foo = 7;
24081let foo = 8;
24082let foo = 9;
24083let foo = 10;
24084let foo = 11;
24085let foo = 12;
24086let foo = 13;
24087let foo = 14;
24088let foo = 15;"#,
24089    );
24090
24091    cx.update_editor(|e, window, cx| {
24092        assert_eq!(
24093            e.next_scroll_position,
24094            NextScrollCursorCenterTopBottom::Center,
24095            "Default next scroll direction is center",
24096        );
24097
24098        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
24099        assert_eq!(
24100            e.next_scroll_position,
24101            NextScrollCursorCenterTopBottom::Top,
24102            "After center, next scroll direction should be top",
24103        );
24104
24105        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
24106        assert_eq!(
24107            e.next_scroll_position,
24108            NextScrollCursorCenterTopBottom::Bottom,
24109            "After top, next scroll direction should be bottom",
24110        );
24111
24112        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
24113        assert_eq!(
24114            e.next_scroll_position,
24115            NextScrollCursorCenterTopBottom::Center,
24116            "After bottom, scrolling should start over",
24117        );
24118
24119        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
24120        assert_eq!(
24121            e.next_scroll_position,
24122            NextScrollCursorCenterTopBottom::Top,
24123            "Scrolling continues if retriggered fast enough"
24124        );
24125    });
24126
24127    cx.executor()
24128        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
24129    cx.executor().run_until_parked();
24130    cx.update_editor(|e, _, _| {
24131        assert_eq!(
24132            e.next_scroll_position,
24133            NextScrollCursorCenterTopBottom::Center,
24134            "If scrolling is not triggered fast enough, it should reset"
24135        );
24136    });
24137}
24138
24139#[gpui::test]
24140async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
24141    init_test(cx, |_| {});
24142    let mut cx = EditorLspTestContext::new_rust(
24143        lsp::ServerCapabilities {
24144            definition_provider: Some(lsp::OneOf::Left(true)),
24145            references_provider: Some(lsp::OneOf::Left(true)),
24146            ..lsp::ServerCapabilities::default()
24147        },
24148        cx,
24149    )
24150    .await;
24151
24152    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
24153        let go_to_definition = cx
24154            .lsp
24155            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
24156                move |params, _| async move {
24157                    if empty_go_to_definition {
24158                        Ok(None)
24159                    } else {
24160                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
24161                            uri: params.text_document_position_params.text_document.uri,
24162                            range: lsp::Range::new(
24163                                lsp::Position::new(4, 3),
24164                                lsp::Position::new(4, 6),
24165                            ),
24166                        })))
24167                    }
24168                },
24169            );
24170        let references = cx
24171            .lsp
24172            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
24173                Ok(Some(vec![lsp::Location {
24174                    uri: params.text_document_position.text_document.uri,
24175                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
24176                }]))
24177            });
24178        (go_to_definition, references)
24179    };
24180
24181    cx.set_state(
24182        &r#"fn one() {
24183            let mut a = ˇtwo();
24184        }
24185
24186        fn two() {}"#
24187            .unindent(),
24188    );
24189    set_up_lsp_handlers(false, &mut cx);
24190    let navigated = cx
24191        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
24192        .await
24193        .expect("Failed to navigate to definition");
24194    assert_eq!(
24195        navigated,
24196        Navigated::Yes,
24197        "Should have navigated to definition from the GetDefinition response"
24198    );
24199    cx.assert_editor_state(
24200        &r#"fn one() {
24201            let mut a = two();
24202        }
24203
24204        fn «twoˇ»() {}"#
24205            .unindent(),
24206    );
24207
24208    let editors = cx.update_workspace(|workspace, _, cx| {
24209        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
24210    });
24211    cx.update_editor(|_, _, test_editor_cx| {
24212        assert_eq!(
24213            editors.len(),
24214            1,
24215            "Initially, only one, test, editor should be open in the workspace"
24216        );
24217        assert_eq!(
24218            test_editor_cx.entity(),
24219            editors.last().expect("Asserted len is 1").clone()
24220        );
24221    });
24222
24223    set_up_lsp_handlers(true, &mut cx);
24224    let navigated = cx
24225        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
24226        .await
24227        .expect("Failed to navigate to lookup references");
24228    assert_eq!(
24229        navigated,
24230        Navigated::Yes,
24231        "Should have navigated to references as a fallback after empty GoToDefinition response"
24232    );
24233    // We should not change the selections in the existing file,
24234    // if opening another milti buffer with the references
24235    cx.assert_editor_state(
24236        &r#"fn one() {
24237            let mut a = two();
24238        }
24239
24240        fn «twoˇ»() {}"#
24241            .unindent(),
24242    );
24243    let editors = cx.update_workspace(|workspace, _, cx| {
24244        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
24245    });
24246    cx.update_editor(|_, _, test_editor_cx| {
24247        assert_eq!(
24248            editors.len(),
24249            2,
24250            "After falling back to references search, we open a new editor with the results"
24251        );
24252        let references_fallback_text = editors
24253            .into_iter()
24254            .find(|new_editor| *new_editor != test_editor_cx.entity())
24255            .expect("Should have one non-test editor now")
24256            .read(test_editor_cx)
24257            .text(test_editor_cx);
24258        assert_eq!(
24259            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
24260            "Should use the range from the references response and not the GoToDefinition one"
24261        );
24262    });
24263}
24264
24265#[gpui::test]
24266async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
24267    init_test(cx, |_| {});
24268    cx.update(|cx| {
24269        let mut editor_settings = EditorSettings::get_global(cx).clone();
24270        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
24271        EditorSettings::override_global(editor_settings, cx);
24272    });
24273    let mut cx = EditorLspTestContext::new_rust(
24274        lsp::ServerCapabilities {
24275            definition_provider: Some(lsp::OneOf::Left(true)),
24276            references_provider: Some(lsp::OneOf::Left(true)),
24277            ..lsp::ServerCapabilities::default()
24278        },
24279        cx,
24280    )
24281    .await;
24282    let original_state = r#"fn one() {
24283        let mut a = ˇtwo();
24284    }
24285
24286    fn two() {}"#
24287        .unindent();
24288    cx.set_state(&original_state);
24289
24290    let mut go_to_definition = cx
24291        .lsp
24292        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
24293            move |_, _| async move { Ok(None) },
24294        );
24295    let _references = cx
24296        .lsp
24297        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
24298            panic!("Should not call for references with no go to definition fallback")
24299        });
24300
24301    let navigated = cx
24302        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
24303        .await
24304        .expect("Failed to navigate to lookup references");
24305    go_to_definition
24306        .next()
24307        .await
24308        .expect("Should have called the go_to_definition handler");
24309
24310    assert_eq!(
24311        navigated,
24312        Navigated::No,
24313        "Should have navigated to references as a fallback after empty GoToDefinition response"
24314    );
24315    cx.assert_editor_state(&original_state);
24316    let editors = cx.update_workspace(|workspace, _, cx| {
24317        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
24318    });
24319    cx.update_editor(|_, _, _| {
24320        assert_eq!(
24321            editors.len(),
24322            1,
24323            "After unsuccessful fallback, no other editor should have been opened"
24324        );
24325    });
24326}
24327
24328#[gpui::test]
24329async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
24330    init_test(cx, |_| {});
24331    let mut cx = EditorLspTestContext::new_rust(
24332        lsp::ServerCapabilities {
24333            references_provider: Some(lsp::OneOf::Left(true)),
24334            ..lsp::ServerCapabilities::default()
24335        },
24336        cx,
24337    )
24338    .await;
24339
24340    cx.set_state(
24341        &r#"
24342        fn one() {
24343            let mut a = two();
24344        }
24345
24346        fn ˇtwo() {}"#
24347            .unindent(),
24348    );
24349    cx.lsp
24350        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
24351            Ok(Some(vec![
24352                lsp::Location {
24353                    uri: params.text_document_position.text_document.uri.clone(),
24354                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
24355                },
24356                lsp::Location {
24357                    uri: params.text_document_position.text_document.uri,
24358                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
24359                },
24360            ]))
24361        });
24362    let navigated = cx
24363        .update_editor(|editor, window, cx| {
24364            editor.find_all_references(&FindAllReferences::default(), window, cx)
24365        })
24366        .unwrap()
24367        .await
24368        .expect("Failed to navigate to references");
24369    assert_eq!(
24370        navigated,
24371        Navigated::Yes,
24372        "Should have navigated to references from the FindAllReferences response"
24373    );
24374    cx.assert_editor_state(
24375        &r#"fn one() {
24376            let mut a = two();
24377        }
24378
24379        fn ˇtwo() {}"#
24380            .unindent(),
24381    );
24382
24383    let editors = cx.update_workspace(|workspace, _, cx| {
24384        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
24385    });
24386    cx.update_editor(|_, _, _| {
24387        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
24388    });
24389
24390    cx.set_state(
24391        &r#"fn one() {
24392            let mut a = ˇtwo();
24393        }
24394
24395        fn two() {}"#
24396            .unindent(),
24397    );
24398    let navigated = cx
24399        .update_editor(|editor, window, cx| {
24400            editor.find_all_references(&FindAllReferences::default(), window, cx)
24401        })
24402        .unwrap()
24403        .await
24404        .expect("Failed to navigate to references");
24405    assert_eq!(
24406        navigated,
24407        Navigated::Yes,
24408        "Should have navigated to references from the FindAllReferences response"
24409    );
24410    cx.assert_editor_state(
24411        &r#"fn one() {
24412            let mut a = ˇtwo();
24413        }
24414
24415        fn two() {}"#
24416            .unindent(),
24417    );
24418    let editors = cx.update_workspace(|workspace, _, cx| {
24419        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
24420    });
24421    cx.update_editor(|_, _, _| {
24422        assert_eq!(
24423            editors.len(),
24424            2,
24425            "should have re-used the previous multibuffer"
24426        );
24427    });
24428
24429    cx.set_state(
24430        &r#"fn one() {
24431            let mut a = ˇtwo();
24432        }
24433        fn three() {}
24434        fn two() {}"#
24435            .unindent(),
24436    );
24437    cx.lsp
24438        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
24439            Ok(Some(vec![
24440                lsp::Location {
24441                    uri: params.text_document_position.text_document.uri.clone(),
24442                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
24443                },
24444                lsp::Location {
24445                    uri: params.text_document_position.text_document.uri,
24446                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
24447                },
24448            ]))
24449        });
24450    let navigated = cx
24451        .update_editor(|editor, window, cx| {
24452            editor.find_all_references(&FindAllReferences::default(), window, cx)
24453        })
24454        .unwrap()
24455        .await
24456        .expect("Failed to navigate to references");
24457    assert_eq!(
24458        navigated,
24459        Navigated::Yes,
24460        "Should have navigated to references from the FindAllReferences response"
24461    );
24462    cx.assert_editor_state(
24463        &r#"fn one() {
24464                let mut a = ˇtwo();
24465            }
24466            fn three() {}
24467            fn two() {}"#
24468            .unindent(),
24469    );
24470    let editors = cx.update_workspace(|workspace, _, cx| {
24471        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
24472    });
24473    cx.update_editor(|_, _, _| {
24474        assert_eq!(
24475            editors.len(),
24476            3,
24477            "should have used a new multibuffer as offsets changed"
24478        );
24479    });
24480}
24481#[gpui::test]
24482async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
24483    init_test(cx, |_| {});
24484
24485    let language = Arc::new(Language::new(
24486        LanguageConfig::default(),
24487        Some(tree_sitter_rust::LANGUAGE.into()),
24488    ));
24489
24490    let text = r#"
24491        #[cfg(test)]
24492        mod tests() {
24493            #[test]
24494            fn runnable_1() {
24495                let a = 1;
24496            }
24497
24498            #[test]
24499            fn runnable_2() {
24500                let a = 1;
24501                let b = 2;
24502            }
24503        }
24504    "#
24505    .unindent();
24506
24507    let fs = FakeFs::new(cx.executor());
24508    fs.insert_file("/file.rs", Default::default()).await;
24509
24510    let project = Project::test(fs, ["/a".as_ref()], cx).await;
24511    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
24512    let cx = &mut VisualTestContext::from_window(*window, cx);
24513    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
24514    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
24515
24516    let editor = cx.new_window_entity(|window, cx| {
24517        Editor::new(
24518            EditorMode::full(),
24519            multi_buffer,
24520            Some(project.clone()),
24521            window,
24522            cx,
24523        )
24524    });
24525
24526    editor.update_in(cx, |editor, window, cx| {
24527        let snapshot = editor.buffer().read(cx).snapshot(cx);
24528        editor.runnables.insert(
24529            buffer.read(cx).remote_id(),
24530            3,
24531            buffer.read(cx).version(),
24532            RunnableTasks {
24533                templates: Vec::new(),
24534                offset: snapshot.anchor_before(MultiBufferOffset(43)),
24535                column: 0,
24536                extra_variables: HashMap::default(),
24537                context_range: BufferOffset(43)..BufferOffset(85),
24538            },
24539        );
24540        editor.runnables.insert(
24541            buffer.read(cx).remote_id(),
24542            8,
24543            buffer.read(cx).version(),
24544            RunnableTasks {
24545                templates: Vec::new(),
24546                offset: snapshot.anchor_before(MultiBufferOffset(86)),
24547                column: 0,
24548                extra_variables: HashMap::default(),
24549                context_range: BufferOffset(86)..BufferOffset(191),
24550            },
24551        );
24552
24553        // Test finding task when cursor is inside function body
24554        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24555            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
24556        });
24557        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
24558        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
24559
24560        // Test finding task when cursor is on function name
24561        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24562            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
24563        });
24564        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
24565        assert_eq!(row, 8, "Should find task when cursor is on function name");
24566    });
24567}
24568
24569#[gpui::test]
24570async fn test_folding_buffers(cx: &mut TestAppContext) {
24571    init_test(cx, |_| {});
24572
24573    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
24574    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
24575    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
24576
24577    let fs = FakeFs::new(cx.executor());
24578    fs.insert_tree(
24579        path!("/a"),
24580        json!({
24581            "first.rs": sample_text_1,
24582            "second.rs": sample_text_2,
24583            "third.rs": sample_text_3,
24584        }),
24585    )
24586    .await;
24587    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24588    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
24589    let cx = &mut VisualTestContext::from_window(*window, cx);
24590    let worktree = project.update(cx, |project, cx| {
24591        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
24592        assert_eq!(worktrees.len(), 1);
24593        worktrees.pop().unwrap()
24594    });
24595    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
24596
24597    let buffer_1 = project
24598        .update(cx, |project, cx| {
24599            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
24600        })
24601        .await
24602        .unwrap();
24603    let buffer_2 = project
24604        .update(cx, |project, cx| {
24605            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
24606        })
24607        .await
24608        .unwrap();
24609    let buffer_3 = project
24610        .update(cx, |project, cx| {
24611            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
24612        })
24613        .await
24614        .unwrap();
24615
24616    let multi_buffer = cx.new(|cx| {
24617        let mut multi_buffer = MultiBuffer::new(ReadWrite);
24618        multi_buffer.set_excerpts_for_path(
24619            PathKey::sorted(0),
24620            buffer_1.clone(),
24621            [
24622                Point::new(0, 0)..Point::new(2, 0),
24623                Point::new(5, 0)..Point::new(6, 0),
24624                Point::new(9, 0)..Point::new(10, 4),
24625            ],
24626            0,
24627            cx,
24628        );
24629        multi_buffer.set_excerpts_for_path(
24630            PathKey::sorted(1),
24631            buffer_2.clone(),
24632            [
24633                Point::new(0, 0)..Point::new(2, 0),
24634                Point::new(5, 0)..Point::new(6, 0),
24635                Point::new(9, 0)..Point::new(10, 4),
24636            ],
24637            0,
24638            cx,
24639        );
24640        multi_buffer.set_excerpts_for_path(
24641            PathKey::sorted(2),
24642            buffer_3.clone(),
24643            [
24644                Point::new(0, 0)..Point::new(2, 0),
24645                Point::new(5, 0)..Point::new(6, 0),
24646                Point::new(9, 0)..Point::new(10, 4),
24647            ],
24648            0,
24649            cx,
24650        );
24651        multi_buffer
24652    });
24653    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
24654        Editor::new(
24655            EditorMode::full(),
24656            multi_buffer.clone(),
24657            Some(project.clone()),
24658            window,
24659            cx,
24660        )
24661    });
24662
24663    assert_eq!(
24664        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24665        "\n\naaaa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n1111\n2222\n\n5555",
24666    );
24667
24668    multi_buffer_editor.update(cx, |editor, cx| {
24669        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
24670    });
24671    assert_eq!(
24672        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24673        "\n\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n1111\n2222\n\n5555",
24674        "After folding the first buffer, its text should not be displayed"
24675    );
24676
24677    multi_buffer_editor.update(cx, |editor, cx| {
24678        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
24679    });
24680    assert_eq!(
24681        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24682        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n1111\n2222\n\n5555",
24683        "After folding the second buffer, its text should not be displayed"
24684    );
24685
24686    multi_buffer_editor.update(cx, |editor, cx| {
24687        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
24688    });
24689    assert_eq!(
24690        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24691        "\n\n\n\n\n",
24692        "After folding the third buffer, its text should not be displayed"
24693    );
24694
24695    // Emulate selection inside the fold logic, that should work
24696    multi_buffer_editor.update_in(cx, |editor, window, cx| {
24697        editor
24698            .snapshot(window, cx)
24699            .next_line_boundary(Point::new(0, 4));
24700    });
24701
24702    multi_buffer_editor.update(cx, |editor, cx| {
24703        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
24704    });
24705    assert_eq!(
24706        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24707        "\n\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n",
24708        "After unfolding the second buffer, its text should be displayed"
24709    );
24710
24711    // Typing inside of buffer 1 causes that buffer to be unfolded.
24712    multi_buffer_editor.update_in(cx, |editor, window, cx| {
24713        assert_eq!(
24714            multi_buffer
24715                .read(cx)
24716                .snapshot(cx)
24717                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
24718                .collect::<String>(),
24719            "bbbb"
24720        );
24721        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24722            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
24723        });
24724        editor.handle_input("B", window, cx);
24725    });
24726
24727    assert_eq!(
24728        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24729        "\n\naaaa\nBbbbb\ncccc\n\nffff\ngggg\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n",
24730        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
24731    );
24732
24733    multi_buffer_editor.update(cx, |editor, cx| {
24734        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
24735    });
24736    assert_eq!(
24737        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24738        "\n\naaaa\nBbbbb\ncccc\n\nffff\ngggg\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n1111\n2222\n\n5555",
24739        "After unfolding the all buffers, all original text should be displayed"
24740    );
24741}
24742
24743#[gpui::test]
24744async fn test_folded_buffers_cleared_on_excerpts_removed(cx: &mut TestAppContext) {
24745    init_test(cx, |_| {});
24746
24747    let fs = FakeFs::new(cx.executor());
24748    fs.insert_tree(
24749        path!("/root"),
24750        json!({
24751            "file_a.txt": "File A\nFile A\nFile A",
24752            "file_b.txt": "File B\nFile B\nFile B",
24753        }),
24754    )
24755    .await;
24756
24757    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
24758    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
24759    let cx = &mut VisualTestContext::from_window(*window, cx);
24760    let worktree = project.update(cx, |project, cx| {
24761        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
24762        assert_eq!(worktrees.len(), 1);
24763        worktrees.pop().unwrap()
24764    });
24765    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
24766
24767    let buffer_a = project
24768        .update(cx, |project, cx| {
24769            project.open_buffer((worktree_id, rel_path("file_a.txt")), cx)
24770        })
24771        .await
24772        .unwrap();
24773    let buffer_b = project
24774        .update(cx, |project, cx| {
24775            project.open_buffer((worktree_id, rel_path("file_b.txt")), cx)
24776        })
24777        .await
24778        .unwrap();
24779
24780    let multi_buffer = cx.new(|cx| {
24781        let mut multi_buffer = MultiBuffer::new(ReadWrite);
24782        let range_a = Point::new(0, 0)..Point::new(2, 4);
24783        let range_b = Point::new(0, 0)..Point::new(2, 4);
24784
24785        multi_buffer.set_excerpts_for_path(PathKey::sorted(0), buffer_a.clone(), [range_a], 0, cx);
24786        multi_buffer.set_excerpts_for_path(PathKey::sorted(1), buffer_b.clone(), [range_b], 0, cx);
24787        multi_buffer
24788    });
24789
24790    let editor = cx.new_window_entity(|window, cx| {
24791        Editor::new(
24792            EditorMode::full(),
24793            multi_buffer.clone(),
24794            Some(project.clone()),
24795            window,
24796            cx,
24797        )
24798    });
24799
24800    editor.update(cx, |editor, cx| {
24801        editor.fold_buffer(buffer_a.read(cx).remote_id(), cx);
24802    });
24803    assert!(editor.update(cx, |editor, cx| editor.has_any_buffer_folded(cx)));
24804
24805    // When the excerpts for `buffer_a` are removed, a
24806    // `multi_buffer::Event::ExcerptsRemoved` event is emitted, which should be
24807    // picked up by the editor and update the display map accordingly.
24808    multi_buffer.update(cx, |multi_buffer, cx| {
24809        multi_buffer.remove_excerpts_for_path(PathKey::sorted(0), cx)
24810    });
24811    assert!(!editor.update(cx, |editor, cx| editor.has_any_buffer_folded(cx)));
24812}
24813
24814#[gpui::test]
24815async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
24816    init_test(cx, |_| {});
24817
24818    let sample_text_1 = "1111\n2222\n3333".to_string();
24819    let sample_text_2 = "4444\n5555\n6666".to_string();
24820    let sample_text_3 = "7777\n8888\n9999".to_string();
24821
24822    let fs = FakeFs::new(cx.executor());
24823    fs.insert_tree(
24824        path!("/a"),
24825        json!({
24826            "first.rs": sample_text_1,
24827            "second.rs": sample_text_2,
24828            "third.rs": sample_text_3,
24829        }),
24830    )
24831    .await;
24832    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24833    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
24834    let cx = &mut VisualTestContext::from_window(*window, cx);
24835    let worktree = project.update(cx, |project, cx| {
24836        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
24837        assert_eq!(worktrees.len(), 1);
24838        worktrees.pop().unwrap()
24839    });
24840    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
24841
24842    let buffer_1 = project
24843        .update(cx, |project, cx| {
24844            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
24845        })
24846        .await
24847        .unwrap();
24848    let buffer_2 = project
24849        .update(cx, |project, cx| {
24850            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
24851        })
24852        .await
24853        .unwrap();
24854    let buffer_3 = project
24855        .update(cx, |project, cx| {
24856            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
24857        })
24858        .await
24859        .unwrap();
24860
24861    let multi_buffer = cx.new(|cx| {
24862        let mut multi_buffer = MultiBuffer::new(ReadWrite);
24863        multi_buffer.set_excerpts_for_path(
24864            PathKey::sorted(0),
24865            buffer_1.clone(),
24866            [Point::new(0, 0)..Point::new(3, 0)],
24867            0,
24868            cx,
24869        );
24870        multi_buffer.set_excerpts_for_path(
24871            PathKey::sorted(1),
24872            buffer_2.clone(),
24873            [Point::new(0, 0)..Point::new(3, 0)],
24874            0,
24875            cx,
24876        );
24877        multi_buffer.set_excerpts_for_path(
24878            PathKey::sorted(2),
24879            buffer_3.clone(),
24880            [Point::new(0, 0)..Point::new(3, 0)],
24881            0,
24882            cx,
24883        );
24884        multi_buffer
24885    });
24886
24887    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
24888        Editor::new(
24889            EditorMode::full(),
24890            multi_buffer,
24891            Some(project.clone()),
24892            window,
24893            cx,
24894        )
24895    });
24896
24897    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
24898    assert_eq!(
24899        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24900        full_text,
24901    );
24902
24903    multi_buffer_editor.update(cx, |editor, cx| {
24904        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
24905    });
24906    assert_eq!(
24907        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24908        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
24909        "After folding the first buffer, its text should not be displayed"
24910    );
24911
24912    multi_buffer_editor.update(cx, |editor, cx| {
24913        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
24914    });
24915
24916    assert_eq!(
24917        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24918        "\n\n\n\n\n\n7777\n8888\n9999",
24919        "After folding the second buffer, its text should not be displayed"
24920    );
24921
24922    multi_buffer_editor.update(cx, |editor, cx| {
24923        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
24924    });
24925    assert_eq!(
24926        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24927        "\n\n\n\n\n",
24928        "After folding the third buffer, its text should not be displayed"
24929    );
24930
24931    multi_buffer_editor.update(cx, |editor, cx| {
24932        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
24933    });
24934    assert_eq!(
24935        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24936        "\n\n\n\n4444\n5555\n6666\n\n",
24937        "After unfolding the second buffer, its text should be displayed"
24938    );
24939
24940    multi_buffer_editor.update(cx, |editor, cx| {
24941        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
24942    });
24943    assert_eq!(
24944        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24945        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
24946        "After unfolding the first buffer, its text should be displayed"
24947    );
24948
24949    multi_buffer_editor.update(cx, |editor, cx| {
24950        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
24951    });
24952    assert_eq!(
24953        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
24954        full_text,
24955        "After unfolding all buffers, all original text should be displayed"
24956    );
24957}
24958
24959#[gpui::test]
24960async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
24961    init_test(cx, |_| {});
24962
24963    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
24964
24965    let fs = FakeFs::new(cx.executor());
24966    fs.insert_tree(
24967        path!("/a"),
24968        json!({
24969            "main.rs": sample_text,
24970        }),
24971    )
24972    .await;
24973    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24974    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
24975    let cx = &mut VisualTestContext::from_window(*window, cx);
24976    let worktree = project.update(cx, |project, cx| {
24977        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
24978        assert_eq!(worktrees.len(), 1);
24979        worktrees.pop().unwrap()
24980    });
24981    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
24982
24983    let buffer_1 = project
24984        .update(cx, |project, cx| {
24985            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24986        })
24987        .await
24988        .unwrap();
24989
24990    let multi_buffer = cx.new(|cx| {
24991        let mut multi_buffer = MultiBuffer::new(ReadWrite);
24992        multi_buffer.set_excerpts_for_path(
24993            PathKey::sorted(0),
24994            buffer_1.clone(),
24995            [Point::new(0, 0)
24996                ..Point::new(
24997                    sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
24998                    0,
24999                )],
25000            0,
25001            cx,
25002        );
25003        multi_buffer
25004    });
25005    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
25006        Editor::new(
25007            EditorMode::full(),
25008            multi_buffer,
25009            Some(project.clone()),
25010            window,
25011            cx,
25012        )
25013    });
25014
25015    let selection_range = Point::new(1, 0)..Point::new(2, 0);
25016    multi_buffer_editor.update_in(cx, |editor, window, cx| {
25017        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25018        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
25019        editor.highlight_text(
25020            HighlightKey::Editor,
25021            vec![highlight_range.clone()],
25022            HighlightStyle::color(Hsla::green()),
25023            cx,
25024        );
25025        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25026            s.select_ranges(Some(highlight_range))
25027        });
25028    });
25029
25030    let full_text = format!("\n\n{sample_text}");
25031    assert_eq!(
25032        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25033        full_text,
25034    );
25035}
25036
25037#[gpui::test]
25038async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
25039    init_test(cx, |_| {});
25040    cx.update(|cx| {
25041        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
25042            "keymaps/default-linux.json",
25043            cx,
25044        )
25045        .unwrap();
25046        cx.bind_keys(default_key_bindings);
25047    });
25048
25049    let (editor, cx) = cx.add_window_view(|window, cx| {
25050        let multi_buffer = MultiBuffer::build_multi(
25051            [
25052                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
25053                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
25054                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
25055                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
25056            ],
25057            cx,
25058        );
25059        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
25060
25061        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
25062        // fold all but the second buffer, so that we test navigating between two
25063        // adjacent folded buffers, as well as folded buffers at the start and
25064        // end the multibuffer
25065        editor.fold_buffer(buffer_ids[0], cx);
25066        editor.fold_buffer(buffer_ids[2], cx);
25067        editor.fold_buffer(buffer_ids[3], cx);
25068
25069        editor
25070    });
25071    cx.simulate_resize(size(px(1000.), px(1000.)));
25072
25073    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
25074    cx.assert_excerpts_with_selections(indoc! {"
25075        [EXCERPT]
25076        ˇ[FOLDED]
25077        [EXCERPT]
25078        a1
25079        b1
25080        [EXCERPT]
25081        [FOLDED]
25082        [EXCERPT]
25083        [FOLDED]
25084        "
25085    });
25086    cx.simulate_keystroke("down");
25087    cx.assert_excerpts_with_selections(indoc! {"
25088        [EXCERPT]
25089        [FOLDED]
25090        [EXCERPT]
25091        ˇa1
25092        b1
25093        [EXCERPT]
25094        [FOLDED]
25095        [EXCERPT]
25096        [FOLDED]
25097        "
25098    });
25099    cx.simulate_keystroke("down");
25100    cx.assert_excerpts_with_selections(indoc! {"
25101        [EXCERPT]
25102        [FOLDED]
25103        [EXCERPT]
25104        a1
25105        ˇb1
25106        [EXCERPT]
25107        [FOLDED]
25108        [EXCERPT]
25109        [FOLDED]
25110        "
25111    });
25112    cx.simulate_keystroke("down");
25113    cx.assert_excerpts_with_selections(indoc! {"
25114        [EXCERPT]
25115        [FOLDED]
25116        [EXCERPT]
25117        a1
25118        b1
25119        ˇ[EXCERPT]
25120        [FOLDED]
25121        [EXCERPT]
25122        [FOLDED]
25123        "
25124    });
25125    cx.simulate_keystroke("down");
25126    cx.assert_excerpts_with_selections(indoc! {"
25127        [EXCERPT]
25128        [FOLDED]
25129        [EXCERPT]
25130        a1
25131        b1
25132        [EXCERPT]
25133        ˇ[FOLDED]
25134        [EXCERPT]
25135        [FOLDED]
25136        "
25137    });
25138    for _ in 0..5 {
25139        cx.simulate_keystroke("down");
25140        cx.assert_excerpts_with_selections(indoc! {"
25141            [EXCERPT]
25142            [FOLDED]
25143            [EXCERPT]
25144            a1
25145            b1
25146            [EXCERPT]
25147            [FOLDED]
25148            [EXCERPT]
25149            ˇ[FOLDED]
25150            "
25151        });
25152    }
25153
25154    cx.simulate_keystroke("up");
25155    cx.assert_excerpts_with_selections(indoc! {"
25156        [EXCERPT]
25157        [FOLDED]
25158        [EXCERPT]
25159        a1
25160        b1
25161        [EXCERPT]
25162        ˇ[FOLDED]
25163        [EXCERPT]
25164        [FOLDED]
25165        "
25166    });
25167    cx.simulate_keystroke("up");
25168    cx.assert_excerpts_with_selections(indoc! {"
25169        [EXCERPT]
25170        [FOLDED]
25171        [EXCERPT]
25172        a1
25173        b1
25174        ˇ[EXCERPT]
25175        [FOLDED]
25176        [EXCERPT]
25177        [FOLDED]
25178        "
25179    });
25180    cx.simulate_keystroke("up");
25181    cx.assert_excerpts_with_selections(indoc! {"
25182        [EXCERPT]
25183        [FOLDED]
25184        [EXCERPT]
25185        a1
25186        ˇb1
25187        [EXCERPT]
25188        [FOLDED]
25189        [EXCERPT]
25190        [FOLDED]
25191        "
25192    });
25193    cx.simulate_keystroke("up");
25194    cx.assert_excerpts_with_selections(indoc! {"
25195        [EXCERPT]
25196        [FOLDED]
25197        [EXCERPT]
25198        ˇa1
25199        b1
25200        [EXCERPT]
25201        [FOLDED]
25202        [EXCERPT]
25203        [FOLDED]
25204        "
25205    });
25206    for _ in 0..5 {
25207        cx.simulate_keystroke("up");
25208        cx.assert_excerpts_with_selections(indoc! {"
25209            [EXCERPT]
25210            ˇ[FOLDED]
25211            [EXCERPT]
25212            a1
25213            b1
25214            [EXCERPT]
25215            [FOLDED]
25216            [EXCERPT]
25217            [FOLDED]
25218            "
25219        });
25220    }
25221}
25222
25223#[gpui::test]
25224async fn test_edit_prediction_text(cx: &mut TestAppContext) {
25225    init_test(cx, |_| {});
25226
25227    // Simple insertion
25228    assert_highlighted_edits(
25229        "Hello, world!",
25230        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
25231        true,
25232        cx,
25233        &|highlighted_edits, cx| {
25234            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
25235            assert_eq!(highlighted_edits.highlights.len(), 1);
25236            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
25237            assert_eq!(
25238                highlighted_edits.highlights[0].1.background_color,
25239                Some(cx.theme().status().created_background)
25240            );
25241        },
25242    )
25243    .await;
25244
25245    // Replacement
25246    assert_highlighted_edits(
25247        "This is a test.",
25248        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
25249        false,
25250        cx,
25251        &|highlighted_edits, cx| {
25252            assert_eq!(highlighted_edits.text, "That is a test.");
25253            assert_eq!(highlighted_edits.highlights.len(), 1);
25254            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
25255            assert_eq!(
25256                highlighted_edits.highlights[0].1.background_color,
25257                Some(cx.theme().status().created_background)
25258            );
25259        },
25260    )
25261    .await;
25262
25263    // Multiple edits
25264    assert_highlighted_edits(
25265        "Hello, world!",
25266        vec![
25267            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
25268            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
25269        ],
25270        false,
25271        cx,
25272        &|highlighted_edits, cx| {
25273            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
25274            assert_eq!(highlighted_edits.highlights.len(), 2);
25275            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
25276            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
25277            assert_eq!(
25278                highlighted_edits.highlights[0].1.background_color,
25279                Some(cx.theme().status().created_background)
25280            );
25281            assert_eq!(
25282                highlighted_edits.highlights[1].1.background_color,
25283                Some(cx.theme().status().created_background)
25284            );
25285        },
25286    )
25287    .await;
25288
25289    // Multiple lines with edits
25290    assert_highlighted_edits(
25291        "First line\nSecond line\nThird line\nFourth line",
25292        vec![
25293            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
25294            (
25295                Point::new(2, 0)..Point::new(2, 10),
25296                "New third line".to_string(),
25297            ),
25298            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
25299        ],
25300        false,
25301        cx,
25302        &|highlighted_edits, cx| {
25303            assert_eq!(
25304                highlighted_edits.text,
25305                "Second modified\nNew third line\nFourth updated line"
25306            );
25307            assert_eq!(highlighted_edits.highlights.len(), 3);
25308            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
25309            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
25310            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
25311            for highlight in &highlighted_edits.highlights {
25312                assert_eq!(
25313                    highlight.1.background_color,
25314                    Some(cx.theme().status().created_background)
25315                );
25316            }
25317        },
25318    )
25319    .await;
25320}
25321
25322#[gpui::test]
25323async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
25324    init_test(cx, |_| {});
25325
25326    // Deletion
25327    assert_highlighted_edits(
25328        "Hello, world!",
25329        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
25330        true,
25331        cx,
25332        &|highlighted_edits, cx| {
25333            assert_eq!(highlighted_edits.text, "Hello, world!");
25334            assert_eq!(highlighted_edits.highlights.len(), 1);
25335            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
25336            assert_eq!(
25337                highlighted_edits.highlights[0].1.background_color,
25338                Some(cx.theme().status().deleted_background)
25339            );
25340        },
25341    )
25342    .await;
25343
25344    // Insertion
25345    assert_highlighted_edits(
25346        "Hello, world!",
25347        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
25348        true,
25349        cx,
25350        &|highlighted_edits, cx| {
25351            assert_eq!(highlighted_edits.highlights.len(), 1);
25352            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
25353            assert_eq!(
25354                highlighted_edits.highlights[0].1.background_color,
25355                Some(cx.theme().status().created_background)
25356            );
25357        },
25358    )
25359    .await;
25360}
25361
25362async fn assert_highlighted_edits(
25363    text: &str,
25364    edits: Vec<(Range<Point>, String)>,
25365    include_deletions: bool,
25366    cx: &mut TestAppContext,
25367    assertion_fn: &dyn Fn(HighlightedText, &App),
25368) {
25369    let window = cx.add_window(|window, cx| {
25370        let buffer = MultiBuffer::build_simple(text, cx);
25371        Editor::new(EditorMode::full(), buffer, None, window, cx)
25372    });
25373    let cx = &mut VisualTestContext::from_window(*window, cx);
25374
25375    let (buffer, snapshot) = window
25376        .update(cx, |editor, _window, cx| {
25377            (
25378                editor.buffer().clone(),
25379                editor.buffer().read(cx).snapshot(cx),
25380            )
25381        })
25382        .unwrap();
25383
25384    let edits = edits
25385        .into_iter()
25386        .map(|(range, edit)| {
25387            (
25388                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
25389                edit,
25390            )
25391        })
25392        .collect::<Vec<_>>();
25393
25394    let text_anchor_edits = edits
25395        .clone()
25396        .into_iter()
25397        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
25398        .collect::<Vec<_>>();
25399
25400    let edit_preview = window
25401        .update(cx, |_, _window, cx| {
25402            buffer
25403                .read(cx)
25404                .as_singleton()
25405                .unwrap()
25406                .read(cx)
25407                .preview_edits(text_anchor_edits.into(), cx)
25408        })
25409        .unwrap()
25410        .await;
25411
25412    cx.update(|_window, cx| {
25413        let highlighted_edits = edit_prediction_edit_text(
25414            snapshot.as_singleton().unwrap().2,
25415            &edits,
25416            &edit_preview,
25417            include_deletions,
25418            cx,
25419        );
25420        assertion_fn(highlighted_edits, cx)
25421    });
25422}
25423
25424#[track_caller]
25425fn assert_breakpoint(
25426    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
25427    path: &Arc<Path>,
25428    expected: Vec<(u32, Breakpoint)>,
25429) {
25430    if expected.is_empty() {
25431        assert!(!breakpoints.contains_key(path), "{}", path.display());
25432    } else {
25433        let mut breakpoint = breakpoints
25434            .get(path)
25435            .unwrap()
25436            .iter()
25437            .map(|breakpoint| {
25438                (
25439                    breakpoint.row,
25440                    Breakpoint {
25441                        message: breakpoint.message.clone(),
25442                        state: breakpoint.state,
25443                        condition: breakpoint.condition.clone(),
25444                        hit_condition: breakpoint.hit_condition.clone(),
25445                    },
25446                )
25447            })
25448            .collect::<Vec<_>>();
25449
25450        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
25451
25452        assert_eq!(expected, breakpoint);
25453    }
25454}
25455
25456fn add_log_breakpoint_at_cursor(
25457    editor: &mut Editor,
25458    log_message: &str,
25459    window: &mut Window,
25460    cx: &mut Context<Editor>,
25461) {
25462    let (anchor, bp) = editor
25463        .breakpoints_at_cursors(window, cx)
25464        .first()
25465        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
25466        .unwrap_or_else(|| {
25467            let snapshot = editor.snapshot(window, cx);
25468            let cursor_position: Point =
25469                editor.selections.newest(&snapshot.display_snapshot).head();
25470
25471            let breakpoint_position = snapshot
25472                .buffer_snapshot()
25473                .anchor_before(Point::new(cursor_position.row, 0));
25474
25475            (breakpoint_position, Breakpoint::new_log(log_message))
25476        });
25477
25478    editor.edit_breakpoint_at_anchor(
25479        anchor,
25480        bp,
25481        BreakpointEditAction::EditLogMessage(log_message.into()),
25482        cx,
25483    );
25484}
25485
25486#[gpui::test]
25487async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
25488    init_test(cx, |_| {});
25489
25490    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
25491    let fs = FakeFs::new(cx.executor());
25492    fs.insert_tree(
25493        path!("/a"),
25494        json!({
25495            "main.rs": sample_text,
25496        }),
25497    )
25498    .await;
25499    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25500    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25501    let cx = &mut VisualTestContext::from_window(*window, cx);
25502
25503    let fs = FakeFs::new(cx.executor());
25504    fs.insert_tree(
25505        path!("/a"),
25506        json!({
25507            "main.rs": sample_text,
25508        }),
25509    )
25510    .await;
25511    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25512    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25513    let workspace = window
25514        .read_with(cx, |mw, _| mw.workspace().clone())
25515        .unwrap();
25516    let cx = &mut VisualTestContext::from_window(*window, cx);
25517    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
25518        workspace.project().update(cx, |project, cx| {
25519            project.worktrees(cx).next().unwrap().read(cx).id()
25520        })
25521    });
25522
25523    let buffer = project
25524        .update(cx, |project, cx| {
25525            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
25526        })
25527        .await
25528        .unwrap();
25529
25530    let (editor, cx) = cx.add_window_view(|window, cx| {
25531        Editor::new(
25532            EditorMode::full(),
25533            MultiBuffer::build_from_buffer(buffer, cx),
25534            Some(project.clone()),
25535            window,
25536            cx,
25537        )
25538    });
25539
25540    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
25541    let abs_path = project.read_with(cx, |project, cx| {
25542        project
25543            .absolute_path(&project_path, cx)
25544            .map(Arc::from)
25545            .unwrap()
25546    });
25547
25548    // assert we can add breakpoint on the first line
25549    editor.update_in(cx, |editor, window, cx| {
25550        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25551        editor.move_to_end(&MoveToEnd, window, cx);
25552        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25553    });
25554
25555    let breakpoints = editor.update(cx, |editor, cx| {
25556        editor
25557            .breakpoint_store()
25558            .as_ref()
25559            .unwrap()
25560            .read(cx)
25561            .all_source_breakpoints(cx)
25562    });
25563
25564    assert_eq!(1, breakpoints.len());
25565    assert_breakpoint(
25566        &breakpoints,
25567        &abs_path,
25568        vec![
25569            (0, Breakpoint::new_standard()),
25570            (3, Breakpoint::new_standard()),
25571        ],
25572    );
25573
25574    editor.update_in(cx, |editor, window, cx| {
25575        editor.move_to_beginning(&MoveToBeginning, window, cx);
25576        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25577    });
25578
25579    let breakpoints = editor.update(cx, |editor, cx| {
25580        editor
25581            .breakpoint_store()
25582            .as_ref()
25583            .unwrap()
25584            .read(cx)
25585            .all_source_breakpoints(cx)
25586    });
25587
25588    assert_eq!(1, breakpoints.len());
25589    assert_breakpoint(
25590        &breakpoints,
25591        &abs_path,
25592        vec![(3, Breakpoint::new_standard())],
25593    );
25594
25595    editor.update_in(cx, |editor, window, cx| {
25596        editor.move_to_end(&MoveToEnd, window, cx);
25597        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25598    });
25599
25600    let breakpoints = editor.update(cx, |editor, cx| {
25601        editor
25602            .breakpoint_store()
25603            .as_ref()
25604            .unwrap()
25605            .read(cx)
25606            .all_source_breakpoints(cx)
25607    });
25608
25609    assert_eq!(0, breakpoints.len());
25610    assert_breakpoint(&breakpoints, &abs_path, vec![]);
25611}
25612
25613#[gpui::test]
25614async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
25615    init_test(cx, |_| {});
25616
25617    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
25618
25619    let fs = FakeFs::new(cx.executor());
25620    fs.insert_tree(
25621        path!("/a"),
25622        json!({
25623            "main.rs": sample_text,
25624        }),
25625    )
25626    .await;
25627    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25628    let (multi_workspace, cx) =
25629        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25630    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
25631
25632    let worktree_id = workspace.update(cx, |workspace, cx| {
25633        workspace.project().update(cx, |project, cx| {
25634            project.worktrees(cx).next().unwrap().read(cx).id()
25635        })
25636    });
25637
25638    let buffer = project
25639        .update(cx, |project, cx| {
25640            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
25641        })
25642        .await
25643        .unwrap();
25644
25645    let (editor, cx) = cx.add_window_view(|window, cx| {
25646        Editor::new(
25647            EditorMode::full(),
25648            MultiBuffer::build_from_buffer(buffer, cx),
25649            Some(project.clone()),
25650            window,
25651            cx,
25652        )
25653    });
25654
25655    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
25656    let abs_path = project.read_with(cx, |project, cx| {
25657        project
25658            .absolute_path(&project_path, cx)
25659            .map(Arc::from)
25660            .unwrap()
25661    });
25662
25663    editor.update_in(cx, |editor, window, cx| {
25664        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
25665    });
25666
25667    let breakpoints = editor.update(cx, |editor, cx| {
25668        editor
25669            .breakpoint_store()
25670            .as_ref()
25671            .unwrap()
25672            .read(cx)
25673            .all_source_breakpoints(cx)
25674    });
25675
25676    assert_breakpoint(
25677        &breakpoints,
25678        &abs_path,
25679        vec![(0, Breakpoint::new_log("hello world"))],
25680    );
25681
25682    // Removing a log message from a log breakpoint should remove it
25683    editor.update_in(cx, |editor, window, cx| {
25684        add_log_breakpoint_at_cursor(editor, "", window, cx);
25685    });
25686
25687    let breakpoints = editor.update(cx, |editor, cx| {
25688        editor
25689            .breakpoint_store()
25690            .as_ref()
25691            .unwrap()
25692            .read(cx)
25693            .all_source_breakpoints(cx)
25694    });
25695
25696    assert_breakpoint(&breakpoints, &abs_path, vec![]);
25697
25698    editor.update_in(cx, |editor, window, cx| {
25699        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25700        editor.move_to_end(&MoveToEnd, window, cx);
25701        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25702        // Not adding a log message to a standard breakpoint shouldn't remove it
25703        add_log_breakpoint_at_cursor(editor, "", window, cx);
25704    });
25705
25706    let breakpoints = editor.update(cx, |editor, cx| {
25707        editor
25708            .breakpoint_store()
25709            .as_ref()
25710            .unwrap()
25711            .read(cx)
25712            .all_source_breakpoints(cx)
25713    });
25714
25715    assert_breakpoint(
25716        &breakpoints,
25717        &abs_path,
25718        vec![
25719            (0, Breakpoint::new_standard()),
25720            (3, Breakpoint::new_standard()),
25721        ],
25722    );
25723
25724    editor.update_in(cx, |editor, window, cx| {
25725        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
25726    });
25727
25728    let breakpoints = editor.update(cx, |editor, cx| {
25729        editor
25730            .breakpoint_store()
25731            .as_ref()
25732            .unwrap()
25733            .read(cx)
25734            .all_source_breakpoints(cx)
25735    });
25736
25737    assert_breakpoint(
25738        &breakpoints,
25739        &abs_path,
25740        vec![
25741            (0, Breakpoint::new_standard()),
25742            (3, Breakpoint::new_log("hello world")),
25743        ],
25744    );
25745
25746    editor.update_in(cx, |editor, window, cx| {
25747        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
25748    });
25749
25750    let breakpoints = editor.update(cx, |editor, cx| {
25751        editor
25752            .breakpoint_store()
25753            .as_ref()
25754            .unwrap()
25755            .read(cx)
25756            .all_source_breakpoints(cx)
25757    });
25758
25759    assert_breakpoint(
25760        &breakpoints,
25761        &abs_path,
25762        vec![
25763            (0, Breakpoint::new_standard()),
25764            (3, Breakpoint::new_log("hello Earth!!")),
25765        ],
25766    );
25767}
25768
25769/// This also tests that Editor::breakpoint_at_cursor_head is working properly
25770/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
25771/// or when breakpoints were placed out of order. This tests for a regression too
25772#[gpui::test]
25773async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
25774    init_test(cx, |_| {});
25775
25776    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
25777    let fs = FakeFs::new(cx.executor());
25778    fs.insert_tree(
25779        path!("/a"),
25780        json!({
25781            "main.rs": sample_text,
25782        }),
25783    )
25784    .await;
25785    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25786    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25787    let cx = &mut VisualTestContext::from_window(*window, cx);
25788
25789    let fs = FakeFs::new(cx.executor());
25790    fs.insert_tree(
25791        path!("/a"),
25792        json!({
25793            "main.rs": sample_text,
25794        }),
25795    )
25796    .await;
25797    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25798    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25799    let workspace = window
25800        .read_with(cx, |mw, _| mw.workspace().clone())
25801        .unwrap();
25802    let cx = &mut VisualTestContext::from_window(*window, cx);
25803    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
25804        workspace.project().update(cx, |project, cx| {
25805            project.worktrees(cx).next().unwrap().read(cx).id()
25806        })
25807    });
25808
25809    let buffer = project
25810        .update(cx, |project, cx| {
25811            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
25812        })
25813        .await
25814        .unwrap();
25815
25816    let (editor, cx) = cx.add_window_view(|window, cx| {
25817        Editor::new(
25818            EditorMode::full(),
25819            MultiBuffer::build_from_buffer(buffer, cx),
25820            Some(project.clone()),
25821            window,
25822            cx,
25823        )
25824    });
25825
25826    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
25827    let abs_path = project.read_with(cx, |project, cx| {
25828        project
25829            .absolute_path(&project_path, cx)
25830            .map(Arc::from)
25831            .unwrap()
25832    });
25833
25834    // assert we can add breakpoint on the first line
25835    editor.update_in(cx, |editor, window, cx| {
25836        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25837        editor.move_to_end(&MoveToEnd, window, cx);
25838        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25839        editor.move_up(&MoveUp, window, cx);
25840        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25841    });
25842
25843    let breakpoints = editor.update(cx, |editor, cx| {
25844        editor
25845            .breakpoint_store()
25846            .as_ref()
25847            .unwrap()
25848            .read(cx)
25849            .all_source_breakpoints(cx)
25850    });
25851
25852    assert_eq!(1, breakpoints.len());
25853    assert_breakpoint(
25854        &breakpoints,
25855        &abs_path,
25856        vec![
25857            (0, Breakpoint::new_standard()),
25858            (2, Breakpoint::new_standard()),
25859            (3, Breakpoint::new_standard()),
25860        ],
25861    );
25862
25863    editor.update_in(cx, |editor, window, cx| {
25864        editor.move_to_beginning(&MoveToBeginning, window, cx);
25865        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
25866        editor.move_to_end(&MoveToEnd, window, cx);
25867        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
25868        // Disabling a breakpoint that doesn't exist should do nothing
25869        editor.move_up(&MoveUp, window, cx);
25870        editor.move_up(&MoveUp, window, cx);
25871        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
25872    });
25873
25874    let breakpoints = editor.update(cx, |editor, cx| {
25875        editor
25876            .breakpoint_store()
25877            .as_ref()
25878            .unwrap()
25879            .read(cx)
25880            .all_source_breakpoints(cx)
25881    });
25882
25883    let disable_breakpoint = {
25884        let mut bp = Breakpoint::new_standard();
25885        bp.state = BreakpointState::Disabled;
25886        bp
25887    };
25888
25889    assert_eq!(1, breakpoints.len());
25890    assert_breakpoint(
25891        &breakpoints,
25892        &abs_path,
25893        vec![
25894            (0, disable_breakpoint.clone()),
25895            (2, Breakpoint::new_standard()),
25896            (3, disable_breakpoint.clone()),
25897        ],
25898    );
25899
25900    editor.update_in(cx, |editor, window, cx| {
25901        editor.move_to_beginning(&MoveToBeginning, window, cx);
25902        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
25903        editor.move_to_end(&MoveToEnd, window, cx);
25904        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
25905        editor.move_up(&MoveUp, window, cx);
25906        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
25907    });
25908
25909    let breakpoints = editor.update(cx, |editor, cx| {
25910        editor
25911            .breakpoint_store()
25912            .as_ref()
25913            .unwrap()
25914            .read(cx)
25915            .all_source_breakpoints(cx)
25916    });
25917
25918    assert_eq!(1, breakpoints.len());
25919    assert_breakpoint(
25920        &breakpoints,
25921        &abs_path,
25922        vec![
25923            (0, Breakpoint::new_standard()),
25924            (2, disable_breakpoint),
25925            (3, Breakpoint::new_standard()),
25926        ],
25927    );
25928}
25929
25930#[gpui::test]
25931async fn test_breakpoint_phantom_indicator_collision_on_toggle(cx: &mut TestAppContext) {
25932    init_test(cx, |_| {});
25933
25934    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
25935    let fs = FakeFs::new(cx.executor());
25936    fs.insert_tree(
25937        path!("/a"),
25938        json!({
25939            "main.rs": sample_text,
25940        }),
25941    )
25942    .await;
25943    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25944    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25945    let workspace = window
25946        .read_with(cx, |mw, _| mw.workspace().clone())
25947        .unwrap();
25948    let cx = &mut VisualTestContext::from_window(*window, cx);
25949    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
25950        workspace.project().update(cx, |project, cx| {
25951            project.worktrees(cx).next().unwrap().read(cx).id()
25952        })
25953    });
25954
25955    let buffer = project
25956        .update(cx, |project, cx| {
25957            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
25958        })
25959        .await
25960        .unwrap();
25961
25962    let (editor, cx) = cx.add_window_view(|window, cx| {
25963        Editor::new(
25964            EditorMode::full(),
25965            MultiBuffer::build_from_buffer(buffer, cx),
25966            Some(project.clone()),
25967            window,
25968            cx,
25969        )
25970    });
25971
25972    // Simulate hovering over row 0 with no existing breakpoint.
25973    editor.update(cx, |editor, _cx| {
25974        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
25975            display_row: DisplayRow(0),
25976            is_active: true,
25977            collides_with_existing_breakpoint: false,
25978        });
25979    });
25980
25981    // Toggle breakpoint on the same row (row 0) — collision should flip to true.
25982    editor.update_in(cx, |editor, window, cx| {
25983        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25984    });
25985    editor.update(cx, |editor, _cx| {
25986        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
25987        assert!(
25988            indicator.collides_with_existing_breakpoint,
25989            "Adding a breakpoint on the hovered row should set collision to true"
25990        );
25991    });
25992
25993    // Toggle again on the same row — breakpoint is removed, collision should flip back to false.
25994    editor.update_in(cx, |editor, window, cx| {
25995        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25996    });
25997    editor.update(cx, |editor, _cx| {
25998        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
25999        assert!(
26000            !indicator.collides_with_existing_breakpoint,
26001            "Removing a breakpoint on the hovered row should set collision to false"
26002        );
26003    });
26004
26005    // Now move cursor to row 2 while phantom indicator stays on row 0.
26006    editor.update_in(cx, |editor, window, cx| {
26007        editor.move_down(&MoveDown, window, cx);
26008        editor.move_down(&MoveDown, window, cx);
26009    });
26010
26011    // Ensure phantom indicator is still on row 0, not colliding.
26012    editor.update(cx, |editor, _cx| {
26013        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
26014            display_row: DisplayRow(0),
26015            is_active: true,
26016            collides_with_existing_breakpoint: false,
26017        });
26018    });
26019
26020    // Toggle breakpoint on row 2 (cursor row) — phantom on row 0 should NOT be affected.
26021    editor.update_in(cx, |editor, window, cx| {
26022        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
26023    });
26024    editor.update(cx, |editor, _cx| {
26025        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
26026        assert!(
26027            !indicator.collides_with_existing_breakpoint,
26028            "Toggling a breakpoint on a different row should not affect the phantom indicator"
26029        );
26030    });
26031}
26032
26033#[gpui::test]
26034async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
26035    init_test(cx, |_| {});
26036    let capabilities = lsp::ServerCapabilities {
26037        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
26038            prepare_provider: Some(true),
26039            work_done_progress_options: Default::default(),
26040        })),
26041        ..Default::default()
26042    };
26043    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
26044
26045    cx.set_state(indoc! {"
26046        struct Fˇoo {}
26047    "});
26048
26049    cx.update_editor(|editor, _, cx| {
26050        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
26051        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
26052        editor.highlight_background(
26053            HighlightKey::DocumentHighlightRead,
26054            &[highlight_range],
26055            |_, theme| theme.colors().editor_document_highlight_read_background,
26056            cx,
26057        );
26058    });
26059
26060    let mut prepare_rename_handler = cx
26061        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
26062            move |_, _, _| async move {
26063                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
26064                    start: lsp::Position {
26065                        line: 0,
26066                        character: 7,
26067                    },
26068                    end: lsp::Position {
26069                        line: 0,
26070                        character: 10,
26071                    },
26072                })))
26073            },
26074        );
26075    let prepare_rename_task = cx
26076        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
26077        .expect("Prepare rename was not started");
26078    prepare_rename_handler.next().await.unwrap();
26079    prepare_rename_task.await.expect("Prepare rename failed");
26080
26081    let mut rename_handler =
26082        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
26083            let edit = lsp::TextEdit {
26084                range: lsp::Range {
26085                    start: lsp::Position {
26086                        line: 0,
26087                        character: 7,
26088                    },
26089                    end: lsp::Position {
26090                        line: 0,
26091                        character: 10,
26092                    },
26093                },
26094                new_text: "FooRenamed".to_string(),
26095            };
26096            Ok(Some(lsp::WorkspaceEdit::new(
26097                // Specify the same edit twice
26098                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
26099            )))
26100        });
26101    let rename_task = cx
26102        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
26103        .expect("Confirm rename was not started");
26104    rename_handler.next().await.unwrap();
26105    rename_task.await.expect("Confirm rename failed");
26106    cx.run_until_parked();
26107
26108    // Despite two edits, only one is actually applied as those are identical
26109    cx.assert_editor_state(indoc! {"
26110        struct FooRenamedˇ {}
26111    "});
26112}
26113
26114#[gpui::test]
26115async fn test_rename_without_prepare(cx: &mut TestAppContext) {
26116    init_test(cx, |_| {});
26117    // These capabilities indicate that the server does not support prepare rename.
26118    let capabilities = lsp::ServerCapabilities {
26119        rename_provider: Some(lsp::OneOf::Left(true)),
26120        ..Default::default()
26121    };
26122    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
26123
26124    cx.set_state(indoc! {"
26125        struct Fˇoo {}
26126    "});
26127
26128    cx.update_editor(|editor, _window, cx| {
26129        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
26130        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
26131        editor.highlight_background(
26132            HighlightKey::DocumentHighlightRead,
26133            &[highlight_range],
26134            |_, theme| theme.colors().editor_document_highlight_read_background,
26135            cx,
26136        );
26137    });
26138
26139    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
26140        .expect("Prepare rename was not started")
26141        .await
26142        .expect("Prepare rename failed");
26143
26144    let mut rename_handler =
26145        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
26146            let edit = lsp::TextEdit {
26147                range: lsp::Range {
26148                    start: lsp::Position {
26149                        line: 0,
26150                        character: 7,
26151                    },
26152                    end: lsp::Position {
26153                        line: 0,
26154                        character: 10,
26155                    },
26156                },
26157                new_text: "FooRenamed".to_string(),
26158            };
26159            Ok(Some(lsp::WorkspaceEdit::new(
26160                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
26161            )))
26162        });
26163    let rename_task = cx
26164        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
26165        .expect("Confirm rename was not started");
26166    rename_handler.next().await.unwrap();
26167    rename_task.await.expect("Confirm rename failed");
26168    cx.run_until_parked();
26169
26170    // Correct range is renamed, as `surrounding_word` is used to find it.
26171    cx.assert_editor_state(indoc! {"
26172        struct FooRenamedˇ {}
26173    "});
26174}
26175
26176#[gpui::test]
26177async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
26178    init_test(cx, |_| {});
26179    let mut cx = EditorTestContext::new(cx).await;
26180
26181    let language = Arc::new(
26182        Language::new(
26183            LanguageConfig::default(),
26184            Some(tree_sitter_html::LANGUAGE.into()),
26185        )
26186        .with_brackets_query(
26187            r#"
26188            ("<" @open "/>" @close)
26189            ("</" @open ">" @close)
26190            ("<" @open ">" @close)
26191            ("\"" @open "\"" @close)
26192            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
26193        "#,
26194        )
26195        .unwrap(),
26196    );
26197    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26198
26199    cx.set_state(indoc! {"
26200        <span>ˇ</span>
26201    "});
26202    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
26203    cx.assert_editor_state(indoc! {"
26204        <span>
26205        ˇ
26206        </span>
26207    "});
26208
26209    cx.set_state(indoc! {"
26210        <span><span></span>ˇ</span>
26211    "});
26212    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
26213    cx.assert_editor_state(indoc! {"
26214        <span><span></span>
26215        ˇ</span>
26216    "});
26217
26218    cx.set_state(indoc! {"
26219        <span>ˇ
26220        </span>
26221    "});
26222    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
26223    cx.assert_editor_state(indoc! {"
26224        <span>
26225        ˇ
26226        </span>
26227    "});
26228}
26229
26230#[gpui::test(iterations = 10)]
26231async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
26232    init_test(cx, |_| {});
26233
26234    let fs = FakeFs::new(cx.executor());
26235    fs.insert_tree(
26236        path!("/dir"),
26237        json!({
26238            "a.ts": "a",
26239        }),
26240    )
26241    .await;
26242
26243    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
26244    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26245    let workspace = window
26246        .read_with(cx, |mw, _| mw.workspace().clone())
26247        .unwrap();
26248    let cx = &mut VisualTestContext::from_window(*window, cx);
26249
26250    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26251    language_registry.add(Arc::new(Language::new(
26252        LanguageConfig {
26253            name: "TypeScript".into(),
26254            matcher: LanguageMatcher {
26255                path_suffixes: vec!["ts".to_string()],
26256                ..Default::default()
26257            },
26258            ..Default::default()
26259        },
26260        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
26261    )));
26262    let mut fake_language_servers = language_registry.register_fake_lsp(
26263        "TypeScript",
26264        FakeLspAdapter {
26265            capabilities: lsp::ServerCapabilities {
26266                code_lens_provider: Some(lsp::CodeLensOptions {
26267                    resolve_provider: Some(true),
26268                }),
26269                execute_command_provider: Some(lsp::ExecuteCommandOptions {
26270                    commands: vec!["_the/command".to_string()],
26271                    ..lsp::ExecuteCommandOptions::default()
26272                }),
26273                ..lsp::ServerCapabilities::default()
26274            },
26275            ..FakeLspAdapter::default()
26276        },
26277    );
26278
26279    let editor = workspace
26280        .update_in(cx, |workspace, window, cx| {
26281            workspace.open_abs_path(
26282                PathBuf::from(path!("/dir/a.ts")),
26283                OpenOptions::default(),
26284                window,
26285                cx,
26286            )
26287        })
26288        .await
26289        .unwrap()
26290        .downcast::<Editor>()
26291        .unwrap();
26292    cx.executor().run_until_parked();
26293
26294    let fake_server = fake_language_servers.next().await.unwrap();
26295
26296    let buffer = editor.update(cx, |editor, cx| {
26297        editor
26298            .buffer()
26299            .read(cx)
26300            .as_singleton()
26301            .expect("have opened a single file by path")
26302    });
26303
26304    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
26305    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
26306    drop(buffer_snapshot);
26307    let actions = cx
26308        .update_window(*window, |_, window, cx| {
26309            project.code_actions(&buffer, anchor..anchor, window, cx)
26310        })
26311        .unwrap();
26312
26313    fake_server
26314        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
26315            Ok(Some(vec![
26316                lsp::CodeLens {
26317                    range: lsp::Range::default(),
26318                    command: Some(lsp::Command {
26319                        title: "Code lens command".to_owned(),
26320                        command: "_the/command".to_owned(),
26321                        arguments: None,
26322                    }),
26323                    data: None,
26324                },
26325                lsp::CodeLens {
26326                    range: lsp::Range::default(),
26327                    command: Some(lsp::Command {
26328                        title: "Command not in capabilities".to_owned(),
26329                        command: "not in capabilities".to_owned(),
26330                        arguments: None,
26331                    }),
26332                    data: None,
26333                },
26334                lsp::CodeLens {
26335                    range: lsp::Range {
26336                        start: lsp::Position {
26337                            line: 1,
26338                            character: 1,
26339                        },
26340                        end: lsp::Position {
26341                            line: 1,
26342                            character: 1,
26343                        },
26344                    },
26345                    command: Some(lsp::Command {
26346                        title: "Command not in range".to_owned(),
26347                        command: "_the/command".to_owned(),
26348                        arguments: None,
26349                    }),
26350                    data: None,
26351                },
26352            ]))
26353        })
26354        .next()
26355        .await;
26356
26357    let actions = actions.await.unwrap();
26358    assert_eq!(
26359        actions.len(),
26360        1,
26361        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
26362    );
26363    let action = actions[0].clone();
26364    let apply = project.update(cx, |project, cx| {
26365        project.apply_code_action(buffer.clone(), action, true, cx)
26366    });
26367
26368    // Resolving the code action does not populate its edits. In absence of
26369    // edits, we must execute the given command.
26370    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
26371        |mut lens, _| async move {
26372            let lens_command = lens.command.as_mut().expect("should have a command");
26373            assert_eq!(lens_command.title, "Code lens command");
26374            lens_command.arguments = Some(vec![json!("the-argument")]);
26375            Ok(lens)
26376        },
26377    );
26378
26379    // While executing the command, the language server sends the editor
26380    // a `workspaceEdit` request.
26381    fake_server
26382        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
26383            let fake = fake_server.clone();
26384            move |params, _| {
26385                assert_eq!(params.command, "_the/command");
26386                let fake = fake.clone();
26387                async move {
26388                    fake.server
26389                        .request::<lsp::request::ApplyWorkspaceEdit>(
26390                            lsp::ApplyWorkspaceEditParams {
26391                                label: None,
26392                                edit: lsp::WorkspaceEdit {
26393                                    changes: Some(
26394                                        [(
26395                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
26396                                            vec![lsp::TextEdit {
26397                                                range: lsp::Range::new(
26398                                                    lsp::Position::new(0, 0),
26399                                                    lsp::Position::new(0, 0),
26400                                                ),
26401                                                new_text: "X".into(),
26402                                            }],
26403                                        )]
26404                                        .into_iter()
26405                                        .collect(),
26406                                    ),
26407                                    ..lsp::WorkspaceEdit::default()
26408                                },
26409                            },
26410                            DEFAULT_LSP_REQUEST_TIMEOUT,
26411                        )
26412                        .await
26413                        .into_response()
26414                        .unwrap();
26415                    Ok(Some(json!(null)))
26416                }
26417            }
26418        })
26419        .next()
26420        .await;
26421
26422    // Applying the code lens command returns a project transaction containing the edits
26423    // sent by the language server in its `workspaceEdit` request.
26424    let transaction = apply.await.unwrap();
26425    assert!(transaction.0.contains_key(&buffer));
26426    buffer.update(cx, |buffer, cx| {
26427        assert_eq!(buffer.text(), "Xa");
26428        buffer.undo(cx);
26429        assert_eq!(buffer.text(), "a");
26430    });
26431
26432    let actions_after_edits = cx
26433        .update(|window, cx| project.code_actions(&buffer, anchor..anchor, window, cx))
26434        .unwrap()
26435        .await;
26436    assert_eq!(
26437        actions, actions_after_edits,
26438        "For the same selection, same code lens actions should be returned"
26439    );
26440
26441    let _responses =
26442        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
26443            panic!("No more code lens requests are expected");
26444        });
26445    editor.update_in(cx, |editor, window, cx| {
26446        editor.select_all(&SelectAll, window, cx);
26447    });
26448    cx.executor().run_until_parked();
26449    let new_actions = cx
26450        .update(|window, cx| project.code_actions(&buffer, anchor..anchor, window, cx))
26451        .unwrap()
26452        .await;
26453    assert_eq!(
26454        actions, new_actions,
26455        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
26456    );
26457}
26458
26459#[gpui::test]
26460async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
26461    init_test(cx, |_| {});
26462
26463    let fs = FakeFs::new(cx.executor());
26464    let main_text = r#"fn main() {
26465println!("1");
26466println!("2");
26467println!("3");
26468println!("4");
26469println!("5");
26470}"#;
26471    let lib_text = "mod foo {}";
26472    fs.insert_tree(
26473        path!("/a"),
26474        json!({
26475            "lib.rs": lib_text,
26476            "main.rs": main_text,
26477        }),
26478    )
26479    .await;
26480
26481    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26482    let (multi_workspace, cx) =
26483        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26484    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
26485    let worktree_id = workspace.update(cx, |workspace, cx| {
26486        workspace.project().update(cx, |project, cx| {
26487            project.worktrees(cx).next().unwrap().read(cx).id()
26488        })
26489    });
26490
26491    let expected_ranges = vec![
26492        Point::new(0, 0)..Point::new(0, 0),
26493        Point::new(1, 0)..Point::new(1, 1),
26494        Point::new(2, 0)..Point::new(2, 2),
26495        Point::new(3, 0)..Point::new(3, 3),
26496    ];
26497
26498    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
26499    let editor_1 = workspace
26500        .update_in(cx, |workspace, window, cx| {
26501            workspace.open_path(
26502                (worktree_id, rel_path("main.rs")),
26503                Some(pane_1.downgrade()),
26504                true,
26505                window,
26506                cx,
26507            )
26508        })
26509        .unwrap()
26510        .await
26511        .downcast::<Editor>()
26512        .unwrap();
26513    pane_1.update(cx, |pane, cx| {
26514        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26515        open_editor.update(cx, |editor, cx| {
26516            assert_eq!(
26517                editor.display_text(cx),
26518                main_text,
26519                "Original main.rs text on initial open",
26520            );
26521            assert_eq!(
26522                editor
26523                    .selections
26524                    .all::<Point>(&editor.display_snapshot(cx))
26525                    .into_iter()
26526                    .map(|s| s.range())
26527                    .collect::<Vec<_>>(),
26528                vec![Point::zero()..Point::zero()],
26529                "Default selections on initial open",
26530            );
26531        })
26532    });
26533    editor_1.update_in(cx, |editor, window, cx| {
26534        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26535            s.select_ranges(expected_ranges.clone());
26536        });
26537    });
26538
26539    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
26540        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
26541    });
26542    let editor_2 = workspace
26543        .update_in(cx, |workspace, window, cx| {
26544            workspace.open_path(
26545                (worktree_id, rel_path("main.rs")),
26546                Some(pane_2.downgrade()),
26547                true,
26548                window,
26549                cx,
26550            )
26551        })
26552        .unwrap()
26553        .await
26554        .downcast::<Editor>()
26555        .unwrap();
26556    pane_2.update(cx, |pane, cx| {
26557        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26558        open_editor.update(cx, |editor, cx| {
26559            assert_eq!(
26560                editor.display_text(cx),
26561                main_text,
26562                "Original main.rs text on initial open in another panel",
26563            );
26564            assert_eq!(
26565                editor
26566                    .selections
26567                    .all::<Point>(&editor.display_snapshot(cx))
26568                    .into_iter()
26569                    .map(|s| s.range())
26570                    .collect::<Vec<_>>(),
26571                vec![Point::zero()..Point::zero()],
26572                "Default selections on initial open in another panel",
26573            );
26574        })
26575    });
26576
26577    editor_2.update_in(cx, |editor, window, cx| {
26578        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
26579    });
26580
26581    let _other_editor_1 = workspace
26582        .update_in(cx, |workspace, window, cx| {
26583            workspace.open_path(
26584                (worktree_id, rel_path("lib.rs")),
26585                Some(pane_1.downgrade()),
26586                true,
26587                window,
26588                cx,
26589            )
26590        })
26591        .unwrap()
26592        .await
26593        .downcast::<Editor>()
26594        .unwrap();
26595    pane_1
26596        .update_in(cx, |pane, window, cx| {
26597            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
26598        })
26599        .await
26600        .unwrap();
26601    drop(editor_1);
26602    pane_1.update(cx, |pane, cx| {
26603        pane.active_item()
26604            .unwrap()
26605            .downcast::<Editor>()
26606            .unwrap()
26607            .update(cx, |editor, cx| {
26608                assert_eq!(
26609                    editor.display_text(cx),
26610                    lib_text,
26611                    "Other file should be open and active",
26612                );
26613            });
26614        assert_eq!(pane.items().count(), 1, "No other editors should be open");
26615    });
26616
26617    let _other_editor_2 = workspace
26618        .update_in(cx, |workspace, window, cx| {
26619            workspace.open_path(
26620                (worktree_id, rel_path("lib.rs")),
26621                Some(pane_2.downgrade()),
26622                true,
26623                window,
26624                cx,
26625            )
26626        })
26627        .unwrap()
26628        .await
26629        .downcast::<Editor>()
26630        .unwrap();
26631    pane_2
26632        .update_in(cx, |pane, window, cx| {
26633            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
26634        })
26635        .await
26636        .unwrap();
26637    drop(editor_2);
26638    pane_2.update(cx, |pane, cx| {
26639        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26640        open_editor.update(cx, |editor, cx| {
26641            assert_eq!(
26642                editor.display_text(cx),
26643                lib_text,
26644                "Other file should be open and active in another panel too",
26645            );
26646        });
26647        assert_eq!(
26648            pane.items().count(),
26649            1,
26650            "No other editors should be open in another pane",
26651        );
26652    });
26653
26654    let _editor_1_reopened = workspace
26655        .update_in(cx, |workspace, window, cx| {
26656            workspace.open_path(
26657                (worktree_id, rel_path("main.rs")),
26658                Some(pane_1.downgrade()),
26659                true,
26660                window,
26661                cx,
26662            )
26663        })
26664        .unwrap()
26665        .await
26666        .downcast::<Editor>()
26667        .unwrap();
26668    let _editor_2_reopened = workspace
26669        .update_in(cx, |workspace, window, cx| {
26670            workspace.open_path(
26671                (worktree_id, rel_path("main.rs")),
26672                Some(pane_2.downgrade()),
26673                true,
26674                window,
26675                cx,
26676            )
26677        })
26678        .unwrap()
26679        .await
26680        .downcast::<Editor>()
26681        .unwrap();
26682    pane_1.update(cx, |pane, cx| {
26683        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26684        open_editor.update(cx, |editor, cx| {
26685            assert_eq!(
26686                editor.display_text(cx),
26687                main_text,
26688                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
26689            );
26690            assert_eq!(
26691                editor
26692                    .selections
26693                    .all::<Point>(&editor.display_snapshot(cx))
26694                    .into_iter()
26695                    .map(|s| s.range())
26696                    .collect::<Vec<_>>(),
26697                expected_ranges,
26698                "Previous editor in the 1st panel had selections and should get them restored on reopen",
26699            );
26700        })
26701    });
26702    pane_2.update(cx, |pane, cx| {
26703        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26704        open_editor.update(cx, |editor, cx| {
26705            assert_eq!(
26706                editor.display_text(cx),
26707                r#"fn main() {
26708⋯rintln!("1");
26709⋯intln!("2");
26710⋯ntln!("3");
26711println!("4");
26712println!("5");
26713}"#,
26714                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
26715            );
26716            assert_eq!(
26717                editor
26718                    .selections
26719                    .all::<Point>(&editor.display_snapshot(cx))
26720                    .into_iter()
26721                    .map(|s| s.range())
26722                    .collect::<Vec<_>>(),
26723                vec![Point::zero()..Point::zero()],
26724                "Previous editor in the 2nd pane had no selections changed hence should restore none",
26725            );
26726        })
26727    });
26728}
26729
26730#[gpui::test]
26731async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
26732    init_test(cx, |_| {});
26733
26734    let fs = FakeFs::new(cx.executor());
26735    let main_text = r#"fn main() {
26736println!("1");
26737println!("2");
26738println!("3");
26739println!("4");
26740println!("5");
26741}"#;
26742    let lib_text = "mod foo {}";
26743    fs.insert_tree(
26744        path!("/a"),
26745        json!({
26746            "lib.rs": lib_text,
26747            "main.rs": main_text,
26748        }),
26749    )
26750    .await;
26751
26752    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26753    let (multi_workspace, cx) =
26754        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26755    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
26756    let worktree_id = workspace.update(cx, |workspace, cx| {
26757        workspace.project().update(cx, |project, cx| {
26758            project.worktrees(cx).next().unwrap().read(cx).id()
26759        })
26760    });
26761
26762    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
26763    let editor = workspace
26764        .update_in(cx, |workspace, window, cx| {
26765            workspace.open_path(
26766                (worktree_id, rel_path("main.rs")),
26767                Some(pane.downgrade()),
26768                true,
26769                window,
26770                cx,
26771            )
26772        })
26773        .unwrap()
26774        .await
26775        .downcast::<Editor>()
26776        .unwrap();
26777    pane.update(cx, |pane, cx| {
26778        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26779        open_editor.update(cx, |editor, cx| {
26780            assert_eq!(
26781                editor.display_text(cx),
26782                main_text,
26783                "Original main.rs text on initial open",
26784            );
26785        })
26786    });
26787    editor.update_in(cx, |editor, window, cx| {
26788        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
26789    });
26790
26791    cx.update_global(|store: &mut SettingsStore, cx| {
26792        store.update_user_settings(cx, |s| {
26793            s.workspace.restore_on_file_reopen = Some(false);
26794        });
26795    });
26796    editor.update_in(cx, |editor, window, cx| {
26797        editor.fold_ranges(
26798            vec![
26799                Point::new(1, 0)..Point::new(1, 1),
26800                Point::new(2, 0)..Point::new(2, 2),
26801                Point::new(3, 0)..Point::new(3, 3),
26802            ],
26803            false,
26804            window,
26805            cx,
26806        );
26807    });
26808    pane.update_in(cx, |pane, window, cx| {
26809        pane.close_all_items(&CloseAllItems::default(), window, cx)
26810    })
26811    .await
26812    .unwrap();
26813    pane.update(cx, |pane, _| {
26814        assert!(pane.active_item().is_none());
26815    });
26816    cx.update_global(|store: &mut SettingsStore, cx| {
26817        store.update_user_settings(cx, |s| {
26818            s.workspace.restore_on_file_reopen = Some(true);
26819        });
26820    });
26821
26822    let _editor_reopened = workspace
26823        .update_in(cx, |workspace, window, cx| {
26824            workspace.open_path(
26825                (worktree_id, rel_path("main.rs")),
26826                Some(pane.downgrade()),
26827                true,
26828                window,
26829                cx,
26830            )
26831        })
26832        .unwrap()
26833        .await
26834        .downcast::<Editor>()
26835        .unwrap();
26836    pane.update(cx, |pane, cx| {
26837        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26838        open_editor.update(cx, |editor, cx| {
26839            assert_eq!(
26840                editor.display_text(cx),
26841                main_text,
26842                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
26843            );
26844        })
26845    });
26846}
26847
26848#[gpui::test]
26849async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
26850    struct EmptyModalView {
26851        focus_handle: gpui::FocusHandle,
26852    }
26853    impl EventEmitter<DismissEvent> for EmptyModalView {}
26854    impl Render for EmptyModalView {
26855        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
26856            div()
26857        }
26858    }
26859    impl Focusable for EmptyModalView {
26860        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
26861            self.focus_handle.clone()
26862        }
26863    }
26864    impl workspace::ModalView for EmptyModalView {}
26865    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
26866        EmptyModalView {
26867            focus_handle: cx.focus_handle(),
26868        }
26869    }
26870
26871    init_test(cx, |_| {});
26872
26873    let fs = FakeFs::new(cx.executor());
26874    let project = Project::test(fs, [], cx).await;
26875    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26876    let workspace = window
26877        .read_with(cx, |mw, _| mw.workspace().clone())
26878        .unwrap();
26879    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
26880    let cx = &mut VisualTestContext::from_window(*window, cx);
26881    let editor = cx.new_window_entity(|window, cx| {
26882        Editor::new(
26883            EditorMode::full(),
26884            buffer,
26885            Some(project.clone()),
26886            window,
26887            cx,
26888        )
26889    });
26890    workspace.update_in(cx, |workspace, window, cx| {
26891        workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
26892    });
26893
26894    editor.update_in(cx, |editor, window, cx| {
26895        editor.open_context_menu(&OpenContextMenu, window, cx);
26896        assert!(editor.mouse_context_menu.is_some());
26897    });
26898    workspace.update_in(cx, |workspace, window, cx| {
26899        workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
26900    });
26901
26902    cx.read(|cx| {
26903        assert!(editor.read(cx).mouse_context_menu.is_none());
26904    });
26905}
26906
26907fn set_linked_edit_ranges(
26908    opening: (Point, Point),
26909    closing: (Point, Point),
26910    editor: &mut Editor,
26911    cx: &mut Context<Editor>,
26912) {
26913    let Some((buffer, _)) = editor
26914        .buffer
26915        .read(cx)
26916        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
26917    else {
26918        panic!("Failed to get buffer for selection position");
26919    };
26920    let buffer = buffer.read(cx);
26921    let buffer_id = buffer.remote_id();
26922    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
26923    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
26924    let mut linked_ranges = HashMap::default();
26925    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
26926    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
26927}
26928
26929#[gpui::test]
26930async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
26931    init_test(cx, |_| {});
26932
26933    let fs = FakeFs::new(cx.executor());
26934    fs.insert_file(path!("/file.html"), Default::default())
26935        .await;
26936
26937    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
26938
26939    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26940    let html_language = Arc::new(Language::new(
26941        LanguageConfig {
26942            name: "HTML".into(),
26943            matcher: LanguageMatcher {
26944                path_suffixes: vec!["html".to_string()],
26945                ..LanguageMatcher::default()
26946            },
26947            brackets: BracketPairConfig {
26948                pairs: vec![BracketPair {
26949                    start: "<".into(),
26950                    end: ">".into(),
26951                    close: true,
26952                    ..Default::default()
26953                }],
26954                ..Default::default()
26955            },
26956            ..Default::default()
26957        },
26958        Some(tree_sitter_html::LANGUAGE.into()),
26959    ));
26960    language_registry.add(html_language);
26961    let mut fake_servers = language_registry.register_fake_lsp(
26962        "HTML",
26963        FakeLspAdapter {
26964            capabilities: lsp::ServerCapabilities {
26965                completion_provider: Some(lsp::CompletionOptions {
26966                    resolve_provider: Some(true),
26967                    ..Default::default()
26968                }),
26969                ..Default::default()
26970            },
26971            ..Default::default()
26972        },
26973    );
26974
26975    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26976    let workspace = window
26977        .read_with(cx, |mw, _| mw.workspace().clone())
26978        .unwrap();
26979    let cx = &mut VisualTestContext::from_window(*window, cx);
26980
26981    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
26982        workspace.project().update(cx, |project, cx| {
26983            project.worktrees(cx).next().unwrap().read(cx).id()
26984        })
26985    });
26986
26987    project
26988        .update(cx, |project, cx| {
26989            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
26990        })
26991        .await
26992        .unwrap();
26993    let editor = workspace
26994        .update_in(cx, |workspace, window, cx| {
26995            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
26996        })
26997        .await
26998        .unwrap()
26999        .downcast::<Editor>()
27000        .unwrap();
27001
27002    let fake_server = fake_servers.next().await.unwrap();
27003    cx.run_until_parked();
27004    editor.update_in(cx, |editor, window, cx| {
27005        editor.set_text("<ad></ad>", window, cx);
27006        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27007            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
27008        });
27009        set_linked_edit_ranges(
27010            (Point::new(0, 1), Point::new(0, 3)),
27011            (Point::new(0, 6), Point::new(0, 8)),
27012            editor,
27013            cx,
27014        );
27015    });
27016    let mut completion_handle =
27017        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
27018            Ok(Some(lsp::CompletionResponse::Array(vec![
27019                lsp::CompletionItem {
27020                    label: "head".to_string(),
27021                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27022                        lsp::InsertReplaceEdit {
27023                            new_text: "head".to_string(),
27024                            insert: lsp::Range::new(
27025                                lsp::Position::new(0, 1),
27026                                lsp::Position::new(0, 3),
27027                            ),
27028                            replace: lsp::Range::new(
27029                                lsp::Position::new(0, 1),
27030                                lsp::Position::new(0, 3),
27031                            ),
27032                        },
27033                    )),
27034                    ..Default::default()
27035                },
27036            ])))
27037        });
27038    editor.update_in(cx, |editor, window, cx| {
27039        editor.show_completions(&ShowCompletions, window, cx);
27040    });
27041    cx.run_until_parked();
27042    completion_handle.next().await.unwrap();
27043    editor.update(cx, |editor, _| {
27044        assert!(
27045            editor.context_menu_visible(),
27046            "Completion menu should be visible"
27047        );
27048    });
27049    editor.update_in(cx, |editor, window, cx| {
27050        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
27051    });
27052    cx.executor().run_until_parked();
27053    editor.update(cx, |editor, cx| {
27054        assert_eq!(editor.text(cx), "<head></head>");
27055    });
27056}
27057
27058#[gpui::test]
27059async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
27060    init_test(cx, |_| {});
27061
27062    let mut cx = EditorTestContext::new(cx).await;
27063    let language = Arc::new(Language::new(
27064        LanguageConfig {
27065            name: "TSX".into(),
27066            matcher: LanguageMatcher {
27067                path_suffixes: vec!["tsx".to_string()],
27068                ..LanguageMatcher::default()
27069            },
27070            brackets: BracketPairConfig {
27071                pairs: vec![BracketPair {
27072                    start: "<".into(),
27073                    end: ">".into(),
27074                    close: true,
27075                    ..Default::default()
27076                }],
27077                ..Default::default()
27078            },
27079            linked_edit_characters: HashSet::from_iter(['.']),
27080            ..Default::default()
27081        },
27082        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
27083    ));
27084    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27085
27086    // Test typing > does not extend linked pair
27087    cx.set_state("<divˇ<div></div>");
27088    cx.update_editor(|editor, _, cx| {
27089        set_linked_edit_ranges(
27090            (Point::new(0, 1), Point::new(0, 4)),
27091            (Point::new(0, 11), Point::new(0, 14)),
27092            editor,
27093            cx,
27094        );
27095    });
27096    cx.update_editor(|editor, window, cx| {
27097        editor.handle_input(">", window, cx);
27098    });
27099    cx.assert_editor_state("<div>ˇ<div></div>");
27100
27101    // Test typing . do extend linked pair
27102    cx.set_state("<Animatedˇ></Animated>");
27103    cx.update_editor(|editor, _, cx| {
27104        set_linked_edit_ranges(
27105            (Point::new(0, 1), Point::new(0, 9)),
27106            (Point::new(0, 12), Point::new(0, 20)),
27107            editor,
27108            cx,
27109        );
27110    });
27111    cx.update_editor(|editor, window, cx| {
27112        editor.handle_input(".", window, cx);
27113    });
27114    cx.assert_editor_state("<Animated.ˇ></Animated.>");
27115    cx.update_editor(|editor, _, cx| {
27116        set_linked_edit_ranges(
27117            (Point::new(0, 1), Point::new(0, 10)),
27118            (Point::new(0, 13), Point::new(0, 21)),
27119            editor,
27120            cx,
27121        );
27122    });
27123    cx.update_editor(|editor, window, cx| {
27124        editor.handle_input("V", window, cx);
27125    });
27126    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
27127}
27128
27129#[gpui::test]
27130async fn test_linked_edits_on_typing_dot_without_language_override(cx: &mut TestAppContext) {
27131    init_test(cx, |_| {});
27132
27133    let mut cx = EditorTestContext::new(cx).await;
27134    let language = Arc::new(Language::new(
27135        LanguageConfig {
27136            name: "HTML".into(),
27137            matcher: LanguageMatcher {
27138                path_suffixes: vec!["html".to_string()],
27139                ..LanguageMatcher::default()
27140            },
27141            brackets: BracketPairConfig {
27142                pairs: vec![BracketPair {
27143                    start: "<".into(),
27144                    end: ">".into(),
27145                    close: true,
27146                    ..Default::default()
27147                }],
27148                ..Default::default()
27149            },
27150            ..Default::default()
27151        },
27152        Some(tree_sitter_html::LANGUAGE.into()),
27153    ));
27154    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27155
27156    cx.set_state("<Tableˇ></Table>");
27157    cx.update_editor(|editor, _, cx| {
27158        set_linked_edit_ranges(
27159            (Point::new(0, 1), Point::new(0, 6)),
27160            (Point::new(0, 9), Point::new(0, 14)),
27161            editor,
27162            cx,
27163        );
27164    });
27165    cx.update_editor(|editor, window, cx| {
27166        editor.handle_input(".", window, cx);
27167    });
27168    cx.assert_editor_state("<Table.ˇ></Table.>");
27169}
27170
27171#[gpui::test]
27172async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
27173    init_test(cx, |_| {});
27174
27175    let fs = FakeFs::new(cx.executor());
27176    fs.insert_tree(
27177        path!("/root"),
27178        json!({
27179            "a": {
27180                "main.rs": "fn main() {}",
27181            },
27182            "foo": {
27183                "bar": {
27184                    "external_file.rs": "pub mod external {}",
27185                }
27186            }
27187        }),
27188    )
27189    .await;
27190
27191    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
27192    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27193    language_registry.add(rust_lang());
27194    let _fake_servers = language_registry.register_fake_lsp(
27195        "Rust",
27196        FakeLspAdapter {
27197            ..FakeLspAdapter::default()
27198        },
27199    );
27200    let (multi_workspace, cx) =
27201        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27202    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
27203    let worktree_id = workspace.update(cx, |workspace, cx| {
27204        workspace.project().update(cx, |project, cx| {
27205            project.worktrees(cx).next().unwrap().read(cx).id()
27206        })
27207    });
27208
27209    let assert_language_servers_count =
27210        |expected: usize, context: &str, cx: &mut VisualTestContext| {
27211            project.update(cx, |project, cx| {
27212                let current = project
27213                    .lsp_store()
27214                    .read(cx)
27215                    .as_local()
27216                    .unwrap()
27217                    .language_servers
27218                    .len();
27219                assert_eq!(expected, current, "{context}");
27220            });
27221        };
27222
27223    assert_language_servers_count(
27224        0,
27225        "No servers should be running before any file is open",
27226        cx,
27227    );
27228    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
27229    let main_editor = workspace
27230        .update_in(cx, |workspace, window, cx| {
27231            workspace.open_path(
27232                (worktree_id, rel_path("main.rs")),
27233                Some(pane.downgrade()),
27234                true,
27235                window,
27236                cx,
27237            )
27238        })
27239        .unwrap()
27240        .await
27241        .downcast::<Editor>()
27242        .unwrap();
27243    pane.update(cx, |pane, cx| {
27244        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
27245        open_editor.update(cx, |editor, cx| {
27246            assert_eq!(
27247                editor.display_text(cx),
27248                "fn main() {}",
27249                "Original main.rs text on initial open",
27250            );
27251        });
27252        assert_eq!(open_editor, main_editor);
27253    });
27254    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
27255
27256    let external_editor = workspace
27257        .update_in(cx, |workspace, window, cx| {
27258            workspace.open_abs_path(
27259                PathBuf::from("/root/foo/bar/external_file.rs"),
27260                OpenOptions::default(),
27261                window,
27262                cx,
27263            )
27264        })
27265        .await
27266        .expect("opening external file")
27267        .downcast::<Editor>()
27268        .expect("downcasted external file's open element to editor");
27269    pane.update(cx, |pane, cx| {
27270        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
27271        open_editor.update(cx, |editor, cx| {
27272            assert_eq!(
27273                editor.display_text(cx),
27274                "pub mod external {}",
27275                "External file is open now",
27276            );
27277        });
27278        assert_eq!(open_editor, external_editor);
27279    });
27280    assert_language_servers_count(
27281        1,
27282        "Second, external, *.rs file should join the existing server",
27283        cx,
27284    );
27285
27286    pane.update_in(cx, |pane, window, cx| {
27287        pane.close_active_item(&CloseActiveItem::default(), window, cx)
27288    })
27289    .await
27290    .unwrap();
27291    pane.update_in(cx, |pane, window, cx| {
27292        pane.navigate_backward(&Default::default(), window, cx);
27293    });
27294    cx.run_until_parked();
27295    pane.update(cx, |pane, cx| {
27296        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
27297        open_editor.update(cx, |editor, cx| {
27298            assert_eq!(
27299                editor.display_text(cx),
27300                "pub mod external {}",
27301                "External file is open now",
27302            );
27303        });
27304    });
27305    assert_language_servers_count(
27306        1,
27307        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
27308        cx,
27309    );
27310
27311    cx.update(|_, cx| {
27312        workspace::reload(cx);
27313    });
27314    assert_language_servers_count(
27315        1,
27316        "After reloading the worktree with local and external files opened, only one project should be started",
27317        cx,
27318    );
27319}
27320
27321#[gpui::test]
27322async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
27323    init_test(cx, |_| {});
27324
27325    let mut cx = EditorTestContext::new(cx).await;
27326    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
27327    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27328
27329    // test cursor move to start of each line on tab
27330    // for `if`, `elif`, `else`, `while`, `with` and `for`
27331    cx.set_state(indoc! {"
27332        def main():
27333        ˇ    for item in items:
27334        ˇ        while item.active:
27335        ˇ            if item.value > 10:
27336        ˇ                continue
27337        ˇ            elif item.value < 0:
27338        ˇ                break
27339        ˇ            else:
27340        ˇ                with item.context() as ctx:
27341        ˇ                    yield count
27342        ˇ        else:
27343        ˇ            log('while else')
27344        ˇ    else:
27345        ˇ        log('for else')
27346    "});
27347    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27348    cx.wait_for_autoindent_applied().await;
27349    cx.assert_editor_state(indoc! {"
27350        def main():
27351            ˇfor item in items:
27352                ˇwhile item.active:
27353                    ˇif item.value > 10:
27354                        ˇcontinue
27355                    ˇelif item.value < 0:
27356                        ˇbreak
27357                    ˇelse:
27358                        ˇwith item.context() as ctx:
27359                            ˇyield count
27360                ˇelse:
27361                    ˇlog('while else')
27362            ˇelse:
27363                ˇlog('for else')
27364    "});
27365    // test relative indent is preserved when tab
27366    // for `if`, `elif`, `else`, `while`, `with` and `for`
27367    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27368    cx.wait_for_autoindent_applied().await;
27369    cx.assert_editor_state(indoc! {"
27370        def main():
27371                ˇfor item in items:
27372                    ˇwhile item.active:
27373                        ˇif item.value > 10:
27374                            ˇcontinue
27375                        ˇelif item.value < 0:
27376                            ˇbreak
27377                        ˇelse:
27378                            ˇwith item.context() as ctx:
27379                                ˇyield count
27380                    ˇelse:
27381                        ˇlog('while else')
27382                ˇelse:
27383                    ˇlog('for else')
27384    "});
27385
27386    // test cursor move to start of each line on tab
27387    // for `try`, `except`, `else`, `finally`, `match` and `def`
27388    cx.set_state(indoc! {"
27389        def main():
27390        ˇ    try:
27391        ˇ        fetch()
27392        ˇ    except ValueError:
27393        ˇ        handle_error()
27394        ˇ    else:
27395        ˇ        match value:
27396        ˇ            case _:
27397        ˇ    finally:
27398        ˇ        def status():
27399        ˇ            return 0
27400    "});
27401    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27402    cx.wait_for_autoindent_applied().await;
27403    cx.assert_editor_state(indoc! {"
27404        def main():
27405            ˇtry:
27406                ˇfetch()
27407            ˇexcept ValueError:
27408                ˇhandle_error()
27409            ˇelse:
27410                ˇmatch value:
27411                    ˇcase _:
27412            ˇfinally:
27413                ˇdef status():
27414                    ˇreturn 0
27415    "});
27416    // test relative indent is preserved when tab
27417    // for `try`, `except`, `else`, `finally`, `match` and `def`
27418    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27419    cx.wait_for_autoindent_applied().await;
27420    cx.assert_editor_state(indoc! {"
27421        def main():
27422                ˇtry:
27423                    ˇfetch()
27424                ˇexcept ValueError:
27425                    ˇhandle_error()
27426                ˇelse:
27427                    ˇmatch value:
27428                        ˇcase _:
27429                ˇfinally:
27430                    ˇdef status():
27431                        ˇreturn 0
27432    "});
27433}
27434
27435#[gpui::test]
27436async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
27437    init_test(cx, |_| {});
27438
27439    let mut cx = EditorTestContext::new(cx).await;
27440    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
27441    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27442
27443    // test `else` auto outdents when typed inside `if` block
27444    cx.set_state(indoc! {"
27445        def main():
27446            if i == 2:
27447                return
27448                ˇ
27449    "});
27450    cx.update_editor(|editor, window, cx| {
27451        editor.handle_input("else:", window, cx);
27452    });
27453    cx.wait_for_autoindent_applied().await;
27454    cx.assert_editor_state(indoc! {"
27455        def main():
27456            if i == 2:
27457                return
27458            else:ˇ
27459    "});
27460
27461    // test `except` auto outdents when typed inside `try` block
27462    cx.set_state(indoc! {"
27463        def main():
27464            try:
27465                i = 2
27466                ˇ
27467    "});
27468    cx.update_editor(|editor, window, cx| {
27469        editor.handle_input("except:", window, cx);
27470    });
27471    cx.wait_for_autoindent_applied().await;
27472    cx.assert_editor_state(indoc! {"
27473        def main():
27474            try:
27475                i = 2
27476            except:ˇ
27477    "});
27478
27479    // test `else` auto outdents when typed inside `except` block
27480    cx.set_state(indoc! {"
27481        def main():
27482            try:
27483                i = 2
27484            except:
27485                j = 2
27486                ˇ
27487    "});
27488    cx.update_editor(|editor, window, cx| {
27489        editor.handle_input("else:", window, cx);
27490    });
27491    cx.wait_for_autoindent_applied().await;
27492    cx.assert_editor_state(indoc! {"
27493        def main():
27494            try:
27495                i = 2
27496            except:
27497                j = 2
27498            else:ˇ
27499    "});
27500
27501    // test `finally` auto outdents when typed inside `else` block
27502    cx.set_state(indoc! {"
27503        def main():
27504            try:
27505                i = 2
27506            except:
27507                j = 2
27508            else:
27509                k = 2
27510                ˇ
27511    "});
27512    cx.update_editor(|editor, window, cx| {
27513        editor.handle_input("finally:", window, cx);
27514    });
27515    cx.wait_for_autoindent_applied().await;
27516    cx.assert_editor_state(indoc! {"
27517        def main():
27518            try:
27519                i = 2
27520            except:
27521                j = 2
27522            else:
27523                k = 2
27524            finally:ˇ
27525    "});
27526
27527    // test `else` does not outdents when typed inside `except` block right after for block
27528    cx.set_state(indoc! {"
27529        def main():
27530            try:
27531                i = 2
27532            except:
27533                for i in range(n):
27534                    pass
27535                ˇ
27536    "});
27537    cx.update_editor(|editor, window, cx| {
27538        editor.handle_input("else:", window, cx);
27539    });
27540    cx.wait_for_autoindent_applied().await;
27541    cx.assert_editor_state(indoc! {"
27542        def main():
27543            try:
27544                i = 2
27545            except:
27546                for i in range(n):
27547                    pass
27548                else:ˇ
27549    "});
27550
27551    // test `finally` auto outdents when typed inside `else` block right after for block
27552    cx.set_state(indoc! {"
27553        def main():
27554            try:
27555                i = 2
27556            except:
27557                j = 2
27558            else:
27559                for i in range(n):
27560                    pass
27561                ˇ
27562    "});
27563    cx.update_editor(|editor, window, cx| {
27564        editor.handle_input("finally:", window, cx);
27565    });
27566    cx.wait_for_autoindent_applied().await;
27567    cx.assert_editor_state(indoc! {"
27568        def main():
27569            try:
27570                i = 2
27571            except:
27572                j = 2
27573            else:
27574                for i in range(n):
27575                    pass
27576            finally:ˇ
27577    "});
27578
27579    // test `except` outdents to inner "try" block
27580    cx.set_state(indoc! {"
27581        def main():
27582            try:
27583                i = 2
27584                if i == 2:
27585                    try:
27586                        i = 3
27587                        ˇ
27588    "});
27589    cx.update_editor(|editor, window, cx| {
27590        editor.handle_input("except:", window, cx);
27591    });
27592    cx.wait_for_autoindent_applied().await;
27593    cx.assert_editor_state(indoc! {"
27594        def main():
27595            try:
27596                i = 2
27597                if i == 2:
27598                    try:
27599                        i = 3
27600                    except:ˇ
27601    "});
27602
27603    // test `except` outdents to outer "try" block
27604    cx.set_state(indoc! {"
27605        def main():
27606            try:
27607                i = 2
27608                if i == 2:
27609                    try:
27610                        i = 3
27611                ˇ
27612    "});
27613    cx.update_editor(|editor, window, cx| {
27614        editor.handle_input("except:", window, cx);
27615    });
27616    cx.wait_for_autoindent_applied().await;
27617    cx.assert_editor_state(indoc! {"
27618        def main():
27619            try:
27620                i = 2
27621                if i == 2:
27622                    try:
27623                        i = 3
27624            except:ˇ
27625    "});
27626
27627    // test `else` stays at correct indent when typed after `for` block
27628    cx.set_state(indoc! {"
27629        def main():
27630            for i in range(10):
27631                if i == 3:
27632                    break
27633            ˇ
27634    "});
27635    cx.update_editor(|editor, window, cx| {
27636        editor.handle_input("else:", window, cx);
27637    });
27638    cx.wait_for_autoindent_applied().await;
27639    cx.assert_editor_state(indoc! {"
27640        def main():
27641            for i in range(10):
27642                if i == 3:
27643                    break
27644            else:ˇ
27645    "});
27646
27647    // test does not outdent on typing after line with square brackets
27648    cx.set_state(indoc! {"
27649        def f() -> list[str]:
27650            ˇ
27651    "});
27652    cx.update_editor(|editor, window, cx| {
27653        editor.handle_input("a", window, cx);
27654    });
27655    cx.wait_for_autoindent_applied().await;
27656    cx.assert_editor_state(indoc! {"
27657        def f() -> list[str]:
2765827659    "});
27660
27661    // test does not outdent on typing : after case keyword
27662    cx.set_state(indoc! {"
27663        match 1:
27664            caseˇ
27665    "});
27666    cx.update_editor(|editor, window, cx| {
27667        editor.handle_input(":", window, cx);
27668    });
27669    cx.wait_for_autoindent_applied().await;
27670    cx.assert_editor_state(indoc! {"
27671        match 1:
27672            case:ˇ
27673    "});
27674}
27675
27676#[gpui::test]
27677async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
27678    init_test(cx, |_| {});
27679    update_test_language_settings(cx, &|settings| {
27680        settings.defaults.extend_comment_on_newline = Some(false);
27681    });
27682    let mut cx = EditorTestContext::new(cx).await;
27683    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
27684    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27685
27686    // test correct indent after newline on comment
27687    cx.set_state(indoc! {"
27688        # COMMENT:ˇ
27689    "});
27690    cx.update_editor(|editor, window, cx| {
27691        editor.newline(&Newline, window, cx);
27692    });
27693    cx.wait_for_autoindent_applied().await;
27694    cx.assert_editor_state(indoc! {"
27695        # COMMENT:
27696        ˇ
27697    "});
27698
27699    // test correct indent after newline in brackets
27700    cx.set_state(indoc! {"
27701        {ˇ}
27702    "});
27703    cx.update_editor(|editor, window, cx| {
27704        editor.newline(&Newline, window, cx);
27705    });
27706    cx.wait_for_autoindent_applied().await;
27707    cx.assert_editor_state(indoc! {"
27708        {
27709            ˇ
27710        }
27711    "});
27712
27713    cx.set_state(indoc! {"
27714        (ˇ)
27715    "});
27716    cx.update_editor(|editor, window, cx| {
27717        editor.newline(&Newline, window, cx);
27718    });
27719    cx.run_until_parked();
27720    cx.assert_editor_state(indoc! {"
27721        (
27722            ˇ
27723        )
27724    "});
27725
27726    // do not indent after empty lists or dictionaries
27727    cx.set_state(indoc! {"
27728        a = []ˇ
27729    "});
27730    cx.update_editor(|editor, window, cx| {
27731        editor.newline(&Newline, window, cx);
27732    });
27733    cx.run_until_parked();
27734    cx.assert_editor_state(indoc! {"
27735        a = []
27736        ˇ
27737    "});
27738}
27739
27740#[gpui::test]
27741async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
27742    init_test(cx, |_| {});
27743
27744    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
27745    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
27746    language_registry.add(markdown_lang());
27747    language_registry.add(python_lang);
27748
27749    let mut cx = EditorTestContext::new(cx).await;
27750    cx.update_buffer(|buffer, cx| {
27751        buffer.set_language_registry(language_registry);
27752        buffer.set_language(Some(markdown_lang()), cx);
27753    });
27754
27755    // Test that `else:` correctly outdents to match `if:` inside the Python code block
27756    cx.set_state(indoc! {"
27757        # Heading
27758
27759        ```python
27760        def main():
27761            if condition:
27762                pass
27763                ˇ
27764        ```
27765    "});
27766    cx.update_editor(|editor, window, cx| {
27767        editor.handle_input("else:", window, cx);
27768    });
27769    cx.run_until_parked();
27770    cx.assert_editor_state(indoc! {"
27771        # Heading
27772
27773        ```python
27774        def main():
27775            if condition:
27776                pass
27777            else:ˇ
27778        ```
27779    "});
27780}
27781
27782#[gpui::test]
27783async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
27784    init_test(cx, |_| {});
27785
27786    let mut cx = EditorTestContext::new(cx).await;
27787    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
27788    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27789
27790    // test cursor move to start of each line on tab
27791    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
27792    cx.set_state(indoc! {"
27793        function main() {
27794        ˇ    for item in $items; do
27795        ˇ        while [ -n \"$item\" ]; do
27796        ˇ            if [ \"$value\" -gt 10 ]; then
27797        ˇ                continue
27798        ˇ            elif [ \"$value\" -lt 0 ]; then
27799        ˇ                break
27800        ˇ            else
27801        ˇ                echo \"$item\"
27802        ˇ            fi
27803        ˇ        done
27804        ˇ    done
27805        ˇ}
27806    "});
27807    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27808    cx.wait_for_autoindent_applied().await;
27809    cx.assert_editor_state(indoc! {"
27810        function main() {
27811            ˇfor item in $items; do
27812                ˇwhile [ -n \"$item\" ]; do
27813                    ˇif [ \"$value\" -gt 10 ]; then
27814                        ˇcontinue
27815                    ˇelif [ \"$value\" -lt 0 ]; then
27816                        ˇbreak
27817                    ˇelse
27818                        ˇecho \"$item\"
27819                    ˇfi
27820                ˇdone
27821            ˇdone
27822        ˇ}
27823    "});
27824    // test relative indent is preserved when tab
27825    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27826    cx.wait_for_autoindent_applied().await;
27827    cx.assert_editor_state(indoc! {"
27828        function main() {
27829                ˇfor item in $items; do
27830                    ˇwhile [ -n \"$item\" ]; do
27831                        ˇif [ \"$value\" -gt 10 ]; then
27832                            ˇcontinue
27833                        ˇelif [ \"$value\" -lt 0 ]; then
27834                            ˇbreak
27835                        ˇelse
27836                            ˇecho \"$item\"
27837                        ˇfi
27838                    ˇdone
27839                ˇdone
27840            ˇ}
27841    "});
27842
27843    // test cursor move to start of each line on tab
27844    // for `case` statement with patterns
27845    cx.set_state(indoc! {"
27846        function handle() {
27847        ˇ    case \"$1\" in
27848        ˇ        start)
27849        ˇ            echo \"a\"
27850        ˇ            ;;
27851        ˇ        stop)
27852        ˇ            echo \"b\"
27853        ˇ            ;;
27854        ˇ        *)
27855        ˇ            echo \"c\"
27856        ˇ            ;;
27857        ˇ    esac
27858        ˇ}
27859    "});
27860    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27861    cx.wait_for_autoindent_applied().await;
27862    cx.assert_editor_state(indoc! {"
27863        function handle() {
27864            ˇcase \"$1\" in
27865                ˇstart)
27866                    ˇecho \"a\"
27867                    ˇ;;
27868                ˇstop)
27869                    ˇecho \"b\"
27870                    ˇ;;
27871                ˇ*)
27872                    ˇecho \"c\"
27873                    ˇ;;
27874            ˇesac
27875        ˇ}
27876    "});
27877}
27878
27879#[gpui::test]
27880async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
27881    init_test(cx, |_| {});
27882
27883    let mut cx = EditorTestContext::new(cx).await;
27884    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
27885    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27886
27887    // test indents on comment insert
27888    cx.set_state(indoc! {"
27889        function main() {
27890        ˇ    for item in $items; do
27891        ˇ        while [ -n \"$item\" ]; do
27892        ˇ            if [ \"$value\" -gt 10 ]; then
27893        ˇ                continue
27894        ˇ            elif [ \"$value\" -lt 0 ]; then
27895        ˇ                break
27896        ˇ            else
27897        ˇ                echo \"$item\"
27898        ˇ            fi
27899        ˇ        done
27900        ˇ    done
27901        ˇ}
27902    "});
27903    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
27904    cx.wait_for_autoindent_applied().await;
27905    cx.assert_editor_state(indoc! {"
27906        function main() {
27907        #ˇ    for item in $items; do
27908        #ˇ        while [ -n \"$item\" ]; do
27909        #ˇ            if [ \"$value\" -gt 10 ]; then
27910        #ˇ                continue
27911        #ˇ            elif [ \"$value\" -lt 0 ]; then
27912        #ˇ                break
27913        #ˇ            else
27914        #ˇ                echo \"$item\"
27915        #ˇ            fi
27916        #ˇ        done
27917        #ˇ    done
27918        #ˇ}
27919    "});
27920}
27921
27922#[gpui::test]
27923async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
27924    init_test(cx, |_| {});
27925
27926    let mut cx = EditorTestContext::new(cx).await;
27927    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
27928    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27929
27930    // test `else` auto outdents when typed inside `if` block
27931    cx.set_state(indoc! {"
27932        if [ \"$1\" = \"test\" ]; then
27933            echo \"foo bar\"
27934            ˇ
27935    "});
27936    cx.update_editor(|editor, window, cx| {
27937        editor.handle_input("else", window, cx);
27938    });
27939    cx.wait_for_autoindent_applied().await;
27940    cx.assert_editor_state(indoc! {"
27941        if [ \"$1\" = \"test\" ]; then
27942            echo \"foo bar\"
27943        elseˇ
27944    "});
27945
27946    // test `elif` auto outdents when typed inside `if` block
27947    cx.set_state(indoc! {"
27948        if [ \"$1\" = \"test\" ]; then
27949            echo \"foo bar\"
27950            ˇ
27951    "});
27952    cx.update_editor(|editor, window, cx| {
27953        editor.handle_input("elif", window, cx);
27954    });
27955    cx.wait_for_autoindent_applied().await;
27956    cx.assert_editor_state(indoc! {"
27957        if [ \"$1\" = \"test\" ]; then
27958            echo \"foo bar\"
27959        elifˇ
27960    "});
27961
27962    // test `fi` auto outdents when typed inside `else` block
27963    cx.set_state(indoc! {"
27964        if [ \"$1\" = \"test\" ]; then
27965            echo \"foo bar\"
27966        else
27967            echo \"bar baz\"
27968            ˇ
27969    "});
27970    cx.update_editor(|editor, window, cx| {
27971        editor.handle_input("fi", window, cx);
27972    });
27973    cx.wait_for_autoindent_applied().await;
27974    cx.assert_editor_state(indoc! {"
27975        if [ \"$1\" = \"test\" ]; then
27976            echo \"foo bar\"
27977        else
27978            echo \"bar baz\"
27979        fiˇ
27980    "});
27981
27982    // test `done` auto outdents when typed inside `while` block
27983    cx.set_state(indoc! {"
27984        while read line; do
27985            echo \"$line\"
27986            ˇ
27987    "});
27988    cx.update_editor(|editor, window, cx| {
27989        editor.handle_input("done", window, cx);
27990    });
27991    cx.wait_for_autoindent_applied().await;
27992    cx.assert_editor_state(indoc! {"
27993        while read line; do
27994            echo \"$line\"
27995        doneˇ
27996    "});
27997
27998    // test `done` auto outdents when typed inside `for` block
27999    cx.set_state(indoc! {"
28000        for file in *.txt; do
28001            cat \"$file\"
28002            ˇ
28003    "});
28004    cx.update_editor(|editor, window, cx| {
28005        editor.handle_input("done", window, cx);
28006    });
28007    cx.wait_for_autoindent_applied().await;
28008    cx.assert_editor_state(indoc! {"
28009        for file in *.txt; do
28010            cat \"$file\"
28011        doneˇ
28012    "});
28013
28014    // test `esac` auto outdents when typed inside `case` block
28015    cx.set_state(indoc! {"
28016        case \"$1\" in
28017            start)
28018                echo \"foo bar\"
28019                ;;
28020            stop)
28021                echo \"bar baz\"
28022                ;;
28023            ˇ
28024    "});
28025    cx.update_editor(|editor, window, cx| {
28026        editor.handle_input("esac", window, cx);
28027    });
28028    cx.wait_for_autoindent_applied().await;
28029    cx.assert_editor_state(indoc! {"
28030        case \"$1\" in
28031            start)
28032                echo \"foo bar\"
28033                ;;
28034            stop)
28035                echo \"bar baz\"
28036                ;;
28037        esacˇ
28038    "});
28039
28040    // test `*)` auto outdents when typed inside `case` block
28041    cx.set_state(indoc! {"
28042        case \"$1\" in
28043            start)
28044                echo \"foo bar\"
28045                ;;
28046                ˇ
28047    "});
28048    cx.update_editor(|editor, window, cx| {
28049        editor.handle_input("*)", window, cx);
28050    });
28051    cx.wait_for_autoindent_applied().await;
28052    cx.assert_editor_state(indoc! {"
28053        case \"$1\" in
28054            start)
28055                echo \"foo bar\"
28056                ;;
28057            *)ˇ
28058    "});
28059
28060    // test `fi` outdents to correct level with nested if blocks
28061    cx.set_state(indoc! {"
28062        if [ \"$1\" = \"test\" ]; then
28063            echo \"outer if\"
28064            if [ \"$2\" = \"debug\" ]; then
28065                echo \"inner if\"
28066                ˇ
28067    "});
28068    cx.update_editor(|editor, window, cx| {
28069        editor.handle_input("fi", window, cx);
28070    });
28071    cx.wait_for_autoindent_applied().await;
28072    cx.assert_editor_state(indoc! {"
28073        if [ \"$1\" = \"test\" ]; then
28074            echo \"outer if\"
28075            if [ \"$2\" = \"debug\" ]; then
28076                echo \"inner if\"
28077            fiˇ
28078    "});
28079}
28080
28081#[gpui::test]
28082async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
28083    init_test(cx, |_| {});
28084    update_test_language_settings(cx, &|settings| {
28085        settings.defaults.extend_comment_on_newline = Some(false);
28086    });
28087    let mut cx = EditorTestContext::new(cx).await;
28088    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
28089    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28090
28091    // test correct indent after newline on comment
28092    cx.set_state(indoc! {"
28093        # COMMENT:ˇ
28094    "});
28095    cx.update_editor(|editor, window, cx| {
28096        editor.newline(&Newline, window, cx);
28097    });
28098    cx.wait_for_autoindent_applied().await;
28099    cx.assert_editor_state(indoc! {"
28100        # COMMENT:
28101        ˇ
28102    "});
28103
28104    // test correct indent after newline after `then`
28105    cx.set_state(indoc! {"
28106
28107        if [ \"$1\" = \"test\" ]; thenˇ
28108    "});
28109    cx.update_editor(|editor, window, cx| {
28110        editor.newline(&Newline, window, cx);
28111    });
28112    cx.wait_for_autoindent_applied().await;
28113    cx.assert_editor_state(indoc! {"
28114
28115        if [ \"$1\" = \"test\" ]; then
28116            ˇ
28117    "});
28118
28119    // test correct indent after newline after `else`
28120    cx.set_state(indoc! {"
28121        if [ \"$1\" = \"test\" ]; then
28122        elseˇ
28123    "});
28124    cx.update_editor(|editor, window, cx| {
28125        editor.newline(&Newline, window, cx);
28126    });
28127    cx.wait_for_autoindent_applied().await;
28128    cx.assert_editor_state(indoc! {"
28129        if [ \"$1\" = \"test\" ]; then
28130        else
28131            ˇ
28132    "});
28133
28134    // test correct indent after newline after `elif`
28135    cx.set_state(indoc! {"
28136        if [ \"$1\" = \"test\" ]; then
28137        elifˇ
28138    "});
28139    cx.update_editor(|editor, window, cx| {
28140        editor.newline(&Newline, window, cx);
28141    });
28142    cx.wait_for_autoindent_applied().await;
28143    cx.assert_editor_state(indoc! {"
28144        if [ \"$1\" = \"test\" ]; then
28145        elif
28146            ˇ
28147    "});
28148
28149    // test correct indent after newline after `do`
28150    cx.set_state(indoc! {"
28151        for file in *.txt; doˇ
28152    "});
28153    cx.update_editor(|editor, window, cx| {
28154        editor.newline(&Newline, window, cx);
28155    });
28156    cx.wait_for_autoindent_applied().await;
28157    cx.assert_editor_state(indoc! {"
28158        for file in *.txt; do
28159            ˇ
28160    "});
28161
28162    // test correct indent after newline after case pattern
28163    cx.set_state(indoc! {"
28164        case \"$1\" in
28165            start)ˇ
28166    "});
28167    cx.update_editor(|editor, window, cx| {
28168        editor.newline(&Newline, window, cx);
28169    });
28170    cx.wait_for_autoindent_applied().await;
28171    cx.assert_editor_state(indoc! {"
28172        case \"$1\" in
28173            start)
28174                ˇ
28175    "});
28176
28177    // test correct indent after newline after case pattern
28178    cx.set_state(indoc! {"
28179        case \"$1\" in
28180            start)
28181                ;;
28182            *)ˇ
28183    "});
28184    cx.update_editor(|editor, window, cx| {
28185        editor.newline(&Newline, window, cx);
28186    });
28187    cx.wait_for_autoindent_applied().await;
28188    cx.assert_editor_state(indoc! {"
28189        case \"$1\" in
28190            start)
28191                ;;
28192            *)
28193                ˇ
28194    "});
28195
28196    // test correct indent after newline after function opening brace
28197    cx.set_state(indoc! {"
28198        function test() {ˇ}
28199    "});
28200    cx.update_editor(|editor, window, cx| {
28201        editor.newline(&Newline, window, cx);
28202    });
28203    cx.wait_for_autoindent_applied().await;
28204    cx.assert_editor_state(indoc! {"
28205        function test() {
28206            ˇ
28207        }
28208    "});
28209
28210    // test no extra indent after semicolon on same line
28211    cx.set_state(indoc! {"
28212        echo \"test\"28213    "});
28214    cx.update_editor(|editor, window, cx| {
28215        editor.newline(&Newline, window, cx);
28216    });
28217    cx.wait_for_autoindent_applied().await;
28218    cx.assert_editor_state(indoc! {"
28219        echo \"test\";
28220        ˇ
28221    "});
28222}
28223
28224fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
28225    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
28226    point..point
28227}
28228
28229#[track_caller]
28230fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
28231    let (text, ranges) = marked_text_ranges(marked_text, true);
28232    assert_eq!(editor.text(cx), text);
28233    assert_eq!(
28234        editor.selections.ranges(&editor.display_snapshot(cx)),
28235        ranges
28236            .iter()
28237            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
28238            .collect::<Vec<_>>(),
28239        "Assert selections are {}",
28240        marked_text
28241    );
28242}
28243
28244pub fn handle_signature_help_request(
28245    cx: &mut EditorLspTestContext,
28246    mocked_response: lsp::SignatureHelp,
28247) -> impl Future<Output = ()> + use<> {
28248    let mut request =
28249        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
28250            let mocked_response = mocked_response.clone();
28251            async move { Ok(Some(mocked_response)) }
28252        });
28253
28254    async move {
28255        request.next().await;
28256    }
28257}
28258
28259#[track_caller]
28260pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
28261    cx.update_editor(|editor, _, _| {
28262        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
28263            let entries = menu.entries.borrow();
28264            let entries = entries
28265                .iter()
28266                .map(|entry| entry.string.as_str())
28267                .collect::<Vec<_>>();
28268            assert_eq!(entries, expected);
28269        } else {
28270            panic!("Expected completions menu");
28271        }
28272    });
28273}
28274
28275#[gpui::test]
28276async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
28277    init_test(cx, |_| {});
28278    let mut cx = EditorLspTestContext::new_rust(
28279        lsp::ServerCapabilities {
28280            completion_provider: Some(lsp::CompletionOptions {
28281                ..Default::default()
28282            }),
28283            ..Default::default()
28284        },
28285        cx,
28286    )
28287    .await;
28288    cx.lsp
28289        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
28290            Ok(Some(lsp::CompletionResponse::Array(vec![
28291                lsp::CompletionItem {
28292                    label: "unsafe".into(),
28293                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
28294                        range: lsp::Range {
28295                            start: lsp::Position {
28296                                line: 0,
28297                                character: 9,
28298                            },
28299                            end: lsp::Position {
28300                                line: 0,
28301                                character: 11,
28302                            },
28303                        },
28304                        new_text: "unsafe".to_string(),
28305                    })),
28306                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
28307                    ..Default::default()
28308                },
28309            ])))
28310        });
28311
28312    cx.update_editor(|editor, _, cx| {
28313        editor.project().unwrap().update(cx, |project, cx| {
28314            project.snippets().update(cx, |snippets, _cx| {
28315                snippets.add_snippet_for_test(
28316                    None,
28317                    PathBuf::from("test_snippets.json"),
28318                    vec![
28319                        Arc::new(project::snippet_provider::Snippet {
28320                            prefix: vec![
28321                                "unlimited word count".to_string(),
28322                                "unlimit word count".to_string(),
28323                                "unlimited unknown".to_string(),
28324                            ],
28325                            body: "this is many words".to_string(),
28326                            description: Some("description".to_string()),
28327                            name: "multi-word snippet test".to_string(),
28328                        }),
28329                        Arc::new(project::snippet_provider::Snippet {
28330                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
28331                            body: "fewer words".to_string(),
28332                            description: Some("alt description".to_string()),
28333                            name: "other name".to_string(),
28334                        }),
28335                        Arc::new(project::snippet_provider::Snippet {
28336                            prefix: vec!["ab aa".to_string()],
28337                            body: "abcd".to_string(),
28338                            description: None,
28339                            name: "alphabet".to_string(),
28340                        }),
28341                    ],
28342                );
28343            });
28344        })
28345    });
28346
28347    let get_completions = |cx: &mut EditorLspTestContext| {
28348        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
28349            Some(CodeContextMenu::Completions(context_menu)) => {
28350                let entries = context_menu.entries.borrow();
28351                entries
28352                    .iter()
28353                    .map(|entry| entry.string.clone())
28354                    .collect_vec()
28355            }
28356            _ => vec![],
28357        })
28358    };
28359
28360    // snippets:
28361    //  @foo
28362    //  foo bar
28363    //
28364    // when typing:
28365    //
28366    // when typing:
28367    //  - if I type a symbol "open the completions with snippets only"
28368    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
28369    //
28370    // stuff we need:
28371    //  - filtering logic change?
28372    //  - remember how far back the completion started.
28373
28374    let test_cases: &[(&str, &[&str])] = &[
28375        (
28376            "un",
28377            &[
28378                "unsafe",
28379                "unlimit word count",
28380                "unlimited unknown",
28381                "unlimited word count",
28382                "unsnip",
28383            ],
28384        ),
28385        (
28386            "u ",
28387            &[
28388                "unlimit word count",
28389                "unlimited unknown",
28390                "unlimited word count",
28391            ],
28392        ),
28393        ("u a", &["ab aa", "unsafe"]), // unsAfe
28394        (
28395            "u u",
28396            &[
28397                "unsafe",
28398                "unlimit word count",
28399                "unlimited unknown", // ranked highest among snippets
28400                "unlimited word count",
28401                "unsnip",
28402            ],
28403        ),
28404        ("uw c", &["unlimit word count", "unlimited word count"]),
28405        (
28406            "u w",
28407            &[
28408                "unlimit word count",
28409                "unlimited word count",
28410                "unlimited unknown",
28411            ],
28412        ),
28413        ("u w ", &["unlimit word count", "unlimited word count"]),
28414        (
28415            "u ",
28416            &[
28417                "unlimit word count",
28418                "unlimited unknown",
28419                "unlimited word count",
28420            ],
28421        ),
28422        ("wor", &[]),
28423        ("uf", &["unsafe"]),
28424        ("af", &["unsafe"]),
28425        ("afu", &[]),
28426        (
28427            "ue",
28428            &["unsafe", "unlimited unknown", "unlimited word count"],
28429        ),
28430        ("@", &["@few"]),
28431        ("@few", &["@few"]),
28432        ("@ ", &[]),
28433        ("a@", &["@few"]),
28434        ("a@f", &["@few", "unsafe"]),
28435        ("a@fw", &["@few"]),
28436        ("a", &["ab aa", "unsafe"]),
28437        ("aa", &["ab aa"]),
28438        ("aaa", &["ab aa"]),
28439        ("ab", &["ab aa"]),
28440        ("ab ", &["ab aa"]),
28441        ("ab a", &["ab aa", "unsafe"]),
28442        ("ab ab", &["ab aa"]),
28443        ("ab ab aa", &["ab aa"]),
28444    ];
28445
28446    for &(input_to_simulate, expected_completions) in test_cases {
28447        cx.set_state("fn a() { ˇ }\n");
28448        for c in input_to_simulate.split("") {
28449            cx.simulate_input(c);
28450            cx.run_until_parked();
28451        }
28452        let expected_completions = expected_completions
28453            .iter()
28454            .map(|s| s.to_string())
28455            .collect_vec();
28456        assert_eq!(
28457            get_completions(&mut cx),
28458            expected_completions,
28459            "< actual / expected >, input = {input_to_simulate:?}",
28460        );
28461    }
28462}
28463
28464/// Handle completion request passing a marked string specifying where the completion
28465/// should be triggered from using '|' character, what range should be replaced, and what completions
28466/// should be returned using '<' and '>' to delimit the range.
28467///
28468/// Also see `handle_completion_request_with_insert_and_replace`.
28469#[track_caller]
28470pub fn handle_completion_request(
28471    marked_string: &str,
28472    completions: Vec<&'static str>,
28473    is_incomplete: bool,
28474    counter: Arc<AtomicUsize>,
28475    cx: &mut EditorLspTestContext,
28476) -> impl Future<Output = ()> {
28477    let complete_from_marker: TextRangeMarker = '|'.into();
28478    let replace_range_marker: TextRangeMarker = ('<', '>').into();
28479    let (_, mut marked_ranges) = marked_text_ranges_by(
28480        marked_string,
28481        vec![complete_from_marker.clone(), replace_range_marker.clone()],
28482    );
28483
28484    let complete_from_position = cx.to_lsp(MultiBufferOffset(
28485        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
28486    ));
28487    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
28488    let replace_range =
28489        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
28490
28491    let mut request =
28492        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
28493            let completions = completions.clone();
28494            counter.fetch_add(1, atomic::Ordering::Release);
28495            async move {
28496                assert_eq!(params.text_document_position.text_document.uri, url.clone());
28497                assert_eq!(
28498                    params.text_document_position.position,
28499                    complete_from_position
28500                );
28501                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
28502                    is_incomplete,
28503                    item_defaults: None,
28504                    items: completions
28505                        .iter()
28506                        .map(|completion_text| lsp::CompletionItem {
28507                            label: completion_text.to_string(),
28508                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
28509                                range: replace_range,
28510                                new_text: completion_text.to_string(),
28511                            })),
28512                            ..Default::default()
28513                        })
28514                        .collect(),
28515                })))
28516            }
28517        });
28518
28519    async move {
28520        request.next().await;
28521    }
28522}
28523
28524/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
28525/// given instead, which also contains an `insert` range.
28526///
28527/// This function uses markers to define ranges:
28528/// - `|` marks the cursor position
28529/// - `<>` marks the replace range
28530/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
28531pub fn handle_completion_request_with_insert_and_replace(
28532    cx: &mut EditorLspTestContext,
28533    marked_string: &str,
28534    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
28535    counter: Arc<AtomicUsize>,
28536) -> impl Future<Output = ()> {
28537    let complete_from_marker: TextRangeMarker = '|'.into();
28538    let replace_range_marker: TextRangeMarker = ('<', '>').into();
28539    let insert_range_marker: TextRangeMarker = ('{', '}').into();
28540
28541    let (_, mut marked_ranges) = marked_text_ranges_by(
28542        marked_string,
28543        vec![
28544            complete_from_marker.clone(),
28545            replace_range_marker.clone(),
28546            insert_range_marker.clone(),
28547        ],
28548    );
28549
28550    let complete_from_position = cx.to_lsp(MultiBufferOffset(
28551        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
28552    ));
28553    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
28554    let replace_range =
28555        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
28556
28557    let insert_range = match marked_ranges.remove(&insert_range_marker) {
28558        Some(ranges) if !ranges.is_empty() => {
28559            let range1 = ranges[0].clone();
28560            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
28561        }
28562        _ => lsp::Range {
28563            start: replace_range.start,
28564            end: complete_from_position,
28565        },
28566    };
28567
28568    let mut request =
28569        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
28570            let completions = completions.clone();
28571            counter.fetch_add(1, atomic::Ordering::Release);
28572            async move {
28573                assert_eq!(params.text_document_position.text_document.uri, url.clone());
28574                assert_eq!(
28575                    params.text_document_position.position, complete_from_position,
28576                    "marker `|` position doesn't match",
28577                );
28578                Ok(Some(lsp::CompletionResponse::Array(
28579                    completions
28580                        .iter()
28581                        .map(|(label, new_text)| lsp::CompletionItem {
28582                            label: label.to_string(),
28583                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
28584                                lsp::InsertReplaceEdit {
28585                                    insert: insert_range,
28586                                    replace: replace_range,
28587                                    new_text: new_text.to_string(),
28588                                },
28589                            )),
28590                            ..Default::default()
28591                        })
28592                        .collect(),
28593                )))
28594            }
28595        });
28596
28597    async move {
28598        request.next().await;
28599    }
28600}
28601
28602fn handle_resolve_completion_request(
28603    cx: &mut EditorLspTestContext,
28604    edits: Option<Vec<(&'static str, &'static str)>>,
28605) -> impl Future<Output = ()> {
28606    let edits = edits.map(|edits| {
28607        edits
28608            .iter()
28609            .map(|(marked_string, new_text)| {
28610                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
28611                let replace_range = cx.to_lsp_range(
28612                    MultiBufferOffset(marked_ranges[0].start)
28613                        ..MultiBufferOffset(marked_ranges[0].end),
28614                );
28615                lsp::TextEdit::new(replace_range, new_text.to_string())
28616            })
28617            .collect::<Vec<_>>()
28618    });
28619
28620    let mut request =
28621        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
28622            let edits = edits.clone();
28623            async move {
28624                Ok(lsp::CompletionItem {
28625                    additional_text_edits: edits,
28626                    ..Default::default()
28627                })
28628            }
28629        });
28630
28631    async move {
28632        request.next().await;
28633    }
28634}
28635
28636pub(crate) fn update_test_language_settings(
28637    cx: &mut TestAppContext,
28638    f: &dyn Fn(&mut AllLanguageSettingsContent),
28639) {
28640    cx.update(|cx| {
28641        SettingsStore::update_global(cx, |store, cx| {
28642            store.update_user_settings(cx, &|settings: &mut SettingsContent| {
28643                f(&mut settings.project.all_languages)
28644            });
28645        });
28646    });
28647}
28648
28649pub(crate) fn update_test_project_settings(
28650    cx: &mut TestAppContext,
28651    f: &dyn Fn(&mut ProjectSettingsContent),
28652) {
28653    cx.update(|cx| {
28654        SettingsStore::update_global(cx, |store, cx| {
28655            store.update_user_settings(cx, |settings| f(&mut settings.project));
28656        });
28657    });
28658}
28659
28660pub(crate) fn update_test_editor_settings(
28661    cx: &mut TestAppContext,
28662    f: &dyn Fn(&mut EditorSettingsContent),
28663) {
28664    cx.update(|cx| {
28665        SettingsStore::update_global(cx, |store, cx| {
28666            store.update_user_settings(cx, |settings| f(&mut settings.editor));
28667        })
28668    })
28669}
28670
28671pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
28672    cx.update(|cx| {
28673        assets::Assets.load_test_fonts(cx);
28674        let store = SettingsStore::test(cx);
28675        cx.set_global(store);
28676        theme::init(theme::LoadThemes::JustBase, cx);
28677        release_channel::init(semver::Version::new(0, 0, 0), cx);
28678        crate::init(cx);
28679    });
28680    zlog::init_test();
28681    update_test_language_settings(cx, &f);
28682}
28683
28684#[track_caller]
28685fn assert_hunk_revert(
28686    not_reverted_text_with_selections: &str,
28687    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
28688    expected_reverted_text_with_selections: &str,
28689    base_text: &str,
28690    cx: &mut EditorLspTestContext,
28691) {
28692    cx.set_state(not_reverted_text_with_selections);
28693    cx.set_head_text(base_text);
28694    cx.executor().run_until_parked();
28695
28696    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
28697        let snapshot = editor.snapshot(window, cx);
28698        let reverted_hunk_statuses = snapshot
28699            .buffer_snapshot()
28700            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
28701            .map(|hunk| hunk.status().kind)
28702            .collect::<Vec<_>>();
28703
28704        editor.git_restore(&Default::default(), window, cx);
28705        reverted_hunk_statuses
28706    });
28707    cx.executor().run_until_parked();
28708    cx.assert_editor_state(expected_reverted_text_with_selections);
28709    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
28710}
28711
28712#[gpui::test(iterations = 10)]
28713async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
28714    init_test(cx, |_| {});
28715
28716    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
28717    let counter = diagnostic_requests.clone();
28718
28719    let fs = FakeFs::new(cx.executor());
28720    fs.insert_tree(
28721        path!("/a"),
28722        json!({
28723            "first.rs": "fn main() { let a = 5; }",
28724            "second.rs": "// Test file",
28725        }),
28726    )
28727    .await;
28728
28729    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
28730    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
28731    let workspace = window
28732        .read_with(cx, |mw, _| mw.workspace().clone())
28733        .unwrap();
28734    let cx = &mut VisualTestContext::from_window(*window, cx);
28735
28736    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28737    language_registry.add(rust_lang());
28738    let mut fake_servers = language_registry.register_fake_lsp(
28739        "Rust",
28740        FakeLspAdapter {
28741            capabilities: lsp::ServerCapabilities {
28742                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
28743                    lsp::DiagnosticOptions {
28744                        identifier: None,
28745                        inter_file_dependencies: true,
28746                        workspace_diagnostics: true,
28747                        work_done_progress_options: Default::default(),
28748                    },
28749                )),
28750                ..Default::default()
28751            },
28752            ..Default::default()
28753        },
28754    );
28755
28756    let editor = workspace
28757        .update_in(cx, |workspace, window, cx| {
28758            workspace.open_abs_path(
28759                PathBuf::from(path!("/a/first.rs")),
28760                OpenOptions::default(),
28761                window,
28762                cx,
28763            )
28764        })
28765        .await
28766        .unwrap()
28767        .downcast::<Editor>()
28768        .unwrap();
28769    let fake_server = fake_servers.next().await.unwrap();
28770    let server_id = fake_server.server.server_id();
28771    let mut first_request = fake_server
28772        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
28773            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
28774            let result_id = Some(new_result_id.to_string());
28775            assert_eq!(
28776                params.text_document.uri,
28777                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
28778            );
28779            async move {
28780                Ok(lsp::DocumentDiagnosticReportResult::Report(
28781                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
28782                        related_documents: None,
28783                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
28784                            items: Vec::new(),
28785                            result_id,
28786                        },
28787                    }),
28788                ))
28789            }
28790        });
28791
28792    let ensure_result_id = |expected_result_id: Option<SharedString>, cx: &mut TestAppContext| {
28793        project.update(cx, |project, cx| {
28794            let buffer_id = editor
28795                .read(cx)
28796                .buffer()
28797                .read(cx)
28798                .as_singleton()
28799                .expect("created a singleton buffer")
28800                .read(cx)
28801                .remote_id();
28802            let buffer_result_id = project
28803                .lsp_store()
28804                .read(cx)
28805                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
28806            assert_eq!(expected_result_id, buffer_result_id);
28807        });
28808    };
28809
28810    ensure_result_id(None, cx);
28811    cx.executor().advance_clock(Duration::from_millis(60));
28812    cx.executor().run_until_parked();
28813    assert_eq!(
28814        diagnostic_requests.load(atomic::Ordering::Acquire),
28815        1,
28816        "Opening file should trigger diagnostic request"
28817    );
28818    first_request
28819        .next()
28820        .await
28821        .expect("should have sent the first diagnostics pull request");
28822    ensure_result_id(Some(SharedString::new_static("1")), cx);
28823
28824    // Editing should trigger diagnostics
28825    editor.update_in(cx, |editor, window, cx| {
28826        editor.handle_input("2", window, cx)
28827    });
28828    cx.executor().advance_clock(Duration::from_millis(60));
28829    cx.executor().run_until_parked();
28830    assert_eq!(
28831        diagnostic_requests.load(atomic::Ordering::Acquire),
28832        2,
28833        "Editing should trigger diagnostic request"
28834    );
28835    ensure_result_id(Some(SharedString::new_static("2")), cx);
28836
28837    // Moving cursor should not trigger diagnostic request
28838    editor.update_in(cx, |editor, window, cx| {
28839        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28840            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
28841        });
28842    });
28843    cx.executor().advance_clock(Duration::from_millis(60));
28844    cx.executor().run_until_parked();
28845    assert_eq!(
28846        diagnostic_requests.load(atomic::Ordering::Acquire),
28847        2,
28848        "Cursor movement should not trigger diagnostic request"
28849    );
28850    ensure_result_id(Some(SharedString::new_static("2")), cx);
28851    // Multiple rapid edits should be debounced
28852    for _ in 0..5 {
28853        editor.update_in(cx, |editor, window, cx| {
28854            editor.handle_input("x", window, cx)
28855        });
28856    }
28857    cx.executor().advance_clock(Duration::from_millis(60));
28858    cx.executor().run_until_parked();
28859
28860    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
28861    assert!(
28862        final_requests <= 4,
28863        "Multiple rapid edits should be debounced (got {final_requests} requests)",
28864    );
28865    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
28866}
28867
28868#[gpui::test]
28869async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
28870    // Regression test for issue #11671
28871    // Previously, adding a cursor after moving multiple cursors would reset
28872    // the cursor count instead of adding to the existing cursors.
28873    init_test(cx, |_| {});
28874    let mut cx = EditorTestContext::new(cx).await;
28875
28876    // Create a simple buffer with cursor at start
28877    cx.set_state(indoc! {"
28878        ˇaaaa
28879        bbbb
28880        cccc
28881        dddd
28882        eeee
28883        ffff
28884        gggg
28885        hhhh"});
28886
28887    // Add 2 cursors below (so we have 3 total)
28888    cx.update_editor(|editor, window, cx| {
28889        editor.add_selection_below(&Default::default(), window, cx);
28890        editor.add_selection_below(&Default::default(), window, cx);
28891    });
28892
28893    // Verify we have 3 cursors
28894    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
28895    assert_eq!(
28896        initial_count, 3,
28897        "Should have 3 cursors after adding 2 below"
28898    );
28899
28900    // Move down one line
28901    cx.update_editor(|editor, window, cx| {
28902        editor.move_down(&MoveDown, window, cx);
28903    });
28904
28905    // Add another cursor below
28906    cx.update_editor(|editor, window, cx| {
28907        editor.add_selection_below(&Default::default(), window, cx);
28908    });
28909
28910    // Should now have 4 cursors (3 original + 1 new)
28911    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
28912    assert_eq!(
28913        final_count, 4,
28914        "Should have 4 cursors after moving and adding another"
28915    );
28916}
28917
28918#[gpui::test]
28919async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
28920    init_test(cx, |_| {});
28921
28922    let mut cx = EditorTestContext::new(cx).await;
28923
28924    cx.set_state(indoc!(
28925        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
28926           Second line here"#
28927    ));
28928
28929    cx.update_editor(|editor, window, cx| {
28930        // Enable soft wrapping with a narrow width to force soft wrapping and
28931        // confirm that more than 2 rows are being displayed.
28932        editor.set_wrap_width(Some(100.0.into()), cx);
28933        assert!(editor.display_text(cx).lines().count() > 2);
28934
28935        editor.add_selection_below(
28936            &AddSelectionBelow {
28937                skip_soft_wrap: true,
28938            },
28939            window,
28940            cx,
28941        );
28942
28943        assert_eq!(
28944            display_ranges(editor, cx),
28945            &[
28946                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
28947                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
28948            ]
28949        );
28950
28951        editor.add_selection_above(
28952            &AddSelectionAbove {
28953                skip_soft_wrap: true,
28954            },
28955            window,
28956            cx,
28957        );
28958
28959        assert_eq!(
28960            display_ranges(editor, cx),
28961            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
28962        );
28963
28964        editor.add_selection_below(
28965            &AddSelectionBelow {
28966                skip_soft_wrap: false,
28967            },
28968            window,
28969            cx,
28970        );
28971
28972        assert_eq!(
28973            display_ranges(editor, cx),
28974            &[
28975                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
28976                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
28977            ]
28978        );
28979
28980        editor.add_selection_above(
28981            &AddSelectionAbove {
28982                skip_soft_wrap: false,
28983            },
28984            window,
28985            cx,
28986        );
28987
28988        assert_eq!(
28989            display_ranges(editor, cx),
28990            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
28991        );
28992    });
28993
28994    // Set up text where selections are in the middle of a soft-wrapped line.
28995    // When adding selection below with `skip_soft_wrap` set to `true`, the new
28996    // selection should be at the same buffer column, not the same pixel
28997    // position.
28998    cx.set_state(indoc!(
28999        r#"1. Very long line to show «howˇ» a wrapped line would look
29000           2. Very long line to show how a wrapped line would look"#
29001    ));
29002
29003    cx.update_editor(|editor, window, cx| {
29004        // Enable soft wrapping with a narrow width to force soft wrapping and
29005        // confirm that more than 2 rows are being displayed.
29006        editor.set_wrap_width(Some(100.0.into()), cx);
29007        assert!(editor.display_text(cx).lines().count() > 2);
29008
29009        editor.add_selection_below(
29010            &AddSelectionBelow {
29011                skip_soft_wrap: true,
29012            },
29013            window,
29014            cx,
29015        );
29016
29017        // Assert that there's now 2 selections, both selecting the same column
29018        // range in the buffer row.
29019        let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
29020        let selections = editor.selections.all::<Point>(&display_map);
29021        assert_eq!(selections.len(), 2);
29022        assert_eq!(selections[0].start.column, selections[1].start.column);
29023        assert_eq!(selections[0].end.column, selections[1].end.column);
29024    });
29025}
29026
29027#[gpui::test]
29028async fn test_insert_snippet(cx: &mut TestAppContext) {
29029    init_test(cx, |_| {});
29030    let mut cx = EditorTestContext::new(cx).await;
29031
29032    cx.update_editor(|editor, _, cx| {
29033        editor.project().unwrap().update(cx, |project, cx| {
29034            project.snippets().update(cx, |snippets, _cx| {
29035                let snippet = project::snippet_provider::Snippet {
29036                    prefix: vec![], // no prefix needed!
29037                    body: "an Unspecified".to_string(),
29038                    description: Some("shhhh it's a secret".to_string()),
29039                    name: "super secret snippet".to_string(),
29040                };
29041                snippets.add_snippet_for_test(
29042                    None,
29043                    PathBuf::from("test_snippets.json"),
29044                    vec![Arc::new(snippet)],
29045                );
29046
29047                let snippet = project::snippet_provider::Snippet {
29048                    prefix: vec![], // no prefix needed!
29049                    body: " Location".to_string(),
29050                    description: Some("the word 'location'".to_string()),
29051                    name: "location word".to_string(),
29052                };
29053                snippets.add_snippet_for_test(
29054                    Some("Markdown".to_string()),
29055                    PathBuf::from("test_snippets.json"),
29056                    vec![Arc::new(snippet)],
29057                );
29058            });
29059        })
29060    });
29061
29062    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
29063
29064    cx.update_editor(|editor, window, cx| {
29065        editor.insert_snippet_at_selections(
29066            &InsertSnippet {
29067                language: None,
29068                name: Some("super secret snippet".to_string()),
29069                snippet: None,
29070            },
29071            window,
29072            cx,
29073        );
29074
29075        // Language is specified in the action,
29076        // so the buffer language does not need to match
29077        editor.insert_snippet_at_selections(
29078            &InsertSnippet {
29079                language: Some("Markdown".to_string()),
29080                name: Some("location word".to_string()),
29081                snippet: None,
29082            },
29083            window,
29084            cx,
29085        );
29086
29087        editor.insert_snippet_at_selections(
29088            &InsertSnippet {
29089                language: None,
29090                name: None,
29091                snippet: Some("$0 after".to_string()),
29092            },
29093            window,
29094            cx,
29095        );
29096    });
29097
29098    cx.assert_editor_state(
29099        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
29100    );
29101}
29102
29103#[gpui::test]
29104async fn test_inlay_hints_request_timeout(cx: &mut TestAppContext) {
29105    use crate::inlays::inlay_hints::InlayHintRefreshReason;
29106    use crate::inlays::inlay_hints::tests::{cached_hint_labels, init_test, visible_hint_labels};
29107    use settings::InlayHintSettingsContent;
29108    use std::sync::atomic::AtomicU32;
29109    use std::time::Duration;
29110
29111    const BASE_TIMEOUT_SECS: u64 = 1;
29112
29113    let request_count = Arc::new(AtomicU32::new(0));
29114    let closure_request_count = request_count.clone();
29115
29116    init_test(cx, &|settings| {
29117        settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
29118            enabled: Some(true),
29119            ..InlayHintSettingsContent::default()
29120        })
29121    });
29122    cx.update(|cx| {
29123        SettingsStore::update_global(cx, |store, cx| {
29124            store.update_user_settings(cx, &|settings: &mut SettingsContent| {
29125                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
29126                    request_timeout: Some(BASE_TIMEOUT_SECS),
29127                    button: Some(true),
29128                    notifications: None,
29129                    semantic_token_rules: None,
29130                });
29131            });
29132        });
29133    });
29134
29135    let fs = FakeFs::new(cx.executor());
29136    fs.insert_tree(
29137        path!("/a"),
29138        json!({
29139            "main.rs": "fn main() { let a = 5; }",
29140        }),
29141    )
29142    .await;
29143
29144    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
29145    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
29146    language_registry.add(rust_lang());
29147    let mut fake_servers = language_registry.register_fake_lsp(
29148        "Rust",
29149        FakeLspAdapter {
29150            capabilities: lsp::ServerCapabilities {
29151                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
29152                ..lsp::ServerCapabilities::default()
29153            },
29154            initializer: Some(Box::new(move |fake_server| {
29155                let request_count = closure_request_count.clone();
29156                fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
29157                    move |params, cx| {
29158                        let request_count = request_count.clone();
29159                        async move {
29160                            cx.background_executor()
29161                                .timer(Duration::from_secs(BASE_TIMEOUT_SECS * 2))
29162                                .await;
29163                            let count = request_count.fetch_add(1, atomic::Ordering::Release) + 1;
29164                            assert_eq!(
29165                                params.text_document.uri,
29166                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
29167                            );
29168                            Ok(Some(vec![lsp::InlayHint {
29169                                position: lsp::Position::new(0, 1),
29170                                label: lsp::InlayHintLabel::String(count.to_string()),
29171                                kind: None,
29172                                text_edits: None,
29173                                tooltip: None,
29174                                padding_left: None,
29175                                padding_right: None,
29176                                data: None,
29177                            }]))
29178                        }
29179                    },
29180                );
29181            })),
29182            ..FakeLspAdapter::default()
29183        },
29184    );
29185
29186    let buffer = project
29187        .update(cx, |project, cx| {
29188            project.open_local_buffer(path!("/a/main.rs"), cx)
29189        })
29190        .await
29191        .unwrap();
29192    let editor = cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
29193
29194    cx.executor().run_until_parked();
29195    let fake_server = fake_servers.next().await.unwrap();
29196
29197    cx.executor()
29198        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
29199    cx.executor().run_until_parked();
29200    editor
29201        .update(cx, |editor, _window, cx| {
29202            assert!(
29203                cached_hint_labels(editor, cx).is_empty(),
29204                "First request should time out, no hints cached"
29205            );
29206        })
29207        .unwrap();
29208
29209    editor
29210        .update(cx, |editor, _window, cx| {
29211            editor.refresh_inlay_hints(
29212                InlayHintRefreshReason::RefreshRequested {
29213                    server_id: fake_server.server.server_id(),
29214                    request_id: Some(1),
29215                },
29216                cx,
29217            );
29218        })
29219        .unwrap();
29220    cx.executor()
29221        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
29222    cx.executor().run_until_parked();
29223    editor
29224        .update(cx, |editor, _window, cx| {
29225            assert!(
29226                cached_hint_labels(editor, cx).is_empty(),
29227                "Second request should also time out with BASE_TIMEOUT, no hints cached"
29228            );
29229        })
29230        .unwrap();
29231
29232    cx.update(|cx| {
29233        SettingsStore::update_global(cx, |store, cx| {
29234            store.update_user_settings(cx, |settings| {
29235                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
29236                    request_timeout: Some(BASE_TIMEOUT_SECS * 4),
29237                    button: Some(true),
29238                    notifications: None,
29239                    semantic_token_rules: None,
29240                });
29241            });
29242        });
29243    });
29244    editor
29245        .update(cx, |editor, _window, cx| {
29246            editor.refresh_inlay_hints(
29247                InlayHintRefreshReason::RefreshRequested {
29248                    server_id: fake_server.server.server_id(),
29249                    request_id: Some(2),
29250                },
29251                cx,
29252            );
29253        })
29254        .unwrap();
29255    cx.executor()
29256        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS * 4) + Duration::from_millis(100));
29257    cx.executor().run_until_parked();
29258    editor
29259        .update(cx, |editor, _window, cx| {
29260            assert_eq!(
29261                vec!["1".to_string()],
29262                cached_hint_labels(editor, cx),
29263                "With extended timeout (BASE * 4), hints should arrive successfully"
29264            );
29265            assert_eq!(vec!["1".to_string()], visible_hint_labels(editor, cx));
29266        })
29267        .unwrap();
29268}
29269
29270#[gpui::test]
29271async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
29272    init_test(cx, |_| {});
29273    let (editor, cx) = cx.add_window_view(Editor::single_line);
29274    editor.update_in(cx, |editor, window, cx| {
29275        editor.set_text("oops\n\nwow\n", window, cx)
29276    });
29277    cx.run_until_parked();
29278    editor.update(cx, |editor, cx| {
29279        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
29280    });
29281    editor.update(cx, |editor, cx| {
29282        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
29283    });
29284    cx.run_until_parked();
29285    editor.update(cx, |editor, cx| {
29286        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
29287    });
29288}
29289
29290#[gpui::test]
29291async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
29292    init_test(cx, |_| {});
29293
29294    cx.update(|cx| {
29295        register_project_item::<Editor>(cx);
29296    });
29297
29298    let fs = FakeFs::new(cx.executor());
29299    fs.insert_tree("/root1", json!({})).await;
29300    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
29301        .await;
29302
29303    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
29304    let (multi_workspace, cx) =
29305        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
29306    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
29307
29308    let worktree_id = project.update(cx, |project, cx| {
29309        project.worktrees(cx).next().unwrap().read(cx).id()
29310    });
29311
29312    let handle = workspace
29313        .update_in(cx, |workspace, window, cx| {
29314            let project_path = (worktree_id, rel_path("one.pdf"));
29315            workspace.open_path(project_path, None, true, window, cx)
29316        })
29317        .await
29318        .unwrap();
29319    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
29320    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
29321    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
29322    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
29323}
29324
29325#[gpui::test]
29326async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
29327    init_test(cx, |_| {});
29328
29329    let language = Arc::new(Language::new(
29330        LanguageConfig::default(),
29331        Some(tree_sitter_rust::LANGUAGE.into()),
29332    ));
29333
29334    // Test hierarchical sibling navigation
29335    let text = r#"
29336        fn outer() {
29337            if condition {
29338                let a = 1;
29339            }
29340            let b = 2;
29341        }
29342
29343        fn another() {
29344            let c = 3;
29345        }
29346    "#;
29347
29348    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
29349    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
29350    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
29351
29352    // Wait for parsing to complete
29353    editor
29354        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
29355        .await;
29356
29357    editor.update_in(cx, |editor, window, cx| {
29358        // Start by selecting "let a = 1;" inside the if block
29359        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
29360            s.select_display_ranges([
29361                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
29362            ]);
29363        });
29364
29365        let initial_selection = editor
29366            .selections
29367            .display_ranges(&editor.display_snapshot(cx));
29368        assert_eq!(initial_selection.len(), 1, "Should have one selection");
29369
29370        // Test select next sibling - should move up levels to find the next sibling
29371        // Since "let a = 1;" has no siblings in the if block, it should move up
29372        // to find "let b = 2;" which is a sibling of the if block
29373        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
29374        let next_selection = editor
29375            .selections
29376            .display_ranges(&editor.display_snapshot(cx));
29377
29378        // Should have a selection and it should be different from the initial
29379        assert_eq!(
29380            next_selection.len(),
29381            1,
29382            "Should have one selection after next"
29383        );
29384        assert_ne!(
29385            next_selection[0], initial_selection[0],
29386            "Next sibling selection should be different"
29387        );
29388
29389        // Test hierarchical navigation by going to the end of the current function
29390        // and trying to navigate to the next function
29391        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
29392            s.select_display_ranges([
29393                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
29394            ]);
29395        });
29396
29397        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
29398        let function_next_selection = editor
29399            .selections
29400            .display_ranges(&editor.display_snapshot(cx));
29401
29402        // Should move to the next function
29403        assert_eq!(
29404            function_next_selection.len(),
29405            1,
29406            "Should have one selection after function next"
29407        );
29408
29409        // Test select previous sibling navigation
29410        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
29411        let prev_selection = editor
29412            .selections
29413            .display_ranges(&editor.display_snapshot(cx));
29414
29415        // Should have a selection and it should be different
29416        assert_eq!(
29417            prev_selection.len(),
29418            1,
29419            "Should have one selection after prev"
29420        );
29421        assert_ne!(
29422            prev_selection[0], function_next_selection[0],
29423            "Previous sibling selection should be different from next"
29424        );
29425    });
29426}
29427
29428#[gpui::test]
29429async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
29430    init_test(cx, |_| {});
29431
29432    let mut cx = EditorTestContext::new(cx).await;
29433    cx.set_state(
29434        "let ˇvariable = 42;
29435let another = variable + 1;
29436let result = variable * 2;",
29437    );
29438
29439    // Set up document highlights manually (simulating LSP response)
29440    cx.update_editor(|editor, _window, cx| {
29441        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
29442
29443        // Create highlights for "variable" occurrences
29444        let highlight_ranges = [
29445            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
29446            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
29447            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
29448        ];
29449
29450        let anchor_ranges: Vec<_> = highlight_ranges
29451            .iter()
29452            .map(|range| range.clone().to_anchors(&buffer_snapshot))
29453            .collect();
29454
29455        editor.highlight_background(
29456            HighlightKey::DocumentHighlightRead,
29457            &anchor_ranges,
29458            |_, theme| theme.colors().editor_document_highlight_read_background,
29459            cx,
29460        );
29461    });
29462
29463    // Go to next highlight - should move to second "variable"
29464    cx.update_editor(|editor, window, cx| {
29465        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
29466    });
29467    cx.assert_editor_state(
29468        "let variable = 42;
29469let another = ˇvariable + 1;
29470let result = variable * 2;",
29471    );
29472
29473    // Go to next highlight - should move to third "variable"
29474    cx.update_editor(|editor, window, cx| {
29475        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
29476    });
29477    cx.assert_editor_state(
29478        "let variable = 42;
29479let another = variable + 1;
29480let result = ˇvariable * 2;",
29481    );
29482
29483    // Go to next highlight - should stay at third "variable" (no wrap-around)
29484    cx.update_editor(|editor, window, cx| {
29485        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
29486    });
29487    cx.assert_editor_state(
29488        "let variable = 42;
29489let another = variable + 1;
29490let result = ˇvariable * 2;",
29491    );
29492
29493    // Now test going backwards from third position
29494    cx.update_editor(|editor, window, cx| {
29495        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
29496    });
29497    cx.assert_editor_state(
29498        "let variable = 42;
29499let another = ˇvariable + 1;
29500let result = variable * 2;",
29501    );
29502
29503    // Go to previous highlight - should move to first "variable"
29504    cx.update_editor(|editor, window, cx| {
29505        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
29506    });
29507    cx.assert_editor_state(
29508        "let ˇvariable = 42;
29509let another = variable + 1;
29510let result = variable * 2;",
29511    );
29512
29513    // Go to previous highlight - should stay on first "variable"
29514    cx.update_editor(|editor, window, cx| {
29515        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
29516    });
29517    cx.assert_editor_state(
29518        "let ˇvariable = 42;
29519let another = variable + 1;
29520let result = variable * 2;",
29521    );
29522}
29523
29524#[gpui::test]
29525async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
29526    cx: &mut gpui::TestAppContext,
29527) {
29528    init_test(cx, |_| {});
29529
29530    let url = "https://zed.dev";
29531
29532    let markdown_language = Arc::new(Language::new(
29533        LanguageConfig {
29534            name: "Markdown".into(),
29535            ..LanguageConfig::default()
29536        },
29537        None,
29538    ));
29539
29540    let mut cx = EditorTestContext::new(cx).await;
29541    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29542    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
29543
29544    cx.update_editor(|editor, window, cx| {
29545        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
29546        editor.paste(&Paste, window, cx);
29547    });
29548
29549    cx.assert_editor_state(&format!(
29550        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
29551    ));
29552}
29553
29554#[gpui::test]
29555async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
29556    init_test(cx, |_| {});
29557
29558    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29559    let mut cx = EditorTestContext::new(cx).await;
29560
29561    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29562
29563    // Case 1: Test if adding a character with multi cursors preserves nested list indents
29564    cx.set_state(&indoc! {"
29565        - [ ] Item 1
29566            - [ ] Item 1.a
29567        - [ˇ] Item 2
29568            - [ˇ] Item 2.a
29569            - [ˇ] Item 2.b
29570        "
29571    });
29572    cx.update_editor(|editor, window, cx| {
29573        editor.handle_input("x", window, cx);
29574    });
29575    cx.run_until_parked();
29576    cx.assert_editor_state(indoc! {"
29577        - [ ] Item 1
29578            - [ ] Item 1.a
29579        - [xˇ] Item 2
29580            - [xˇ] Item 2.a
29581            - [xˇ] Item 2.b
29582        "
29583    });
29584
29585    // Case 2: Test adding new line after nested list continues the list with unchecked task
29586    cx.set_state(&indoc! {"
29587        - [ ] Item 1
29588            - [ ] Item 1.a
29589        - [x] Item 2
29590            - [x] Item 2.a
29591            - [x] Item 2.bˇ"
29592    });
29593    cx.update_editor(|editor, window, cx| {
29594        editor.newline(&Newline, window, cx);
29595    });
29596    cx.assert_editor_state(indoc! {"
29597        - [ ] Item 1
29598            - [ ] Item 1.a
29599        - [x] Item 2
29600            - [x] Item 2.a
29601            - [x] Item 2.b
29602            - [ ] ˇ"
29603    });
29604
29605    // Case 3: Test adding content to continued list item
29606    cx.update_editor(|editor, window, cx| {
29607        editor.handle_input("Item 2.c", window, cx);
29608    });
29609    cx.run_until_parked();
29610    cx.assert_editor_state(indoc! {"
29611        - [ ] Item 1
29612            - [ ] Item 1.a
29613        - [x] Item 2
29614            - [x] Item 2.a
29615            - [x] Item 2.b
29616            - [ ] Item 2.cˇ"
29617    });
29618
29619    // Case 4: Test adding new line after nested ordered list continues with next number
29620    cx.set_state(indoc! {"
29621        1. Item 1
29622            1. Item 1.a
29623        2. Item 2
29624            1. Item 2.a
29625            2. Item 2.bˇ"
29626    });
29627    cx.update_editor(|editor, window, cx| {
29628        editor.newline(&Newline, window, cx);
29629    });
29630    cx.assert_editor_state(indoc! {"
29631        1. Item 1
29632            1. Item 1.a
29633        2. Item 2
29634            1. Item 2.a
29635            2. Item 2.b
29636            3. ˇ"
29637    });
29638
29639    // Case 5: Adding content to continued ordered list item
29640    cx.update_editor(|editor, window, cx| {
29641        editor.handle_input("Item 2.c", window, cx);
29642    });
29643    cx.run_until_parked();
29644    cx.assert_editor_state(indoc! {"
29645        1. Item 1
29646            1. Item 1.a
29647        2. Item 2
29648            1. Item 2.a
29649            2. Item 2.b
29650            3. Item 2.cˇ"
29651    });
29652
29653    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
29654    cx.set_state(indoc! {"
29655        - Item 1
29656            - Item 1.a
29657            - Item 1.a
29658        ˇ"});
29659    cx.update_editor(|editor, window, cx| {
29660        editor.handle_input("-", window, cx);
29661    });
29662    cx.run_until_parked();
29663    cx.assert_editor_state(indoc! {"
29664        - Item 1
29665            - Item 1.a
29666            - Item 1.a
29667"});
29668
29669    // Case 7: Test blockquote newline preserves something
29670    cx.set_state(indoc! {"
29671        > Item 1ˇ"
29672    });
29673    cx.update_editor(|editor, window, cx| {
29674        editor.newline(&Newline, window, cx);
29675    });
29676    cx.assert_editor_state(indoc! {"
29677        > Item 1
29678        ˇ"
29679    });
29680}
29681
29682#[gpui::test]
29683async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
29684    cx: &mut gpui::TestAppContext,
29685) {
29686    init_test(cx, |_| {});
29687
29688    let url = "https://zed.dev";
29689
29690    let markdown_language = Arc::new(Language::new(
29691        LanguageConfig {
29692            name: "Markdown".into(),
29693            ..LanguageConfig::default()
29694        },
29695        None,
29696    ));
29697
29698    let mut cx = EditorTestContext::new(cx).await;
29699    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29700    cx.set_state(&format!(
29701        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
29702    ));
29703
29704    cx.update_editor(|editor, window, cx| {
29705        editor.copy(&Copy, window, cx);
29706    });
29707
29708    cx.set_state(&format!(
29709        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
29710    ));
29711
29712    cx.update_editor(|editor, window, cx| {
29713        editor.paste(&Paste, window, cx);
29714    });
29715
29716    cx.assert_editor_state(&format!(
29717        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
29718    ));
29719}
29720
29721#[gpui::test]
29722async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
29723    cx: &mut gpui::TestAppContext,
29724) {
29725    init_test(cx, |_| {});
29726
29727    let url = "https://zed.dev";
29728
29729    let markdown_language = Arc::new(Language::new(
29730        LanguageConfig {
29731            name: "Markdown".into(),
29732            ..LanguageConfig::default()
29733        },
29734        None,
29735    ));
29736
29737    let mut cx = EditorTestContext::new(cx).await;
29738    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29739    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
29740
29741    cx.update_editor(|editor, window, cx| {
29742        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
29743        editor.paste(&Paste, window, cx);
29744    });
29745
29746    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
29747}
29748
29749#[gpui::test]
29750async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
29751    cx: &mut gpui::TestAppContext,
29752) {
29753    init_test(cx, |_| {});
29754
29755    let text = "Awesome";
29756
29757    let markdown_language = Arc::new(Language::new(
29758        LanguageConfig {
29759            name: "Markdown".into(),
29760            ..LanguageConfig::default()
29761        },
29762        None,
29763    ));
29764
29765    let mut cx = EditorTestContext::new(cx).await;
29766    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29767    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
29768
29769    cx.update_editor(|editor, window, cx| {
29770        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
29771        editor.paste(&Paste, window, cx);
29772    });
29773
29774    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
29775}
29776
29777#[gpui::test]
29778async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
29779    cx: &mut gpui::TestAppContext,
29780) {
29781    init_test(cx, |_| {});
29782
29783    let url = "https://zed.dev";
29784
29785    let markdown_language = Arc::new(Language::new(
29786        LanguageConfig {
29787            name: "Rust".into(),
29788            ..LanguageConfig::default()
29789        },
29790        None,
29791    ));
29792
29793    let mut cx = EditorTestContext::new(cx).await;
29794    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29795    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
29796
29797    cx.update_editor(|editor, window, cx| {
29798        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
29799        editor.paste(&Paste, window, cx);
29800    });
29801
29802    cx.assert_editor_state(&format!(
29803        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
29804    ));
29805}
29806
29807#[gpui::test]
29808async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
29809    cx: &mut TestAppContext,
29810) {
29811    init_test(cx, |_| {});
29812
29813    let url = "https://zed.dev";
29814
29815    let markdown_language = Arc::new(Language::new(
29816        LanguageConfig {
29817            name: "Markdown".into(),
29818            ..LanguageConfig::default()
29819        },
29820        None,
29821    ));
29822
29823    let (editor, cx) = cx.add_window_view(|window, cx| {
29824        let multi_buffer = MultiBuffer::build_multi(
29825            [
29826                ("this will embed -> link", vec![Point::row_range(0..1)]),
29827                ("this will replace -> link", vec![Point::row_range(0..1)]),
29828            ],
29829            cx,
29830        );
29831        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
29832        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
29833            s.select_ranges(vec![
29834                Point::new(0, 19)..Point::new(0, 23),
29835                Point::new(1, 21)..Point::new(1, 25),
29836            ])
29837        });
29838        let first_buffer_id = multi_buffer
29839            .read(cx)
29840            .excerpt_buffer_ids()
29841            .into_iter()
29842            .next()
29843            .unwrap();
29844        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
29845        first_buffer.update(cx, |buffer, cx| {
29846            buffer.set_language(Some(markdown_language.clone()), cx);
29847        });
29848
29849        editor
29850    });
29851    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29852
29853    cx.update_editor(|editor, window, cx| {
29854        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
29855        editor.paste(&Paste, window, cx);
29856    });
29857
29858    cx.assert_editor_state(&format!(
29859        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
29860    ));
29861}
29862
29863#[gpui::test]
29864async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
29865    init_test(cx, |_| {});
29866
29867    let fs = FakeFs::new(cx.executor());
29868    fs.insert_tree(
29869        path!("/project"),
29870        json!({
29871            "first.rs": "# First Document\nSome content here.",
29872            "second.rs": "Plain text content for second file.",
29873        }),
29874    )
29875    .await;
29876
29877    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
29878    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
29879    let cx = &mut VisualTestContext::from_window(*window, cx);
29880
29881    let language = rust_lang();
29882    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
29883    language_registry.add(language.clone());
29884    let mut fake_servers = language_registry.register_fake_lsp(
29885        "Rust",
29886        FakeLspAdapter {
29887            ..FakeLspAdapter::default()
29888        },
29889    );
29890
29891    let buffer1 = project
29892        .update(cx, |project, cx| {
29893            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
29894        })
29895        .await
29896        .unwrap();
29897    let buffer2 = project
29898        .update(cx, |project, cx| {
29899            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
29900        })
29901        .await
29902        .unwrap();
29903
29904    let multi_buffer = cx.new(|cx| {
29905        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
29906        multi_buffer.set_excerpts_for_path(
29907            PathKey::for_buffer(&buffer1, cx),
29908            buffer1.clone(),
29909            [Point::zero()..buffer1.read(cx).max_point()],
29910            3,
29911            cx,
29912        );
29913        multi_buffer.set_excerpts_for_path(
29914            PathKey::for_buffer(&buffer2, cx),
29915            buffer2.clone(),
29916            [Point::zero()..buffer1.read(cx).max_point()],
29917            3,
29918            cx,
29919        );
29920        multi_buffer
29921    });
29922
29923    let (editor, cx) = cx.add_window_view(|window, cx| {
29924        Editor::new(
29925            EditorMode::full(),
29926            multi_buffer,
29927            Some(project.clone()),
29928            window,
29929            cx,
29930        )
29931    });
29932
29933    let fake_language_server = fake_servers.next().await.unwrap();
29934
29935    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
29936
29937    let save = editor.update_in(cx, |editor, window, cx| {
29938        assert!(editor.is_dirty(cx));
29939
29940        editor.save(
29941            SaveOptions {
29942                format: true,
29943                autosave: true,
29944            },
29945            project,
29946            window,
29947            cx,
29948        )
29949    });
29950    let (start_edit_tx, start_edit_rx) = oneshot::channel();
29951    let (done_edit_tx, done_edit_rx) = oneshot::channel();
29952    let mut done_edit_rx = Some(done_edit_rx);
29953    let mut start_edit_tx = Some(start_edit_tx);
29954
29955    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
29956        start_edit_tx.take().unwrap().send(()).unwrap();
29957        let done_edit_rx = done_edit_rx.take().unwrap();
29958        async move {
29959            done_edit_rx.await.unwrap();
29960            Ok(None)
29961        }
29962    });
29963
29964    start_edit_rx.await.unwrap();
29965    buffer2
29966        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
29967        .unwrap();
29968
29969    done_edit_tx.send(()).unwrap();
29970
29971    save.await.unwrap();
29972    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
29973}
29974
29975#[gpui::test]
29976fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
29977    init_test(cx, |_| {});
29978
29979    let editor = cx.add_window(|window, cx| {
29980        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
29981        build_editor(buffer, window, cx)
29982    });
29983
29984    editor
29985        .update(cx, |editor, window, cx| {
29986            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
29987                s.select_display_ranges([
29988                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
29989                ])
29990            });
29991
29992            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
29993
29994            assert_eq!(
29995                editor.display_text(cx),
29996                "line1\nline2\nline2",
29997                "Duplicating last line upward should create duplicate above, not on same line"
29998            );
29999
30000            assert_eq!(
30001                editor
30002                    .selections
30003                    .display_ranges(&editor.display_snapshot(cx)),
30004                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
30005                "Selection should move to the duplicated line"
30006            );
30007        })
30008        .unwrap();
30009}
30010
30011#[gpui::test]
30012async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
30013    init_test(cx, |_| {});
30014
30015    let mut cx = EditorTestContext::new(cx).await;
30016
30017    cx.set_state("line1\nline2ˇ");
30018
30019    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
30020
30021    let clipboard_text = cx
30022        .read_from_clipboard()
30023        .and_then(|item| item.text().as_deref().map(str::to_string));
30024
30025    assert_eq!(
30026        clipboard_text,
30027        Some("line2\n".to_string()),
30028        "Copying a line without trailing newline should include a newline"
30029    );
30030
30031    cx.set_state("line1\nˇ");
30032
30033    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
30034
30035    cx.assert_editor_state("line1\nline2\nˇ");
30036}
30037
30038#[gpui::test]
30039async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
30040    init_test(cx, |_| {});
30041
30042    let mut cx = EditorTestContext::new(cx).await;
30043
30044    cx.set_state("ˇline1\nˇline2\nˇline3\n");
30045
30046    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
30047
30048    let clipboard_text = cx
30049        .read_from_clipboard()
30050        .and_then(|item| item.text().as_deref().map(str::to_string));
30051
30052    assert_eq!(
30053        clipboard_text,
30054        Some("line1\nline2\nline3\n".to_string()),
30055        "Copying multiple lines should include a single newline between lines"
30056    );
30057
30058    cx.set_state("lineA\nˇ");
30059
30060    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
30061
30062    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
30063}
30064
30065#[gpui::test]
30066async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
30067    init_test(cx, |_| {});
30068
30069    let mut cx = EditorTestContext::new(cx).await;
30070
30071    cx.set_state("ˇline1\nˇline2\nˇline3\n");
30072
30073    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
30074
30075    let clipboard_text = cx
30076        .read_from_clipboard()
30077        .and_then(|item| item.text().as_deref().map(str::to_string));
30078
30079    assert_eq!(
30080        clipboard_text,
30081        Some("line1\nline2\nline3\n".to_string()),
30082        "Copying multiple lines should include a single newline between lines"
30083    );
30084
30085    cx.set_state("lineA\nˇ");
30086
30087    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
30088
30089    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
30090}
30091
30092#[gpui::test]
30093async fn test_end_of_editor_context(cx: &mut TestAppContext) {
30094    init_test(cx, |_| {});
30095
30096    let mut cx = EditorTestContext::new(cx).await;
30097
30098    cx.set_state("line1\nline2ˇ");
30099    cx.update_editor(|e, window, cx| {
30100        e.set_mode(EditorMode::SingleLine);
30101        assert!(e.key_context(window, cx).contains("end_of_input"));
30102    });
30103    cx.set_state("ˇline1\nline2");
30104    cx.update_editor(|e, window, cx| {
30105        assert!(!e.key_context(window, cx).contains("end_of_input"));
30106    });
30107    cx.set_state("line1ˇ\nline2");
30108    cx.update_editor(|e, window, cx| {
30109        assert!(!e.key_context(window, cx).contains("end_of_input"));
30110    });
30111}
30112
30113#[gpui::test]
30114async fn test_sticky_scroll(cx: &mut TestAppContext) {
30115    init_test(cx, |_| {});
30116    let mut cx = EditorTestContext::new(cx).await;
30117
30118    let buffer = indoc! {"
30119            ˇfn foo() {
30120                let abc = 123;
30121            }
30122            struct Bar;
30123            impl Bar {
30124                fn new() -> Self {
30125                    Self
30126                }
30127            }
30128            fn baz() {
30129            }
30130        "};
30131    cx.set_state(&buffer);
30132
30133    cx.update_editor(|e, _, cx| {
30134        e.buffer()
30135            .read(cx)
30136            .as_singleton()
30137            .unwrap()
30138            .update(cx, |buffer, cx| {
30139                buffer.set_language(Some(rust_lang()), cx);
30140            })
30141    });
30142
30143    let mut sticky_headers = |offset: ScrollOffset| {
30144        cx.update_editor(|e, window, cx| {
30145            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
30146        });
30147        cx.run_until_parked();
30148        cx.update_editor(|e, window, cx| {
30149            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
30150                .into_iter()
30151                .map(
30152                    |StickyHeader {
30153                         start_point,
30154                         offset,
30155                         ..
30156                     }| { (start_point, offset) },
30157                )
30158                .collect::<Vec<_>>()
30159        })
30160    };
30161
30162    let fn_foo = Point { row: 0, column: 0 };
30163    let impl_bar = Point { row: 4, column: 0 };
30164    let fn_new = Point { row: 5, column: 4 };
30165
30166    assert_eq!(sticky_headers(0.0), vec![]);
30167    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
30168    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
30169    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
30170    assert_eq!(sticky_headers(2.0), vec![]);
30171    assert_eq!(sticky_headers(2.5), vec![]);
30172    assert_eq!(sticky_headers(3.0), vec![]);
30173    assert_eq!(sticky_headers(3.5), vec![]);
30174    assert_eq!(sticky_headers(4.0), vec![]);
30175    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
30176    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
30177    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
30178    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
30179    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
30180    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
30181    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
30182    assert_eq!(sticky_headers(8.0), vec![]);
30183    assert_eq!(sticky_headers(8.5), vec![]);
30184    assert_eq!(sticky_headers(9.0), vec![]);
30185    assert_eq!(sticky_headers(9.5), vec![]);
30186    assert_eq!(sticky_headers(10.0), vec![]);
30187}
30188
30189#[gpui::test]
30190async fn test_sticky_scroll_with_expanded_deleted_diff_hunks(
30191    executor: BackgroundExecutor,
30192    cx: &mut TestAppContext,
30193) {
30194    init_test(cx, |_| {});
30195    let mut cx = EditorTestContext::new(cx).await;
30196
30197    let diff_base = indoc! {"
30198        fn foo() {
30199            let a = 1;
30200            let b = 2;
30201            let c = 3;
30202            let d = 4;
30203            let e = 5;
30204        }
30205    "};
30206
30207    let buffer = indoc! {"
30208        ˇfn foo() {
30209        }
30210    "};
30211
30212    cx.set_state(&buffer);
30213
30214    cx.update_editor(|e, _, cx| {
30215        e.buffer()
30216            .read(cx)
30217            .as_singleton()
30218            .unwrap()
30219            .update(cx, |buffer, cx| {
30220                buffer.set_language(Some(rust_lang()), cx);
30221            })
30222    });
30223
30224    cx.set_head_text(diff_base);
30225    executor.run_until_parked();
30226
30227    cx.update_editor(|editor, window, cx| {
30228        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
30229    });
30230    executor.run_until_parked();
30231
30232    // After expanding, the display should look like:
30233    //   row 0: fn foo() {
30234    //   row 1: -    let a = 1;   (deleted)
30235    //   row 2: -    let b = 2;   (deleted)
30236    //   row 3: -    let c = 3;   (deleted)
30237    //   row 4: -    let d = 4;   (deleted)
30238    //   row 5: -    let e = 5;   (deleted)
30239    //   row 6: }
30240    //
30241    // fn foo() spans display rows 0-6. Scrolling into the deleted region
30242    // (rows 1-5) should still show fn foo() as a sticky header.
30243
30244    let fn_foo = Point { row: 0, column: 0 };
30245
30246    let mut sticky_headers = |offset: ScrollOffset| {
30247        cx.update_editor(|e, window, cx| {
30248            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
30249        });
30250        cx.run_until_parked();
30251        cx.update_editor(|e, window, cx| {
30252            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
30253                .into_iter()
30254                .map(
30255                    |StickyHeader {
30256                         start_point,
30257                         offset,
30258                         ..
30259                     }| { (start_point, offset) },
30260                )
30261                .collect::<Vec<_>>()
30262        })
30263    };
30264
30265    assert_eq!(sticky_headers(0.0), vec![]);
30266    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
30267    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
30268    // Scrolling into deleted lines: fn foo() should still be a sticky header.
30269    assert_eq!(sticky_headers(2.0), vec![(fn_foo, 0.0)]);
30270    assert_eq!(sticky_headers(3.0), vec![(fn_foo, 0.0)]);
30271    assert_eq!(sticky_headers(4.0), vec![(fn_foo, 0.0)]);
30272    assert_eq!(sticky_headers(5.0), vec![(fn_foo, 0.0)]);
30273    assert_eq!(sticky_headers(5.5), vec![(fn_foo, -0.5)]);
30274    // Past the closing brace: no more sticky header.
30275    assert_eq!(sticky_headers(6.0), vec![]);
30276}
30277
30278#[gpui::test]
30279fn test_relative_line_numbers(cx: &mut TestAppContext) {
30280    init_test(cx, |_| {});
30281
30282    let buffer_1 = cx.new(|cx| Buffer::local("aaaaaaaaaa\nbbb\n", cx));
30283    let buffer_2 = cx.new(|cx| Buffer::local("cccccccccc\nddd\n", cx));
30284    let buffer_3 = cx.new(|cx| Buffer::local("eee\nffffffffff\n", cx));
30285
30286    let multibuffer = cx.new(|cx| {
30287        let mut multibuffer = MultiBuffer::new(ReadWrite);
30288        multibuffer.set_excerpts_for_path(
30289            PathKey::sorted(0),
30290            buffer_1.clone(),
30291            [Point::new(0, 0)..Point::new(2, 0)],
30292            0,
30293            cx,
30294        );
30295        multibuffer.set_excerpts_for_path(
30296            PathKey::sorted(1),
30297            buffer_2.clone(),
30298            [Point::new(0, 0)..Point::new(2, 0)],
30299            0,
30300            cx,
30301        );
30302        multibuffer.set_excerpts_for_path(
30303            PathKey::sorted(2),
30304            buffer_3.clone(),
30305            [Point::new(0, 0)..Point::new(2, 0)],
30306            0,
30307            cx,
30308        );
30309        multibuffer
30310    });
30311
30312    // wrapped contents of multibuffer:
30313    //    aaa
30314    //    aaa
30315    //    aaa
30316    //    a
30317    //    bbb
30318    //
30319    //    ccc
30320    //    ccc
30321    //    ccc
30322    //    c
30323    //    ddd
30324    //
30325    //    eee
30326    //    fff
30327    //    fff
30328    //    fff
30329    //    f
30330
30331    let editor = cx.add_window(|window, cx| build_editor(multibuffer, window, cx));
30332    _ = editor.update(cx, |editor, window, cx| {
30333        editor.set_wrap_width(Some(30.0.into()), cx); // every 3 characters
30334
30335        // includes trailing newlines.
30336        let expected_line_numbers = [2, 6, 7, 10, 14, 15, 18, 19, 23];
30337        let expected_wrapped_line_numbers = [
30338            2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22, 23,
30339        ];
30340
30341        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30342            s.select_ranges([
30343                Point::new(7, 0)..Point::new(7, 1), // second row of `ccc`
30344            ]);
30345        });
30346
30347        let snapshot = editor.snapshot(window, cx);
30348
30349        // these are all 0-indexed
30350        let base_display_row = DisplayRow(11);
30351        let base_row = 3;
30352        let wrapped_base_row = 7;
30353
30354        // test not counting wrapped lines
30355        let expected_relative_numbers = expected_line_numbers
30356            .into_iter()
30357            .enumerate()
30358            .map(|(i, row)| (DisplayRow(row), i.abs_diff(base_row) as u32))
30359            .filter(|(_, relative_line_number)| *relative_line_number != 0)
30360            .collect_vec();
30361        let actual_relative_numbers = snapshot
30362            .calculate_relative_line_numbers(
30363                &(DisplayRow(0)..DisplayRow(24)),
30364                base_display_row,
30365                false,
30366            )
30367            .into_iter()
30368            .sorted()
30369            .collect_vec();
30370        assert_eq!(expected_relative_numbers, actual_relative_numbers);
30371        // check `calculate_relative_line_numbers()` against `relative_line_delta()` for each line
30372        for (display_row, relative_number) in expected_relative_numbers {
30373            assert_eq!(
30374                relative_number,
30375                snapshot
30376                    .relative_line_delta(display_row, base_display_row, false)
30377                    .unsigned_abs() as u32,
30378            );
30379        }
30380
30381        // test counting wrapped lines
30382        let expected_wrapped_relative_numbers = expected_wrapped_line_numbers
30383            .into_iter()
30384            .enumerate()
30385            .map(|(i, row)| (DisplayRow(row), i.abs_diff(wrapped_base_row) as u32))
30386            .filter(|(row, _)| *row != base_display_row)
30387            .collect_vec();
30388        let actual_relative_numbers = snapshot
30389            .calculate_relative_line_numbers(
30390                &(DisplayRow(0)..DisplayRow(24)),
30391                base_display_row,
30392                true,
30393            )
30394            .into_iter()
30395            .sorted()
30396            .collect_vec();
30397        assert_eq!(expected_wrapped_relative_numbers, actual_relative_numbers);
30398        // check `calculate_relative_line_numbers()` against `relative_wrapped_line_delta()` for each line
30399        for (display_row, relative_number) in expected_wrapped_relative_numbers {
30400            assert_eq!(
30401                relative_number,
30402                snapshot
30403                    .relative_line_delta(display_row, base_display_row, true)
30404                    .unsigned_abs() as u32,
30405            );
30406        }
30407    });
30408}
30409
30410#[gpui::test]
30411async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
30412    init_test(cx, |_| {});
30413    cx.update(|cx| {
30414        SettingsStore::update_global(cx, |store, cx| {
30415            store.update_user_settings(cx, |settings| {
30416                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
30417                    enabled: Some(true),
30418                })
30419            });
30420        });
30421    });
30422    let mut cx = EditorTestContext::new(cx).await;
30423
30424    let line_height = cx.update_editor(|editor, window, cx| {
30425        editor
30426            .style(cx)
30427            .text
30428            .line_height_in_pixels(window.rem_size())
30429    });
30430
30431    let buffer = indoc! {"
30432            ˇfn foo() {
30433                let abc = 123;
30434            }
30435            struct Bar;
30436            impl Bar {
30437                fn new() -> Self {
30438                    Self
30439                }
30440            }
30441            fn baz() {
30442            }
30443        "};
30444    cx.set_state(&buffer);
30445
30446    cx.update_editor(|e, _, cx| {
30447        e.buffer()
30448            .read(cx)
30449            .as_singleton()
30450            .unwrap()
30451            .update(cx, |buffer, cx| {
30452                buffer.set_language(Some(rust_lang()), cx);
30453            })
30454    });
30455
30456    let fn_foo = || empty_range(0, 0);
30457    let impl_bar = || empty_range(4, 0);
30458    let fn_new = || empty_range(5, 4);
30459
30460    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
30461        cx.update_editor(|e, window, cx| {
30462            e.scroll(
30463                gpui::Point {
30464                    x: 0.,
30465                    y: scroll_offset,
30466                },
30467                None,
30468                window,
30469                cx,
30470            );
30471        });
30472        cx.run_until_parked();
30473        cx.simulate_click(
30474            gpui::Point {
30475                x: px(0.),
30476                y: click_offset as f32 * line_height,
30477            },
30478            Modifiers::none(),
30479        );
30480        cx.run_until_parked();
30481        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
30482    };
30483    assert_eq!(
30484        scroll_and_click(
30485            4.5, // impl Bar is halfway off the screen
30486            0.0  // click top of screen
30487        ),
30488        // scrolled to impl Bar
30489        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
30490    );
30491
30492    assert_eq!(
30493        scroll_and_click(
30494            4.5,  // impl Bar is halfway off the screen
30495            0.25  // click middle of impl Bar
30496        ),
30497        // scrolled to impl Bar
30498        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
30499    );
30500
30501    assert_eq!(
30502        scroll_and_click(
30503            4.5, // impl Bar is halfway off the screen
30504            1.5  // click below impl Bar (e.g. fn new())
30505        ),
30506        // scrolled to fn new() - this is below the impl Bar header which has persisted
30507        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
30508    );
30509
30510    assert_eq!(
30511        scroll_and_click(
30512            5.5,  // fn new is halfway underneath impl Bar
30513            0.75  // click on the overlap of impl Bar and fn new()
30514        ),
30515        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
30516    );
30517
30518    assert_eq!(
30519        scroll_and_click(
30520            5.5,  // fn new is halfway underneath impl Bar
30521            1.25  // click on the visible part of fn new()
30522        ),
30523        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
30524    );
30525
30526    assert_eq!(
30527        scroll_and_click(
30528            1.5, // fn foo is halfway off the screen
30529            0.0  // click top of screen
30530        ),
30531        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
30532    );
30533
30534    assert_eq!(
30535        scroll_and_click(
30536            1.5,  // fn foo is halfway off the screen
30537            0.75  // click visible part of let abc...
30538        )
30539        .0,
30540        // no change in scroll
30541        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
30542        (gpui::Point { x: 0., y: 1.5 })
30543    );
30544}
30545
30546#[gpui::test]
30547async fn test_next_prev_reference(cx: &mut TestAppContext) {
30548    const CYCLE_POSITIONS: &[&'static str] = &[
30549        indoc! {"
30550            fn foo() {
30551                let ˇabc = 123;
30552                let x = abc + 1;
30553                let y = abc + 2;
30554                let z = abc + 2;
30555            }
30556        "},
30557        indoc! {"
30558            fn foo() {
30559                let abc = 123;
30560                let x = ˇabc + 1;
30561                let y = abc + 2;
30562                let z = abc + 2;
30563            }
30564        "},
30565        indoc! {"
30566            fn foo() {
30567                let abc = 123;
30568                let x = abc + 1;
30569                let y = ˇabc + 2;
30570                let z = abc + 2;
30571            }
30572        "},
30573        indoc! {"
30574            fn foo() {
30575                let abc = 123;
30576                let x = abc + 1;
30577                let y = abc + 2;
30578                let z = ˇabc + 2;
30579            }
30580        "},
30581    ];
30582
30583    init_test(cx, |_| {});
30584
30585    let mut cx = EditorLspTestContext::new_rust(
30586        lsp::ServerCapabilities {
30587            references_provider: Some(lsp::OneOf::Left(true)),
30588            ..Default::default()
30589        },
30590        cx,
30591    )
30592    .await;
30593
30594    // importantly, the cursor is in the middle
30595    cx.set_state(indoc! {"
30596        fn foo() {
30597            let aˇbc = 123;
30598            let x = abc + 1;
30599            let y = abc + 2;
30600            let z = abc + 2;
30601        }
30602    "});
30603
30604    let reference_ranges = [
30605        lsp::Position::new(1, 8),
30606        lsp::Position::new(2, 12),
30607        lsp::Position::new(3, 12),
30608        lsp::Position::new(4, 12),
30609    ]
30610    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
30611
30612    cx.lsp
30613        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
30614            Ok(Some(
30615                reference_ranges
30616                    .map(|range| lsp::Location {
30617                        uri: params.text_document_position.text_document.uri.clone(),
30618                        range,
30619                    })
30620                    .to_vec(),
30621            ))
30622        });
30623
30624    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
30625        cx.update_editor(|editor, window, cx| {
30626            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
30627        })
30628        .unwrap()
30629        .await
30630        .unwrap()
30631    };
30632
30633    _move(Direction::Next, 1, &mut cx).await;
30634    cx.assert_editor_state(CYCLE_POSITIONS[1]);
30635
30636    _move(Direction::Next, 1, &mut cx).await;
30637    cx.assert_editor_state(CYCLE_POSITIONS[2]);
30638
30639    _move(Direction::Next, 1, &mut cx).await;
30640    cx.assert_editor_state(CYCLE_POSITIONS[3]);
30641
30642    // loops back to the start
30643    _move(Direction::Next, 1, &mut cx).await;
30644    cx.assert_editor_state(CYCLE_POSITIONS[0]);
30645
30646    // loops back to the end
30647    _move(Direction::Prev, 1, &mut cx).await;
30648    cx.assert_editor_state(CYCLE_POSITIONS[3]);
30649
30650    _move(Direction::Prev, 1, &mut cx).await;
30651    cx.assert_editor_state(CYCLE_POSITIONS[2]);
30652
30653    _move(Direction::Prev, 1, &mut cx).await;
30654    cx.assert_editor_state(CYCLE_POSITIONS[1]);
30655
30656    _move(Direction::Prev, 1, &mut cx).await;
30657    cx.assert_editor_state(CYCLE_POSITIONS[0]);
30658
30659    _move(Direction::Next, 3, &mut cx).await;
30660    cx.assert_editor_state(CYCLE_POSITIONS[3]);
30661
30662    _move(Direction::Prev, 2, &mut cx).await;
30663    cx.assert_editor_state(CYCLE_POSITIONS[1]);
30664}
30665
30666#[gpui::test]
30667async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
30668    init_test(cx, |_| {});
30669
30670    let (editor, cx) = cx.add_window_view(|window, cx| {
30671        let multi_buffer = MultiBuffer::build_multi(
30672            [
30673                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
30674                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
30675            ],
30676            cx,
30677        );
30678        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
30679    });
30680
30681    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
30682    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
30683
30684    cx.assert_excerpts_with_selections(indoc! {"
30685        [EXCERPT]
30686        ˇ1
30687        2
30688        3
30689        [EXCERPT]
30690        1
30691        2
30692        3
30693        "});
30694
30695    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
30696    cx.update_editor(|editor, window, cx| {
30697        editor.change_selections(None.into(), window, cx, |s| {
30698            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
30699        });
30700    });
30701    cx.assert_excerpts_with_selections(indoc! {"
30702        [EXCERPT]
30703        1
3070430705        3
30706        [EXCERPT]
30707        1
30708        2
30709        3
30710        "});
30711
30712    cx.update_editor(|editor, window, cx| {
30713        editor
30714            .select_all_matches(&SelectAllMatches, window, cx)
30715            .unwrap();
30716    });
30717    cx.assert_excerpts_with_selections(indoc! {"
30718        [EXCERPT]
30719        1
3072030721        3
30722        [EXCERPT]
30723        1
3072430725        3
30726        "});
30727
30728    cx.update_editor(|editor, window, cx| {
30729        editor.handle_input("X", window, cx);
30730    });
30731    cx.assert_excerpts_with_selections(indoc! {"
30732        [EXCERPT]
30733        1
3073430735        3
30736        [EXCERPT]
30737        1
3073830739        3
30740        "});
30741
30742    // Scenario 2: Select "2", then fold second buffer before insertion
30743    cx.update_multibuffer(|mb, cx| {
30744        for buffer_id in buffer_ids.iter() {
30745            let buffer = mb.buffer(*buffer_id).unwrap();
30746            buffer.update(cx, |buffer, cx| {
30747                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
30748            });
30749        }
30750    });
30751
30752    // Select "2" and select all matches
30753    cx.update_editor(|editor, window, cx| {
30754        editor.change_selections(None.into(), window, cx, |s| {
30755            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
30756        });
30757        editor
30758            .select_all_matches(&SelectAllMatches, window, cx)
30759            .unwrap();
30760    });
30761
30762    // Fold second buffer - should remove selections from folded buffer
30763    cx.update_editor(|editor, _, cx| {
30764        editor.fold_buffer(buffer_ids[1], cx);
30765    });
30766    cx.assert_excerpts_with_selections(indoc! {"
30767        [EXCERPT]
30768        1
3076930770        3
30771        [EXCERPT]
30772        [FOLDED]
30773        "});
30774
30775    // Insert text - should only affect first buffer
30776    cx.update_editor(|editor, window, cx| {
30777        editor.handle_input("Y", window, cx);
30778    });
30779    cx.update_editor(|editor, _, cx| {
30780        editor.unfold_buffer(buffer_ids[1], cx);
30781    });
30782    cx.assert_excerpts_with_selections(indoc! {"
30783        [EXCERPT]
30784        1
3078530786        3
30787        [EXCERPT]
30788        1
30789        2
30790        3
30791        "});
30792
30793    // Scenario 3: Select "2", then fold first buffer before insertion
30794    cx.update_multibuffer(|mb, cx| {
30795        for buffer_id in buffer_ids.iter() {
30796            let buffer = mb.buffer(*buffer_id).unwrap();
30797            buffer.update(cx, |buffer, cx| {
30798                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
30799            });
30800        }
30801    });
30802
30803    // Select "2" and select all matches
30804    cx.update_editor(|editor, window, cx| {
30805        editor.change_selections(None.into(), window, cx, |s| {
30806            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
30807        });
30808        editor
30809            .select_all_matches(&SelectAllMatches, window, cx)
30810            .unwrap();
30811    });
30812
30813    // Fold first buffer - should remove selections from folded buffer
30814    cx.update_editor(|editor, _, cx| {
30815        editor.fold_buffer(buffer_ids[0], cx);
30816    });
30817    cx.assert_excerpts_with_selections(indoc! {"
30818        [EXCERPT]
30819        [FOLDED]
30820        [EXCERPT]
30821        1
3082230823        3
30824        "});
30825
30826    // Insert text - should only affect second buffer
30827    cx.update_editor(|editor, window, cx| {
30828        editor.handle_input("Z", window, cx);
30829    });
30830    cx.update_editor(|editor, _, cx| {
30831        editor.unfold_buffer(buffer_ids[0], cx);
30832    });
30833    cx.assert_excerpts_with_selections(indoc! {"
30834        [EXCERPT]
30835        1
30836        2
30837        3
30838        [EXCERPT]
30839        1
3084030841        3
30842        "});
30843
30844    // Test correct folded header is selected upon fold
30845    cx.update_editor(|editor, _, cx| {
30846        editor.fold_buffer(buffer_ids[0], cx);
30847        editor.fold_buffer(buffer_ids[1], cx);
30848    });
30849    cx.assert_excerpts_with_selections(indoc! {"
30850        [EXCERPT]
30851        [FOLDED]
30852        [EXCERPT]
30853        ˇ[FOLDED]
30854        "});
30855
30856    // Test selection inside folded buffer unfolds it on type
30857    cx.update_editor(|editor, window, cx| {
30858        editor.handle_input("W", window, cx);
30859    });
30860    cx.update_editor(|editor, _, cx| {
30861        editor.unfold_buffer(buffer_ids[0], cx);
30862    });
30863    cx.assert_excerpts_with_selections(indoc! {"
30864        [EXCERPT]
30865        1
30866        2
30867        3
30868        [EXCERPT]
30869        Wˇ1
30870        Z
30871        3
30872        "});
30873}
30874
30875#[gpui::test]
30876async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
30877    init_test(cx, |_| {});
30878
30879    let (editor, cx) = cx.add_window_view(|window, cx| {
30880        let multi_buffer = MultiBuffer::build_multi(
30881            [
30882                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
30883                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
30884            ],
30885            cx,
30886        );
30887        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
30888    });
30889
30890    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
30891
30892    cx.assert_excerpts_with_selections(indoc! {"
30893        [EXCERPT]
30894        ˇ1
30895        2
30896        3
30897        [EXCERPT]
30898        1
30899        2
30900        3
30901        4
30902        5
30903        6
30904        7
30905        8
30906        9
30907        "});
30908
30909    cx.update_editor(|editor, window, cx| {
30910        editor.change_selections(None.into(), window, cx, |s| {
30911            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
30912        });
30913    });
30914
30915    cx.assert_excerpts_with_selections(indoc! {"
30916        [EXCERPT]
30917        1
30918        2
30919        3
30920        [EXCERPT]
30921        1
30922        2
30923        3
30924        4
30925        5
30926        6
30927        ˇ7
30928        8
30929        9
30930        "});
30931
30932    cx.update_editor(|editor, _window, cx| {
30933        editor.set_vertical_scroll_margin(0, cx);
30934    });
30935
30936    cx.update_editor(|editor, window, cx| {
30937        assert_eq!(editor.vertical_scroll_margin(), 0);
30938        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
30939        assert_eq!(
30940            editor.snapshot(window, cx).scroll_position(),
30941            gpui::Point::new(0., 12.0)
30942        );
30943    });
30944
30945    cx.update_editor(|editor, _window, cx| {
30946        editor.set_vertical_scroll_margin(3, cx);
30947    });
30948
30949    cx.update_editor(|editor, window, cx| {
30950        assert_eq!(editor.vertical_scroll_margin(), 3);
30951        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
30952        assert_eq!(
30953            editor.snapshot(window, cx).scroll_position(),
30954            gpui::Point::new(0., 9.0)
30955        );
30956    });
30957}
30958
30959#[gpui::test]
30960async fn test_find_references_single_case(cx: &mut TestAppContext) {
30961    init_test(cx, |_| {});
30962    let mut cx = EditorLspTestContext::new_rust(
30963        lsp::ServerCapabilities {
30964            references_provider: Some(lsp::OneOf::Left(true)),
30965            ..lsp::ServerCapabilities::default()
30966        },
30967        cx,
30968    )
30969    .await;
30970
30971    let before = indoc!(
30972        r#"
30973        fn main() {
30974            let aˇbc = 123;
30975            let xyz = abc;
30976        }
30977        "#
30978    );
30979    let after = indoc!(
30980        r#"
30981        fn main() {
30982            let abc = 123;
30983            let xyz = ˇabc;
30984        }
30985        "#
30986    );
30987
30988    cx.lsp
30989        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
30990            Ok(Some(vec![
30991                lsp::Location {
30992                    uri: params.text_document_position.text_document.uri.clone(),
30993                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
30994                },
30995                lsp::Location {
30996                    uri: params.text_document_position.text_document.uri,
30997                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
30998                },
30999            ]))
31000        });
31001
31002    cx.set_state(before);
31003
31004    let action = FindAllReferences {
31005        always_open_multibuffer: false,
31006    };
31007
31008    let navigated = cx
31009        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
31010        .expect("should have spawned a task")
31011        .await
31012        .unwrap();
31013
31014    assert_eq!(navigated, Navigated::No);
31015
31016    cx.run_until_parked();
31017
31018    cx.assert_editor_state(after);
31019}
31020
31021#[gpui::test]
31022async fn test_newline_task_list_continuation(cx: &mut TestAppContext) {
31023    init_test(cx, |settings| {
31024        settings.defaults.tab_size = Some(2.try_into().unwrap());
31025    });
31026
31027    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
31028    let mut cx = EditorTestContext::new(cx).await;
31029    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31030
31031    // Case 1: Adding newline after (whitespace + prefix + any non-whitespace) adds marker
31032    cx.set_state(indoc! {"
31033        - [ ] taskˇ
31034    "});
31035    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31036    cx.wait_for_autoindent_applied().await;
31037    cx.assert_editor_state(indoc! {"
31038        - [ ] task
31039        - [ ] ˇ
31040    "});
31041
31042    // Case 2: Works with checked task items too
31043    cx.set_state(indoc! {"
31044        - [x] completed taskˇ
31045    "});
31046    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31047    cx.wait_for_autoindent_applied().await;
31048    cx.assert_editor_state(indoc! {"
31049        - [x] completed task
31050        - [ ] ˇ
31051    "});
31052
31053    // Case 2.1: Works with uppercase checked marker too
31054    cx.set_state(indoc! {"
31055        - [X] completed taskˇ
31056    "});
31057    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31058    cx.wait_for_autoindent_applied().await;
31059    cx.assert_editor_state(indoc! {"
31060        - [X] completed task
31061        - [ ] ˇ
31062    "});
31063
31064    // Case 3: Cursor position doesn't matter - content after marker is what counts
31065    cx.set_state(indoc! {"
31066        - [ ] taˇsk
31067    "});
31068    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31069    cx.wait_for_autoindent_applied().await;
31070    cx.assert_editor_state(indoc! {"
31071        - [ ] ta
31072        - [ ] ˇsk
31073    "});
31074
31075    // Case 4: Adding newline after (whitespace + prefix + some whitespace) does NOT add marker
31076    cx.set_state(indoc! {"
31077        - [ ]  ˇ
31078    "});
31079    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31080    cx.wait_for_autoindent_applied().await;
31081    cx.assert_editor_state(
31082        indoc! {"
31083        - [ ]$$
31084        ˇ
31085    "}
31086        .replace("$", " ")
31087        .as_str(),
31088    );
31089
31090    // Case 5: Adding newline with content adds marker preserving indentation
31091    cx.set_state(indoc! {"
31092        - [ ] task
31093          - [ ] indentedˇ
31094    "});
31095    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31096    cx.wait_for_autoindent_applied().await;
31097    cx.assert_editor_state(indoc! {"
31098        - [ ] task
31099          - [ ] indented
31100          - [ ] ˇ
31101    "});
31102
31103    // Case 6: Adding newline with cursor right after prefix, unindents
31104    cx.set_state(indoc! {"
31105        - [ ] task
31106          - [ ] sub task
31107            - [ ] ˇ
31108    "});
31109    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31110    cx.wait_for_autoindent_applied().await;
31111    cx.assert_editor_state(indoc! {"
31112        - [ ] task
31113          - [ ] sub task
31114          - [ ] ˇ
31115    "});
31116    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31117    cx.wait_for_autoindent_applied().await;
31118
31119    // Case 7: Adding newline with cursor right after prefix, removes marker
31120    cx.assert_editor_state(indoc! {"
31121        - [ ] task
31122          - [ ] sub task
31123        - [ ] ˇ
31124    "});
31125    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31126    cx.wait_for_autoindent_applied().await;
31127    cx.assert_editor_state(indoc! {"
31128        - [ ] task
31129          - [ ] sub task
31130        ˇ
31131    "});
31132
31133    // Case 8: Cursor before or inside prefix does not add marker
31134    cx.set_state(indoc! {"
31135        ˇ- [ ] task
31136    "});
31137    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31138    cx.wait_for_autoindent_applied().await;
31139    cx.assert_editor_state(indoc! {"
31140
31141        ˇ- [ ] task
31142    "});
31143
31144    cx.set_state(indoc! {"
31145        - [ˇ ] task
31146    "});
31147    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31148    cx.wait_for_autoindent_applied().await;
31149    cx.assert_editor_state(indoc! {"
31150        - [
31151        ˇ
31152        ] task
31153    "});
31154}
31155
31156#[gpui::test]
31157async fn test_newline_unordered_list_continuation(cx: &mut TestAppContext) {
31158    init_test(cx, |settings| {
31159        settings.defaults.tab_size = Some(2.try_into().unwrap());
31160    });
31161
31162    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
31163    let mut cx = EditorTestContext::new(cx).await;
31164    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31165
31166    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) adds marker
31167    cx.set_state(indoc! {"
31168        - itemˇ
31169    "});
31170    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31171    cx.wait_for_autoindent_applied().await;
31172    cx.assert_editor_state(indoc! {"
31173        - item
31174        - ˇ
31175    "});
31176
31177    // Case 2: Works with different markers
31178    cx.set_state(indoc! {"
31179        * starred itemˇ
31180    "});
31181    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31182    cx.wait_for_autoindent_applied().await;
31183    cx.assert_editor_state(indoc! {"
31184        * starred item
31185        * ˇ
31186    "});
31187
31188    cx.set_state(indoc! {"
31189        + plus itemˇ
31190    "});
31191    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31192    cx.wait_for_autoindent_applied().await;
31193    cx.assert_editor_state(indoc! {"
31194        + plus item
31195        + ˇ
31196    "});
31197
31198    // Case 3: Cursor position doesn't matter - content after marker is what counts
31199    cx.set_state(indoc! {"
31200        - itˇem
31201    "});
31202    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31203    cx.wait_for_autoindent_applied().await;
31204    cx.assert_editor_state(indoc! {"
31205        - it
31206        - ˇem
31207    "});
31208
31209    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
31210    cx.set_state(indoc! {"
31211        -  ˇ
31212    "});
31213    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31214    cx.wait_for_autoindent_applied().await;
31215    cx.assert_editor_state(
31216        indoc! {"
31217        - $
31218        ˇ
31219    "}
31220        .replace("$", " ")
31221        .as_str(),
31222    );
31223
31224    // Case 5: Adding newline with content adds marker preserving indentation
31225    cx.set_state(indoc! {"
31226        - item
31227          - indentedˇ
31228    "});
31229    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31230    cx.wait_for_autoindent_applied().await;
31231    cx.assert_editor_state(indoc! {"
31232        - item
31233          - indented
31234          - ˇ
31235    "});
31236
31237    // Case 6: Adding newline with cursor right after marker, unindents
31238    cx.set_state(indoc! {"
31239        - item
31240          - sub item
31241            - ˇ
31242    "});
31243    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31244    cx.wait_for_autoindent_applied().await;
31245    cx.assert_editor_state(indoc! {"
31246        - item
31247          - sub item
31248          - ˇ
31249    "});
31250    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31251    cx.wait_for_autoindent_applied().await;
31252
31253    // Case 7: Adding newline with cursor right after marker, removes marker
31254    cx.assert_editor_state(indoc! {"
31255        - item
31256          - sub item
31257        - ˇ
31258    "});
31259    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31260    cx.wait_for_autoindent_applied().await;
31261    cx.assert_editor_state(indoc! {"
31262        - item
31263          - sub item
31264        ˇ
31265    "});
31266
31267    // Case 8: Cursor before or inside prefix does not add marker
31268    cx.set_state(indoc! {"
31269        ˇ- item
31270    "});
31271    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31272    cx.wait_for_autoindent_applied().await;
31273    cx.assert_editor_state(indoc! {"
31274
31275        ˇ- item
31276    "});
31277
31278    cx.set_state(indoc! {"
31279        -ˇ item
31280    "});
31281    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31282    cx.wait_for_autoindent_applied().await;
31283    cx.assert_editor_state(indoc! {"
31284        -
31285        ˇitem
31286    "});
31287}
31288
31289#[gpui::test]
31290async fn test_newline_ordered_list_continuation(cx: &mut TestAppContext) {
31291    init_test(cx, |settings| {
31292        settings.defaults.tab_size = Some(2.try_into().unwrap());
31293    });
31294
31295    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
31296    let mut cx = EditorTestContext::new(cx).await;
31297    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31298
31299    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
31300    cx.set_state(indoc! {"
31301        1. first itemˇ
31302    "});
31303    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31304    cx.wait_for_autoindent_applied().await;
31305    cx.assert_editor_state(indoc! {"
31306        1. first item
31307        2. ˇ
31308    "});
31309
31310    // Case 2: Works with larger numbers
31311    cx.set_state(indoc! {"
31312        10. tenth itemˇ
31313    "});
31314    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31315    cx.wait_for_autoindent_applied().await;
31316    cx.assert_editor_state(indoc! {"
31317        10. tenth item
31318        11. ˇ
31319    "});
31320
31321    // Case 3: Cursor position doesn't matter - content after marker is what counts
31322    cx.set_state(indoc! {"
31323        1. itˇem
31324    "});
31325    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31326    cx.wait_for_autoindent_applied().await;
31327    cx.assert_editor_state(indoc! {"
31328        1. it
31329        2. ˇem
31330    "});
31331
31332    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
31333    cx.set_state(indoc! {"
31334        1.  ˇ
31335    "});
31336    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31337    cx.wait_for_autoindent_applied().await;
31338    cx.assert_editor_state(
31339        indoc! {"
31340        1. $
31341        ˇ
31342    "}
31343        .replace("$", " ")
31344        .as_str(),
31345    );
31346
31347    // Case 5: Adding newline with content adds marker preserving indentation
31348    cx.set_state(indoc! {"
31349        1. item
31350          2. indentedˇ
31351    "});
31352    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31353    cx.wait_for_autoindent_applied().await;
31354    cx.assert_editor_state(indoc! {"
31355        1. item
31356          2. indented
31357          3. ˇ
31358    "});
31359
31360    // Case 6: Adding newline with cursor right after marker, unindents
31361    cx.set_state(indoc! {"
31362        1. item
31363          2. sub item
31364            3. ˇ
31365    "});
31366    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31367    cx.wait_for_autoindent_applied().await;
31368    cx.assert_editor_state(indoc! {"
31369        1. item
31370          2. sub item
31371          1. ˇ
31372    "});
31373    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31374    cx.wait_for_autoindent_applied().await;
31375
31376    // Case 7: Adding newline with cursor right after marker, removes marker
31377    cx.assert_editor_state(indoc! {"
31378        1. item
31379          2. sub item
31380        1. ˇ
31381    "});
31382    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31383    cx.wait_for_autoindent_applied().await;
31384    cx.assert_editor_state(indoc! {"
31385        1. item
31386          2. sub item
31387        ˇ
31388    "});
31389
31390    // Case 8: Cursor before or inside prefix does not add marker
31391    cx.set_state(indoc! {"
31392        ˇ1. item
31393    "});
31394    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31395    cx.wait_for_autoindent_applied().await;
31396    cx.assert_editor_state(indoc! {"
31397
31398        ˇ1. item
31399    "});
31400
31401    cx.set_state(indoc! {"
31402        1ˇ. item
31403    "});
31404    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31405    cx.wait_for_autoindent_applied().await;
31406    cx.assert_editor_state(indoc! {"
31407        1
31408        ˇ. item
31409    "});
31410}
31411
31412#[gpui::test]
31413async fn test_newline_should_not_autoindent_ordered_list(cx: &mut TestAppContext) {
31414    init_test(cx, |settings| {
31415        settings.defaults.tab_size = Some(2.try_into().unwrap());
31416    });
31417
31418    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
31419    let mut cx = EditorTestContext::new(cx).await;
31420    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31421
31422    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
31423    cx.set_state(indoc! {"
31424        1. first item
31425          1. sub first item
31426          2. sub second item
31427          3. ˇ
31428    "});
31429    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31430    cx.wait_for_autoindent_applied().await;
31431    cx.assert_editor_state(indoc! {"
31432        1. first item
31433          1. sub first item
31434          2. sub second item
31435        1. ˇ
31436    "});
31437}
31438
31439#[gpui::test]
31440async fn test_tab_list_indent(cx: &mut TestAppContext) {
31441    init_test(cx, |settings| {
31442        settings.defaults.tab_size = Some(2.try_into().unwrap());
31443    });
31444
31445    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
31446    let mut cx = EditorTestContext::new(cx).await;
31447    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31448
31449    // Case 1: Unordered list - cursor after prefix, adds indent before prefix
31450    cx.set_state(indoc! {"
31451        - ˇitem
31452    "});
31453    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31454    cx.wait_for_autoindent_applied().await;
31455    let expected = indoc! {"
31456        $$- ˇitem
31457    "};
31458    cx.assert_editor_state(expected.replace("$", " ").as_str());
31459
31460    // Case 2: Task list - cursor after prefix
31461    cx.set_state(indoc! {"
31462        - [ ] ˇtask
31463    "});
31464    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31465    cx.wait_for_autoindent_applied().await;
31466    let expected = indoc! {"
31467        $$- [ ] ˇtask
31468    "};
31469    cx.assert_editor_state(expected.replace("$", " ").as_str());
31470
31471    // Case 3: Ordered list - cursor after prefix
31472    cx.set_state(indoc! {"
31473        1. ˇfirst
31474    "});
31475    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31476    cx.wait_for_autoindent_applied().await;
31477    let expected = indoc! {"
31478        $$1. ˇfirst
31479    "};
31480    cx.assert_editor_state(expected.replace("$", " ").as_str());
31481
31482    // Case 4: With existing indentation - adds more indent
31483    let initial = indoc! {"
31484        $$- ˇitem
31485    "};
31486    cx.set_state(initial.replace("$", " ").as_str());
31487    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31488    cx.wait_for_autoindent_applied().await;
31489    let expected = indoc! {"
31490        $$$$- ˇitem
31491    "};
31492    cx.assert_editor_state(expected.replace("$", " ").as_str());
31493
31494    // Case 5: Empty list item
31495    cx.set_state(indoc! {"
31496        - ˇ
31497    "});
31498    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31499    cx.wait_for_autoindent_applied().await;
31500    let expected = indoc! {"
31501        $$- ˇ
31502    "};
31503    cx.assert_editor_state(expected.replace("$", " ").as_str());
31504
31505    // Case 6: Cursor at end of line with content
31506    cx.set_state(indoc! {"
31507        - itemˇ
31508    "});
31509    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31510    cx.wait_for_autoindent_applied().await;
31511    let expected = indoc! {"
31512        $$- itemˇ
31513    "};
31514    cx.assert_editor_state(expected.replace("$", " ").as_str());
31515
31516    // Case 7: Cursor at start of list item, indents it
31517    cx.set_state(indoc! {"
31518        - item
31519        ˇ  - sub item
31520    "});
31521    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31522    cx.wait_for_autoindent_applied().await;
31523    let expected = indoc! {"
31524        - item
31525          ˇ  - sub item
31526    "};
31527    cx.assert_editor_state(expected);
31528
31529    // Case 8: Cursor at start of list item, moves the cursor when "indent_list_on_tab" is false
31530    cx.update_editor(|_, _, cx| {
31531        SettingsStore::update_global(cx, |store, cx| {
31532            store.update_user_settings(cx, |settings| {
31533                settings.project.all_languages.defaults.indent_list_on_tab = Some(false);
31534            });
31535        });
31536    });
31537    cx.set_state(indoc! {"
31538        - item
31539        ˇ  - sub item
31540    "});
31541    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31542    cx.wait_for_autoindent_applied().await;
31543    let expected = indoc! {"
31544        - item
31545          ˇ- sub item
31546    "};
31547    cx.assert_editor_state(expected);
31548}
31549
31550#[gpui::test]
31551async fn test_local_worktree_trust(cx: &mut TestAppContext) {
31552    init_test(cx, |_| {});
31553    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), cx));
31554
31555    cx.update(|cx| {
31556        SettingsStore::update_global(cx, |store, cx| {
31557            store.update_user_settings(cx, |settings| {
31558                settings.project.all_languages.defaults.inlay_hints =
31559                    Some(InlayHintSettingsContent {
31560                        enabled: Some(true),
31561                        ..InlayHintSettingsContent::default()
31562                    });
31563            });
31564        });
31565    });
31566
31567    let fs = FakeFs::new(cx.executor());
31568    fs.insert_tree(
31569        path!("/project"),
31570        json!({
31571            ".zed": {
31572                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
31573            },
31574            "main.rs": "fn main() {}"
31575        }),
31576    )
31577    .await;
31578
31579    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
31580    let server_name = "override-rust-analyzer";
31581    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
31582
31583    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
31584    language_registry.add(rust_lang());
31585
31586    let capabilities = lsp::ServerCapabilities {
31587        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
31588        ..lsp::ServerCapabilities::default()
31589    };
31590    let mut fake_language_servers = language_registry.register_fake_lsp(
31591        "Rust",
31592        FakeLspAdapter {
31593            name: server_name,
31594            capabilities,
31595            initializer: Some(Box::new({
31596                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
31597                move |fake_server| {
31598                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
31599                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
31600                        move |_params, _| {
31601                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
31602                            async move {
31603                                Ok(Some(vec![lsp::InlayHint {
31604                                    position: lsp::Position::new(0, 0),
31605                                    label: lsp::InlayHintLabel::String("hint".to_string()),
31606                                    kind: None,
31607                                    text_edits: None,
31608                                    tooltip: None,
31609                                    padding_left: None,
31610                                    padding_right: None,
31611                                    data: None,
31612                                }]))
31613                            }
31614                        },
31615                    );
31616                }
31617            })),
31618            ..FakeLspAdapter::default()
31619        },
31620    );
31621
31622    cx.run_until_parked();
31623
31624    let worktree_id = project.read_with(cx, |project, cx| {
31625        project
31626            .worktrees(cx)
31627            .next()
31628            .map(|wt| wt.read(cx).id())
31629            .expect("should have a worktree")
31630    });
31631    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
31632
31633    let trusted_worktrees =
31634        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
31635
31636    let can_trust = trusted_worktrees.update(cx, |store, cx| {
31637        store.can_trust(&worktree_store, worktree_id, cx)
31638    });
31639    assert!(!can_trust, "worktree should be restricted initially");
31640
31641    let buffer_before_approval = project
31642        .update(cx, |project, cx| {
31643            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
31644        })
31645        .await
31646        .unwrap();
31647
31648    let (editor, cx) = cx.add_window_view(|window, cx| {
31649        Editor::new(
31650            EditorMode::full(),
31651            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
31652            Some(project.clone()),
31653            window,
31654            cx,
31655        )
31656    });
31657    cx.run_until_parked();
31658    let fake_language_server = fake_language_servers.next();
31659
31660    cx.read(|cx| {
31661        let file = buffer_before_approval.read(cx).file();
31662        assert_eq!(
31663            language::language_settings::language_settings(Some("Rust".into()), file, cx)
31664                .language_servers,
31665            ["...".to_string()],
31666            "local .zed/settings.json must not apply before trust approval"
31667        )
31668    });
31669
31670    editor.update_in(cx, |editor, window, cx| {
31671        editor.handle_input("1", window, cx);
31672    });
31673    cx.run_until_parked();
31674    cx.executor()
31675        .advance_clock(std::time::Duration::from_secs(1));
31676    assert_eq!(
31677        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
31678        0,
31679        "inlay hints must not be queried before trust approval"
31680    );
31681
31682    trusted_worktrees.update(cx, |store, cx| {
31683        store.trust(
31684            &worktree_store,
31685            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
31686            cx,
31687        );
31688    });
31689    cx.run_until_parked();
31690
31691    cx.read(|cx| {
31692        let file = buffer_before_approval.read(cx).file();
31693        assert_eq!(
31694            language::language_settings::language_settings(Some("Rust".into()), file, cx)
31695                .language_servers,
31696            ["override-rust-analyzer".to_string()],
31697            "local .zed/settings.json should apply after trust approval"
31698        )
31699    });
31700    let _fake_language_server = fake_language_server.await.unwrap();
31701    editor.update_in(cx, |editor, window, cx| {
31702        editor.handle_input("1", window, cx);
31703    });
31704    cx.run_until_parked();
31705    cx.executor()
31706        .advance_clock(std::time::Duration::from_secs(1));
31707    assert!(
31708        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
31709        "inlay hints should be queried after trust approval"
31710    );
31711
31712    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
31713        store.can_trust(&worktree_store, worktree_id, cx)
31714    });
31715    assert!(can_trust_after, "worktree should be trusted after trust()");
31716}
31717
31718#[gpui::test]
31719fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
31720    // This test reproduces a bug where drawing an editor at a position above the viewport
31721    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
31722    // causes an infinite loop in blocks_in_range.
31723    //
31724    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
31725    // the content mask intersection produces visible_bounds with origin at the viewport top.
31726    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
31727    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
31728    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
31729    init_test(cx, |_| {});
31730
31731    let window = cx.add_window(|_, _| gpui::Empty);
31732    let mut cx = VisualTestContext::from_window(*window, cx);
31733
31734    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
31735    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
31736
31737    // Simulate a small viewport (500x500 pixels at origin 0,0)
31738    cx.simulate_resize(gpui::size(px(500.), px(500.)));
31739
31740    // Draw the editor at a very negative Y position, simulating an editor that's been
31741    // scrolled way above the visible viewport (like in a List that has scrolled past it).
31742    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
31743    // This should NOT hang - it should just render nothing.
31744    cx.draw(
31745        gpui::point(px(0.), px(-10000.)),
31746        gpui::size(px(500.), px(3000.)),
31747        |_, _| editor.clone().into_any_element(),
31748    );
31749
31750    // If we get here without hanging, the test passes
31751}
31752
31753#[gpui::test]
31754async fn test_diff_review_indicator_created_on_gutter_hover(cx: &mut TestAppContext) {
31755    init_test(cx, |_| {});
31756
31757    let fs = FakeFs::new(cx.executor());
31758    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
31759        .await;
31760
31761    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
31762    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
31763    let workspace = window
31764        .read_with(cx, |mw, _| mw.workspace().clone())
31765        .unwrap();
31766    let cx = &mut VisualTestContext::from_window(*window, cx);
31767
31768    let editor = workspace
31769        .update_in(cx, |workspace, window, cx| {
31770            workspace.open_abs_path(
31771                PathBuf::from(path!("/root/file.txt")),
31772                OpenOptions::default(),
31773                window,
31774                cx,
31775            )
31776        })
31777        .await
31778        .unwrap()
31779        .downcast::<Editor>()
31780        .unwrap();
31781
31782    // Enable diff review button mode
31783    editor.update(cx, |editor, cx| {
31784        editor.set_show_diff_review_button(true, cx);
31785    });
31786
31787    // Initially, no indicator should be present
31788    editor.update(cx, |editor, _cx| {
31789        assert!(
31790            editor.gutter_diff_review_indicator.0.is_none(),
31791            "Indicator should be None initially"
31792        );
31793    });
31794}
31795
31796#[gpui::test]
31797async fn test_diff_review_button_hidden_when_ai_disabled(cx: &mut TestAppContext) {
31798    init_test(cx, |_| {});
31799
31800    // Register DisableAiSettings and set disable_ai to true
31801    cx.update(|cx| {
31802        project::DisableAiSettings::register(cx);
31803        project::DisableAiSettings::override_global(
31804            project::DisableAiSettings { disable_ai: true },
31805            cx,
31806        );
31807    });
31808
31809    let fs = FakeFs::new(cx.executor());
31810    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
31811        .await;
31812
31813    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
31814    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
31815    let workspace = window
31816        .read_with(cx, |mw, _| mw.workspace().clone())
31817        .unwrap();
31818    let cx = &mut VisualTestContext::from_window(*window, cx);
31819
31820    let editor = workspace
31821        .update_in(cx, |workspace, window, cx| {
31822            workspace.open_abs_path(
31823                PathBuf::from(path!("/root/file.txt")),
31824                OpenOptions::default(),
31825                window,
31826                cx,
31827            )
31828        })
31829        .await
31830        .unwrap()
31831        .downcast::<Editor>()
31832        .unwrap();
31833
31834    // Enable diff review button mode
31835    editor.update(cx, |editor, cx| {
31836        editor.set_show_diff_review_button(true, cx);
31837    });
31838
31839    // Verify AI is disabled
31840    cx.read(|cx| {
31841        assert!(
31842            project::DisableAiSettings::get_global(cx).disable_ai,
31843            "AI should be disabled"
31844        );
31845    });
31846
31847    // The indicator should not be created when AI is disabled
31848    // (The mouse_moved handler checks DisableAiSettings before creating the indicator)
31849    editor.update(cx, |editor, _cx| {
31850        assert!(
31851            editor.gutter_diff_review_indicator.0.is_none(),
31852            "Indicator should be None when AI is disabled"
31853        );
31854    });
31855}
31856
31857#[gpui::test]
31858async fn test_diff_review_button_shown_when_ai_enabled(cx: &mut TestAppContext) {
31859    init_test(cx, |_| {});
31860
31861    // Register DisableAiSettings and set disable_ai to false
31862    cx.update(|cx| {
31863        project::DisableAiSettings::register(cx);
31864        project::DisableAiSettings::override_global(
31865            project::DisableAiSettings { disable_ai: false },
31866            cx,
31867        );
31868    });
31869
31870    let fs = FakeFs::new(cx.executor());
31871    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
31872        .await;
31873
31874    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
31875    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
31876    let workspace = window
31877        .read_with(cx, |mw, _| mw.workspace().clone())
31878        .unwrap();
31879    let cx = &mut VisualTestContext::from_window(*window, cx);
31880
31881    let editor = workspace
31882        .update_in(cx, |workspace, window, cx| {
31883            workspace.open_abs_path(
31884                PathBuf::from(path!("/root/file.txt")),
31885                OpenOptions::default(),
31886                window,
31887                cx,
31888            )
31889        })
31890        .await
31891        .unwrap()
31892        .downcast::<Editor>()
31893        .unwrap();
31894
31895    // Enable diff review button mode
31896    editor.update(cx, |editor, cx| {
31897        editor.set_show_diff_review_button(true, cx);
31898    });
31899
31900    // Verify AI is enabled
31901    cx.read(|cx| {
31902        assert!(
31903            !project::DisableAiSettings::get_global(cx).disable_ai,
31904            "AI should be enabled"
31905        );
31906    });
31907
31908    // The show_diff_review_button flag should be true
31909    editor.update(cx, |editor, _cx| {
31910        assert!(
31911            editor.show_diff_review_button(),
31912            "show_diff_review_button should be true"
31913        );
31914    });
31915}
31916
31917/// Helper function to create a DiffHunkKey for testing.
31918/// Uses Anchor::min() as a placeholder anchor since these tests don't need
31919/// real buffer positioning.
31920fn test_hunk_key(file_path: &str) -> DiffHunkKey {
31921    DiffHunkKey {
31922        file_path: if file_path.is_empty() {
31923            Arc::from(util::rel_path::RelPath::empty())
31924        } else {
31925            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
31926        },
31927        hunk_start_anchor: Anchor::min(),
31928    }
31929}
31930
31931/// Helper function to create a DiffHunkKey with a specific anchor for testing.
31932fn test_hunk_key_with_anchor(file_path: &str, anchor: Anchor) -> DiffHunkKey {
31933    DiffHunkKey {
31934        file_path: if file_path.is_empty() {
31935            Arc::from(util::rel_path::RelPath::empty())
31936        } else {
31937            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
31938        },
31939        hunk_start_anchor: anchor,
31940    }
31941}
31942
31943/// Helper function to add a review comment with default anchors for testing.
31944fn add_test_comment(
31945    editor: &mut Editor,
31946    key: DiffHunkKey,
31947    comment: &str,
31948    cx: &mut Context<Editor>,
31949) -> usize {
31950    editor.add_review_comment(key, comment.to_string(), Anchor::min()..Anchor::max(), cx)
31951}
31952
31953#[gpui::test]
31954fn test_review_comment_add_to_hunk(cx: &mut TestAppContext) {
31955    init_test(cx, |_| {});
31956
31957    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31958
31959    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
31960        let key = test_hunk_key("");
31961
31962        let id = add_test_comment(editor, key.clone(), "Test comment", cx);
31963
31964        let snapshot = editor.buffer().read(cx).snapshot(cx);
31965        assert_eq!(editor.total_review_comment_count(), 1);
31966        assert_eq!(editor.hunk_comment_count(&key, &snapshot), 1);
31967
31968        let comments = editor.comments_for_hunk(&key, &snapshot);
31969        assert_eq!(comments.len(), 1);
31970        assert_eq!(comments[0].comment, "Test comment");
31971        assert_eq!(comments[0].id, id);
31972    });
31973}
31974
31975#[gpui::test]
31976fn test_review_comments_are_per_hunk(cx: &mut TestAppContext) {
31977    init_test(cx, |_| {});
31978
31979    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
31980
31981    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
31982        let snapshot = editor.buffer().read(cx).snapshot(cx);
31983        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
31984        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
31985        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
31986        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
31987
31988        add_test_comment(editor, key1.clone(), "Comment for file1", cx);
31989        add_test_comment(editor, key2.clone(), "Comment for file2", cx);
31990
31991        let snapshot = editor.buffer().read(cx).snapshot(cx);
31992        assert_eq!(editor.total_review_comment_count(), 2);
31993        assert_eq!(editor.hunk_comment_count(&key1, &snapshot), 1);
31994        assert_eq!(editor.hunk_comment_count(&key2, &snapshot), 1);
31995
31996        assert_eq!(
31997            editor.comments_for_hunk(&key1, &snapshot)[0].comment,
31998            "Comment for file1"
31999        );
32000        assert_eq!(
32001            editor.comments_for_hunk(&key2, &snapshot)[0].comment,
32002            "Comment for file2"
32003        );
32004    });
32005}
32006
32007#[gpui::test]
32008fn test_review_comment_remove(cx: &mut TestAppContext) {
32009    init_test(cx, |_| {});
32010
32011    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32012
32013    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
32014        let key = test_hunk_key("");
32015
32016        let id = add_test_comment(editor, key, "To be removed", cx);
32017
32018        assert_eq!(editor.total_review_comment_count(), 1);
32019
32020        let removed = editor.remove_review_comment(id, cx);
32021        assert!(removed);
32022        assert_eq!(editor.total_review_comment_count(), 0);
32023
32024        // Try to remove again
32025        let removed_again = editor.remove_review_comment(id, cx);
32026        assert!(!removed_again);
32027    });
32028}
32029
32030#[gpui::test]
32031fn test_review_comment_update(cx: &mut TestAppContext) {
32032    init_test(cx, |_| {});
32033
32034    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32035
32036    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
32037        let key = test_hunk_key("");
32038
32039        let id = add_test_comment(editor, key.clone(), "Original text", cx);
32040
32041        let updated = editor.update_review_comment(id, "Updated text".to_string(), cx);
32042        assert!(updated);
32043
32044        let snapshot = editor.buffer().read(cx).snapshot(cx);
32045        let comments = editor.comments_for_hunk(&key, &snapshot);
32046        assert_eq!(comments[0].comment, "Updated text");
32047        assert!(!comments[0].is_editing); // Should clear editing flag
32048    });
32049}
32050
32051#[gpui::test]
32052fn test_review_comment_take_all(cx: &mut TestAppContext) {
32053    init_test(cx, |_| {});
32054
32055    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32056
32057    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
32058        let snapshot = editor.buffer().read(cx).snapshot(cx);
32059        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
32060        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
32061        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
32062        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
32063
32064        let id1 = add_test_comment(editor, key1.clone(), "Comment 1", cx);
32065        let id2 = add_test_comment(editor, key1.clone(), "Comment 2", cx);
32066        let id3 = add_test_comment(editor, key2.clone(), "Comment 3", cx);
32067
32068        // IDs should be sequential starting from 0
32069        assert_eq!(id1, 0);
32070        assert_eq!(id2, 1);
32071        assert_eq!(id3, 2);
32072
32073        assert_eq!(editor.total_review_comment_count(), 3);
32074
32075        let taken = editor.take_all_review_comments(cx);
32076
32077        // Should have 2 entries (one per hunk)
32078        assert_eq!(taken.len(), 2);
32079
32080        // Total comments should be 3
32081        let total: usize = taken
32082            .iter()
32083            .map(|(_, comments): &(DiffHunkKey, Vec<StoredReviewComment>)| comments.len())
32084            .sum();
32085        assert_eq!(total, 3);
32086
32087        // Storage should be empty
32088        assert_eq!(editor.total_review_comment_count(), 0);
32089
32090        // After taking all comments, ID counter should reset
32091        // New comments should get IDs starting from 0 again
32092        let new_id1 = add_test_comment(editor, key1, "New Comment 1", cx);
32093        let new_id2 = add_test_comment(editor, key2, "New Comment 2", cx);
32094
32095        assert_eq!(new_id1, 0, "ID counter should reset after take_all");
32096        assert_eq!(new_id2, 1, "IDs should be sequential after reset");
32097    });
32098}
32099
32100#[gpui::test]
32101fn test_diff_review_overlay_show_and_dismiss(cx: &mut TestAppContext) {
32102    init_test(cx, |_| {});
32103
32104    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32105
32106    // Show overlay
32107    editor
32108        .update(cx, |editor, window, cx| {
32109            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
32110        })
32111        .unwrap();
32112
32113    // Verify overlay is shown
32114    editor
32115        .update(cx, |editor, _window, cx| {
32116            assert!(!editor.diff_review_overlays.is_empty());
32117            assert_eq!(editor.diff_review_line_range(cx), Some((0, 0)));
32118            assert!(editor.diff_review_prompt_editor().is_some());
32119        })
32120        .unwrap();
32121
32122    // Dismiss overlay
32123    editor
32124        .update(cx, |editor, _window, cx| {
32125            editor.dismiss_all_diff_review_overlays(cx);
32126        })
32127        .unwrap();
32128
32129    // Verify overlay is dismissed
32130    editor
32131        .update(cx, |editor, _window, cx| {
32132            assert!(editor.diff_review_overlays.is_empty());
32133            assert_eq!(editor.diff_review_line_range(cx), None);
32134            assert!(editor.diff_review_prompt_editor().is_none());
32135        })
32136        .unwrap();
32137}
32138
32139#[gpui::test]
32140fn test_diff_review_overlay_dismiss_via_cancel(cx: &mut TestAppContext) {
32141    init_test(cx, |_| {});
32142
32143    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32144
32145    // Show overlay
32146    editor
32147        .update(cx, |editor, window, cx| {
32148            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
32149        })
32150        .unwrap();
32151
32152    // Verify overlay is shown
32153    editor
32154        .update(cx, |editor, _window, _cx| {
32155            assert!(!editor.diff_review_overlays.is_empty());
32156        })
32157        .unwrap();
32158
32159    // Dismiss via dismiss_menus_and_popups (which is called by cancel action)
32160    editor
32161        .update(cx, |editor, window, cx| {
32162            editor.dismiss_menus_and_popups(true, window, cx);
32163        })
32164        .unwrap();
32165
32166    // Verify overlay is dismissed
32167    editor
32168        .update(cx, |editor, _window, _cx| {
32169            assert!(editor.diff_review_overlays.is_empty());
32170        })
32171        .unwrap();
32172}
32173
32174#[gpui::test]
32175fn test_diff_review_empty_comment_not_submitted(cx: &mut TestAppContext) {
32176    init_test(cx, |_| {});
32177
32178    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32179
32180    // Show overlay
32181    editor
32182        .update(cx, |editor, window, cx| {
32183            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
32184        })
32185        .unwrap();
32186
32187    // Try to submit without typing anything (empty comment)
32188    editor
32189        .update(cx, |editor, window, cx| {
32190            editor.submit_diff_review_comment(window, cx);
32191        })
32192        .unwrap();
32193
32194    // Verify no comment was added
32195    editor
32196        .update(cx, |editor, _window, _cx| {
32197            assert_eq!(editor.total_review_comment_count(), 0);
32198        })
32199        .unwrap();
32200
32201    // Try to submit with whitespace-only comment
32202    editor
32203        .update(cx, |editor, window, cx| {
32204            if let Some(prompt_editor) = editor.diff_review_prompt_editor().cloned() {
32205                prompt_editor.update(cx, |pe, cx| {
32206                    pe.insert("   \n\t  ", window, cx);
32207                });
32208            }
32209            editor.submit_diff_review_comment(window, cx);
32210        })
32211        .unwrap();
32212
32213    // Verify still no comment was added
32214    editor
32215        .update(cx, |editor, _window, _cx| {
32216            assert_eq!(editor.total_review_comment_count(), 0);
32217        })
32218        .unwrap();
32219}
32220
32221#[gpui::test]
32222fn test_diff_review_inline_edit_flow(cx: &mut TestAppContext) {
32223    init_test(cx, |_| {});
32224
32225    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32226
32227    // Add a comment directly
32228    let comment_id = editor
32229        .update(cx, |editor, _window, cx| {
32230            let key = test_hunk_key("");
32231            add_test_comment(editor, key, "Original comment", cx)
32232        })
32233        .unwrap();
32234
32235    // Set comment to editing mode
32236    editor
32237        .update(cx, |editor, _window, cx| {
32238            editor.set_comment_editing(comment_id, true, cx);
32239        })
32240        .unwrap();
32241
32242    // Verify editing flag is set
32243    editor
32244        .update(cx, |editor, _window, cx| {
32245            let key = test_hunk_key("");
32246            let snapshot = editor.buffer().read(cx).snapshot(cx);
32247            let comments = editor.comments_for_hunk(&key, &snapshot);
32248            assert_eq!(comments.len(), 1);
32249            assert!(comments[0].is_editing);
32250        })
32251        .unwrap();
32252
32253    // Update the comment
32254    editor
32255        .update(cx, |editor, _window, cx| {
32256            let updated =
32257                editor.update_review_comment(comment_id, "Updated comment".to_string(), cx);
32258            assert!(updated);
32259        })
32260        .unwrap();
32261
32262    // Verify comment was updated and editing flag is cleared
32263    editor
32264        .update(cx, |editor, _window, cx| {
32265            let key = test_hunk_key("");
32266            let snapshot = editor.buffer().read(cx).snapshot(cx);
32267            let comments = editor.comments_for_hunk(&key, &snapshot);
32268            assert_eq!(comments[0].comment, "Updated comment");
32269            assert!(!comments[0].is_editing);
32270        })
32271        .unwrap();
32272}
32273
32274#[gpui::test]
32275fn test_orphaned_comments_are_cleaned_up(cx: &mut TestAppContext) {
32276    init_test(cx, |_| {});
32277
32278    // Create an editor with some text
32279    let editor = cx.add_window(|window, cx| {
32280        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
32281        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32282        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
32283    });
32284
32285    // Add a comment with an anchor on line 2
32286    editor
32287        .update(cx, |editor, _window, cx| {
32288            let snapshot = editor.buffer().read(cx).snapshot(cx);
32289            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
32290            let key = DiffHunkKey {
32291                file_path: Arc::from(util::rel_path::RelPath::empty()),
32292                hunk_start_anchor: anchor,
32293            };
32294            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
32295            assert_eq!(editor.total_review_comment_count(), 1);
32296        })
32297        .unwrap();
32298
32299    // Delete all content (this should orphan the comment's anchor)
32300    editor
32301        .update(cx, |editor, window, cx| {
32302            editor.select_all(&SelectAll, window, cx);
32303            editor.insert("completely new content", window, cx);
32304        })
32305        .unwrap();
32306
32307    // Trigger cleanup
32308    editor
32309        .update(cx, |editor, _window, cx| {
32310            editor.cleanup_orphaned_review_comments(cx);
32311            // Comment should be removed because its anchor is invalid
32312            assert_eq!(editor.total_review_comment_count(), 0);
32313        })
32314        .unwrap();
32315}
32316
32317#[gpui::test]
32318fn test_orphaned_comments_cleanup_called_on_buffer_edit(cx: &mut TestAppContext) {
32319    init_test(cx, |_| {});
32320
32321    // Create an editor with some text
32322    let editor = cx.add_window(|window, cx| {
32323        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
32324        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32325        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
32326    });
32327
32328    // Add a comment with an anchor on line 2
32329    editor
32330        .update(cx, |editor, _window, cx| {
32331            let snapshot = editor.buffer().read(cx).snapshot(cx);
32332            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
32333            let key = DiffHunkKey {
32334                file_path: Arc::from(util::rel_path::RelPath::empty()),
32335                hunk_start_anchor: anchor,
32336            };
32337            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
32338            assert_eq!(editor.total_review_comment_count(), 1);
32339        })
32340        .unwrap();
32341
32342    // Edit the buffer - this should trigger cleanup via on_buffer_event
32343    // Delete all content which orphans the anchor
32344    editor
32345        .update(cx, |editor, window, cx| {
32346            editor.select_all(&SelectAll, window, cx);
32347            editor.insert("completely new content", window, cx);
32348            // The cleanup is called automatically in on_buffer_event when Edited fires
32349        })
32350        .unwrap();
32351
32352    // Verify cleanup happened automatically (not manually triggered)
32353    editor
32354        .update(cx, |editor, _window, _cx| {
32355            // Comment should be removed because its anchor became invalid
32356            // and cleanup was called automatically on buffer edit
32357            assert_eq!(editor.total_review_comment_count(), 0);
32358        })
32359        .unwrap();
32360}
32361
32362#[gpui::test]
32363fn test_comments_stored_for_multiple_hunks(cx: &mut TestAppContext) {
32364    init_test(cx, |_| {});
32365
32366    // This test verifies that comments can be stored for multiple different hunks
32367    // and that hunk_comment_count correctly identifies comments per hunk.
32368    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32369
32370    _ = editor.update(cx, |editor, _window, cx| {
32371        let snapshot = editor.buffer().read(cx).snapshot(cx);
32372
32373        // Create two different hunk keys (simulating two different files)
32374        let anchor = snapshot.anchor_before(Point::new(0, 0));
32375        let key1 = DiffHunkKey {
32376            file_path: Arc::from(util::rel_path::RelPath::unix("file1.rs").unwrap()),
32377            hunk_start_anchor: anchor,
32378        };
32379        let key2 = DiffHunkKey {
32380            file_path: Arc::from(util::rel_path::RelPath::unix("file2.rs").unwrap()),
32381            hunk_start_anchor: anchor,
32382        };
32383
32384        // Add comments to first hunk
32385        editor.add_review_comment(
32386            key1.clone(),
32387            "Comment 1 for file1".to_string(),
32388            anchor..anchor,
32389            cx,
32390        );
32391        editor.add_review_comment(
32392            key1.clone(),
32393            "Comment 2 for file1".to_string(),
32394            anchor..anchor,
32395            cx,
32396        );
32397
32398        // Add comment to second hunk
32399        editor.add_review_comment(
32400            key2.clone(),
32401            "Comment for file2".to_string(),
32402            anchor..anchor,
32403            cx,
32404        );
32405
32406        // Verify total count
32407        assert_eq!(editor.total_review_comment_count(), 3);
32408
32409        // Verify per-hunk counts
32410        let snapshot = editor.buffer().read(cx).snapshot(cx);
32411        assert_eq!(
32412            editor.hunk_comment_count(&key1, &snapshot),
32413            2,
32414            "file1 should have 2 comments"
32415        );
32416        assert_eq!(
32417            editor.hunk_comment_count(&key2, &snapshot),
32418            1,
32419            "file2 should have 1 comment"
32420        );
32421
32422        // Verify comments_for_hunk returns correct comments
32423        let file1_comments = editor.comments_for_hunk(&key1, &snapshot);
32424        assert_eq!(file1_comments.len(), 2);
32425        assert_eq!(file1_comments[0].comment, "Comment 1 for file1");
32426        assert_eq!(file1_comments[1].comment, "Comment 2 for file1");
32427
32428        let file2_comments = editor.comments_for_hunk(&key2, &snapshot);
32429        assert_eq!(file2_comments.len(), 1);
32430        assert_eq!(file2_comments[0].comment, "Comment for file2");
32431    });
32432}
32433
32434#[gpui::test]
32435fn test_same_hunk_detected_by_matching_keys(cx: &mut TestAppContext) {
32436    init_test(cx, |_| {});
32437
32438    // This test verifies that hunk_keys_match correctly identifies when two
32439    // DiffHunkKeys refer to the same hunk (same file path and anchor point).
32440    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32441
32442    _ = editor.update(cx, |editor, _window, cx| {
32443        let snapshot = editor.buffer().read(cx).snapshot(cx);
32444        let anchor = snapshot.anchor_before(Point::new(0, 0));
32445
32446        // Create two keys with the same file path and anchor
32447        let key1 = DiffHunkKey {
32448            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
32449            hunk_start_anchor: anchor,
32450        };
32451        let key2 = DiffHunkKey {
32452            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
32453            hunk_start_anchor: anchor,
32454        };
32455
32456        // Add comment to first key
32457        editor.add_review_comment(key1, "Test comment".to_string(), anchor..anchor, cx);
32458
32459        // Verify second key (same hunk) finds the comment
32460        let snapshot = editor.buffer().read(cx).snapshot(cx);
32461        assert_eq!(
32462            editor.hunk_comment_count(&key2, &snapshot),
32463            1,
32464            "Same hunk should find the comment"
32465        );
32466
32467        // Create a key with different file path
32468        let different_file_key = DiffHunkKey {
32469            file_path: Arc::from(util::rel_path::RelPath::unix("other.rs").unwrap()),
32470            hunk_start_anchor: anchor,
32471        };
32472
32473        // Different file should not find the comment
32474        assert_eq!(
32475            editor.hunk_comment_count(&different_file_key, &snapshot),
32476            0,
32477            "Different file should not find the comment"
32478        );
32479    });
32480}
32481
32482#[gpui::test]
32483fn test_overlay_comments_expanded_state(cx: &mut TestAppContext) {
32484    init_test(cx, |_| {});
32485
32486    // This test verifies that set_diff_review_comments_expanded correctly
32487    // updates the expanded state of overlays.
32488    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32489
32490    // Show overlay
32491    editor
32492        .update(cx, |editor, window, cx| {
32493            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
32494        })
32495        .unwrap();
32496
32497    // Verify initially expanded (default)
32498    editor
32499        .update(cx, |editor, _window, _cx| {
32500            assert!(
32501                editor.diff_review_overlays[0].comments_expanded,
32502                "Should be expanded by default"
32503            );
32504        })
32505        .unwrap();
32506
32507    // Set to collapsed using the public method
32508    editor
32509        .update(cx, |editor, _window, cx| {
32510            editor.set_diff_review_comments_expanded(false, cx);
32511        })
32512        .unwrap();
32513
32514    // Verify collapsed
32515    editor
32516        .update(cx, |editor, _window, _cx| {
32517            assert!(
32518                !editor.diff_review_overlays[0].comments_expanded,
32519                "Should be collapsed after setting to false"
32520            );
32521        })
32522        .unwrap();
32523
32524    // Set back to expanded
32525    editor
32526        .update(cx, |editor, _window, cx| {
32527            editor.set_diff_review_comments_expanded(true, cx);
32528        })
32529        .unwrap();
32530
32531    // Verify expanded again
32532    editor
32533        .update(cx, |editor, _window, _cx| {
32534            assert!(
32535                editor.diff_review_overlays[0].comments_expanded,
32536                "Should be expanded after setting to true"
32537            );
32538        })
32539        .unwrap();
32540}
32541
32542#[gpui::test]
32543fn test_diff_review_multiline_selection(cx: &mut TestAppContext) {
32544    init_test(cx, |_| {});
32545
32546    // Create an editor with multiple lines of text
32547    let editor = cx.add_window(|window, cx| {
32548        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\nline 4\nline 5\n", cx));
32549        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32550        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
32551    });
32552
32553    // Test showing overlay with a multi-line selection (lines 1-3, which are rows 0-2)
32554    editor
32555        .update(cx, |editor, window, cx| {
32556            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(2), window, cx);
32557        })
32558        .unwrap();
32559
32560    // Verify line range
32561    editor
32562        .update(cx, |editor, _window, cx| {
32563            assert!(!editor.diff_review_overlays.is_empty());
32564            assert_eq!(editor.diff_review_line_range(cx), Some((0, 2)));
32565        })
32566        .unwrap();
32567
32568    // Dismiss and test with reversed range (end < start)
32569    editor
32570        .update(cx, |editor, _window, cx| {
32571            editor.dismiss_all_diff_review_overlays(cx);
32572        })
32573        .unwrap();
32574
32575    // Show overlay with reversed range - should normalize it
32576    editor
32577        .update(cx, |editor, window, cx| {
32578            editor.show_diff_review_overlay(DisplayRow(3)..DisplayRow(1), window, cx);
32579        })
32580        .unwrap();
32581
32582    // Verify range is normalized (start <= end)
32583    editor
32584        .update(cx, |editor, _window, cx| {
32585            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
32586        })
32587        .unwrap();
32588}
32589
32590#[gpui::test]
32591fn test_diff_review_drag_state(cx: &mut TestAppContext) {
32592    init_test(cx, |_| {});
32593
32594    let editor = cx.add_window(|window, cx| {
32595        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
32596        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32597        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
32598    });
32599
32600    // Initially no drag state
32601    editor
32602        .update(cx, |editor, _window, _cx| {
32603            assert!(editor.diff_review_drag_state.is_none());
32604        })
32605        .unwrap();
32606
32607    // Start drag at row 1
32608    editor
32609        .update(cx, |editor, window, cx| {
32610            editor.start_diff_review_drag(DisplayRow(1), window, cx);
32611        })
32612        .unwrap();
32613
32614    // Verify drag state is set
32615    editor
32616        .update(cx, |editor, window, cx| {
32617            assert!(editor.diff_review_drag_state.is_some());
32618            let snapshot = editor.snapshot(window, cx);
32619            let range = editor
32620                .diff_review_drag_state
32621                .as_ref()
32622                .unwrap()
32623                .row_range(&snapshot.display_snapshot);
32624            assert_eq!(*range.start(), DisplayRow(1));
32625            assert_eq!(*range.end(), DisplayRow(1));
32626        })
32627        .unwrap();
32628
32629    // Update drag to row 3
32630    editor
32631        .update(cx, |editor, window, cx| {
32632            editor.update_diff_review_drag(DisplayRow(3), window, cx);
32633        })
32634        .unwrap();
32635
32636    // Verify drag state is updated
32637    editor
32638        .update(cx, |editor, window, cx| {
32639            assert!(editor.diff_review_drag_state.is_some());
32640            let snapshot = editor.snapshot(window, cx);
32641            let range = editor
32642                .diff_review_drag_state
32643                .as_ref()
32644                .unwrap()
32645                .row_range(&snapshot.display_snapshot);
32646            assert_eq!(*range.start(), DisplayRow(1));
32647            assert_eq!(*range.end(), DisplayRow(3));
32648        })
32649        .unwrap();
32650
32651    // End drag - should show overlay
32652    editor
32653        .update(cx, |editor, window, cx| {
32654            editor.end_diff_review_drag(window, cx);
32655        })
32656        .unwrap();
32657
32658    // Verify drag state is cleared and overlay is shown
32659    editor
32660        .update(cx, |editor, _window, cx| {
32661            assert!(editor.diff_review_drag_state.is_none());
32662            assert!(!editor.diff_review_overlays.is_empty());
32663            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
32664        })
32665        .unwrap();
32666}
32667
32668#[gpui::test]
32669fn test_diff_review_drag_cancel(cx: &mut TestAppContext) {
32670    init_test(cx, |_| {});
32671
32672    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32673
32674    // Start drag
32675    editor
32676        .update(cx, |editor, window, cx| {
32677            editor.start_diff_review_drag(DisplayRow(0), window, cx);
32678        })
32679        .unwrap();
32680
32681    // Verify drag state is set
32682    editor
32683        .update(cx, |editor, _window, _cx| {
32684            assert!(editor.diff_review_drag_state.is_some());
32685        })
32686        .unwrap();
32687
32688    // Cancel drag
32689    editor
32690        .update(cx, |editor, _window, cx| {
32691            editor.cancel_diff_review_drag(cx);
32692        })
32693        .unwrap();
32694
32695    // Verify drag state is cleared and no overlay was created
32696    editor
32697        .update(cx, |editor, _window, _cx| {
32698            assert!(editor.diff_review_drag_state.is_none());
32699            assert!(editor.diff_review_overlays.is_empty());
32700        })
32701        .unwrap();
32702}
32703
32704#[gpui::test]
32705fn test_calculate_overlay_height(cx: &mut TestAppContext) {
32706    init_test(cx, |_| {});
32707
32708    // This test verifies that calculate_overlay_height returns correct heights
32709    // based on comment count and expanded state.
32710    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32711
32712    _ = editor.update(cx, |editor, _window, cx| {
32713        let snapshot = editor.buffer().read(cx).snapshot(cx);
32714        let anchor = snapshot.anchor_before(Point::new(0, 0));
32715        let key = DiffHunkKey {
32716            file_path: Arc::from(util::rel_path::RelPath::empty()),
32717            hunk_start_anchor: anchor,
32718        };
32719
32720        // No comments: base height of 2
32721        let height_no_comments = editor.calculate_overlay_height(&key, true, &snapshot);
32722        assert_eq!(
32723            height_no_comments, 2,
32724            "Base height should be 2 with no comments"
32725        );
32726
32727        // Add one comment
32728        editor.add_review_comment(key.clone(), "Comment 1".to_string(), anchor..anchor, cx);
32729
32730        let snapshot = editor.buffer().read(cx).snapshot(cx);
32731
32732        // With comments expanded: base (2) + header (1) + 2 per comment
32733        let height_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
32734        assert_eq!(
32735            height_expanded,
32736            2 + 1 + 2, // base + header + 1 comment * 2
32737            "Height with 1 comment expanded"
32738        );
32739
32740        // With comments collapsed: base (2) + header (1)
32741        let height_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
32742        assert_eq!(
32743            height_collapsed,
32744            2 + 1, // base + header only
32745            "Height with comments collapsed"
32746        );
32747
32748        // Add more comments
32749        editor.add_review_comment(key.clone(), "Comment 2".to_string(), anchor..anchor, cx);
32750        editor.add_review_comment(key.clone(), "Comment 3".to_string(), anchor..anchor, cx);
32751
32752        let snapshot = editor.buffer().read(cx).snapshot(cx);
32753
32754        // With 3 comments expanded
32755        let height_3_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
32756        assert_eq!(
32757            height_3_expanded,
32758            2 + 1 + (3 * 2), // base + header + 3 comments * 2
32759            "Height with 3 comments expanded"
32760        );
32761
32762        // Collapsed height stays the same regardless of comment count
32763        let height_3_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
32764        assert_eq!(
32765            height_3_collapsed,
32766            2 + 1, // base + header only
32767            "Height with 3 comments collapsed should be same as 1 comment collapsed"
32768        );
32769    });
32770}
32771
32772#[gpui::test]
32773async fn test_move_to_start_end_of_larger_syntax_node_single_cursor(cx: &mut TestAppContext) {
32774    init_test(cx, |_| {});
32775
32776    let language = Arc::new(Language::new(
32777        LanguageConfig::default(),
32778        Some(tree_sitter_rust::LANGUAGE.into()),
32779    ));
32780
32781    let text = r#"
32782        fn main() {
32783            let x = foo(1, 2);
32784        }
32785    "#
32786    .unindent();
32787
32788    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
32789    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32790    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32791
32792    editor
32793        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32794        .await;
32795
32796    // Test case 1: Move to end of syntax nodes
32797    editor.update_in(cx, |editor, window, cx| {
32798        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32799            s.select_display_ranges([
32800                DisplayPoint::new(DisplayRow(1), 16)..DisplayPoint::new(DisplayRow(1), 16)
32801            ]);
32802        });
32803    });
32804    editor.update(cx, |editor, cx| {
32805        assert_text_with_selections(
32806            editor,
32807            indoc! {r#"
32808                fn main() {
32809                    let x = foo(ˇ1, 2);
32810                }
32811            "#},
32812            cx,
32813        );
32814    });
32815    editor.update_in(cx, |editor, window, cx| {
32816        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
32817    });
32818    editor.update(cx, |editor, cx| {
32819        assert_text_with_selections(
32820            editor,
32821            indoc! {r#"
32822                fn main() {
32823                    let x = foo(1ˇ, 2);
32824                }
32825            "#},
32826            cx,
32827        );
32828    });
32829    editor.update_in(cx, |editor, window, cx| {
32830        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
32831    });
32832    editor.update(cx, |editor, cx| {
32833        assert_text_with_selections(
32834            editor,
32835            indoc! {r#"
32836                fn main() {
32837                    let x = foo(1, 2)ˇ;
32838                }
32839            "#},
32840            cx,
32841        );
32842    });
32843    editor.update_in(cx, |editor, window, cx| {
32844        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
32845    });
32846    editor.update(cx, |editor, cx| {
32847        assert_text_with_selections(
32848            editor,
32849            indoc! {r#"
32850                fn main() {
32851                    let x = foo(1, 2);ˇ
32852                }
32853            "#},
32854            cx,
32855        );
32856    });
32857    editor.update_in(cx, |editor, window, cx| {
32858        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
32859    });
32860    editor.update(cx, |editor, cx| {
32861        assert_text_with_selections(
32862            editor,
32863            indoc! {r#"
32864                fn main() {
32865                    let x = foo(1, 2);
3286632867            "#},
32868            cx,
32869        );
32870    });
32871
32872    // Test case 2: Move to start of syntax nodes
32873    editor.update_in(cx, |editor, window, cx| {
32874        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32875            s.select_display_ranges([
32876                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20)
32877            ]);
32878        });
32879    });
32880    editor.update(cx, |editor, cx| {
32881        assert_text_with_selections(
32882            editor,
32883            indoc! {r#"
32884                fn main() {
32885                    let x = foo(1, 2ˇ);
32886                }
32887            "#},
32888            cx,
32889        );
32890    });
32891    editor.update_in(cx, |editor, window, cx| {
32892        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32893    });
32894    editor.update(cx, |editor, cx| {
32895        assert_text_with_selections(
32896            editor,
32897            indoc! {r#"
32898                fn main() {
32899                    let x = fooˇ(1, 2);
32900                }
32901            "#},
32902            cx,
32903        );
32904    });
32905    editor.update_in(cx, |editor, window, cx| {
32906        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32907    });
32908    editor.update(cx, |editor, cx| {
32909        assert_text_with_selections(
32910            editor,
32911            indoc! {r#"
32912                fn main() {
32913                    let x = ˇfoo(1, 2);
32914                }
32915            "#},
32916            cx,
32917        );
32918    });
32919    editor.update_in(cx, |editor, window, cx| {
32920        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32921    });
32922    editor.update(cx, |editor, cx| {
32923        assert_text_with_selections(
32924            editor,
32925            indoc! {r#"
32926                fn main() {
32927                    ˇlet x = foo(1, 2);
32928                }
32929            "#},
32930            cx,
32931        );
32932    });
32933    editor.update_in(cx, |editor, window, cx| {
32934        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32935    });
32936    editor.update(cx, |editor, cx| {
32937        assert_text_with_selections(
32938            editor,
32939            indoc! {r#"
32940                fn main() ˇ{
32941                    let x = foo(1, 2);
32942                }
32943            "#},
32944            cx,
32945        );
32946    });
32947    editor.update_in(cx, |editor, window, cx| {
32948        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
32949    });
32950    editor.update(cx, |editor, cx| {
32951        assert_text_with_selections(
32952            editor,
32953            indoc! {r#"
32954                ˇfn main() {
32955                    let x = foo(1, 2);
32956                }
32957            "#},
32958            cx,
32959        );
32960    });
32961}
32962
32963#[gpui::test]
32964async fn test_move_to_start_end_of_larger_syntax_node_two_cursors(cx: &mut TestAppContext) {
32965    init_test(cx, |_| {});
32966
32967    let language = Arc::new(Language::new(
32968        LanguageConfig::default(),
32969        Some(tree_sitter_rust::LANGUAGE.into()),
32970    ));
32971
32972    let text = r#"
32973        fn main() {
32974            let x = foo(1, 2);
32975            let y = bar(3, 4);
32976        }
32977    "#
32978    .unindent();
32979
32980    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
32981    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32982    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
32983
32984    editor
32985        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
32986        .await;
32987
32988    // Test case 1: Move to end of syntax nodes with two cursors
32989    editor.update_in(cx, |editor, window, cx| {
32990        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
32991            s.select_display_ranges([
32992                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20),
32993                DisplayPoint::new(DisplayRow(2), 20)..DisplayPoint::new(DisplayRow(2), 20),
32994            ]);
32995        });
32996    });
32997    editor.update(cx, |editor, cx| {
32998        assert_text_with_selections(
32999            editor,
33000            indoc! {r#"
33001                fn main() {
33002                    let x = foo(1, 2ˇ);
33003                    let y = bar(3, 4ˇ);
33004                }
33005            "#},
33006            cx,
33007        );
33008    });
33009    editor.update_in(cx, |editor, window, cx| {
33010        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
33011    });
33012    editor.update(cx, |editor, cx| {
33013        assert_text_with_selections(
33014            editor,
33015            indoc! {r#"
33016                fn main() {
33017                    let x = foo(1, 2)ˇ;
33018                    let y = bar(3, 4)ˇ;
33019                }
33020            "#},
33021            cx,
33022        );
33023    });
33024    editor.update_in(cx, |editor, window, cx| {
33025        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
33026    });
33027    editor.update(cx, |editor, cx| {
33028        assert_text_with_selections(
33029            editor,
33030            indoc! {r#"
33031                fn main() {
33032                    let x = foo(1, 2);ˇ
33033                    let y = bar(3, 4);ˇ
33034                }
33035            "#},
33036            cx,
33037        );
33038    });
33039
33040    // Test case 2: Move to start of syntax nodes with two cursors
33041    editor.update_in(cx, |editor, window, cx| {
33042        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33043            s.select_display_ranges([
33044                DisplayPoint::new(DisplayRow(1), 19)..DisplayPoint::new(DisplayRow(1), 19),
33045                DisplayPoint::new(DisplayRow(2), 19)..DisplayPoint::new(DisplayRow(2), 19),
33046            ]);
33047        });
33048    });
33049    editor.update(cx, |editor, cx| {
33050        assert_text_with_selections(
33051            editor,
33052            indoc! {r#"
33053                fn main() {
33054                    let x = foo(1, ˇ2);
33055                    let y = bar(3, ˇ4);
33056                }
33057            "#},
33058            cx,
33059        );
33060    });
33061    editor.update_in(cx, |editor, window, cx| {
33062        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
33063    });
33064    editor.update(cx, |editor, cx| {
33065        assert_text_with_selections(
33066            editor,
33067            indoc! {r#"
33068                fn main() {
33069                    let x = fooˇ(1, 2);
33070                    let y = barˇ(3, 4);
33071                }
33072            "#},
33073            cx,
33074        );
33075    });
33076    editor.update_in(cx, |editor, window, cx| {
33077        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
33078    });
33079    editor.update(cx, |editor, cx| {
33080        assert_text_with_selections(
33081            editor,
33082            indoc! {r#"
33083                fn main() {
33084                    let x = ˇfoo(1, 2);
33085                    let y = ˇbar(3, 4);
33086                }
33087            "#},
33088            cx,
33089        );
33090    });
33091    editor.update_in(cx, |editor, window, cx| {
33092        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
33093    });
33094    editor.update(cx, |editor, cx| {
33095        assert_text_with_selections(
33096            editor,
33097            indoc! {r#"
33098                fn main() {
33099                    ˇlet x = foo(1, 2);
33100                    ˇlet y = bar(3, 4);
33101                }
33102            "#},
33103            cx,
33104        );
33105    });
33106}
33107
33108#[gpui::test]
33109async fn test_move_to_start_end_of_larger_syntax_node_with_selections_and_strings(
33110    cx: &mut TestAppContext,
33111) {
33112    init_test(cx, |_| {});
33113
33114    let language = Arc::new(Language::new(
33115        LanguageConfig::default(),
33116        Some(tree_sitter_rust::LANGUAGE.into()),
33117    ));
33118
33119    let text = r#"
33120        fn main() {
33121            let x = foo(1, 2);
33122            let msg = "hello world";
33123        }
33124    "#
33125    .unindent();
33126
33127    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
33128    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33129    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33130
33131    editor
33132        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33133        .await;
33134
33135    // Test case 1: With existing selection, move_to_end keeps selection
33136    editor.update_in(cx, |editor, window, cx| {
33137        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33138            s.select_display_ranges([
33139                DisplayPoint::new(DisplayRow(1), 12)..DisplayPoint::new(DisplayRow(1), 21)
33140            ]);
33141        });
33142    });
33143    editor.update(cx, |editor, cx| {
33144        assert_text_with_selections(
33145            editor,
33146            indoc! {r#"
33147                fn main() {
33148                    let x = «foo(1, 2)ˇ»;
33149                    let msg = "hello world";
33150                }
33151            "#},
33152            cx,
33153        );
33154    });
33155    editor.update_in(cx, |editor, window, cx| {
33156        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
33157    });
33158    editor.update(cx, |editor, cx| {
33159        assert_text_with_selections(
33160            editor,
33161            indoc! {r#"
33162                fn main() {
33163                    let x = «foo(1, 2)ˇ»;
33164                    let msg = "hello world";
33165                }
33166            "#},
33167            cx,
33168        );
33169    });
33170
33171    // Test case 2: Move to end within a string
33172    editor.update_in(cx, |editor, window, cx| {
33173        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33174            s.select_display_ranges([
33175                DisplayPoint::new(DisplayRow(2), 15)..DisplayPoint::new(DisplayRow(2), 15)
33176            ]);
33177        });
33178    });
33179    editor.update(cx, |editor, cx| {
33180        assert_text_with_selections(
33181            editor,
33182            indoc! {r#"
33183                fn main() {
33184                    let x = foo(1, 2);
33185                    let msg = "ˇhello world";
33186                }
33187            "#},
33188            cx,
33189        );
33190    });
33191    editor.update_in(cx, |editor, window, cx| {
33192        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
33193    });
33194    editor.update(cx, |editor, cx| {
33195        assert_text_with_selections(
33196            editor,
33197            indoc! {r#"
33198                fn main() {
33199                    let x = foo(1, 2);
33200                    let msg = "hello worldˇ";
33201                }
33202            "#},
33203            cx,
33204        );
33205    });
33206
33207    // Test case 3: Move to start within a string
33208    editor.update_in(cx, |editor, window, cx| {
33209        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33210            s.select_display_ranges([
33211                DisplayPoint::new(DisplayRow(2), 21)..DisplayPoint::new(DisplayRow(2), 21)
33212            ]);
33213        });
33214    });
33215    editor.update(cx, |editor, cx| {
33216        assert_text_with_selections(
33217            editor,
33218            indoc! {r#"
33219                fn main() {
33220                    let x = foo(1, 2);
33221                    let msg = "hello ˇworld";
33222                }
33223            "#},
33224            cx,
33225        );
33226    });
33227    editor.update_in(cx, |editor, window, cx| {
33228        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
33229    });
33230    editor.update(cx, |editor, cx| {
33231        assert_text_with_selections(
33232            editor,
33233            indoc! {r#"
33234                fn main() {
33235                    let x = foo(1, 2);
33236                    let msg = "ˇhello world";
33237                }
33238            "#},
33239            cx,
33240        );
33241    });
33242}
33243
33244#[gpui::test]
33245async fn test_select_to_start_end_of_larger_syntax_node(cx: &mut TestAppContext) {
33246    init_test(cx, |_| {});
33247
33248    let language = Arc::new(Language::new(
33249        LanguageConfig::default(),
33250        Some(tree_sitter_rust::LANGUAGE.into()),
33251    ));
33252
33253    // Test Group 1.1: Cursor in String - First Jump (Select to End)
33254    let text = r#"let msg = "foo bar baz";"#.unindent();
33255
33256    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33257    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33258    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33259
33260    editor
33261        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33262        .await;
33263
33264    editor.update_in(cx, |editor, window, cx| {
33265        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33266            s.select_display_ranges([
33267                DisplayPoint::new(DisplayRow(0), 14)..DisplayPoint::new(DisplayRow(0), 14)
33268            ]);
33269        });
33270    });
33271    editor.update(cx, |editor, cx| {
33272        assert_text_with_selections(editor, indoc! {r#"let msg = "fooˇ bar baz";"#}, cx);
33273    });
33274    editor.update_in(cx, |editor, window, cx| {
33275        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33276    });
33277    editor.update(cx, |editor, cx| {
33278        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar bazˇ»";"#}, cx);
33279    });
33280
33281    // Test Group 1.2: Cursor in String - Second Jump (Select to End)
33282    editor.update_in(cx, |editor, window, cx| {
33283        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33284    });
33285    editor.update(cx, |editor, cx| {
33286        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz"ˇ»;"#}, cx);
33287    });
33288
33289    // Test Group 1.3: Cursor in String - Third Jump (Select to End)
33290    editor.update_in(cx, |editor, window, cx| {
33291        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33292    });
33293    editor.update(cx, |editor, cx| {
33294        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz";ˇ»"#}, cx);
33295    });
33296
33297    // Test Group 1.4: Cursor in String - First Jump (Select to Start)
33298    editor.update_in(cx, |editor, window, cx| {
33299        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33300            s.select_display_ranges([
33301                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18)
33302            ]);
33303        });
33304    });
33305    editor.update(cx, |editor, cx| {
33306        assert_text_with_selections(editor, indoc! {r#"let msg = "foo barˇ baz";"#}, cx);
33307    });
33308    editor.update_in(cx, |editor, window, cx| {
33309        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
33310    });
33311    editor.update(cx, |editor, cx| {
33312        assert_text_with_selections(editor, indoc! {r#"let msg = "«ˇfoo bar» baz";"#}, cx);
33313    });
33314
33315    // Test Group 1.5: Cursor in String - Second Jump (Select to Start)
33316    editor.update_in(cx, |editor, window, cx| {
33317        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
33318    });
33319    editor.update(cx, |editor, cx| {
33320        assert_text_with_selections(editor, indoc! {r#"let msg = «ˇ"foo bar» baz";"#}, cx);
33321    });
33322
33323    // Test Group 1.6: Cursor in String - Third Jump (Select to Start)
33324    editor.update_in(cx, |editor, window, cx| {
33325        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
33326    });
33327    editor.update(cx, |editor, cx| {
33328        assert_text_with_selections(editor, indoc! {r#"«ˇlet msg = "foo bar» baz";"#}, cx);
33329    });
33330
33331    // Test Group 2.1: Let Statement Progression (Select to End)
33332    let text = r#"
33333fn main() {
33334    let x = "hello";
33335}
33336"#
33337    .unindent();
33338
33339    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33340    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33341    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33342
33343    editor
33344        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33345        .await;
33346
33347    editor.update_in(cx, |editor, window, cx| {
33348        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33349            s.select_display_ranges([
33350                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)
33351            ]);
33352        });
33353    });
33354    editor.update(cx, |editor, cx| {
33355        assert_text_with_selections(
33356            editor,
33357            indoc! {r#"
33358                fn main() {
33359                    let xˇ = "hello";
33360                }
33361            "#},
33362            cx,
33363        );
33364    });
33365    editor.update_in(cx, |editor, window, cx| {
33366        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33367    });
33368    editor.update(cx, |editor, cx| {
33369        assert_text_with_selections(
33370            editor,
33371            indoc! {r##"
33372                fn main() {
33373                    let x« = "hello";ˇ»
33374                }
33375            "##},
33376            cx,
33377        );
33378    });
33379    editor.update_in(cx, |editor, window, cx| {
33380        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33381    });
33382    editor.update(cx, |editor, cx| {
33383        assert_text_with_selections(
33384            editor,
33385            indoc! {r#"
33386                fn main() {
33387                    let x« = "hello";
33388                }ˇ»
33389            "#},
33390            cx,
33391        );
33392    });
33393
33394    // Test Group 2.2a: From Inside String Content Node To String Content Boundary
33395    let text = r#"let x = "hello";"#.unindent();
33396
33397    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33398    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33399    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33400
33401    editor
33402        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33403        .await;
33404
33405    editor.update_in(cx, |editor, window, cx| {
33406        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33407            s.select_display_ranges([
33408                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12)
33409            ]);
33410        });
33411    });
33412    editor.update(cx, |editor, cx| {
33413        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo";"#}, cx);
33414    });
33415    editor.update_in(cx, |editor, window, cx| {
33416        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
33417    });
33418    editor.update(cx, |editor, cx| {
33419        assert_text_with_selections(editor, indoc! {r#"let x = "«ˇhel»lo";"#}, cx);
33420    });
33421
33422    // Test Group 2.2b: From Edge of String Content Node To String Literal Boundary
33423    editor.update_in(cx, |editor, window, cx| {
33424        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33425            s.select_display_ranges([
33426                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
33427            ]);
33428        });
33429    });
33430    editor.update(cx, |editor, cx| {
33431        assert_text_with_selections(editor, indoc! {r#"let x = "ˇhello";"#}, cx);
33432    });
33433    editor.update_in(cx, |editor, window, cx| {
33434        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
33435    });
33436    editor.update(cx, |editor, cx| {
33437        assert_text_with_selections(editor, indoc! {r#"let x = «ˇ"»hello";"#}, cx);
33438    });
33439
33440    // Test Group 3.1: Create Selection from Cursor (Select to End)
33441    let text = r#"let x = "hello world";"#.unindent();
33442
33443    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33444    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33445    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33446
33447    editor
33448        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33449        .await;
33450
33451    editor.update_in(cx, |editor, window, cx| {
33452        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33453            s.select_display_ranges([
33454                DisplayPoint::new(DisplayRow(0), 14)..DisplayPoint::new(DisplayRow(0), 14)
33455            ]);
33456        });
33457    });
33458    editor.update(cx, |editor, cx| {
33459        assert_text_with_selections(editor, indoc! {r#"let x = "helloˇ world";"#}, cx);
33460    });
33461    editor.update_in(cx, |editor, window, cx| {
33462        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33463    });
33464    editor.update(cx, |editor, cx| {
33465        assert_text_with_selections(editor, indoc! {r#"let x = "hello« worldˇ»";"#}, cx);
33466    });
33467
33468    // Test Group 3.2: Extend Existing Selection (Select to End)
33469    editor.update_in(cx, |editor, window, cx| {
33470        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33471            s.select_display_ranges([
33472                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 17)
33473            ]);
33474        });
33475    });
33476    editor.update(cx, |editor, cx| {
33477        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo woˇ»rld";"#}, cx);
33478    });
33479    editor.update_in(cx, |editor, window, cx| {
33480        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33481    });
33482    editor.update(cx, |editor, cx| {
33483        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo worldˇ»";"#}, cx);
33484    });
33485
33486    // Test Group 4.1: Multiple Cursors - All Expand to Different Syntax Nodes
33487    let text = r#"let x = "hello"; let y = 42;"#.unindent();
33488
33489    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33490    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33491    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33492
33493    editor
33494        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33495        .await;
33496
33497    editor.update_in(cx, |editor, window, cx| {
33498        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33499            s.select_display_ranges([
33500                // Cursor inside string content
33501                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
33502                // Cursor at let statement semicolon
33503                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18),
33504                // Cursor inside integer literal
33505                DisplayPoint::new(DisplayRow(0), 26)..DisplayPoint::new(DisplayRow(0), 26),
33506            ]);
33507        });
33508    });
33509    editor.update(cx, |editor, cx| {
33510        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo"; lˇet y = 4ˇ2;"#}, cx);
33511    });
33512    editor.update_in(cx, |editor, window, cx| {
33513        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33514    });
33515    editor.update(cx, |editor, cx| {
33516        assert_text_with_selections(editor, indoc! {r#"let x = "hel«loˇ»"; l«et y = 42;ˇ»"#}, cx);
33517    });
33518
33519    // Test Group 4.2: Multiple Cursors on Separate Lines
33520    let text = r#"
33521let x = "hello";
33522let y = 42;
33523"#
33524    .unindent();
33525
33526    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33527    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33528    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33529
33530    editor
33531        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33532        .await;
33533
33534    editor.update_in(cx, |editor, window, cx| {
33535        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33536            s.select_display_ranges([
33537                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
33538                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9),
33539            ]);
33540        });
33541    });
33542
33543    editor.update(cx, |editor, cx| {
33544        assert_text_with_selections(
33545            editor,
33546            indoc! {r#"
33547                let x = "helˇlo";
33548                let y = 4ˇ2;
33549            "#},
33550            cx,
33551        );
33552    });
33553    editor.update_in(cx, |editor, window, cx| {
33554        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33555    });
33556    editor.update(cx, |editor, cx| {
33557        assert_text_with_selections(
33558            editor,
33559            indoc! {r#"
33560                let x = "hel«loˇ»";
33561                let y = 4«2ˇ»;
33562            "#},
33563            cx,
33564        );
33565    });
33566
33567    // Test Group 5.1: Nested Function Calls
33568    let text = r#"let result = foo(bar("arg"));"#.unindent();
33569
33570    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33571    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33572    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33573
33574    editor
33575        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33576        .await;
33577
33578    editor.update_in(cx, |editor, window, cx| {
33579        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33580            s.select_display_ranges([
33581                DisplayPoint::new(DisplayRow(0), 22)..DisplayPoint::new(DisplayRow(0), 22)
33582            ]);
33583        });
33584    });
33585    editor.update(cx, |editor, cx| {
33586        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("ˇarg"));"#}, cx);
33587    });
33588    editor.update_in(cx, |editor, window, cx| {
33589        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33590    });
33591    editor.update(cx, |editor, cx| {
33592        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«argˇ»"));"#}, cx);
33593    });
33594    editor.update_in(cx, |editor, window, cx| {
33595        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33596    });
33597    editor.update(cx, |editor, cx| {
33598        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg"ˇ»));"#}, cx);
33599    });
33600    editor.update_in(cx, |editor, window, cx| {
33601        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33602    });
33603    editor.update(cx, |editor, cx| {
33604        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg")ˇ»);"#}, cx);
33605    });
33606
33607    // Test Group 6.1: Block Comments
33608    let text = r#"let x = /* multi
33609                             line
33610                             comment */;"#
33611        .unindent();
33612
33613    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33614    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33615    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33616
33617    editor
33618        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33619        .await;
33620
33621    editor.update_in(cx, |editor, window, cx| {
33622        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33623            s.select_display_ranges([
33624                DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16)
33625            ]);
33626        });
33627    });
33628    editor.update(cx, |editor, cx| {
33629        assert_text_with_selections(
33630            editor,
33631            indoc! {r#"
33632let x = /* multiˇ
33633line
33634comment */;"#},
33635            cx,
33636        );
33637    });
33638    editor.update_in(cx, |editor, window, cx| {
33639        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33640    });
33641    editor.update(cx, |editor, cx| {
33642        assert_text_with_selections(
33643            editor,
33644            indoc! {r#"
33645let x = /* multi«
33646line
33647comment */ˇ»;"#},
33648            cx,
33649        );
33650    });
33651
33652    // Test Group 6.2: Array/Vector Literals
33653    let text = r#"let arr = [1, 2, 3];"#.unindent();
33654
33655    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33656    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33657    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33658
33659    editor
33660        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33661        .await;
33662
33663    editor.update_in(cx, |editor, window, cx| {
33664        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33665            s.select_display_ranges([
33666                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
33667            ]);
33668        });
33669    });
33670    editor.update(cx, |editor, cx| {
33671        assert_text_with_selections(editor, indoc! {r#"let arr = [ˇ1, 2, 3];"#}, cx);
33672    });
33673    editor.update_in(cx, |editor, window, cx| {
33674        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33675    });
33676    editor.update(cx, |editor, cx| {
33677        assert_text_with_selections(editor, indoc! {r#"let arr = [«1ˇ», 2, 3];"#}, cx);
33678    });
33679    editor.update_in(cx, |editor, window, cx| {
33680        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33681    });
33682    editor.update(cx, |editor, cx| {
33683        assert_text_with_selections(editor, indoc! {r#"let arr = [«1, 2, 3]ˇ»;"#}, cx);
33684    });
33685}
33686
33687#[gpui::test]
33688async fn test_restore_and_next(cx: &mut TestAppContext) {
33689    init_test(cx, |_| {});
33690    let mut cx = EditorTestContext::new(cx).await;
33691
33692    let diff_base = r#"
33693        one
33694        two
33695        three
33696        four
33697        five
33698        "#
33699    .unindent();
33700
33701    cx.set_state(
33702        &r#"
33703        ONE
33704        two
33705        ˇTHREE
33706        four
33707        FIVE
33708        "#
33709        .unindent(),
33710    );
33711    cx.set_head_text(&diff_base);
33712
33713    cx.update_editor(|editor, window, cx| {
33714        editor.set_expand_all_diff_hunks(cx);
33715        editor.restore_and_next(&Default::default(), window, cx);
33716    });
33717    cx.run_until_parked();
33718
33719    cx.assert_state_with_diff(
33720        r#"
33721        - one
33722        + ONE
33723          two
33724          three
33725          four
33726        - ˇfive
33727        + FIVE
33728        "#
33729        .unindent(),
33730    );
33731
33732    cx.update_editor(|editor, window, cx| {
33733        editor.restore_and_next(&Default::default(), window, cx);
33734    });
33735    cx.run_until_parked();
33736
33737    cx.assert_state_with_diff(
33738        r#"
33739        - one
33740        + ONE
33741          two
33742          three
33743          four
33744          ˇfive
33745        "#
33746        .unindent(),
33747    );
33748}