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    // Ensure multiple cursors have consistent direction after expanding
10209    editor.update_in(cx, |editor, window, cx| {
10210        editor.unfold_all(&UnfoldAll, window, cx);
10211        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10212            s.select_display_ranges([
10213                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
10214                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
10215            ]);
10216        });
10217        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10218    });
10219    editor.update(cx, |editor, cx| {
10220        assert_text_with_selections(
10221            editor,
10222            indoc! {r#"
10223                use mod1::mod2::{mod3, «mod4ˇ»};
10224
10225                fn fn_1(param1: bool, param2: &str) {
10226                    let var1 = "«textˇ»";
10227                }
10228            "#},
10229            cx,
10230        );
10231    });
10232}
10233
10234#[gpui::test]
10235async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
10236    init_test(cx, |_| {});
10237
10238    let language = Arc::new(Language::new(
10239        LanguageConfig::default(),
10240        Some(tree_sitter_rust::LANGUAGE.into()),
10241    ));
10242
10243    let text = "let a = 2;";
10244
10245    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10246    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10247    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10248
10249    editor
10250        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10251        .await;
10252
10253    // Test case 1: Cursor at end of word
10254    editor.update_in(cx, |editor, window, cx| {
10255        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10256            s.select_display_ranges([
10257                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
10258            ]);
10259        });
10260    });
10261    editor.update(cx, |editor, cx| {
10262        assert_text_with_selections(editor, "let aˇ = 2;", cx);
10263    });
10264    editor.update_in(cx, |editor, window, cx| {
10265        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10266    });
10267    editor.update(cx, |editor, cx| {
10268        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
10269    });
10270    editor.update_in(cx, |editor, window, cx| {
10271        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10272    });
10273    editor.update(cx, |editor, cx| {
10274        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
10275    });
10276
10277    // Test case 2: Cursor at end of statement
10278    editor.update_in(cx, |editor, window, cx| {
10279        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10280            s.select_display_ranges([
10281                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
10282            ]);
10283        });
10284    });
10285    editor.update(cx, |editor, cx| {
10286        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
10287    });
10288    editor.update_in(cx, |editor, window, cx| {
10289        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10290    });
10291    editor.update(cx, |editor, cx| {
10292        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
10293    });
10294}
10295
10296#[gpui::test]
10297async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
10298    init_test(cx, |_| {});
10299
10300    let language = Arc::new(Language::new(
10301        LanguageConfig {
10302            name: "JavaScript".into(),
10303            ..Default::default()
10304        },
10305        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10306    ));
10307
10308    let text = r#"
10309        let a = {
10310            key: "value",
10311        };
10312    "#
10313    .unindent();
10314
10315    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10316    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10317    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10318
10319    editor
10320        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10321        .await;
10322
10323    // Test case 1: Cursor after '{'
10324    editor.update_in(cx, |editor, window, cx| {
10325        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10326            s.select_display_ranges([
10327                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
10328            ]);
10329        });
10330    });
10331    editor.update(cx, |editor, cx| {
10332        assert_text_with_selections(
10333            editor,
10334            indoc! {r#"
10335                let a = {ˇ
10336                    key: "value",
10337                };
10338            "#},
10339            cx,
10340        );
10341    });
10342    editor.update_in(cx, |editor, window, cx| {
10343        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10344    });
10345    editor.update(cx, |editor, cx| {
10346        assert_text_with_selections(
10347            editor,
10348            indoc! {r#"
10349                let a = «ˇ{
10350                    key: "value",
10351                }»;
10352            "#},
10353            cx,
10354        );
10355    });
10356
10357    // Test case 2: Cursor after ':'
10358    editor.update_in(cx, |editor, window, cx| {
10359        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10360            s.select_display_ranges([
10361                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
10362            ]);
10363        });
10364    });
10365    editor.update(cx, |editor, cx| {
10366        assert_text_with_selections(
10367            editor,
10368            indoc! {r#"
10369                let a = {
10370                    key:ˇ "value",
10371                };
10372            "#},
10373            cx,
10374        );
10375    });
10376    editor.update_in(cx, |editor, window, cx| {
10377        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10378    });
10379    editor.update(cx, |editor, cx| {
10380        assert_text_with_selections(
10381            editor,
10382            indoc! {r#"
10383                let a = {
10384                    «ˇkey: "value"»,
10385                };
10386            "#},
10387            cx,
10388        );
10389    });
10390    editor.update_in(cx, |editor, window, cx| {
10391        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10392    });
10393    editor.update(cx, |editor, cx| {
10394        assert_text_with_selections(
10395            editor,
10396            indoc! {r#"
10397                let a = «ˇ{
10398                    key: "value",
10399                }»;
10400            "#},
10401            cx,
10402        );
10403    });
10404
10405    // Test case 3: Cursor after ','
10406    editor.update_in(cx, |editor, window, cx| {
10407        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10408            s.select_display_ranges([
10409                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
10410            ]);
10411        });
10412    });
10413    editor.update(cx, |editor, cx| {
10414        assert_text_with_selections(
10415            editor,
10416            indoc! {r#"
10417                let a = {
10418                    key: "value",ˇ
10419                };
10420            "#},
10421            cx,
10422        );
10423    });
10424    editor.update_in(cx, |editor, window, cx| {
10425        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10426    });
10427    editor.update(cx, |editor, cx| {
10428        assert_text_with_selections(
10429            editor,
10430            indoc! {r#"
10431                let a = «ˇ{
10432                    key: "value",
10433                }»;
10434            "#},
10435            cx,
10436        );
10437    });
10438
10439    // Test case 4: Cursor after ';'
10440    editor.update_in(cx, |editor, window, cx| {
10441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10442            s.select_display_ranges([
10443                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
10444            ]);
10445        });
10446    });
10447    editor.update(cx, |editor, cx| {
10448        assert_text_with_selections(
10449            editor,
10450            indoc! {r#"
10451                let a = {
10452                    key: "value",
10453                };ˇ
10454            "#},
10455            cx,
10456        );
10457    });
10458    editor.update_in(cx, |editor, window, cx| {
10459        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10460    });
10461    editor.update(cx, |editor, cx| {
10462        assert_text_with_selections(
10463            editor,
10464            indoc! {r#"
10465                «ˇlet a = {
10466                    key: "value",
10467                };
10468                »"#},
10469            cx,
10470        );
10471    });
10472}
10473
10474#[gpui::test]
10475async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
10476    init_test(cx, |_| {});
10477
10478    let language = Arc::new(Language::new(
10479        LanguageConfig::default(),
10480        Some(tree_sitter_rust::LANGUAGE.into()),
10481    ));
10482
10483    let text = r#"
10484        use mod1::mod2::{mod3, mod4};
10485
10486        fn fn_1(param1: bool, param2: &str) {
10487            let var1 = "hello world";
10488        }
10489    "#
10490    .unindent();
10491
10492    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10493    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10494    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10495
10496    editor
10497        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10498        .await;
10499
10500    // Test 1: Cursor on a letter of a string word
10501    editor.update_in(cx, |editor, window, cx| {
10502        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10503            s.select_display_ranges([
10504                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
10505            ]);
10506        });
10507    });
10508    editor.update_in(cx, |editor, window, cx| {
10509        assert_text_with_selections(
10510            editor,
10511            indoc! {r#"
10512                use mod1::mod2::{mod3, mod4};
10513
10514                fn fn_1(param1: bool, param2: &str) {
10515                    let var1 = "hˇello world";
10516                }
10517            "#},
10518            cx,
10519        );
10520        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10521        assert_text_with_selections(
10522            editor,
10523            indoc! {r#"
10524                use mod1::mod2::{mod3, mod4};
10525
10526                fn fn_1(param1: bool, param2: &str) {
10527                    let var1 = "«ˇhello» world";
10528                }
10529            "#},
10530            cx,
10531        );
10532    });
10533
10534    // Test 2: Partial selection within a word
10535    editor.update_in(cx, |editor, window, cx| {
10536        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10537            s.select_display_ranges([
10538                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
10539            ]);
10540        });
10541    });
10542    editor.update_in(cx, |editor, window, cx| {
10543        assert_text_with_selections(
10544            editor,
10545            indoc! {r#"
10546                use mod1::mod2::{mod3, mod4};
10547
10548                fn fn_1(param1: bool, param2: &str) {
10549                    let var1 = "h«elˇ»lo world";
10550                }
10551            "#},
10552            cx,
10553        );
10554        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10555        assert_text_with_selections(
10556            editor,
10557            indoc! {r#"
10558                use mod1::mod2::{mod3, mod4};
10559
10560                fn fn_1(param1: bool, param2: &str) {
10561                    let var1 = "«ˇhello» world";
10562                }
10563            "#},
10564            cx,
10565        );
10566    });
10567
10568    // Test 3: Complete word already selected
10569    editor.update_in(cx, |editor, window, cx| {
10570        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10571            s.select_display_ranges([
10572                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
10573            ]);
10574        });
10575    });
10576    editor.update_in(cx, |editor, window, cx| {
10577        assert_text_with_selections(
10578            editor,
10579            indoc! {r#"
10580                use mod1::mod2::{mod3, mod4};
10581
10582                fn fn_1(param1: bool, param2: &str) {
10583                    let var1 = "«helloˇ» world";
10584                }
10585            "#},
10586            cx,
10587        );
10588        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10589        assert_text_with_selections(
10590            editor,
10591            indoc! {r#"
10592                use mod1::mod2::{mod3, mod4};
10593
10594                fn fn_1(param1: bool, param2: &str) {
10595                    let var1 = "«hello worldˇ»";
10596                }
10597            "#},
10598            cx,
10599        );
10600    });
10601
10602    // Test 4: Selection spanning across words
10603    editor.update_in(cx, |editor, window, cx| {
10604        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10605            s.select_display_ranges([
10606                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
10607            ]);
10608        });
10609    });
10610    editor.update_in(cx, |editor, window, cx| {
10611        assert_text_with_selections(
10612            editor,
10613            indoc! {r#"
10614                use mod1::mod2::{mod3, mod4};
10615
10616                fn fn_1(param1: bool, param2: &str) {
10617                    let var1 = "hel«lo woˇ»rld";
10618                }
10619            "#},
10620            cx,
10621        );
10622        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10623        assert_text_with_selections(
10624            editor,
10625            indoc! {r#"
10626                use mod1::mod2::{mod3, mod4};
10627
10628                fn fn_1(param1: bool, param2: &str) {
10629                    let var1 = "«ˇhello world»";
10630                }
10631            "#},
10632            cx,
10633        );
10634    });
10635
10636    // Test 5: Expansion beyond string
10637    editor.update_in(cx, |editor, window, cx| {
10638        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10639        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
10640        assert_text_with_selections(
10641            editor,
10642            indoc! {r#"
10643                use mod1::mod2::{mod3, mod4};
10644
10645                fn fn_1(param1: bool, param2: &str) {
10646                    «ˇlet var1 = "hello world";»
10647                }
10648            "#},
10649            cx,
10650        );
10651    });
10652}
10653
10654#[gpui::test]
10655async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
10656    init_test(cx, |_| {});
10657
10658    let mut cx = EditorTestContext::new(cx).await;
10659
10660    let language = Arc::new(Language::new(
10661        LanguageConfig::default(),
10662        Some(tree_sitter_rust::LANGUAGE.into()),
10663    ));
10664
10665    cx.update_buffer(|buffer, cx| {
10666        buffer.set_language(Some(language), cx);
10667    });
10668
10669    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
10670    cx.update_editor(|editor, window, cx| {
10671        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
10672    });
10673
10674    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
10675
10676    cx.set_state(indoc! { r#"fn a() {
10677          // what
10678          // a
10679          // ˇlong
10680          // method
10681          // I
10682          // sure
10683          // hope
10684          // it
10685          // works
10686    }"# });
10687
10688    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
10689    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
10690    cx.update(|_, cx| {
10691        multi_buffer.update(cx, |multi_buffer, cx| {
10692            multi_buffer.set_excerpts_for_path(
10693                PathKey::for_buffer(&buffer, cx),
10694                buffer,
10695                [Point::new(1, 0)..Point::new(1, 0)],
10696                3,
10697                cx,
10698            );
10699        });
10700    });
10701
10702    let editor2 = cx.new_window_entity(|window, cx| {
10703        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
10704    });
10705
10706    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
10707    cx.update_editor(|editor, window, cx| {
10708        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
10709            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
10710        })
10711    });
10712
10713    cx.assert_editor_state(indoc! { "
10714        fn a() {
10715              // what
10716              // a
10717        ˇ      // long
10718              // method"});
10719
10720    cx.update_editor(|editor, window, cx| {
10721        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
10722    });
10723
10724    // Although we could potentially make the action work when the syntax node
10725    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
10726    // did. Maybe we could also expand the excerpt to contain the range?
10727    cx.assert_editor_state(indoc! { "
10728        fn a() {
10729              // what
10730              // a
10731        ˇ      // long
10732              // method"});
10733}
10734
10735#[gpui::test]
10736async fn test_fold_function_bodies(cx: &mut TestAppContext) {
10737    init_test(cx, |_| {});
10738
10739    let base_text = r#"
10740        impl A {
10741            // this is an uncommitted comment
10742
10743            fn b() {
10744                c();
10745            }
10746
10747            // this is another uncommitted comment
10748
10749            fn d() {
10750                // e
10751                // f
10752            }
10753        }
10754
10755        fn g() {
10756            // h
10757        }
10758    "#
10759    .unindent();
10760
10761    let text = r#"
10762        ˇimpl A {
10763
10764            fn b() {
10765                c();
10766            }
10767
10768            fn d() {
10769                // e
10770                // f
10771            }
10772        }
10773
10774        fn g() {
10775            // h
10776        }
10777    "#
10778    .unindent();
10779
10780    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10781    cx.set_state(&text);
10782    cx.set_head_text(&base_text);
10783    cx.update_editor(|editor, window, cx| {
10784        editor.expand_all_diff_hunks(&Default::default(), window, cx);
10785    });
10786
10787    cx.assert_state_with_diff(
10788        "
10789        ˇimpl A {
10790      -     // this is an uncommitted comment
10791
10792            fn b() {
10793                c();
10794            }
10795
10796      -     // this is another uncommitted comment
10797      -
10798            fn d() {
10799                // e
10800                // f
10801            }
10802        }
10803
10804        fn g() {
10805            // h
10806        }
10807    "
10808        .unindent(),
10809    );
10810
10811    let expected_display_text = "
10812        impl A {
10813            // this is an uncommitted comment
10814
10815            fn b() {
1081610817            }
10818
10819            // this is another uncommitted comment
10820
10821            fn d() {
1082210823            }
10824        }
10825
10826        fn g() {
1082710828        }
10829        "
10830    .unindent();
10831
10832    cx.update_editor(|editor, window, cx| {
10833        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
10834        assert_eq!(editor.display_text(cx), expected_display_text);
10835    });
10836}
10837
10838#[gpui::test]
10839async fn test_autoindent(cx: &mut TestAppContext) {
10840    init_test(cx, |_| {});
10841
10842    let language = Arc::new(
10843        Language::new(
10844            LanguageConfig {
10845                brackets: BracketPairConfig {
10846                    pairs: vec![
10847                        BracketPair {
10848                            start: "{".to_string(),
10849                            end: "}".to_string(),
10850                            close: false,
10851                            surround: false,
10852                            newline: true,
10853                        },
10854                        BracketPair {
10855                            start: "(".to_string(),
10856                            end: ")".to_string(),
10857                            close: false,
10858                            surround: false,
10859                            newline: true,
10860                        },
10861                    ],
10862                    ..Default::default()
10863                },
10864                ..Default::default()
10865            },
10866            Some(tree_sitter_rust::LANGUAGE.into()),
10867        )
10868        .with_indents_query(
10869            r#"
10870                (_ "(" ")" @end) @indent
10871                (_ "{" "}" @end) @indent
10872            "#,
10873        )
10874        .unwrap(),
10875    );
10876
10877    let text = "fn a() {}";
10878
10879    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10880    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10881    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10882    editor
10883        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10884        .await;
10885
10886    editor.update_in(cx, |editor, window, cx| {
10887        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10888            s.select_ranges([
10889                MultiBufferOffset(5)..MultiBufferOffset(5),
10890                MultiBufferOffset(8)..MultiBufferOffset(8),
10891                MultiBufferOffset(9)..MultiBufferOffset(9),
10892            ])
10893        });
10894        editor.newline(&Newline, window, cx);
10895        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
10896        assert_eq!(
10897            editor.selections.ranges(&editor.display_snapshot(cx)),
10898            &[
10899                Point::new(1, 4)..Point::new(1, 4),
10900                Point::new(3, 4)..Point::new(3, 4),
10901                Point::new(5, 0)..Point::new(5, 0)
10902            ]
10903        );
10904    });
10905}
10906
10907#[gpui::test]
10908async fn test_autoindent_disabled(cx: &mut TestAppContext) {
10909    init_test(cx, |settings| {
10910        settings.defaults.auto_indent = Some(settings::AutoIndentMode::None)
10911    });
10912
10913    let language = Arc::new(
10914        Language::new(
10915            LanguageConfig {
10916                brackets: BracketPairConfig {
10917                    pairs: vec![
10918                        BracketPair {
10919                            start: "{".to_string(),
10920                            end: "}".to_string(),
10921                            close: false,
10922                            surround: false,
10923                            newline: true,
10924                        },
10925                        BracketPair {
10926                            start: "(".to_string(),
10927                            end: ")".to_string(),
10928                            close: false,
10929                            surround: false,
10930                            newline: true,
10931                        },
10932                    ],
10933                    ..Default::default()
10934                },
10935                ..Default::default()
10936            },
10937            Some(tree_sitter_rust::LANGUAGE.into()),
10938        )
10939        .with_indents_query(
10940            r#"
10941                (_ "(" ")" @end) @indent
10942                (_ "{" "}" @end) @indent
10943            "#,
10944        )
10945        .unwrap(),
10946    );
10947
10948    let text = "fn a() {}";
10949
10950    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10951    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10952    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10953    editor
10954        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10955        .await;
10956
10957    editor.update_in(cx, |editor, window, cx| {
10958        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10959            s.select_ranges([
10960                MultiBufferOffset(5)..MultiBufferOffset(5),
10961                MultiBufferOffset(8)..MultiBufferOffset(8),
10962                MultiBufferOffset(9)..MultiBufferOffset(9),
10963            ])
10964        });
10965        editor.newline(&Newline, window, cx);
10966        assert_eq!(
10967            editor.text(cx),
10968            indoc!(
10969                "
10970                fn a(
10971
10972                ) {
10973
10974                }
10975                "
10976            )
10977        );
10978        assert_eq!(
10979            editor.selections.ranges(&editor.display_snapshot(cx)),
10980            &[
10981                Point::new(1, 0)..Point::new(1, 0),
10982                Point::new(3, 0)..Point::new(3, 0),
10983                Point::new(5, 0)..Point::new(5, 0)
10984            ]
10985        );
10986    });
10987}
10988
10989#[gpui::test]
10990async fn test_autoindent_none_does_not_preserve_indentation_on_newline(cx: &mut TestAppContext) {
10991    init_test(cx, |settings| {
10992        settings.defaults.auto_indent = Some(settings::AutoIndentMode::None)
10993    });
10994
10995    let mut cx = EditorTestContext::new(cx).await;
10996
10997    cx.set_state(indoc! {"
10998        hello
10999            indented lineˇ
11000        world
11001    "});
11002
11003    cx.update_editor(|editor, window, cx| {
11004        editor.newline(&Newline, window, cx);
11005    });
11006
11007    cx.assert_editor_state(indoc! {"
11008        hello
11009            indented line
11010        ˇ
11011        world
11012    "});
11013}
11014
11015#[gpui::test]
11016async fn test_autoindent_preserve_indent_maintains_indentation_on_newline(cx: &mut TestAppContext) {
11017    // When auto_indent is "preserve_indent", pressing Enter on an indented line
11018    // should preserve the indentation but not adjust based on syntax.
11019    init_test(cx, |settings| {
11020        settings.defaults.auto_indent = Some(settings::AutoIndentMode::PreserveIndent)
11021    });
11022
11023    let mut cx = EditorTestContext::new(cx).await;
11024
11025    cx.set_state(indoc! {"
11026        hello
11027            indented lineˇ
11028        world
11029    "});
11030
11031    cx.update_editor(|editor, window, cx| {
11032        editor.newline(&Newline, window, cx);
11033    });
11034
11035    // The new line SHOULD have the same indentation as the previous line
11036    cx.assert_editor_state(indoc! {"
11037        hello
11038            indented line
11039            ˇ
11040        world
11041    "});
11042}
11043
11044#[gpui::test]
11045async fn test_autoindent_preserve_indent_does_not_apply_syntax_indent(cx: &mut TestAppContext) {
11046    init_test(cx, |settings| {
11047        settings.defaults.auto_indent = Some(settings::AutoIndentMode::PreserveIndent)
11048    });
11049
11050    let language = Arc::new(
11051        Language::new(
11052            LanguageConfig {
11053                brackets: BracketPairConfig {
11054                    pairs: vec![BracketPair {
11055                        start: "{".to_string(),
11056                        end: "}".to_string(),
11057                        close: false,
11058                        surround: false,
11059                        newline: false, // Disable extra newline behavior to isolate syntax indent test
11060                    }],
11061                    ..Default::default()
11062                },
11063                ..Default::default()
11064            },
11065            Some(tree_sitter_rust::LANGUAGE.into()),
11066        )
11067        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
11068        .unwrap(),
11069    );
11070
11071    let buffer =
11072        cx.new(|cx| Buffer::local("fn foo() {\n}", cx).with_language(language.clone(), cx));
11073    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11074    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11075    editor
11076        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11077        .await;
11078
11079    // Position cursor at end of line containing `{`
11080    editor.update_in(cx, |editor, window, cx| {
11081        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11082            s.select_ranges([MultiBufferOffset(10)..MultiBufferOffset(10)]) // After "fn foo() {"
11083        });
11084        editor.newline(&Newline, window, cx);
11085
11086        // With PreserveIndent, the new line should have 0 indentation (same as the fn line)
11087        // NOT 4 spaces (which tree-sitter would add for being inside `{}`)
11088        assert_eq!(editor.text(cx), "fn foo() {\n\n}");
11089    });
11090}
11091
11092#[gpui::test]
11093async fn test_autoindent_syntax_aware_applies_syntax_indent(cx: &mut TestAppContext) {
11094    // Companion test to show that SyntaxAware DOES apply tree-sitter indentation
11095    init_test(cx, |settings| {
11096        settings.defaults.auto_indent = Some(settings::AutoIndentMode::SyntaxAware)
11097    });
11098
11099    let language = Arc::new(
11100        Language::new(
11101            LanguageConfig {
11102                brackets: BracketPairConfig {
11103                    pairs: vec![BracketPair {
11104                        start: "{".to_string(),
11105                        end: "}".to_string(),
11106                        close: false,
11107                        surround: false,
11108                        newline: false, // Disable extra newline behavior to isolate syntax indent test
11109                    }],
11110                    ..Default::default()
11111                },
11112                ..Default::default()
11113            },
11114            Some(tree_sitter_rust::LANGUAGE.into()),
11115        )
11116        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
11117        .unwrap(),
11118    );
11119
11120    let buffer =
11121        cx.new(|cx| Buffer::local("fn foo() {\n}", cx).with_language(language.clone(), cx));
11122    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11123    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11124    editor
11125        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11126        .await;
11127
11128    // Position cursor at end of line containing `{`
11129    editor.update_in(cx, |editor, window, cx| {
11130        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11131            s.select_ranges([MultiBufferOffset(10)..MultiBufferOffset(10)]) // After "fn foo() {"
11132        });
11133        editor.newline(&Newline, window, cx);
11134
11135        // With SyntaxAware, tree-sitter adds indentation for being inside `{}`
11136        assert_eq!(editor.text(cx), "fn foo() {\n    \n}");
11137    });
11138}
11139
11140#[gpui::test]
11141async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
11142    init_test(cx, |settings| {
11143        settings.defaults.auto_indent = Some(settings::AutoIndentMode::SyntaxAware);
11144        settings.languages.0.insert(
11145            "python".into(),
11146            LanguageSettingsContent {
11147                auto_indent: Some(settings::AutoIndentMode::None),
11148                ..Default::default()
11149            },
11150        );
11151    });
11152
11153    let mut cx = EditorTestContext::new(cx).await;
11154
11155    let injected_language = Arc::new(
11156        Language::new(
11157            LanguageConfig {
11158                brackets: BracketPairConfig {
11159                    pairs: vec![
11160                        BracketPair {
11161                            start: "{".to_string(),
11162                            end: "}".to_string(),
11163                            close: false,
11164                            surround: false,
11165                            newline: true,
11166                        },
11167                        BracketPair {
11168                            start: "(".to_string(),
11169                            end: ")".to_string(),
11170                            close: true,
11171                            surround: false,
11172                            newline: true,
11173                        },
11174                    ],
11175                    ..Default::default()
11176                },
11177                name: "python".into(),
11178                ..Default::default()
11179            },
11180            Some(tree_sitter_python::LANGUAGE.into()),
11181        )
11182        .with_indents_query(
11183            r#"
11184                (_ "(" ")" @end) @indent
11185                (_ "{" "}" @end) @indent
11186            "#,
11187        )
11188        .unwrap(),
11189    );
11190
11191    let language = Arc::new(
11192        Language::new(
11193            LanguageConfig {
11194                brackets: BracketPairConfig {
11195                    pairs: vec![
11196                        BracketPair {
11197                            start: "{".to_string(),
11198                            end: "}".to_string(),
11199                            close: false,
11200                            surround: false,
11201                            newline: true,
11202                        },
11203                        BracketPair {
11204                            start: "(".to_string(),
11205                            end: ")".to_string(),
11206                            close: true,
11207                            surround: false,
11208                            newline: true,
11209                        },
11210                    ],
11211                    ..Default::default()
11212                },
11213                name: LanguageName::new_static("rust"),
11214                ..Default::default()
11215            },
11216            Some(tree_sitter_rust::LANGUAGE.into()),
11217        )
11218        .with_indents_query(
11219            r#"
11220                (_ "(" ")" @end) @indent
11221                (_ "{" "}" @end) @indent
11222            "#,
11223        )
11224        .unwrap()
11225        .with_injection_query(
11226            r#"
11227            (macro_invocation
11228                macro: (identifier) @_macro_name
11229                (token_tree) @injection.content
11230                (#set! injection.language "python"))
11231           "#,
11232        )
11233        .unwrap(),
11234    );
11235
11236    cx.language_registry().add(injected_language);
11237    cx.language_registry().add(language.clone());
11238
11239    cx.update_buffer(|buffer, cx| {
11240        buffer.set_language(Some(language), cx);
11241    });
11242
11243    cx.set_state(r#"struct A {ˇ}"#);
11244
11245    cx.update_editor(|editor, window, cx| {
11246        editor.newline(&Default::default(), window, cx);
11247    });
11248
11249    cx.assert_editor_state(indoc!(
11250        "struct A {
11251            ˇ
11252        }"
11253    ));
11254
11255    cx.set_state(r#"select_biased!(ˇ)"#);
11256
11257    cx.update_editor(|editor, window, cx| {
11258        editor.newline(&Default::default(), window, cx);
11259        editor.handle_input("def ", window, cx);
11260        editor.handle_input("(", window, cx);
11261        editor.newline(&Default::default(), window, cx);
11262        editor.handle_input("a", window, cx);
11263    });
11264
11265    cx.assert_editor_state(indoc!(
11266        "select_biased!(
11267        def (
1126811269        )
11270        )"
11271    ));
11272}
11273
11274#[gpui::test]
11275async fn test_autoindent_selections(cx: &mut TestAppContext) {
11276    init_test(cx, |_| {});
11277
11278    {
11279        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
11280        cx.set_state(indoc! {"
11281            impl A {
11282
11283                fn b() {}
11284
11285            «fn c() {
11286
11287            }ˇ»
11288            }
11289        "});
11290
11291        cx.update_editor(|editor, window, cx| {
11292            editor.autoindent(&Default::default(), window, cx);
11293        });
11294        cx.wait_for_autoindent_applied().await;
11295
11296        cx.assert_editor_state(indoc! {"
11297            impl A {
11298
11299                fn b() {}
11300
11301                «fn c() {
11302
11303                }ˇ»
11304            }
11305        "});
11306    }
11307
11308    {
11309        let mut cx = EditorTestContext::new_multibuffer(
11310            cx,
11311            [indoc! { "
11312                impl A {
11313                «
11314                // a
11315                fn b(){}
11316                »
11317                «
11318                    }
11319                    fn c(){}
11320                »
11321            "}],
11322        );
11323
11324        let buffer = cx.update_editor(|editor, _, cx| {
11325            let buffer = editor.buffer().update(cx, |buffer, _| {
11326                buffer.all_buffers().iter().next().unwrap().clone()
11327            });
11328            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
11329            buffer
11330        });
11331
11332        cx.run_until_parked();
11333        cx.update_editor(|editor, window, cx| {
11334            editor.select_all(&Default::default(), window, cx);
11335            editor.autoindent(&Default::default(), window, cx)
11336        });
11337        cx.run_until_parked();
11338
11339        cx.update(|_, cx| {
11340            assert_eq!(
11341                buffer.read(cx).text(),
11342                indoc! { "
11343                    impl A {
11344
11345                        // a
11346                        fn b(){}
11347
11348
11349                    }
11350                    fn c(){}
11351
11352                " }
11353            )
11354        });
11355    }
11356}
11357
11358#[gpui::test]
11359async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
11360    init_test(cx, |_| {});
11361
11362    let mut cx = EditorTestContext::new(cx).await;
11363
11364    let language = Arc::new(Language::new(
11365        LanguageConfig {
11366            brackets: BracketPairConfig {
11367                pairs: vec![
11368                    BracketPair {
11369                        start: "{".to_string(),
11370                        end: "}".to_string(),
11371                        close: true,
11372                        surround: true,
11373                        newline: true,
11374                    },
11375                    BracketPair {
11376                        start: "(".to_string(),
11377                        end: ")".to_string(),
11378                        close: true,
11379                        surround: true,
11380                        newline: true,
11381                    },
11382                    BracketPair {
11383                        start: "/*".to_string(),
11384                        end: " */".to_string(),
11385                        close: true,
11386                        surround: true,
11387                        newline: true,
11388                    },
11389                    BracketPair {
11390                        start: "[".to_string(),
11391                        end: "]".to_string(),
11392                        close: false,
11393                        surround: false,
11394                        newline: true,
11395                    },
11396                    BracketPair {
11397                        start: "\"".to_string(),
11398                        end: "\"".to_string(),
11399                        close: true,
11400                        surround: true,
11401                        newline: false,
11402                    },
11403                    BracketPair {
11404                        start: "<".to_string(),
11405                        end: ">".to_string(),
11406                        close: false,
11407                        surround: true,
11408                        newline: true,
11409                    },
11410                ],
11411                ..Default::default()
11412            },
11413            autoclose_before: "})]".to_string(),
11414            ..Default::default()
11415        },
11416        Some(tree_sitter_rust::LANGUAGE.into()),
11417    ));
11418
11419    cx.language_registry().add(language.clone());
11420    cx.update_buffer(|buffer, cx| {
11421        buffer.set_language(Some(language), cx);
11422    });
11423
11424    cx.set_state(
11425        &r#"
11426            🏀ˇ
11427            εˇ
11428            ❤️ˇ
11429        "#
11430        .unindent(),
11431    );
11432
11433    // autoclose multiple nested brackets at multiple cursors
11434    cx.update_editor(|editor, window, cx| {
11435        editor.handle_input("{", window, cx);
11436        editor.handle_input("{", window, cx);
11437        editor.handle_input("{", window, cx);
11438    });
11439    cx.assert_editor_state(
11440        &"
11441            🏀{{{ˇ}}}
11442            ε{{{ˇ}}}
11443            ❤️{{{ˇ}}}
11444        "
11445        .unindent(),
11446    );
11447
11448    // insert a different closing bracket
11449    cx.update_editor(|editor, window, cx| {
11450        editor.handle_input(")", window, cx);
11451    });
11452    cx.assert_editor_state(
11453        &"
11454            🏀{{{)ˇ}}}
11455            ε{{{)ˇ}}}
11456            ❤️{{{)ˇ}}}
11457        "
11458        .unindent(),
11459    );
11460
11461    // skip over the auto-closed brackets when typing a closing bracket
11462    cx.update_editor(|editor, window, cx| {
11463        editor.move_right(&MoveRight, window, cx);
11464        editor.handle_input("}", window, cx);
11465        editor.handle_input("}", window, cx);
11466        editor.handle_input("}", window, cx);
11467    });
11468    cx.assert_editor_state(
11469        &"
11470            🏀{{{)}}}}ˇ
11471            ε{{{)}}}}ˇ
11472            ❤️{{{)}}}}ˇ
11473        "
11474        .unindent(),
11475    );
11476
11477    // autoclose multi-character pairs
11478    cx.set_state(
11479        &"
11480            ˇ
11481            ˇ
11482        "
11483        .unindent(),
11484    );
11485    cx.update_editor(|editor, window, cx| {
11486        editor.handle_input("/", window, cx);
11487        editor.handle_input("*", window, cx);
11488    });
11489    cx.assert_editor_state(
11490        &"
11491            /*ˇ */
11492            /*ˇ */
11493        "
11494        .unindent(),
11495    );
11496
11497    // one cursor autocloses a multi-character pair, one cursor
11498    // does not autoclose.
11499    cx.set_state(
11500        &"
1150111502            ˇ
11503        "
11504        .unindent(),
11505    );
11506    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
11507    cx.assert_editor_state(
11508        &"
11509            /*ˇ */
1151011511        "
11512        .unindent(),
11513    );
11514
11515    // Don't autoclose if the next character isn't whitespace and isn't
11516    // listed in the language's "autoclose_before" section.
11517    cx.set_state("ˇa b");
11518    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
11519    cx.assert_editor_state("{ˇa b");
11520
11521    // Don't autoclose if `close` is false for the bracket pair
11522    cx.set_state("ˇ");
11523    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
11524    cx.assert_editor_state("");
11525
11526    // Surround with brackets if text is selected
11527    cx.set_state("«aˇ» b");
11528    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
11529    cx.assert_editor_state("{«aˇ»} b");
11530
11531    // Autoclose when not immediately after a word character
11532    cx.set_state("a ˇ");
11533    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
11534    cx.assert_editor_state("a \"ˇ\"");
11535
11536    // Autoclose pair where the start and end characters are the same
11537    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
11538    cx.assert_editor_state("a \"\"ˇ");
11539
11540    // Don't autoclose when immediately after a word character
11541    cx.set_state("");
11542    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
11543    cx.assert_editor_state("a\"ˇ");
11544
11545    // Do autoclose when after a non-word character
11546    cx.set_state("");
11547    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
11548    cx.assert_editor_state("{\"ˇ\"");
11549
11550    // Non identical pairs autoclose regardless of preceding character
11551    cx.set_state("");
11552    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
11553    cx.assert_editor_state("a{ˇ}");
11554
11555    // Don't autoclose pair if autoclose is disabled
11556    cx.set_state("ˇ");
11557    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
11558    cx.assert_editor_state("");
11559
11560    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
11561    cx.set_state("«aˇ» b");
11562    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
11563    cx.assert_editor_state("<«aˇ»> b");
11564}
11565
11566#[gpui::test]
11567async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
11568    init_test(cx, |settings| {
11569        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11570    });
11571
11572    let mut cx = EditorTestContext::new(cx).await;
11573
11574    let language = Arc::new(Language::new(
11575        LanguageConfig {
11576            brackets: BracketPairConfig {
11577                pairs: vec![
11578                    BracketPair {
11579                        start: "{".to_string(),
11580                        end: "}".to_string(),
11581                        close: true,
11582                        surround: true,
11583                        newline: true,
11584                    },
11585                    BracketPair {
11586                        start: "(".to_string(),
11587                        end: ")".to_string(),
11588                        close: true,
11589                        surround: true,
11590                        newline: true,
11591                    },
11592                    BracketPair {
11593                        start: "[".to_string(),
11594                        end: "]".to_string(),
11595                        close: false,
11596                        surround: false,
11597                        newline: true,
11598                    },
11599                ],
11600                ..Default::default()
11601            },
11602            autoclose_before: "})]".to_string(),
11603            ..Default::default()
11604        },
11605        Some(tree_sitter_rust::LANGUAGE.into()),
11606    ));
11607
11608    cx.language_registry().add(language.clone());
11609    cx.update_buffer(|buffer, cx| {
11610        buffer.set_language(Some(language), cx);
11611    });
11612
11613    cx.set_state(
11614        &"
11615            ˇ
11616            ˇ
11617            ˇ
11618        "
11619        .unindent(),
11620    );
11621
11622    // ensure only matching closing brackets are skipped over
11623    cx.update_editor(|editor, window, cx| {
11624        editor.handle_input("}", window, cx);
11625        editor.move_left(&MoveLeft, window, cx);
11626        editor.handle_input(")", window, cx);
11627        editor.move_left(&MoveLeft, window, cx);
11628    });
11629    cx.assert_editor_state(
11630        &"
11631            ˇ)}
11632            ˇ)}
11633            ˇ)}
11634        "
11635        .unindent(),
11636    );
11637
11638    // skip-over closing brackets at multiple cursors
11639    cx.update_editor(|editor, window, cx| {
11640        editor.handle_input(")", window, cx);
11641        editor.handle_input("}", window, cx);
11642    });
11643    cx.assert_editor_state(
11644        &"
11645            )}ˇ
11646            )}ˇ
11647            )}ˇ
11648        "
11649        .unindent(),
11650    );
11651
11652    // ignore non-close brackets
11653    cx.update_editor(|editor, window, cx| {
11654        editor.handle_input("]", window, cx);
11655        editor.move_left(&MoveLeft, window, cx);
11656        editor.handle_input("]", window, cx);
11657    });
11658    cx.assert_editor_state(
11659        &"
11660            )}]ˇ]
11661            )}]ˇ]
11662            )}]ˇ]
11663        "
11664        .unindent(),
11665    );
11666}
11667
11668#[gpui::test]
11669async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
11670    init_test(cx, |_| {});
11671
11672    let mut cx = EditorTestContext::new(cx).await;
11673
11674    let html_language = Arc::new(
11675        Language::new(
11676            LanguageConfig {
11677                name: "HTML".into(),
11678                brackets: BracketPairConfig {
11679                    pairs: vec![
11680                        BracketPair {
11681                            start: "<".into(),
11682                            end: ">".into(),
11683                            close: true,
11684                            ..Default::default()
11685                        },
11686                        BracketPair {
11687                            start: "{".into(),
11688                            end: "}".into(),
11689                            close: true,
11690                            ..Default::default()
11691                        },
11692                        BracketPair {
11693                            start: "(".into(),
11694                            end: ")".into(),
11695                            close: true,
11696                            ..Default::default()
11697                        },
11698                    ],
11699                    ..Default::default()
11700                },
11701                autoclose_before: "})]>".into(),
11702                ..Default::default()
11703            },
11704            Some(tree_sitter_html::LANGUAGE.into()),
11705        )
11706        .with_injection_query(
11707            r#"
11708            (script_element
11709                (raw_text) @injection.content
11710                (#set! injection.language "javascript"))
11711            "#,
11712        )
11713        .unwrap(),
11714    );
11715
11716    let javascript_language = Arc::new(Language::new(
11717        LanguageConfig {
11718            name: "JavaScript".into(),
11719            brackets: BracketPairConfig {
11720                pairs: vec![
11721                    BracketPair {
11722                        start: "/*".into(),
11723                        end: " */".into(),
11724                        close: true,
11725                        ..Default::default()
11726                    },
11727                    BracketPair {
11728                        start: "{".into(),
11729                        end: "}".into(),
11730                        close: true,
11731                        ..Default::default()
11732                    },
11733                    BracketPair {
11734                        start: "(".into(),
11735                        end: ")".into(),
11736                        close: true,
11737                        ..Default::default()
11738                    },
11739                ],
11740                ..Default::default()
11741            },
11742            autoclose_before: "})]>".into(),
11743            ..Default::default()
11744        },
11745        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
11746    ));
11747
11748    cx.language_registry().add(html_language.clone());
11749    cx.language_registry().add(javascript_language);
11750    cx.executor().run_until_parked();
11751
11752    cx.update_buffer(|buffer, cx| {
11753        buffer.set_language(Some(html_language), cx);
11754    });
11755
11756    cx.set_state(
11757        &r#"
11758            <body>ˇ
11759                <script>
11760                    var x = 1;ˇ
11761                </script>
11762            </body>ˇ
11763        "#
11764        .unindent(),
11765    );
11766
11767    // Precondition: different languages are active at different locations.
11768    cx.update_editor(|editor, window, cx| {
11769        let snapshot = editor.snapshot(window, cx);
11770        let cursors = editor
11771            .selections
11772            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
11773        let languages = cursors
11774            .iter()
11775            .map(|c| snapshot.language_at(c.start).unwrap().name())
11776            .collect::<Vec<_>>();
11777        assert_eq!(
11778            languages,
11779            &[
11780                LanguageName::from("HTML"),
11781                LanguageName::from("JavaScript"),
11782                LanguageName::from("HTML"),
11783            ]
11784        );
11785    });
11786
11787    // Angle brackets autoclose in HTML, but not JavaScript.
11788    cx.update_editor(|editor, window, cx| {
11789        editor.handle_input("<", window, cx);
11790        editor.handle_input("a", window, cx);
11791    });
11792    cx.assert_editor_state(
11793        &r#"
11794            <body><aˇ>
11795                <script>
11796                    var x = 1;<aˇ
11797                </script>
11798            </body><aˇ>
11799        "#
11800        .unindent(),
11801    );
11802
11803    // Curly braces and parens autoclose in both HTML and JavaScript.
11804    cx.update_editor(|editor, window, cx| {
11805        editor.handle_input(" b=", window, cx);
11806        editor.handle_input("{", window, cx);
11807        editor.handle_input("c", window, cx);
11808        editor.handle_input("(", window, cx);
11809    });
11810    cx.assert_editor_state(
11811        &r#"
11812            <body><a b={c(ˇ)}>
11813                <script>
11814                    var x = 1;<a b={c(ˇ)}
11815                </script>
11816            </body><a b={c(ˇ)}>
11817        "#
11818        .unindent(),
11819    );
11820
11821    // Brackets that were already autoclosed are skipped.
11822    cx.update_editor(|editor, window, cx| {
11823        editor.handle_input(")", window, cx);
11824        editor.handle_input("d", window, cx);
11825        editor.handle_input("}", window, cx);
11826    });
11827    cx.assert_editor_state(
11828        &r#"
11829            <body><a b={c()d}ˇ>
11830                <script>
11831                    var x = 1;<a b={c()d}ˇ
11832                </script>
11833            </body><a b={c()d}ˇ>
11834        "#
11835        .unindent(),
11836    );
11837    cx.update_editor(|editor, window, cx| {
11838        editor.handle_input(">", window, cx);
11839    });
11840    cx.assert_editor_state(
11841        &r#"
11842            <body><a b={c()d}>ˇ
11843                <script>
11844                    var x = 1;<a b={c()d}>ˇ
11845                </script>
11846            </body><a b={c()d}>ˇ
11847        "#
11848        .unindent(),
11849    );
11850
11851    // Reset
11852    cx.set_state(
11853        &r#"
11854            <body>ˇ
11855                <script>
11856                    var x = 1;ˇ
11857                </script>
11858            </body>ˇ
11859        "#
11860        .unindent(),
11861    );
11862
11863    cx.update_editor(|editor, window, cx| {
11864        editor.handle_input("<", window, cx);
11865    });
11866    cx.assert_editor_state(
11867        &r#"
11868            <body><ˇ>
11869                <script>
11870                    var x = 1;<ˇ
11871                </script>
11872            </body><ˇ>
11873        "#
11874        .unindent(),
11875    );
11876
11877    // When backspacing, the closing angle brackets are removed.
11878    cx.update_editor(|editor, window, cx| {
11879        editor.backspace(&Backspace, window, cx);
11880    });
11881    cx.assert_editor_state(
11882        &r#"
11883            <body>ˇ
11884                <script>
11885                    var x = 1;ˇ
11886                </script>
11887            </body>ˇ
11888        "#
11889        .unindent(),
11890    );
11891
11892    // Block comments autoclose in JavaScript, but not HTML.
11893    cx.update_editor(|editor, window, cx| {
11894        editor.handle_input("/", window, cx);
11895        editor.handle_input("*", window, cx);
11896    });
11897    cx.assert_editor_state(
11898        &r#"
11899            <body>/*ˇ
11900                <script>
11901                    var x = 1;/*ˇ */
11902                </script>
11903            </body>/*ˇ
11904        "#
11905        .unindent(),
11906    );
11907}
11908
11909#[gpui::test]
11910async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
11911    init_test(cx, |_| {});
11912
11913    let mut cx = EditorTestContext::new(cx).await;
11914
11915    let rust_language = Arc::new(
11916        Language::new(
11917            LanguageConfig {
11918                name: "Rust".into(),
11919                brackets: serde_json::from_value(json!([
11920                    { "start": "{", "end": "}", "close": true, "newline": true },
11921                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
11922                ]))
11923                .unwrap(),
11924                autoclose_before: "})]>".into(),
11925                ..Default::default()
11926            },
11927            Some(tree_sitter_rust::LANGUAGE.into()),
11928        )
11929        .with_override_query("(string_literal) @string")
11930        .unwrap(),
11931    );
11932
11933    cx.language_registry().add(rust_language.clone());
11934    cx.update_buffer(|buffer, cx| {
11935        buffer.set_language(Some(rust_language), cx);
11936    });
11937
11938    cx.set_state(
11939        &r#"
11940            let x = ˇ
11941        "#
11942        .unindent(),
11943    );
11944
11945    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
11946    cx.update_editor(|editor, window, cx| {
11947        editor.handle_input("\"", window, cx);
11948    });
11949    cx.assert_editor_state(
11950        &r#"
11951            let x = "ˇ"
11952        "#
11953        .unindent(),
11954    );
11955
11956    // Inserting another quotation mark. The cursor moves across the existing
11957    // automatically-inserted quotation mark.
11958    cx.update_editor(|editor, window, cx| {
11959        editor.handle_input("\"", window, cx);
11960    });
11961    cx.assert_editor_state(
11962        &r#"
11963            let x = ""ˇ
11964        "#
11965        .unindent(),
11966    );
11967
11968    // Reset
11969    cx.set_state(
11970        &r#"
11971            let x = ˇ
11972        "#
11973        .unindent(),
11974    );
11975
11976    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
11977    cx.update_editor(|editor, window, cx| {
11978        editor.handle_input("\"", window, cx);
11979        editor.handle_input(" ", window, cx);
11980        editor.move_left(&Default::default(), window, cx);
11981        editor.handle_input("\\", window, cx);
11982        editor.handle_input("\"", window, cx);
11983    });
11984    cx.assert_editor_state(
11985        &r#"
11986            let x = "\"ˇ "
11987        "#
11988        .unindent(),
11989    );
11990
11991    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
11992    // mark. Nothing is inserted.
11993    cx.update_editor(|editor, window, cx| {
11994        editor.move_right(&Default::default(), window, cx);
11995        editor.handle_input("\"", window, cx);
11996    });
11997    cx.assert_editor_state(
11998        &r#"
11999            let x = "\" "ˇ
12000        "#
12001        .unindent(),
12002    );
12003}
12004
12005#[gpui::test]
12006async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
12007    init_test(cx, |_| {});
12008
12009    let mut cx = EditorTestContext::new(cx).await;
12010    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
12011
12012    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12013
12014    // Double quote inside single-quoted string
12015    cx.set_state(indoc! {r#"
12016        def main():
12017            items = ['"', ˇ]
12018    "#});
12019    cx.update_editor(|editor, window, cx| {
12020        editor.handle_input("\"", window, cx);
12021    });
12022    cx.assert_editor_state(indoc! {r#"
12023        def main():
12024            items = ['"', "ˇ"]
12025    "#});
12026
12027    // Two double quotes inside single-quoted string
12028    cx.set_state(indoc! {r#"
12029        def main():
12030            items = ['""', ˇ]
12031    "#});
12032    cx.update_editor(|editor, window, cx| {
12033        editor.handle_input("\"", window, cx);
12034    });
12035    cx.assert_editor_state(indoc! {r#"
12036        def main():
12037            items = ['""', "ˇ"]
12038    "#});
12039
12040    // Single quote inside double-quoted string
12041    cx.set_state(indoc! {r#"
12042        def main():
12043            items = ["'", ˇ]
12044    "#});
12045    cx.update_editor(|editor, window, cx| {
12046        editor.handle_input("'", window, cx);
12047    });
12048    cx.assert_editor_state(indoc! {r#"
12049        def main():
12050            items = ["'", 'ˇ']
12051    "#});
12052
12053    // Two single quotes inside double-quoted string
12054    cx.set_state(indoc! {r#"
12055        def main():
12056            items = ["''", ˇ]
12057    "#});
12058    cx.update_editor(|editor, window, cx| {
12059        editor.handle_input("'", window, cx);
12060    });
12061    cx.assert_editor_state(indoc! {r#"
12062        def main():
12063            items = ["''", 'ˇ']
12064    "#});
12065
12066    // Mixed quotes on same line
12067    cx.set_state(indoc! {r#"
12068        def main():
12069            items = ['"""', "'''''", ˇ]
12070    "#});
12071    cx.update_editor(|editor, window, cx| {
12072        editor.handle_input("\"", window, cx);
12073    });
12074    cx.assert_editor_state(indoc! {r#"
12075        def main():
12076            items = ['"""', "'''''", "ˇ"]
12077    "#});
12078    cx.update_editor(|editor, window, cx| {
12079        editor.move_right(&MoveRight, window, cx);
12080    });
12081    cx.update_editor(|editor, window, cx| {
12082        editor.handle_input(", ", window, cx);
12083    });
12084    cx.update_editor(|editor, window, cx| {
12085        editor.handle_input("'", window, cx);
12086    });
12087    cx.assert_editor_state(indoc! {r#"
12088        def main():
12089            items = ['"""', "'''''", "", 'ˇ']
12090    "#});
12091}
12092
12093#[gpui::test]
12094async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
12095    init_test(cx, |_| {});
12096
12097    let mut cx = EditorTestContext::new(cx).await;
12098    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
12099    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
12100
12101    cx.set_state(indoc! {r#"
12102        def main():
12103            items = ["🎉", ˇ]
12104    "#});
12105    cx.update_editor(|editor, window, cx| {
12106        editor.handle_input("\"", window, cx);
12107    });
12108    cx.assert_editor_state(indoc! {r#"
12109        def main():
12110            items = ["🎉", "ˇ"]
12111    "#});
12112}
12113
12114#[gpui::test]
12115async fn test_surround_with_pair(cx: &mut TestAppContext) {
12116    init_test(cx, |_| {});
12117
12118    let language = Arc::new(Language::new(
12119        LanguageConfig {
12120            brackets: BracketPairConfig {
12121                pairs: vec![
12122                    BracketPair {
12123                        start: "{".to_string(),
12124                        end: "}".to_string(),
12125                        close: true,
12126                        surround: true,
12127                        newline: true,
12128                    },
12129                    BracketPair {
12130                        start: "/* ".to_string(),
12131                        end: "*/".to_string(),
12132                        close: true,
12133                        surround: true,
12134                        ..Default::default()
12135                    },
12136                ],
12137                ..Default::default()
12138            },
12139            ..Default::default()
12140        },
12141        Some(tree_sitter_rust::LANGUAGE.into()),
12142    ));
12143
12144    let text = r#"
12145        a
12146        b
12147        c
12148    "#
12149    .unindent();
12150
12151    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12152    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12153    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12154    editor
12155        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12156        .await;
12157
12158    editor.update_in(cx, |editor, window, cx| {
12159        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12160            s.select_display_ranges([
12161                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
12162                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
12163                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
12164            ])
12165        });
12166
12167        editor.handle_input("{", window, cx);
12168        editor.handle_input("{", window, cx);
12169        editor.handle_input("{", window, cx);
12170        assert_eq!(
12171            editor.text(cx),
12172            "
12173                {{{a}}}
12174                {{{b}}}
12175                {{{c}}}
12176            "
12177            .unindent()
12178        );
12179        assert_eq!(
12180            display_ranges(editor, cx),
12181            [
12182                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
12183                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
12184                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
12185            ]
12186        );
12187
12188        editor.undo(&Undo, window, cx);
12189        editor.undo(&Undo, window, cx);
12190        editor.undo(&Undo, window, cx);
12191        assert_eq!(
12192            editor.text(cx),
12193            "
12194                a
12195                b
12196                c
12197            "
12198            .unindent()
12199        );
12200        assert_eq!(
12201            display_ranges(editor, cx),
12202            [
12203                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
12204                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
12205                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
12206            ]
12207        );
12208
12209        // Ensure inserting the first character of a multi-byte bracket pair
12210        // doesn't surround the selections with the bracket.
12211        editor.handle_input("/", window, cx);
12212        assert_eq!(
12213            editor.text(cx),
12214            "
12215                /
12216                /
12217                /
12218            "
12219            .unindent()
12220        );
12221        assert_eq!(
12222            display_ranges(editor, cx),
12223            [
12224                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
12225                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
12226                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
12227            ]
12228        );
12229
12230        editor.undo(&Undo, window, cx);
12231        assert_eq!(
12232            editor.text(cx),
12233            "
12234                a
12235                b
12236                c
12237            "
12238            .unindent()
12239        );
12240        assert_eq!(
12241            display_ranges(editor, cx),
12242            [
12243                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
12244                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
12245                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
12246            ]
12247        );
12248
12249        // Ensure inserting the last character of a multi-byte bracket pair
12250        // doesn't surround the selections with the bracket.
12251        editor.handle_input("*", window, cx);
12252        assert_eq!(
12253            editor.text(cx),
12254            "
12255                *
12256                *
12257                *
12258            "
12259            .unindent()
12260        );
12261        assert_eq!(
12262            display_ranges(editor, cx),
12263            [
12264                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
12265                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
12266                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
12267            ]
12268        );
12269    });
12270}
12271
12272#[gpui::test]
12273async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
12274    init_test(cx, |_| {});
12275
12276    let language = Arc::new(Language::new(
12277        LanguageConfig {
12278            brackets: BracketPairConfig {
12279                pairs: vec![BracketPair {
12280                    start: "{".to_string(),
12281                    end: "}".to_string(),
12282                    close: true,
12283                    surround: true,
12284                    newline: true,
12285                }],
12286                ..Default::default()
12287            },
12288            autoclose_before: "}".to_string(),
12289            ..Default::default()
12290        },
12291        Some(tree_sitter_rust::LANGUAGE.into()),
12292    ));
12293
12294    let text = r#"
12295        a
12296        b
12297        c
12298    "#
12299    .unindent();
12300
12301    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
12302    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12303    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12304    editor
12305        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12306        .await;
12307
12308    editor.update_in(cx, |editor, window, cx| {
12309        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12310            s.select_ranges([
12311                Point::new(0, 1)..Point::new(0, 1),
12312                Point::new(1, 1)..Point::new(1, 1),
12313                Point::new(2, 1)..Point::new(2, 1),
12314            ])
12315        });
12316
12317        editor.handle_input("{", window, cx);
12318        editor.handle_input("{", window, cx);
12319        editor.handle_input("_", window, cx);
12320        assert_eq!(
12321            editor.text(cx),
12322            "
12323                a{{_}}
12324                b{{_}}
12325                c{{_}}
12326            "
12327            .unindent()
12328        );
12329        assert_eq!(
12330            editor
12331                .selections
12332                .ranges::<Point>(&editor.display_snapshot(cx)),
12333            [
12334                Point::new(0, 4)..Point::new(0, 4),
12335                Point::new(1, 4)..Point::new(1, 4),
12336                Point::new(2, 4)..Point::new(2, 4)
12337            ]
12338        );
12339
12340        editor.backspace(&Default::default(), window, cx);
12341        editor.backspace(&Default::default(), window, cx);
12342        assert_eq!(
12343            editor.text(cx),
12344            "
12345                a{}
12346                b{}
12347                c{}
12348            "
12349            .unindent()
12350        );
12351        assert_eq!(
12352            editor
12353                .selections
12354                .ranges::<Point>(&editor.display_snapshot(cx)),
12355            [
12356                Point::new(0, 2)..Point::new(0, 2),
12357                Point::new(1, 2)..Point::new(1, 2),
12358                Point::new(2, 2)..Point::new(2, 2)
12359            ]
12360        );
12361
12362        editor.delete_to_previous_word_start(&Default::default(), window, cx);
12363        assert_eq!(
12364            editor.text(cx),
12365            "
12366                a
12367                b
12368                c
12369            "
12370            .unindent()
12371        );
12372        assert_eq!(
12373            editor
12374                .selections
12375                .ranges::<Point>(&editor.display_snapshot(cx)),
12376            [
12377                Point::new(0, 1)..Point::new(0, 1),
12378                Point::new(1, 1)..Point::new(1, 1),
12379                Point::new(2, 1)..Point::new(2, 1)
12380            ]
12381        );
12382    });
12383}
12384
12385#[gpui::test]
12386async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
12387    init_test(cx, |settings| {
12388        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
12389    });
12390
12391    let mut cx = EditorTestContext::new(cx).await;
12392
12393    let language = Arc::new(Language::new(
12394        LanguageConfig {
12395            brackets: BracketPairConfig {
12396                pairs: vec![
12397                    BracketPair {
12398                        start: "{".to_string(),
12399                        end: "}".to_string(),
12400                        close: true,
12401                        surround: true,
12402                        newline: true,
12403                    },
12404                    BracketPair {
12405                        start: "(".to_string(),
12406                        end: ")".to_string(),
12407                        close: true,
12408                        surround: true,
12409                        newline: true,
12410                    },
12411                    BracketPair {
12412                        start: "[".to_string(),
12413                        end: "]".to_string(),
12414                        close: false,
12415                        surround: true,
12416                        newline: true,
12417                    },
12418                ],
12419                ..Default::default()
12420            },
12421            autoclose_before: "})]".to_string(),
12422            ..Default::default()
12423        },
12424        Some(tree_sitter_rust::LANGUAGE.into()),
12425    ));
12426
12427    cx.language_registry().add(language.clone());
12428    cx.update_buffer(|buffer, cx| {
12429        buffer.set_language(Some(language), cx);
12430    });
12431
12432    cx.set_state(
12433        &"
12434            {(ˇ)}
12435            [[ˇ]]
12436            {(ˇ)}
12437        "
12438        .unindent(),
12439    );
12440
12441    cx.update_editor(|editor, window, cx| {
12442        editor.backspace(&Default::default(), window, cx);
12443        editor.backspace(&Default::default(), window, cx);
12444    });
12445
12446    cx.assert_editor_state(
12447        &"
12448            ˇ
12449            ˇ]]
12450            ˇ
12451        "
12452        .unindent(),
12453    );
12454
12455    cx.update_editor(|editor, window, cx| {
12456        editor.handle_input("{", window, cx);
12457        editor.handle_input("{", window, cx);
12458        editor.move_right(&MoveRight, window, cx);
12459        editor.move_right(&MoveRight, window, cx);
12460        editor.move_left(&MoveLeft, window, cx);
12461        editor.move_left(&MoveLeft, window, cx);
12462        editor.backspace(&Default::default(), window, cx);
12463    });
12464
12465    cx.assert_editor_state(
12466        &"
12467            {ˇ}
12468            {ˇ}]]
12469            {ˇ}
12470        "
12471        .unindent(),
12472    );
12473
12474    cx.update_editor(|editor, window, cx| {
12475        editor.backspace(&Default::default(), window, cx);
12476    });
12477
12478    cx.assert_editor_state(
12479        &"
12480            ˇ
12481            ˇ]]
12482            ˇ
12483        "
12484        .unindent(),
12485    );
12486}
12487
12488#[gpui::test]
12489async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
12490    init_test(cx, |_| {});
12491
12492    let language = Arc::new(Language::new(
12493        LanguageConfig::default(),
12494        Some(tree_sitter_rust::LANGUAGE.into()),
12495    ));
12496
12497    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
12498    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12499    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12500    editor
12501        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
12502        .await;
12503
12504    editor.update_in(cx, |editor, window, cx| {
12505        editor.set_auto_replace_emoji_shortcode(true);
12506
12507        editor.handle_input("Hello ", window, cx);
12508        editor.handle_input(":wave", window, cx);
12509        assert_eq!(editor.text(cx), "Hello :wave".unindent());
12510
12511        editor.handle_input(":", window, cx);
12512        assert_eq!(editor.text(cx), "Hello 👋".unindent());
12513
12514        editor.handle_input(" :smile", window, cx);
12515        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
12516
12517        editor.handle_input(":", window, cx);
12518        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
12519
12520        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
12521        editor.handle_input(":wave", window, cx);
12522        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
12523
12524        editor.handle_input(":", window, cx);
12525        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
12526
12527        editor.handle_input(":1", window, cx);
12528        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
12529
12530        editor.handle_input(":", window, cx);
12531        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
12532
12533        // Ensure shortcode does not get replaced when it is part of a word
12534        editor.handle_input(" Test:wave", window, cx);
12535        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
12536
12537        editor.handle_input(":", window, cx);
12538        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
12539
12540        editor.set_auto_replace_emoji_shortcode(false);
12541
12542        // Ensure shortcode does not get replaced when auto replace is off
12543        editor.handle_input(" :wave", window, cx);
12544        assert_eq!(
12545            editor.text(cx),
12546            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
12547        );
12548
12549        editor.handle_input(":", window, cx);
12550        assert_eq!(
12551            editor.text(cx),
12552            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
12553        );
12554    });
12555}
12556
12557#[gpui::test]
12558async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
12559    init_test(cx, |_| {});
12560
12561    let (text, insertion_ranges) = marked_text_ranges(
12562        indoc! {"
12563            ˇ
12564        "},
12565        false,
12566    );
12567
12568    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
12569    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12570
12571    _ = editor.update_in(cx, |editor, window, cx| {
12572        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
12573
12574        editor
12575            .insert_snippet(
12576                &insertion_ranges
12577                    .iter()
12578                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
12579                    .collect::<Vec<_>>(),
12580                snippet,
12581                window,
12582                cx,
12583            )
12584            .unwrap();
12585
12586        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
12587            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
12588            assert_eq!(editor.text(cx), expected_text);
12589            assert_eq!(
12590                editor.selections.ranges(&editor.display_snapshot(cx)),
12591                selection_ranges
12592                    .iter()
12593                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
12594                    .collect::<Vec<_>>()
12595            );
12596        }
12597
12598        assert(
12599            editor,
12600            cx,
12601            indoc! {"
12602            type «» =•
12603            "},
12604        );
12605
12606        assert!(editor.context_menu_visible(), "There should be a matches");
12607    });
12608}
12609
12610#[gpui::test]
12611async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
12612    init_test(cx, |_| {});
12613
12614    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
12615        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
12616        assert_eq!(editor.text(cx), expected_text);
12617        assert_eq!(
12618            editor.selections.ranges(&editor.display_snapshot(cx)),
12619            selection_ranges
12620                .iter()
12621                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
12622                .collect::<Vec<_>>()
12623        );
12624    }
12625
12626    let (text, insertion_ranges) = marked_text_ranges(
12627        indoc! {"
12628            ˇ
12629        "},
12630        false,
12631    );
12632
12633    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
12634    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
12635
12636    _ = editor.update_in(cx, |editor, window, cx| {
12637        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
12638
12639        editor
12640            .insert_snippet(
12641                &insertion_ranges
12642                    .iter()
12643                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
12644                    .collect::<Vec<_>>(),
12645                snippet,
12646                window,
12647                cx,
12648            )
12649            .unwrap();
12650
12651        assert_state(
12652            editor,
12653            cx,
12654            indoc! {"
12655            type «» = ;•
12656            "},
12657        );
12658
12659        assert!(
12660            editor.context_menu_visible(),
12661            "Context menu should be visible for placeholder choices"
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        assert!(
12675            !editor.context_menu_visible(),
12676            "Context menu should be hidden after moving to next tabstop"
12677        );
12678
12679        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
12680
12681        assert_state(
12682            editor,
12683            cx,
12684            indoc! {"
12685            type  = ; ˇ
12686            "},
12687        );
12688
12689        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
12690
12691        assert_state(
12692            editor,
12693            cx,
12694            indoc! {"
12695            type  = ; ˇ
12696            "},
12697        );
12698    });
12699
12700    _ = editor.update_in(cx, |editor, window, cx| {
12701        editor.select_all(&SelectAll, window, cx);
12702        editor.backspace(&Backspace, window, cx);
12703
12704        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
12705        let insertion_ranges = editor
12706            .selections
12707            .all(&editor.display_snapshot(cx))
12708            .iter()
12709            .map(|s| s.range())
12710            .collect::<Vec<_>>();
12711
12712        editor
12713            .insert_snippet(&insertion_ranges, snippet, window, cx)
12714            .unwrap();
12715
12716        assert_state(editor, cx, "fn «» = value;•");
12717
12718        assert!(
12719            editor.context_menu_visible(),
12720            "Context menu should be visible for placeholder choices"
12721        );
12722
12723        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
12724
12725        assert_state(editor, cx, "fn  = «valueˇ»;•");
12726
12727        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
12728
12729        assert_state(editor, cx, "fn «» = value;•");
12730
12731        assert!(
12732            editor.context_menu_visible(),
12733            "Context menu should be visible again after returning to first tabstop"
12734        );
12735
12736        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
12737
12738        assert_state(editor, cx, "fn «» = value;•");
12739    });
12740}
12741
12742#[gpui::test]
12743async fn test_snippets(cx: &mut TestAppContext) {
12744    init_test(cx, |_| {});
12745
12746    let mut cx = EditorTestContext::new(cx).await;
12747
12748    cx.set_state(indoc! {"
12749        a.ˇ b
12750        a.ˇ b
12751        a.ˇ b
12752    "});
12753
12754    cx.update_editor(|editor, window, cx| {
12755        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
12756        let insertion_ranges = editor
12757            .selections
12758            .all(&editor.display_snapshot(cx))
12759            .iter()
12760            .map(|s| s.range())
12761            .collect::<Vec<_>>();
12762        editor
12763            .insert_snippet(&insertion_ranges, snippet, window, cx)
12764            .unwrap();
12765    });
12766
12767    cx.assert_editor_state(indoc! {"
12768        a.f(«oneˇ», two, «threeˇ») b
12769        a.f(«oneˇ», two, «threeˇ») b
12770        a.f(«oneˇ», two, «threeˇ») b
12771    "});
12772
12773    // Can't move earlier than the first tab stop
12774    cx.update_editor(|editor, window, cx| {
12775        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
12776    });
12777    cx.assert_editor_state(indoc! {"
12778        a.f(«oneˇ», two, «threeˇ») b
12779        a.f(«oneˇ», two, «threeˇ») b
12780        a.f(«oneˇ», two, «threeˇ») b
12781    "});
12782
12783    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
12784    cx.assert_editor_state(indoc! {"
12785        a.f(one, «twoˇ», three) b
12786        a.f(one, «twoˇ», three) b
12787        a.f(one, «twoˇ», three) b
12788    "});
12789
12790    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
12791    cx.assert_editor_state(indoc! {"
12792        a.f(«oneˇ», two, «threeˇ») b
12793        a.f(«oneˇ», two, «threeˇ») b
12794        a.f(«oneˇ», two, «threeˇ») b
12795    "});
12796
12797    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
12798    cx.assert_editor_state(indoc! {"
12799        a.f(one, «twoˇ», three) b
12800        a.f(one, «twoˇ», three) b
12801        a.f(one, «twoˇ», three) b
12802    "});
12803    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
12804    cx.assert_editor_state(indoc! {"
12805        a.f(one, two, three)ˇ b
12806        a.f(one, two, three)ˇ b
12807        a.f(one, two, three)ˇ b
12808    "});
12809
12810    // As soon as the last tab stop is reached, snippet state is gone
12811    cx.update_editor(|editor, window, cx| {
12812        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
12813    });
12814    cx.assert_editor_state(indoc! {"
12815        a.f(one, two, three)ˇ b
12816        a.f(one, two, three)ˇ b
12817        a.f(one, two, three)ˇ b
12818    "});
12819}
12820
12821#[gpui::test]
12822async fn test_snippet_indentation(cx: &mut TestAppContext) {
12823    init_test(cx, |_| {});
12824
12825    let mut cx = EditorTestContext::new(cx).await;
12826
12827    cx.update_editor(|editor, window, cx| {
12828        let snippet = Snippet::parse(indoc! {"
12829            /*
12830             * Multiline comment with leading indentation
12831             *
12832             * $1
12833             */
12834            $0"})
12835        .unwrap();
12836        let insertion_ranges = editor
12837            .selections
12838            .all(&editor.display_snapshot(cx))
12839            .iter()
12840            .map(|s| s.range())
12841            .collect::<Vec<_>>();
12842        editor
12843            .insert_snippet(&insertion_ranges, snippet, window, cx)
12844            .unwrap();
12845    });
12846
12847    cx.assert_editor_state(indoc! {"
12848        /*
12849         * Multiline comment with leading indentation
12850         *
12851         * ˇ
12852         */
12853    "});
12854
12855    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
12856    cx.assert_editor_state(indoc! {"
12857        /*
12858         * Multiline comment with leading indentation
12859         *
12860         *•
12861         */
12862        ˇ"});
12863}
12864
12865#[gpui::test]
12866async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
12867    init_test(cx, |_| {});
12868
12869    let mut cx = EditorTestContext::new(cx).await;
12870    cx.update_editor(|editor, _, cx| {
12871        editor.project().unwrap().update(cx, |project, cx| {
12872            project.snippets().update(cx, |snippets, _cx| {
12873                let snippet = project::snippet_provider::Snippet {
12874                    prefix: vec!["multi word".to_string()],
12875                    body: "this is many words".to_string(),
12876                    description: Some("description".to_string()),
12877                    name: "multi-word snippet test".to_string(),
12878                };
12879                snippets.add_snippet_for_test(
12880                    None,
12881                    PathBuf::from("test_snippets.json"),
12882                    vec![Arc::new(snippet)],
12883                );
12884            });
12885        })
12886    });
12887
12888    for (input_to_simulate, should_match_snippet) in [
12889        ("m", true),
12890        ("m ", true),
12891        ("m w", true),
12892        ("aa m w", true),
12893        ("aa m g", false),
12894    ] {
12895        cx.set_state("ˇ");
12896        cx.simulate_input(input_to_simulate); // fails correctly
12897
12898        cx.update_editor(|editor, _, _| {
12899            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
12900            else {
12901                assert!(!should_match_snippet); // no completions! don't even show the menu
12902                return;
12903            };
12904            assert!(context_menu.visible());
12905            let completions = context_menu.completions.borrow();
12906
12907            assert_eq!(!completions.is_empty(), should_match_snippet);
12908        });
12909    }
12910}
12911
12912#[gpui::test]
12913async fn test_document_format_during_save(cx: &mut TestAppContext) {
12914    init_test(cx, |_| {});
12915
12916    let fs = FakeFs::new(cx.executor());
12917    fs.insert_file(path!("/file.rs"), Default::default()).await;
12918
12919    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
12920
12921    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12922    language_registry.add(rust_lang());
12923    let mut fake_servers = language_registry.register_fake_lsp(
12924        "Rust",
12925        FakeLspAdapter {
12926            capabilities: lsp::ServerCapabilities {
12927                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12928                ..Default::default()
12929            },
12930            ..Default::default()
12931        },
12932    );
12933
12934    let buffer = project
12935        .update(cx, |project, cx| {
12936            project.open_local_buffer(path!("/file.rs"), cx)
12937        })
12938        .await
12939        .unwrap();
12940
12941    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12942    let (editor, cx) = cx.add_window_view(|window, cx| {
12943        build_editor_with_project(project.clone(), buffer, window, cx)
12944    });
12945    editor.update_in(cx, |editor, window, cx| {
12946        editor.set_text("one\ntwo\nthree\n", window, cx)
12947    });
12948    assert!(cx.read(|cx| editor.is_dirty(cx)));
12949
12950    let fake_server = fake_servers.next().await.unwrap();
12951
12952    {
12953        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12954            move |params, _| async move {
12955                assert_eq!(
12956                    params.text_document.uri,
12957                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12958                );
12959                assert_eq!(params.options.tab_size, 4);
12960                Ok(Some(vec![lsp::TextEdit::new(
12961                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12962                    ", ".to_string(),
12963                )]))
12964            },
12965        );
12966        let save = editor
12967            .update_in(cx, |editor, window, cx| {
12968                editor.save(
12969                    SaveOptions {
12970                        format: true,
12971                        autosave: false,
12972                    },
12973                    project.clone(),
12974                    window,
12975                    cx,
12976                )
12977            })
12978            .unwrap();
12979        save.await;
12980
12981        assert_eq!(
12982            editor.update(cx, |editor, cx| editor.text(cx)),
12983            "one, two\nthree\n"
12984        );
12985        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12986    }
12987
12988    {
12989        editor.update_in(cx, |editor, window, cx| {
12990            editor.set_text("one\ntwo\nthree\n", window, cx)
12991        });
12992        assert!(cx.read(|cx| editor.is_dirty(cx)));
12993
12994        // Ensure we can still save even if formatting hangs.
12995        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12996            move |params, _| async move {
12997                assert_eq!(
12998                    params.text_document.uri,
12999                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13000                );
13001                futures::future::pending::<()>().await;
13002                unreachable!()
13003            },
13004        );
13005        let save = editor
13006            .update_in(cx, |editor, window, cx| {
13007                editor.save(
13008                    SaveOptions {
13009                        format: true,
13010                        autosave: false,
13011                    },
13012                    project.clone(),
13013                    window,
13014                    cx,
13015                )
13016            })
13017            .unwrap();
13018        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
13019        save.await;
13020        assert_eq!(
13021            editor.update(cx, |editor, cx| editor.text(cx)),
13022            "one\ntwo\nthree\n"
13023        );
13024    }
13025
13026    // Set rust language override and assert overridden tabsize is sent to language server
13027    update_test_language_settings(cx, &|settings| {
13028        settings.languages.0.insert(
13029            "Rust".into(),
13030            LanguageSettingsContent {
13031                tab_size: NonZeroU32::new(8),
13032                ..Default::default()
13033            },
13034        );
13035    });
13036
13037    {
13038        editor.update_in(cx, |editor, window, cx| {
13039            editor.set_text("somehting_new\n", window, cx)
13040        });
13041        assert!(cx.read(|cx| editor.is_dirty(cx)));
13042        let _formatting_request_signal = fake_server
13043            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
13044                assert_eq!(
13045                    params.text_document.uri,
13046                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13047                );
13048                assert_eq!(params.options.tab_size, 8);
13049                Ok(Some(vec![]))
13050            });
13051        let save = editor
13052            .update_in(cx, |editor, window, cx| {
13053                editor.save(
13054                    SaveOptions {
13055                        format: true,
13056                        autosave: false,
13057                    },
13058                    project.clone(),
13059                    window,
13060                    cx,
13061                )
13062            })
13063            .unwrap();
13064        save.await;
13065    }
13066}
13067
13068#[gpui::test]
13069async fn test_auto_formatter_skips_server_without_formatting(cx: &mut TestAppContext) {
13070    init_test(cx, |_| {});
13071
13072    let fs = FakeFs::new(cx.executor());
13073    fs.insert_file(path!("/file.rs"), Default::default()).await;
13074
13075    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
13076
13077    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13078    language_registry.add(rust_lang());
13079
13080    // First server: no formatting capability
13081    let mut no_format_servers = language_registry.register_fake_lsp(
13082        "Rust",
13083        FakeLspAdapter {
13084            name: "no-format-server",
13085            capabilities: lsp::ServerCapabilities {
13086                completion_provider: Some(lsp::CompletionOptions::default()),
13087                ..Default::default()
13088            },
13089            ..Default::default()
13090        },
13091    );
13092
13093    // Second server: has formatting capability
13094    let mut format_servers = language_registry.register_fake_lsp(
13095        "Rust",
13096        FakeLspAdapter {
13097            name: "format-server",
13098            capabilities: lsp::ServerCapabilities {
13099                document_formatting_provider: Some(lsp::OneOf::Left(true)),
13100                ..Default::default()
13101            },
13102            ..Default::default()
13103        },
13104    );
13105
13106    let buffer = project
13107        .update(cx, |project, cx| {
13108            project.open_local_buffer(path!("/file.rs"), cx)
13109        })
13110        .await
13111        .unwrap();
13112
13113    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13114    let (editor, cx) = cx.add_window_view(|window, cx| {
13115        build_editor_with_project(project.clone(), buffer, window, cx)
13116    });
13117    editor.update_in(cx, |editor, window, cx| {
13118        editor.set_text("one\ntwo\nthree\n", window, cx)
13119    });
13120
13121    let _no_format_server = no_format_servers.next().await.unwrap();
13122    let format_server = format_servers.next().await.unwrap();
13123
13124    format_server.set_request_handler::<lsp::request::Formatting, _, _>(
13125        move |params, _| async move {
13126            assert_eq!(
13127                params.text_document.uri,
13128                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13129            );
13130            Ok(Some(vec![lsp::TextEdit::new(
13131                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
13132                ", ".to_string(),
13133            )]))
13134        },
13135    );
13136
13137    let save = editor
13138        .update_in(cx, |editor, window, cx| {
13139            editor.save(
13140                SaveOptions {
13141                    format: true,
13142                    autosave: false,
13143                },
13144                project.clone(),
13145                window,
13146                cx,
13147            )
13148        })
13149        .unwrap();
13150    save.await;
13151
13152    assert_eq!(
13153        editor.update(cx, |editor, cx| editor.text(cx)),
13154        "one, two\nthree\n"
13155    );
13156}
13157
13158#[gpui::test]
13159async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
13160    init_test(cx, |settings| {
13161        settings.defaults.ensure_final_newline_on_save = Some(false);
13162    });
13163
13164    let fs = FakeFs::new(cx.executor());
13165    fs.insert_file(path!("/file.txt"), "foo".into()).await;
13166
13167    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
13168
13169    let buffer = project
13170        .update(cx, |project, cx| {
13171            project.open_local_buffer(path!("/file.txt"), cx)
13172        })
13173        .await
13174        .unwrap();
13175
13176    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13177    let (editor, cx) = cx.add_window_view(|window, cx| {
13178        build_editor_with_project(project.clone(), buffer, window, cx)
13179    });
13180    editor.update_in(cx, |editor, window, cx| {
13181        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
13182            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13183        });
13184    });
13185    assert!(!cx.read(|cx| editor.is_dirty(cx)));
13186
13187    editor.update_in(cx, |editor, window, cx| {
13188        editor.handle_input("\n", window, cx)
13189    });
13190    cx.run_until_parked();
13191    save(&editor, &project, cx).await;
13192    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
13193
13194    editor.update_in(cx, |editor, window, cx| {
13195        editor.undo(&Default::default(), window, cx);
13196    });
13197    save(&editor, &project, cx).await;
13198    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
13199
13200    editor.update_in(cx, |editor, window, cx| {
13201        editor.redo(&Default::default(), window, cx);
13202    });
13203    cx.run_until_parked();
13204    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
13205
13206    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
13207        let save = editor
13208            .update_in(cx, |editor, window, cx| {
13209                editor.save(
13210                    SaveOptions {
13211                        format: true,
13212                        autosave: false,
13213                    },
13214                    project.clone(),
13215                    window,
13216                    cx,
13217                )
13218            })
13219            .unwrap();
13220        save.await;
13221        assert!(!cx.read(|cx| editor.is_dirty(cx)));
13222    }
13223}
13224
13225#[gpui::test]
13226async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
13227    init_test(cx, |_| {});
13228
13229    let cols = 4;
13230    let rows = 10;
13231    let sample_text_1 = sample_text(rows, cols, 'a');
13232    assert_eq!(
13233        sample_text_1,
13234        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13235    );
13236    let sample_text_2 = sample_text(rows, cols, 'l');
13237    assert_eq!(
13238        sample_text_2,
13239        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13240    );
13241    let sample_text_3 = sample_text(rows, cols, 'v').replace('\u{7f}', ".");
13242    assert_eq!(
13243        sample_text_3,
13244        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n...."
13245    );
13246
13247    let fs = FakeFs::new(cx.executor());
13248    fs.insert_tree(
13249        path!("/a"),
13250        json!({
13251            "main.rs": sample_text_1,
13252            "other.rs": sample_text_2,
13253            "lib.rs": sample_text_3,
13254        }),
13255    )
13256    .await;
13257
13258    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13259    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
13260    let cx = &mut VisualTestContext::from_window(*window, cx);
13261
13262    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13263    language_registry.add(rust_lang());
13264    let mut fake_servers = language_registry.register_fake_lsp(
13265        "Rust",
13266        FakeLspAdapter {
13267            capabilities: lsp::ServerCapabilities {
13268                document_formatting_provider: Some(lsp::OneOf::Left(true)),
13269                ..Default::default()
13270            },
13271            ..Default::default()
13272        },
13273    );
13274
13275    let worktree = project.update(cx, |project, cx| {
13276        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
13277        assert_eq!(worktrees.len(), 1);
13278        worktrees.pop().unwrap()
13279    });
13280    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
13281
13282    let buffer_1 = project
13283        .update(cx, |project, cx| {
13284            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
13285        })
13286        .await
13287        .unwrap();
13288    let buffer_2 = project
13289        .update(cx, |project, cx| {
13290            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
13291        })
13292        .await
13293        .unwrap();
13294    let buffer_3 = project
13295        .update(cx, |project, cx| {
13296            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
13297        })
13298        .await
13299        .unwrap();
13300
13301    let multi_buffer = cx.new(|cx| {
13302        let mut multi_buffer = MultiBuffer::new(ReadWrite);
13303        multi_buffer.set_excerpts_for_path(
13304            PathKey::sorted(0),
13305            buffer_1.clone(),
13306            [
13307                Point::new(0, 0)..Point::new(2, 4),
13308                Point::new(5, 0)..Point::new(6, 4),
13309                Point::new(9, 0)..Point::new(9, 4),
13310            ],
13311            0,
13312            cx,
13313        );
13314        multi_buffer.set_excerpts_for_path(
13315            PathKey::sorted(1),
13316            buffer_2.clone(),
13317            [
13318                Point::new(0, 0)..Point::new(2, 4),
13319                Point::new(5, 0)..Point::new(6, 4),
13320                Point::new(9, 0)..Point::new(9, 4),
13321            ],
13322            0,
13323            cx,
13324        );
13325        multi_buffer.set_excerpts_for_path(
13326            PathKey::sorted(2),
13327            buffer_3.clone(),
13328            [
13329                Point::new(0, 0)..Point::new(2, 4),
13330                Point::new(5, 0)..Point::new(6, 4),
13331                Point::new(9, 0)..Point::new(9, 4),
13332            ],
13333            0,
13334            cx,
13335        );
13336        assert_eq!(multi_buffer.excerpt_ids().len(), 9);
13337        multi_buffer
13338    });
13339    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13340        Editor::new(
13341            EditorMode::full(),
13342            multi_buffer,
13343            Some(project.clone()),
13344            window,
13345            cx,
13346        )
13347    });
13348
13349    multi_buffer_editor.update_in(cx, |editor, window, cx| {
13350        let a = editor.text(cx).find("aaaa").unwrap();
13351        editor.change_selections(
13352            SelectionEffects::scroll(Autoscroll::Next),
13353            window,
13354            cx,
13355            |s| s.select_ranges(Some(MultiBufferOffset(a + 1)..MultiBufferOffset(a + 2))),
13356        );
13357        editor.insert("|one|two|three|", window, cx);
13358    });
13359    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
13360    multi_buffer_editor.update_in(cx, |editor, window, cx| {
13361        let n = editor.text(cx).find("nnnn").unwrap();
13362        editor.change_selections(
13363            SelectionEffects::scroll(Autoscroll::Next),
13364            window,
13365            cx,
13366            |s| s.select_ranges(Some(MultiBufferOffset(n + 4)..MultiBufferOffset(n + 14))),
13367        );
13368        editor.insert("|four|five|six|", window, cx);
13369    });
13370    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
13371
13372    // First two buffers should be edited, but not the third one.
13373    pretty_assertions::assert_eq!(
13374        editor_content_with_blocks(&multi_buffer_editor, cx),
13375        indoc! {"
13376            § main.rs
13377            § -----
13378            a|one|two|three|aa
13379            bbbb
13380            cccc
13381            § -----
13382            ffff
13383            gggg
13384            § -----
13385            jjjj
13386            § other.rs
13387            § -----
13388            llll
13389            mmmm
13390            nnnn|four|five|six|
13391            § -----
13392
13393            § -----
13394            uuuu
13395            § lib.rs
13396            § -----
13397            vvvv
13398            wwww
13399            xxxx
13400            § -----
13401            {{{{
13402            ||||
13403            § -----
13404            ...."}
13405    );
13406    buffer_1.update(cx, |buffer, _| {
13407        assert!(buffer.is_dirty());
13408        assert_eq!(
13409            buffer.text(),
13410            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
13411        )
13412    });
13413    buffer_2.update(cx, |buffer, _| {
13414        assert!(buffer.is_dirty());
13415        assert_eq!(
13416            buffer.text(),
13417            "llll\nmmmm\nnnnn|four|five|six|\noooo\npppp\n\nssss\ntttt\nuuuu",
13418        )
13419    });
13420    buffer_3.update(cx, |buffer, _| {
13421        assert!(!buffer.is_dirty());
13422        assert_eq!(buffer.text(), sample_text_3,)
13423    });
13424    cx.executor().run_until_parked();
13425
13426    let save = multi_buffer_editor
13427        .update_in(cx, |editor, window, cx| {
13428            editor.save(
13429                SaveOptions {
13430                    format: true,
13431                    autosave: false,
13432                },
13433                project.clone(),
13434                window,
13435                cx,
13436            )
13437        })
13438        .unwrap();
13439
13440    let fake_server = fake_servers.next().await.unwrap();
13441    fake_server
13442        .server
13443        .on_request::<lsp::request::Formatting, _, _>(move |_params, _| async move {
13444            Ok(Some(vec![lsp::TextEdit::new(
13445                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
13446                "[formatted]".to_string(),
13447            )]))
13448        })
13449        .detach();
13450    save.await;
13451
13452    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
13453    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
13454    assert_eq!(
13455        editor_content_with_blocks(&multi_buffer_editor, cx),
13456        indoc! {"
13457            § main.rs
13458            § -----
13459            a|o[formatted]bbbb
13460            cccc
13461            § -----
13462            ffff
13463            gggg
13464            § -----
13465            jjjj
13466
13467            § other.rs
13468            § -----
13469            lll[formatted]mmmm
13470            nnnn|four|five|six|
13471            § -----
13472
13473            § -----
13474            uuuu
13475
13476            § lib.rs
13477            § -----
13478            vvvv
13479            wwww
13480            xxxx
13481            § -----
13482            {{{{
13483            ||||
13484            § -----
13485            ...."}
13486    );
13487    buffer_1.update(cx, |buffer, _| {
13488        assert!(!buffer.is_dirty());
13489        assert_eq!(
13490            buffer.text(),
13491            "a|o[formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n",
13492        )
13493    });
13494    // Diff < left / right > :
13495    //  lll[formatted]mmmm
13496    // <nnnn|four|five|six|
13497    // <oooo
13498    // >nnnn|four|five|six|oooo
13499    //  pppp
13500    // <
13501    //  ssss
13502    //  tttt
13503    //  uuuu
13504
13505    buffer_2.update(cx, |buffer, _| {
13506        assert!(!buffer.is_dirty());
13507        assert_eq!(
13508            buffer.text(),
13509            "lll[formatted]mmmm\nnnnn|four|five|six|\noooo\npppp\n\nssss\ntttt\nuuuu\n",
13510        )
13511    });
13512    buffer_3.update(cx, |buffer, _| {
13513        assert!(!buffer.is_dirty());
13514        assert_eq!(buffer.text(), sample_text_3,)
13515    });
13516}
13517
13518#[gpui::test]
13519async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
13520    init_test(cx, |_| {});
13521
13522    let fs = FakeFs::new(cx.executor());
13523    fs.insert_tree(
13524        path!("/dir"),
13525        json!({
13526            "file1.rs": "fn main() { println!(\"hello\"); }",
13527            "file2.rs": "fn test() { println!(\"test\"); }",
13528            "file3.rs": "fn other() { println!(\"other\"); }\n",
13529        }),
13530    )
13531    .await;
13532
13533    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
13534    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
13535    let cx = &mut VisualTestContext::from_window(*window, cx);
13536
13537    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13538    language_registry.add(rust_lang());
13539
13540    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
13541    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
13542
13543    // Open three buffers
13544    let buffer_1 = project
13545        .update(cx, |project, cx| {
13546            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
13547        })
13548        .await
13549        .unwrap();
13550    let buffer_2 = project
13551        .update(cx, |project, cx| {
13552            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
13553        })
13554        .await
13555        .unwrap();
13556    let buffer_3 = project
13557        .update(cx, |project, cx| {
13558            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
13559        })
13560        .await
13561        .unwrap();
13562
13563    // Create a multi-buffer with all three buffers
13564    let multi_buffer = cx.new(|cx| {
13565        let mut multi_buffer = MultiBuffer::new(ReadWrite);
13566        multi_buffer.set_excerpts_for_path(
13567            PathKey::sorted(0),
13568            buffer_1.clone(),
13569            [Point::new(0, 0)..Point::new(1, 0)],
13570            0,
13571            cx,
13572        );
13573        multi_buffer.set_excerpts_for_path(
13574            PathKey::sorted(1),
13575            buffer_2.clone(),
13576            [Point::new(0, 0)..Point::new(1, 0)],
13577            0,
13578            cx,
13579        );
13580        multi_buffer.set_excerpts_for_path(
13581            PathKey::sorted(2),
13582            buffer_3.clone(),
13583            [Point::new(0, 0)..Point::new(1, 0)],
13584            0,
13585            cx,
13586        );
13587        multi_buffer
13588    });
13589
13590    let editor = cx.new_window_entity(|window, cx| {
13591        Editor::new(
13592            EditorMode::full(),
13593            multi_buffer,
13594            Some(project.clone()),
13595            window,
13596            cx,
13597        )
13598    });
13599
13600    // Edit only the first buffer
13601    editor.update_in(cx, |editor, window, cx| {
13602        editor.change_selections(
13603            SelectionEffects::scroll(Autoscroll::Next),
13604            window,
13605            cx,
13606            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
13607        );
13608        editor.insert("// edited", window, cx);
13609    });
13610
13611    // Verify that only buffer 1 is dirty
13612    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
13613    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
13614    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
13615
13616    // Get write counts after file creation (files were created with initial content)
13617    // We expect each file to have been written once during creation
13618    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
13619    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
13620    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
13621
13622    // Perform autosave
13623    let save_task = editor.update_in(cx, |editor, window, cx| {
13624        editor.save(
13625            SaveOptions {
13626                format: true,
13627                autosave: true,
13628            },
13629            project.clone(),
13630            window,
13631            cx,
13632        )
13633    });
13634    save_task.await.unwrap();
13635
13636    // Only the dirty buffer should have been saved
13637    assert_eq!(
13638        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
13639        1,
13640        "Buffer 1 was dirty, so it should have been written once during autosave"
13641    );
13642    assert_eq!(
13643        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
13644        0,
13645        "Buffer 2 was clean, so it should not have been written during autosave"
13646    );
13647    assert_eq!(
13648        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
13649        0,
13650        "Buffer 3 was clean, so it should not have been written during autosave"
13651    );
13652
13653    // Verify buffer states after autosave
13654    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
13655    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
13656    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
13657
13658    // Now perform a manual save (format = true)
13659    let save_task = editor.update_in(cx, |editor, window, cx| {
13660        editor.save(
13661            SaveOptions {
13662                format: true,
13663                autosave: false,
13664            },
13665            project.clone(),
13666            window,
13667            cx,
13668        )
13669    });
13670    save_task.await.unwrap();
13671
13672    // During manual save, clean buffers don't get written to disk
13673    // They just get did_save called for language server notifications
13674    assert_eq!(
13675        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
13676        1,
13677        "Buffer 1 should only have been written once total (during autosave, not manual save)"
13678    );
13679    assert_eq!(
13680        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
13681        0,
13682        "Buffer 2 should not have been written at all"
13683    );
13684    assert_eq!(
13685        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
13686        0,
13687        "Buffer 3 should not have been written at all"
13688    );
13689}
13690
13691async fn setup_range_format_test(
13692    cx: &mut TestAppContext,
13693) -> (
13694    Entity<Project>,
13695    Entity<Editor>,
13696    &mut gpui::VisualTestContext,
13697    lsp::FakeLanguageServer,
13698) {
13699    init_test(cx, |_| {});
13700
13701    let fs = FakeFs::new(cx.executor());
13702    fs.insert_file(path!("/file.rs"), Default::default()).await;
13703
13704    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13705
13706    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13707    language_registry.add(rust_lang());
13708    let mut fake_servers = language_registry.register_fake_lsp(
13709        "Rust",
13710        FakeLspAdapter {
13711            capabilities: lsp::ServerCapabilities {
13712                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
13713                ..lsp::ServerCapabilities::default()
13714            },
13715            ..FakeLspAdapter::default()
13716        },
13717    );
13718
13719    let buffer = project
13720        .update(cx, |project, cx| {
13721            project.open_local_buffer(path!("/file.rs"), cx)
13722        })
13723        .await
13724        .unwrap();
13725
13726    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13727    let (editor, cx) = cx.add_window_view(|window, cx| {
13728        build_editor_with_project(project.clone(), buffer, window, cx)
13729    });
13730
13731    let fake_server = fake_servers.next().await.unwrap();
13732
13733    (project, editor, cx, fake_server)
13734}
13735
13736#[gpui::test]
13737async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
13738    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
13739
13740    editor.update_in(cx, |editor, window, cx| {
13741        editor.set_text("one\ntwo\nthree\n", window, cx)
13742    });
13743    assert!(cx.read(|cx| editor.is_dirty(cx)));
13744
13745    let save = editor
13746        .update_in(cx, |editor, window, cx| {
13747            editor.save(
13748                SaveOptions {
13749                    format: true,
13750                    autosave: false,
13751                },
13752                project.clone(),
13753                window,
13754                cx,
13755            )
13756        })
13757        .unwrap();
13758    fake_server
13759        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
13760            assert_eq!(
13761                params.text_document.uri,
13762                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13763            );
13764            assert_eq!(params.options.tab_size, 4);
13765            Ok(Some(vec![lsp::TextEdit::new(
13766                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
13767                ", ".to_string(),
13768            )]))
13769        })
13770        .next()
13771        .await;
13772    save.await;
13773    assert_eq!(
13774        editor.update(cx, |editor, cx| editor.text(cx)),
13775        "one, two\nthree\n"
13776    );
13777    assert!(!cx.read(|cx| editor.is_dirty(cx)));
13778}
13779
13780#[gpui::test]
13781async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
13782    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
13783
13784    editor.update_in(cx, |editor, window, cx| {
13785        editor.set_text("one\ntwo\nthree\n", window, cx)
13786    });
13787    assert!(cx.read(|cx| editor.is_dirty(cx)));
13788
13789    // Test that save still works when formatting hangs
13790    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
13791        move |params, _| async move {
13792            assert_eq!(
13793                params.text_document.uri,
13794                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13795            );
13796            futures::future::pending::<()>().await;
13797            unreachable!()
13798        },
13799    );
13800    let save = editor
13801        .update_in(cx, |editor, window, cx| {
13802            editor.save(
13803                SaveOptions {
13804                    format: true,
13805                    autosave: false,
13806                },
13807                project.clone(),
13808                window,
13809                cx,
13810            )
13811        })
13812        .unwrap();
13813    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
13814    save.await;
13815    assert_eq!(
13816        editor.update(cx, |editor, cx| editor.text(cx)),
13817        "one\ntwo\nthree\n"
13818    );
13819    assert!(!cx.read(|cx| editor.is_dirty(cx)));
13820}
13821
13822#[gpui::test]
13823async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
13824    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
13825
13826    // Buffer starts clean, no formatting should be requested
13827    let save = editor
13828        .update_in(cx, |editor, window, cx| {
13829            editor.save(
13830                SaveOptions {
13831                    format: false,
13832                    autosave: false,
13833                },
13834                project.clone(),
13835                window,
13836                cx,
13837            )
13838        })
13839        .unwrap();
13840    let _pending_format_request = fake_server
13841        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
13842            panic!("Should not be invoked");
13843        })
13844        .next();
13845    save.await;
13846    cx.run_until_parked();
13847}
13848
13849#[gpui::test]
13850async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
13851    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
13852
13853    // Set Rust language override and assert overridden tabsize is sent to language server
13854    update_test_language_settings(cx, &|settings| {
13855        settings.languages.0.insert(
13856            "Rust".into(),
13857            LanguageSettingsContent {
13858                tab_size: NonZeroU32::new(8),
13859                ..Default::default()
13860            },
13861        );
13862    });
13863
13864    editor.update_in(cx, |editor, window, cx| {
13865        editor.set_text("something_new\n", window, cx)
13866    });
13867    assert!(cx.read(|cx| editor.is_dirty(cx)));
13868    let save = editor
13869        .update_in(cx, |editor, window, cx| {
13870            editor.save(
13871                SaveOptions {
13872                    format: true,
13873                    autosave: false,
13874                },
13875                project.clone(),
13876                window,
13877                cx,
13878            )
13879        })
13880        .unwrap();
13881    fake_server
13882        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
13883            assert_eq!(
13884                params.text_document.uri,
13885                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13886            );
13887            assert_eq!(params.options.tab_size, 8);
13888            Ok(Some(Vec::new()))
13889        })
13890        .next()
13891        .await;
13892    save.await;
13893}
13894
13895#[gpui::test]
13896async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
13897    init_test(cx, |settings| {
13898        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
13899            settings::LanguageServerFormatterSpecifier::Current,
13900        )))
13901    });
13902
13903    let fs = FakeFs::new(cx.executor());
13904    fs.insert_file(path!("/file.rs"), Default::default()).await;
13905
13906    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13907
13908    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13909    language_registry.add(Arc::new(Language::new(
13910        LanguageConfig {
13911            name: "Rust".into(),
13912            matcher: LanguageMatcher {
13913                path_suffixes: vec!["rs".to_string()],
13914                ..Default::default()
13915            },
13916            ..LanguageConfig::default()
13917        },
13918        Some(tree_sitter_rust::LANGUAGE.into()),
13919    )));
13920    update_test_language_settings(cx, &|settings| {
13921        // Enable Prettier formatting for the same buffer, and ensure
13922        // LSP is called instead of Prettier.
13923        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
13924    });
13925    let mut fake_servers = language_registry.register_fake_lsp(
13926        "Rust",
13927        FakeLspAdapter {
13928            capabilities: lsp::ServerCapabilities {
13929                document_formatting_provider: Some(lsp::OneOf::Left(true)),
13930                ..Default::default()
13931            },
13932            ..Default::default()
13933        },
13934    );
13935
13936    let buffer = project
13937        .update(cx, |project, cx| {
13938            project.open_local_buffer(path!("/file.rs"), cx)
13939        })
13940        .await
13941        .unwrap();
13942
13943    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13944    let (editor, cx) = cx.add_window_view(|window, cx| {
13945        build_editor_with_project(project.clone(), buffer, window, cx)
13946    });
13947    editor.update_in(cx, |editor, window, cx| {
13948        editor.set_text("one\ntwo\nthree\n", window, cx)
13949    });
13950
13951    let fake_server = fake_servers.next().await.unwrap();
13952
13953    let format = editor
13954        .update_in(cx, |editor, window, cx| {
13955            editor.perform_format(
13956                project.clone(),
13957                FormatTrigger::Manual,
13958                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
13959                window,
13960                cx,
13961            )
13962        })
13963        .unwrap();
13964    fake_server
13965        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
13966            assert_eq!(
13967                params.text_document.uri,
13968                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13969            );
13970            assert_eq!(params.options.tab_size, 4);
13971            Ok(Some(vec![lsp::TextEdit::new(
13972                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
13973                ", ".to_string(),
13974            )]))
13975        })
13976        .next()
13977        .await;
13978    format.await;
13979    assert_eq!(
13980        editor.update(cx, |editor, cx| editor.text(cx)),
13981        "one, two\nthree\n"
13982    );
13983
13984    editor.update_in(cx, |editor, window, cx| {
13985        editor.set_text("one\ntwo\nthree\n", window, cx)
13986    });
13987    // Ensure we don't lock if formatting hangs.
13988    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
13989        move |params, _| async move {
13990            assert_eq!(
13991                params.text_document.uri,
13992                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
13993            );
13994            futures::future::pending::<()>().await;
13995            unreachable!()
13996        },
13997    );
13998    let format = editor
13999        .update_in(cx, |editor, window, cx| {
14000            editor.perform_format(
14001                project,
14002                FormatTrigger::Manual,
14003                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
14004                window,
14005                cx,
14006            )
14007        })
14008        .unwrap();
14009    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
14010    format.await;
14011    assert_eq!(
14012        editor.update(cx, |editor, cx| editor.text(cx)),
14013        "one\ntwo\nthree\n"
14014    );
14015}
14016
14017#[gpui::test]
14018async fn test_multiple_formatters(cx: &mut TestAppContext) {
14019    init_test(cx, |settings| {
14020        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
14021        settings.defaults.formatter = Some(FormatterList::Vec(vec![
14022            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
14023            Formatter::CodeAction("code-action-1".into()),
14024            Formatter::CodeAction("code-action-2".into()),
14025        ]))
14026    });
14027
14028    let fs = FakeFs::new(cx.executor());
14029    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
14030        .await;
14031
14032    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14033    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14034    language_registry.add(rust_lang());
14035
14036    let mut fake_servers = language_registry.register_fake_lsp(
14037        "Rust",
14038        FakeLspAdapter {
14039            capabilities: lsp::ServerCapabilities {
14040                document_formatting_provider: Some(lsp::OneOf::Left(true)),
14041                execute_command_provider: Some(lsp::ExecuteCommandOptions {
14042                    commands: vec!["the-command-for-code-action-1".into()],
14043                    ..Default::default()
14044                }),
14045                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14046                ..Default::default()
14047            },
14048            ..Default::default()
14049        },
14050    );
14051
14052    let buffer = project
14053        .update(cx, |project, cx| {
14054            project.open_local_buffer(path!("/file.rs"), cx)
14055        })
14056        .await
14057        .unwrap();
14058
14059    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14060    let (editor, cx) = cx.add_window_view(|window, cx| {
14061        build_editor_with_project(project.clone(), buffer, window, cx)
14062    });
14063
14064    let fake_server = fake_servers.next().await.unwrap();
14065    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
14066        move |_params, _| async move {
14067            Ok(Some(vec![lsp::TextEdit::new(
14068                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
14069                "applied-formatting\n".to_string(),
14070            )]))
14071        },
14072    );
14073    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14074        move |params, _| async move {
14075            let requested_code_actions = params.context.only.expect("Expected code action request");
14076            assert_eq!(requested_code_actions.len(), 1);
14077
14078            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
14079            let code_action = match requested_code_actions[0].as_str() {
14080                "code-action-1" => lsp::CodeAction {
14081                    kind: Some("code-action-1".into()),
14082                    edit: Some(lsp::WorkspaceEdit::new(
14083                        [(
14084                            uri,
14085                            vec![lsp::TextEdit::new(
14086                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
14087                                "applied-code-action-1-edit\n".to_string(),
14088                            )],
14089                        )]
14090                        .into_iter()
14091                        .collect(),
14092                    )),
14093                    command: Some(lsp::Command {
14094                        command: "the-command-for-code-action-1".into(),
14095                        ..Default::default()
14096                    }),
14097                    ..Default::default()
14098                },
14099                "code-action-2" => lsp::CodeAction {
14100                    kind: Some("code-action-2".into()),
14101                    edit: Some(lsp::WorkspaceEdit::new(
14102                        [(
14103                            uri,
14104                            vec![lsp::TextEdit::new(
14105                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
14106                                "applied-code-action-2-edit\n".to_string(),
14107                            )],
14108                        )]
14109                        .into_iter()
14110                        .collect(),
14111                    )),
14112                    ..Default::default()
14113                },
14114                req => panic!("Unexpected code action request: {:?}", req),
14115            };
14116            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14117                code_action,
14118            )]))
14119        },
14120    );
14121
14122    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
14123        move |params, _| async move { Ok(params) }
14124    });
14125
14126    let command_lock = Arc::new(futures::lock::Mutex::new(()));
14127    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
14128        let fake = fake_server.clone();
14129        let lock = command_lock.clone();
14130        move |params, _| {
14131            assert_eq!(params.command, "the-command-for-code-action-1");
14132            let fake = fake.clone();
14133            let lock = lock.clone();
14134            async move {
14135                lock.lock().await;
14136                fake.server
14137                    .request::<lsp::request::ApplyWorkspaceEdit>(
14138                        lsp::ApplyWorkspaceEditParams {
14139                            label: None,
14140                            edit: lsp::WorkspaceEdit {
14141                                changes: Some(
14142                                    [(
14143                                        lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
14144                                        vec![lsp::TextEdit {
14145                                            range: lsp::Range::new(
14146                                                lsp::Position::new(0, 0),
14147                                                lsp::Position::new(0, 0),
14148                                            ),
14149                                            new_text: "applied-code-action-1-command\n".into(),
14150                                        }],
14151                                    )]
14152                                    .into_iter()
14153                                    .collect(),
14154                                ),
14155                                ..Default::default()
14156                            },
14157                        },
14158                        DEFAULT_LSP_REQUEST_TIMEOUT,
14159                    )
14160                    .await
14161                    .into_response()
14162                    .unwrap();
14163                Ok(Some(json!(null)))
14164            }
14165        }
14166    });
14167
14168    editor
14169        .update_in(cx, |editor, window, cx| {
14170            editor.perform_format(
14171                project.clone(),
14172                FormatTrigger::Manual,
14173                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
14174                window,
14175                cx,
14176            )
14177        })
14178        .unwrap()
14179        .await;
14180    editor.update(cx, |editor, cx| {
14181        assert_eq!(
14182            editor.text(cx),
14183            r#"
14184                applied-code-action-2-edit
14185                applied-code-action-1-command
14186                applied-code-action-1-edit
14187                applied-formatting
14188                one
14189                two
14190                three
14191            "#
14192            .unindent()
14193        );
14194    });
14195
14196    editor.update_in(cx, |editor, window, cx| {
14197        editor.undo(&Default::default(), window, cx);
14198        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
14199    });
14200
14201    // Perform a manual edit while waiting for an LSP command
14202    // that's being run as part of a formatting code action.
14203    let lock_guard = command_lock.lock().await;
14204    let format = editor
14205        .update_in(cx, |editor, window, cx| {
14206            editor.perform_format(
14207                project.clone(),
14208                FormatTrigger::Manual,
14209                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
14210                window,
14211                cx,
14212            )
14213        })
14214        .unwrap();
14215    cx.run_until_parked();
14216    editor.update(cx, |editor, cx| {
14217        assert_eq!(
14218            editor.text(cx),
14219            r#"
14220                applied-code-action-1-edit
14221                applied-formatting
14222                one
14223                two
14224                three
14225            "#
14226            .unindent()
14227        );
14228
14229        editor.buffer.update(cx, |buffer, cx| {
14230            let ix = buffer.len(cx);
14231            buffer.edit([(ix..ix, "edited\n")], None, cx);
14232        });
14233    });
14234
14235    // Allow the LSP command to proceed. Because the buffer was edited,
14236    // the second code action will not be run.
14237    drop(lock_guard);
14238    format.await;
14239    editor.update_in(cx, |editor, window, cx| {
14240        assert_eq!(
14241            editor.text(cx),
14242            r#"
14243                applied-code-action-1-command
14244                applied-code-action-1-edit
14245                applied-formatting
14246                one
14247                two
14248                three
14249                edited
14250            "#
14251            .unindent()
14252        );
14253
14254        // The manual edit is undone first, because it is the last thing the user did
14255        // (even though the command completed afterwards).
14256        editor.undo(&Default::default(), window, cx);
14257        assert_eq!(
14258            editor.text(cx),
14259            r#"
14260                applied-code-action-1-command
14261                applied-code-action-1-edit
14262                applied-formatting
14263                one
14264                two
14265                three
14266            "#
14267            .unindent()
14268        );
14269
14270        // All the formatting (including the command, which completed after the manual edit)
14271        // is undone together.
14272        editor.undo(&Default::default(), window, cx);
14273        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
14274    });
14275}
14276
14277#[gpui::test]
14278async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
14279    init_test(cx, |settings| {
14280        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
14281            settings::LanguageServerFormatterSpecifier::Current,
14282        )]))
14283    });
14284
14285    let fs = FakeFs::new(cx.executor());
14286    fs.insert_file(path!("/file.ts"), Default::default()).await;
14287
14288    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
14289
14290    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14291    language_registry.add(Arc::new(Language::new(
14292        LanguageConfig {
14293            name: "TypeScript".into(),
14294            matcher: LanguageMatcher {
14295                path_suffixes: vec!["ts".to_string()],
14296                ..Default::default()
14297            },
14298            ..LanguageConfig::default()
14299        },
14300        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14301    )));
14302    update_test_language_settings(cx, &|settings| {
14303        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
14304    });
14305    let mut fake_servers = language_registry.register_fake_lsp(
14306        "TypeScript",
14307        FakeLspAdapter {
14308            capabilities: lsp::ServerCapabilities {
14309                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
14310                ..Default::default()
14311            },
14312            ..Default::default()
14313        },
14314    );
14315
14316    let buffer = project
14317        .update(cx, |project, cx| {
14318            project.open_local_buffer(path!("/file.ts"), cx)
14319        })
14320        .await
14321        .unwrap();
14322
14323    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14324    let (editor, cx) = cx.add_window_view(|window, cx| {
14325        build_editor_with_project(project.clone(), buffer, window, cx)
14326    });
14327    editor.update_in(cx, |editor, window, cx| {
14328        editor.set_text(
14329            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
14330            window,
14331            cx,
14332        )
14333    });
14334
14335    let fake_server = fake_servers.next().await.unwrap();
14336
14337    let format = editor
14338        .update_in(cx, |editor, window, cx| {
14339            editor.perform_code_action_kind(
14340                project.clone(),
14341                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14342                window,
14343                cx,
14344            )
14345        })
14346        .unwrap();
14347    fake_server
14348        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
14349            assert_eq!(
14350                params.text_document.uri,
14351                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
14352            );
14353            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
14354                lsp::CodeAction {
14355                    title: "Organize Imports".to_string(),
14356                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
14357                    edit: Some(lsp::WorkspaceEdit {
14358                        changes: Some(
14359                            [(
14360                                params.text_document.uri.clone(),
14361                                vec![lsp::TextEdit::new(
14362                                    lsp::Range::new(
14363                                        lsp::Position::new(1, 0),
14364                                        lsp::Position::new(2, 0),
14365                                    ),
14366                                    "".to_string(),
14367                                )],
14368                            )]
14369                            .into_iter()
14370                            .collect(),
14371                        ),
14372                        ..Default::default()
14373                    }),
14374                    ..Default::default()
14375                },
14376            )]))
14377        })
14378        .next()
14379        .await;
14380    format.await;
14381    assert_eq!(
14382        editor.update(cx, |editor, cx| editor.text(cx)),
14383        "import { a } from 'module';\n\nconst x = a;\n"
14384    );
14385
14386    editor.update_in(cx, |editor, window, cx| {
14387        editor.set_text(
14388            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
14389            window,
14390            cx,
14391        )
14392    });
14393    // Ensure we don't lock if code action hangs.
14394    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
14395        move |params, _| async move {
14396            assert_eq!(
14397                params.text_document.uri,
14398                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
14399            );
14400            futures::future::pending::<()>().await;
14401            unreachable!()
14402        },
14403    );
14404    let format = editor
14405        .update_in(cx, |editor, window, cx| {
14406            editor.perform_code_action_kind(
14407                project,
14408                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
14409                window,
14410                cx,
14411            )
14412        })
14413        .unwrap();
14414    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
14415    format.await;
14416    assert_eq!(
14417        editor.update(cx, |editor, cx| editor.text(cx)),
14418        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
14419    );
14420}
14421
14422#[gpui::test]
14423async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
14424    init_test(cx, |_| {});
14425
14426    let mut cx = EditorLspTestContext::new_rust(
14427        lsp::ServerCapabilities {
14428            document_formatting_provider: Some(lsp::OneOf::Left(true)),
14429            ..Default::default()
14430        },
14431        cx,
14432    )
14433    .await;
14434
14435    cx.set_state(indoc! {"
14436        one.twoˇ
14437    "});
14438
14439    // The format request takes a long time. When it completes, it inserts
14440    // a newline and an indent before the `.`
14441    cx.lsp
14442        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
14443            let executor = cx.background_executor().clone();
14444            async move {
14445                executor.timer(Duration::from_millis(100)).await;
14446                Ok(Some(vec![lsp::TextEdit {
14447                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
14448                    new_text: "\n    ".into(),
14449                }]))
14450            }
14451        });
14452
14453    // Submit a format request.
14454    let format_1 = cx
14455        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
14456        .unwrap();
14457    cx.executor().run_until_parked();
14458
14459    // Submit a second format request.
14460    let format_2 = cx
14461        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
14462        .unwrap();
14463    cx.executor().run_until_parked();
14464
14465    // Wait for both format requests to complete
14466    cx.executor().advance_clock(Duration::from_millis(200));
14467    format_1.await.unwrap();
14468    format_2.await.unwrap();
14469
14470    // The formatting edits only happens once.
14471    cx.assert_editor_state(indoc! {"
14472        one
14473            .twoˇ
14474    "});
14475}
14476
14477#[gpui::test]
14478async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
14479    init_test(cx, |settings| {
14480        settings.defaults.formatter = Some(FormatterList::default())
14481    });
14482
14483    let mut cx = EditorLspTestContext::new_rust(
14484        lsp::ServerCapabilities {
14485            document_formatting_provider: Some(lsp::OneOf::Left(true)),
14486            ..Default::default()
14487        },
14488        cx,
14489    )
14490    .await;
14491
14492    // Record which buffer changes have been sent to the language server
14493    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
14494    cx.lsp
14495        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
14496            let buffer_changes = buffer_changes.clone();
14497            move |params, _| {
14498                buffer_changes.lock().extend(
14499                    params
14500                        .content_changes
14501                        .into_iter()
14502                        .map(|e| (e.range.unwrap(), e.text)),
14503                );
14504            }
14505        });
14506    // Handle formatting requests to the language server.
14507    cx.lsp
14508        .set_request_handler::<lsp::request::Formatting, _, _>({
14509            move |_, _| {
14510                // Insert blank lines between each line of the buffer.
14511                async move {
14512                    // TODO: this assertion is not reliably true. Currently nothing guarantees that we deliver
14513                    // DidChangedTextDocument to the LSP before sending the formatting request.
14514                    // assert_eq!(
14515                    //     &buffer_changes.lock()[1..],
14516                    //     &[
14517                    //         (
14518                    //             lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
14519                    //             "".into()
14520                    //         ),
14521                    //         (
14522                    //             lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
14523                    //             "".into()
14524                    //         ),
14525                    //         (
14526                    //             lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
14527                    //             "\n".into()
14528                    //         ),
14529                    //     ]
14530                    // );
14531
14532                    Ok(Some(vec![
14533                        lsp::TextEdit {
14534                            range: lsp::Range::new(
14535                                lsp::Position::new(1, 0),
14536                                lsp::Position::new(1, 0),
14537                            ),
14538                            new_text: "\n".into(),
14539                        },
14540                        lsp::TextEdit {
14541                            range: lsp::Range::new(
14542                                lsp::Position::new(2, 0),
14543                                lsp::Position::new(2, 0),
14544                            ),
14545                            new_text: "\n".into(),
14546                        },
14547                    ]))
14548                }
14549            }
14550        });
14551
14552    // Set up a buffer white some trailing whitespace and no trailing newline.
14553    cx.set_state(
14554        &[
14555            "one ",   //
14556            "twoˇ",   //
14557            "three ", //
14558            "four",   //
14559        ]
14560        .join("\n"),
14561    );
14562
14563    // Submit a format request.
14564    let format = cx
14565        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
14566        .unwrap();
14567
14568    cx.run_until_parked();
14569    // After formatting the buffer, the trailing whitespace is stripped,
14570    // a newline is appended, and the edits provided by the language server
14571    // have been applied.
14572    format.await.unwrap();
14573
14574    cx.assert_editor_state(
14575        &[
14576            "one",   //
14577            "",      //
14578            "twoˇ",  //
14579            "",      //
14580            "three", //
14581            "four",  //
14582            "",      //
14583        ]
14584        .join("\n"),
14585    );
14586
14587    // Undoing the formatting undoes the trailing whitespace removal, the
14588    // trailing newline, and the LSP edits.
14589    cx.update_buffer(|buffer, cx| buffer.undo(cx));
14590    cx.assert_editor_state(
14591        &[
14592            "one ",   //
14593            "twoˇ",   //
14594            "three ", //
14595            "four",   //
14596        ]
14597        .join("\n"),
14598    );
14599}
14600
14601#[gpui::test]
14602async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
14603    cx: &mut TestAppContext,
14604) {
14605    init_test(cx, |_| {});
14606
14607    cx.update(|cx| {
14608        cx.update_global::<SettingsStore, _>(|settings, cx| {
14609            settings.update_user_settings(cx, |settings| {
14610                settings.editor.auto_signature_help = Some(true);
14611                settings.editor.hover_popover_delay = Some(DelayMs(300));
14612            });
14613        });
14614    });
14615
14616    let mut cx = EditorLspTestContext::new_rust(
14617        lsp::ServerCapabilities {
14618            signature_help_provider: Some(lsp::SignatureHelpOptions {
14619                ..Default::default()
14620            }),
14621            ..Default::default()
14622        },
14623        cx,
14624    )
14625    .await;
14626
14627    let language = Language::new(
14628        LanguageConfig {
14629            name: "Rust".into(),
14630            brackets: BracketPairConfig {
14631                pairs: vec![
14632                    BracketPair {
14633                        start: "{".to_string(),
14634                        end: "}".to_string(),
14635                        close: true,
14636                        surround: true,
14637                        newline: true,
14638                    },
14639                    BracketPair {
14640                        start: "(".to_string(),
14641                        end: ")".to_string(),
14642                        close: true,
14643                        surround: true,
14644                        newline: true,
14645                    },
14646                    BracketPair {
14647                        start: "/*".to_string(),
14648                        end: " */".to_string(),
14649                        close: true,
14650                        surround: true,
14651                        newline: true,
14652                    },
14653                    BracketPair {
14654                        start: "[".to_string(),
14655                        end: "]".to_string(),
14656                        close: false,
14657                        surround: false,
14658                        newline: true,
14659                    },
14660                    BracketPair {
14661                        start: "\"".to_string(),
14662                        end: "\"".to_string(),
14663                        close: true,
14664                        surround: true,
14665                        newline: false,
14666                    },
14667                    BracketPair {
14668                        start: "<".to_string(),
14669                        end: ">".to_string(),
14670                        close: false,
14671                        surround: true,
14672                        newline: true,
14673                    },
14674                ],
14675                ..Default::default()
14676            },
14677            autoclose_before: "})]".to_string(),
14678            ..Default::default()
14679        },
14680        Some(tree_sitter_rust::LANGUAGE.into()),
14681    );
14682    let language = Arc::new(language);
14683
14684    cx.language_registry().add(language.clone());
14685    cx.update_buffer(|buffer, cx| {
14686        buffer.set_language(Some(language), cx);
14687    });
14688
14689    cx.set_state(
14690        &r#"
14691            fn main() {
14692                sampleˇ
14693            }
14694        "#
14695        .unindent(),
14696    );
14697
14698    cx.update_editor(|editor, window, cx| {
14699        editor.handle_input("(", window, cx);
14700    });
14701    cx.assert_editor_state(
14702        &"
14703            fn main() {
14704                sample(ˇ)
14705            }
14706        "
14707        .unindent(),
14708    );
14709
14710    let mocked_response = lsp::SignatureHelp {
14711        signatures: vec![lsp::SignatureInformation {
14712            label: "fn sample(param1: u8, param2: u8)".to_string(),
14713            documentation: None,
14714            parameters: Some(vec![
14715                lsp::ParameterInformation {
14716                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14717                    documentation: None,
14718                },
14719                lsp::ParameterInformation {
14720                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
14721                    documentation: None,
14722                },
14723            ]),
14724            active_parameter: None,
14725        }],
14726        active_signature: Some(0),
14727        active_parameter: Some(0),
14728    };
14729    handle_signature_help_request(&mut cx, mocked_response).await;
14730
14731    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14732        .await;
14733
14734    cx.editor(|editor, _, _| {
14735        let signature_help_state = editor.signature_help_state.popover().cloned();
14736        let signature = signature_help_state.unwrap();
14737        assert_eq!(
14738            signature.signatures[signature.current_signature].label,
14739            "fn sample(param1: u8, param2: u8)"
14740        );
14741    });
14742}
14743
14744#[gpui::test]
14745async fn test_signature_help_delay_only_for_auto(cx: &mut TestAppContext) {
14746    init_test(cx, |_| {});
14747
14748    let delay_ms = 500;
14749    cx.update(|cx| {
14750        cx.update_global::<SettingsStore, _>(|settings, cx| {
14751            settings.update_user_settings(cx, |settings| {
14752                settings.editor.auto_signature_help = Some(true);
14753                settings.editor.show_signature_help_after_edits = Some(false);
14754                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
14755            });
14756        });
14757    });
14758
14759    let mut cx = EditorLspTestContext::new_rust(
14760        lsp::ServerCapabilities {
14761            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14762            ..lsp::ServerCapabilities::default()
14763        },
14764        cx,
14765    )
14766    .await;
14767
14768    let mocked_response = lsp::SignatureHelp {
14769        signatures: vec![lsp::SignatureInformation {
14770            label: "fn sample(param1: u8)".to_string(),
14771            documentation: None,
14772            parameters: Some(vec![lsp::ParameterInformation {
14773                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14774                documentation: None,
14775            }]),
14776            active_parameter: None,
14777        }],
14778        active_signature: Some(0),
14779        active_parameter: Some(0),
14780    };
14781
14782    cx.set_state(indoc! {"
14783        fn main() {
14784            sample(ˇ);
14785        }
14786
14787        fn sample(param1: u8) {}
14788    "});
14789
14790    // Manual trigger should show immediately without delay
14791    cx.update_editor(|editor, window, cx| {
14792        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14793    });
14794    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14795    cx.run_until_parked();
14796    cx.editor(|editor, _, _| {
14797        assert!(
14798            editor.signature_help_state.is_shown(),
14799            "Manual trigger should show signature help without delay"
14800        );
14801    });
14802
14803    cx.update_editor(|editor, _, cx| {
14804        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
14805    });
14806    cx.run_until_parked();
14807    cx.editor(|editor, _, _| {
14808        assert!(!editor.signature_help_state.is_shown());
14809    });
14810
14811    // Auto trigger (cursor movement into brackets) should respect delay
14812    cx.set_state(indoc! {"
14813        fn main() {
14814            sampleˇ();
14815        }
14816
14817        fn sample(param1: u8) {}
14818    "});
14819    cx.update_editor(|editor, window, cx| {
14820        editor.move_right(&MoveRight, window, cx);
14821    });
14822    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
14823    cx.run_until_parked();
14824    cx.editor(|editor, _, _| {
14825        assert!(
14826            !editor.signature_help_state.is_shown(),
14827            "Auto trigger should wait for delay before showing signature help"
14828        );
14829    });
14830
14831    cx.executor()
14832        .advance_clock(Duration::from_millis(delay_ms + 50));
14833    cx.run_until_parked();
14834    cx.editor(|editor, _, _| {
14835        assert!(
14836            editor.signature_help_state.is_shown(),
14837            "Auto trigger should show signature help after delay elapsed"
14838        );
14839    });
14840}
14841
14842#[gpui::test]
14843async fn test_signature_help_after_edits_no_delay(cx: &mut TestAppContext) {
14844    init_test(cx, |_| {});
14845
14846    let delay_ms = 500;
14847    cx.update(|cx| {
14848        cx.update_global::<SettingsStore, _>(|settings, cx| {
14849            settings.update_user_settings(cx, |settings| {
14850                settings.editor.auto_signature_help = Some(false);
14851                settings.editor.show_signature_help_after_edits = Some(true);
14852                settings.editor.hover_popover_delay = Some(DelayMs(delay_ms));
14853            });
14854        });
14855    });
14856
14857    let mut cx = EditorLspTestContext::new_rust(
14858        lsp::ServerCapabilities {
14859            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14860            ..lsp::ServerCapabilities::default()
14861        },
14862        cx,
14863    )
14864    .await;
14865
14866    let language = Arc::new(Language::new(
14867        LanguageConfig {
14868            name: "Rust".into(),
14869            brackets: BracketPairConfig {
14870                pairs: vec![BracketPair {
14871                    start: "(".to_string(),
14872                    end: ")".to_string(),
14873                    close: true,
14874                    surround: true,
14875                    newline: true,
14876                }],
14877                ..BracketPairConfig::default()
14878            },
14879            autoclose_before: "})".to_string(),
14880            ..LanguageConfig::default()
14881        },
14882        Some(tree_sitter_rust::LANGUAGE.into()),
14883    ));
14884    cx.language_registry().add(language.clone());
14885    cx.update_buffer(|buffer, cx| {
14886        buffer.set_language(Some(language), cx);
14887    });
14888
14889    let mocked_response = lsp::SignatureHelp {
14890        signatures: vec![lsp::SignatureInformation {
14891            label: "fn sample(param1: u8)".to_string(),
14892            documentation: None,
14893            parameters: Some(vec![lsp::ParameterInformation {
14894                label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
14895                documentation: None,
14896            }]),
14897            active_parameter: None,
14898        }],
14899        active_signature: Some(0),
14900        active_parameter: Some(0),
14901    };
14902
14903    cx.set_state(indoc! {"
14904        fn main() {
14905            sampleˇ
14906        }
14907    "});
14908
14909    // Typing bracket should show signature help immediately without delay
14910    cx.update_editor(|editor, window, cx| {
14911        editor.handle_input("(", window, cx);
14912    });
14913    handle_signature_help_request(&mut cx, mocked_response).await;
14914    cx.run_until_parked();
14915    cx.editor(|editor, _, _| {
14916        assert!(
14917            editor.signature_help_state.is_shown(),
14918            "show_signature_help_after_edits should show signature help without delay"
14919        );
14920    });
14921}
14922
14923#[gpui::test]
14924async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
14925    init_test(cx, |_| {});
14926
14927    cx.update(|cx| {
14928        cx.update_global::<SettingsStore, _>(|settings, cx| {
14929            settings.update_user_settings(cx, |settings| {
14930                settings.editor.auto_signature_help = Some(false);
14931                settings.editor.show_signature_help_after_edits = Some(false);
14932            });
14933        });
14934    });
14935
14936    let mut cx = EditorLspTestContext::new_rust(
14937        lsp::ServerCapabilities {
14938            signature_help_provider: Some(lsp::SignatureHelpOptions {
14939                ..Default::default()
14940            }),
14941            ..Default::default()
14942        },
14943        cx,
14944    )
14945    .await;
14946
14947    let language = Language::new(
14948        LanguageConfig {
14949            name: "Rust".into(),
14950            brackets: BracketPairConfig {
14951                pairs: vec![
14952                    BracketPair {
14953                        start: "{".to_string(),
14954                        end: "}".to_string(),
14955                        close: true,
14956                        surround: true,
14957                        newline: true,
14958                    },
14959                    BracketPair {
14960                        start: "(".to_string(),
14961                        end: ")".to_string(),
14962                        close: true,
14963                        surround: true,
14964                        newline: true,
14965                    },
14966                    BracketPair {
14967                        start: "/*".to_string(),
14968                        end: " */".to_string(),
14969                        close: true,
14970                        surround: true,
14971                        newline: true,
14972                    },
14973                    BracketPair {
14974                        start: "[".to_string(),
14975                        end: "]".to_string(),
14976                        close: false,
14977                        surround: false,
14978                        newline: true,
14979                    },
14980                    BracketPair {
14981                        start: "\"".to_string(),
14982                        end: "\"".to_string(),
14983                        close: true,
14984                        surround: true,
14985                        newline: false,
14986                    },
14987                    BracketPair {
14988                        start: "<".to_string(),
14989                        end: ">".to_string(),
14990                        close: false,
14991                        surround: true,
14992                        newline: true,
14993                    },
14994                ],
14995                ..Default::default()
14996            },
14997            autoclose_before: "})]".to_string(),
14998            ..Default::default()
14999        },
15000        Some(tree_sitter_rust::LANGUAGE.into()),
15001    );
15002    let language = Arc::new(language);
15003
15004    cx.language_registry().add(language.clone());
15005    cx.update_buffer(|buffer, cx| {
15006        buffer.set_language(Some(language), cx);
15007    });
15008
15009    // Ensure that signature_help is not called when no signature help is enabled.
15010    cx.set_state(
15011        &r#"
15012            fn main() {
15013                sampleˇ
15014            }
15015        "#
15016        .unindent(),
15017    );
15018    cx.update_editor(|editor, window, cx| {
15019        editor.handle_input("(", window, cx);
15020    });
15021    cx.assert_editor_state(
15022        &"
15023            fn main() {
15024                sample(ˇ)
15025            }
15026        "
15027        .unindent(),
15028    );
15029    cx.editor(|editor, _, _| {
15030        assert!(editor.signature_help_state.task().is_none());
15031    });
15032
15033    let mocked_response = lsp::SignatureHelp {
15034        signatures: vec![lsp::SignatureInformation {
15035            label: "fn sample(param1: u8, param2: u8)".to_string(),
15036            documentation: None,
15037            parameters: Some(vec![
15038                lsp::ParameterInformation {
15039                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15040                    documentation: None,
15041                },
15042                lsp::ParameterInformation {
15043                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15044                    documentation: None,
15045                },
15046            ]),
15047            active_parameter: None,
15048        }],
15049        active_signature: Some(0),
15050        active_parameter: Some(0),
15051    };
15052
15053    // Ensure that signature_help is called when enabled afte edits
15054    cx.update(|_, cx| {
15055        cx.update_global::<SettingsStore, _>(|settings, cx| {
15056            settings.update_user_settings(cx, |settings| {
15057                settings.editor.auto_signature_help = Some(false);
15058                settings.editor.show_signature_help_after_edits = Some(true);
15059            });
15060        });
15061    });
15062    cx.set_state(
15063        &r#"
15064            fn main() {
15065                sampleˇ
15066            }
15067        "#
15068        .unindent(),
15069    );
15070    cx.update_editor(|editor, window, cx| {
15071        editor.handle_input("(", window, cx);
15072    });
15073    cx.assert_editor_state(
15074        &"
15075            fn main() {
15076                sample(ˇ)
15077            }
15078        "
15079        .unindent(),
15080    );
15081    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15082    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15083        .await;
15084    cx.update_editor(|editor, _, _| {
15085        let signature_help_state = editor.signature_help_state.popover().cloned();
15086        assert!(signature_help_state.is_some());
15087        let signature = signature_help_state.unwrap();
15088        assert_eq!(
15089            signature.signatures[signature.current_signature].label,
15090            "fn sample(param1: u8, param2: u8)"
15091        );
15092        editor.signature_help_state = SignatureHelpState::default();
15093    });
15094
15095    // Ensure that signature_help is called when auto signature help override is enabled
15096    cx.update(|_, cx| {
15097        cx.update_global::<SettingsStore, _>(|settings, cx| {
15098            settings.update_user_settings(cx, |settings| {
15099                settings.editor.auto_signature_help = Some(true);
15100                settings.editor.show_signature_help_after_edits = Some(false);
15101            });
15102        });
15103    });
15104    cx.set_state(
15105        &r#"
15106            fn main() {
15107                sampleˇ
15108            }
15109        "#
15110        .unindent(),
15111    );
15112    cx.update_editor(|editor, window, cx| {
15113        editor.handle_input("(", window, cx);
15114    });
15115    cx.assert_editor_state(
15116        &"
15117            fn main() {
15118                sample(ˇ)
15119            }
15120        "
15121        .unindent(),
15122    );
15123    handle_signature_help_request(&mut cx, mocked_response).await;
15124    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15125        .await;
15126    cx.editor(|editor, _, _| {
15127        let signature_help_state = editor.signature_help_state.popover().cloned();
15128        assert!(signature_help_state.is_some());
15129        let signature = signature_help_state.unwrap();
15130        assert_eq!(
15131            signature.signatures[signature.current_signature].label,
15132            "fn sample(param1: u8, param2: u8)"
15133        );
15134    });
15135}
15136
15137#[gpui::test]
15138async fn test_signature_help(cx: &mut TestAppContext) {
15139    init_test(cx, |_| {});
15140    cx.update(|cx| {
15141        cx.update_global::<SettingsStore, _>(|settings, cx| {
15142            settings.update_user_settings(cx, |settings| {
15143                settings.editor.auto_signature_help = Some(true);
15144            });
15145        });
15146    });
15147
15148    let mut cx = EditorLspTestContext::new_rust(
15149        lsp::ServerCapabilities {
15150            signature_help_provider: Some(lsp::SignatureHelpOptions {
15151                ..Default::default()
15152            }),
15153            ..Default::default()
15154        },
15155        cx,
15156    )
15157    .await;
15158
15159    // A test that directly calls `show_signature_help`
15160    cx.update_editor(|editor, window, cx| {
15161        editor.show_signature_help(&ShowSignatureHelp, window, cx);
15162    });
15163
15164    let mocked_response = lsp::SignatureHelp {
15165        signatures: vec![lsp::SignatureInformation {
15166            label: "fn sample(param1: u8, param2: u8)".to_string(),
15167            documentation: None,
15168            parameters: Some(vec![
15169                lsp::ParameterInformation {
15170                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15171                    documentation: None,
15172                },
15173                lsp::ParameterInformation {
15174                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15175                    documentation: None,
15176                },
15177            ]),
15178            active_parameter: None,
15179        }],
15180        active_signature: Some(0),
15181        active_parameter: Some(0),
15182    };
15183    handle_signature_help_request(&mut cx, mocked_response).await;
15184
15185    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15186        .await;
15187
15188    cx.editor(|editor, _, _| {
15189        let signature_help_state = editor.signature_help_state.popover().cloned();
15190        assert!(signature_help_state.is_some());
15191        let signature = signature_help_state.unwrap();
15192        assert_eq!(
15193            signature.signatures[signature.current_signature].label,
15194            "fn sample(param1: u8, param2: u8)"
15195        );
15196    });
15197
15198    // When exiting outside from inside the brackets, `signature_help` is closed.
15199    cx.set_state(indoc! {"
15200        fn main() {
15201            sample(ˇ);
15202        }
15203
15204        fn sample(param1: u8, param2: u8) {}
15205    "});
15206
15207    cx.update_editor(|editor, window, cx| {
15208        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15209            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
15210        });
15211    });
15212
15213    let mocked_response = lsp::SignatureHelp {
15214        signatures: Vec::new(),
15215        active_signature: None,
15216        active_parameter: None,
15217    };
15218    handle_signature_help_request(&mut cx, mocked_response).await;
15219
15220    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
15221        .await;
15222
15223    cx.editor(|editor, _, _| {
15224        assert!(!editor.signature_help_state.is_shown());
15225    });
15226
15227    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
15228    cx.set_state(indoc! {"
15229        fn main() {
15230            sample(ˇ);
15231        }
15232
15233        fn sample(param1: u8, param2: u8) {}
15234    "});
15235
15236    let mocked_response = lsp::SignatureHelp {
15237        signatures: vec![lsp::SignatureInformation {
15238            label: "fn sample(param1: u8, param2: u8)".to_string(),
15239            documentation: None,
15240            parameters: Some(vec![
15241                lsp::ParameterInformation {
15242                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15243                    documentation: None,
15244                },
15245                lsp::ParameterInformation {
15246                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15247                    documentation: None,
15248                },
15249            ]),
15250            active_parameter: None,
15251        }],
15252        active_signature: Some(0),
15253        active_parameter: Some(0),
15254    };
15255    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15256    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15257        .await;
15258    cx.editor(|editor, _, _| {
15259        assert!(editor.signature_help_state.is_shown());
15260    });
15261
15262    // Restore the popover with more parameter input
15263    cx.set_state(indoc! {"
15264        fn main() {
15265            sample(param1, param2ˇ);
15266        }
15267
15268        fn sample(param1: u8, param2: u8) {}
15269    "});
15270
15271    let mocked_response = lsp::SignatureHelp {
15272        signatures: vec![lsp::SignatureInformation {
15273            label: "fn sample(param1: u8, param2: u8)".to_string(),
15274            documentation: None,
15275            parameters: Some(vec![
15276                lsp::ParameterInformation {
15277                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15278                    documentation: None,
15279                },
15280                lsp::ParameterInformation {
15281                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15282                    documentation: None,
15283                },
15284            ]),
15285            active_parameter: None,
15286        }],
15287        active_signature: Some(0),
15288        active_parameter: Some(1),
15289    };
15290    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15291    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15292        .await;
15293
15294    // When selecting a range, the popover is gone.
15295    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
15296    cx.update_editor(|editor, window, cx| {
15297        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15298            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
15299        })
15300    });
15301    cx.assert_editor_state(indoc! {"
15302        fn main() {
15303            sample(param1, «ˇparam2»);
15304        }
15305
15306        fn sample(param1: u8, param2: u8) {}
15307    "});
15308    cx.editor(|editor, _, _| {
15309        assert!(!editor.signature_help_state.is_shown());
15310    });
15311
15312    // When unselecting again, the popover is back if within the brackets.
15313    cx.update_editor(|editor, window, cx| {
15314        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15315            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
15316        })
15317    });
15318    cx.assert_editor_state(indoc! {"
15319        fn main() {
15320            sample(param1, ˇparam2);
15321        }
15322
15323        fn sample(param1: u8, param2: u8) {}
15324    "});
15325    handle_signature_help_request(&mut cx, mocked_response).await;
15326    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15327        .await;
15328    cx.editor(|editor, _, _| {
15329        assert!(editor.signature_help_state.is_shown());
15330    });
15331
15332    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
15333    cx.update_editor(|editor, window, cx| {
15334        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15335            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
15336            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
15337        })
15338    });
15339    cx.assert_editor_state(indoc! {"
15340        fn main() {
15341            sample(param1, ˇparam2);
15342        }
15343
15344        fn sample(param1: u8, param2: u8) {}
15345    "});
15346
15347    let mocked_response = lsp::SignatureHelp {
15348        signatures: vec![lsp::SignatureInformation {
15349            label: "fn sample(param1: u8, param2: u8)".to_string(),
15350            documentation: None,
15351            parameters: Some(vec![
15352                lsp::ParameterInformation {
15353                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
15354                    documentation: None,
15355                },
15356                lsp::ParameterInformation {
15357                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
15358                    documentation: None,
15359                },
15360            ]),
15361            active_parameter: None,
15362        }],
15363        active_signature: Some(0),
15364        active_parameter: Some(1),
15365    };
15366    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
15367    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15368        .await;
15369    cx.update_editor(|editor, _, cx| {
15370        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
15371    });
15372    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
15373        .await;
15374    cx.update_editor(|editor, window, cx| {
15375        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15376            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
15377        })
15378    });
15379    cx.assert_editor_state(indoc! {"
15380        fn main() {
15381            sample(param1, «ˇparam2»);
15382        }
15383
15384        fn sample(param1: u8, param2: u8) {}
15385    "});
15386    cx.update_editor(|editor, window, cx| {
15387        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15388            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
15389        })
15390    });
15391    cx.assert_editor_state(indoc! {"
15392        fn main() {
15393            sample(param1, ˇparam2);
15394        }
15395
15396        fn sample(param1: u8, param2: u8) {}
15397    "});
15398    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
15399        .await;
15400}
15401
15402#[gpui::test]
15403async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
15404    init_test(cx, |_| {});
15405
15406    let mut cx = EditorLspTestContext::new_rust(
15407        lsp::ServerCapabilities {
15408            signature_help_provider: Some(lsp::SignatureHelpOptions {
15409                ..Default::default()
15410            }),
15411            ..Default::default()
15412        },
15413        cx,
15414    )
15415    .await;
15416
15417    cx.set_state(indoc! {"
15418        fn main() {
15419            overloadedˇ
15420        }
15421    "});
15422
15423    cx.update_editor(|editor, window, cx| {
15424        editor.handle_input("(", window, cx);
15425        editor.show_signature_help(&ShowSignatureHelp, window, cx);
15426    });
15427
15428    // Mock response with 3 signatures
15429    let mocked_response = lsp::SignatureHelp {
15430        signatures: vec![
15431            lsp::SignatureInformation {
15432                label: "fn overloaded(x: i32)".to_string(),
15433                documentation: None,
15434                parameters: Some(vec![lsp::ParameterInformation {
15435                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
15436                    documentation: None,
15437                }]),
15438                active_parameter: None,
15439            },
15440            lsp::SignatureInformation {
15441                label: "fn overloaded(x: i32, y: i32)".to_string(),
15442                documentation: None,
15443                parameters: Some(vec![
15444                    lsp::ParameterInformation {
15445                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
15446                        documentation: None,
15447                    },
15448                    lsp::ParameterInformation {
15449                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
15450                        documentation: None,
15451                    },
15452                ]),
15453                active_parameter: None,
15454            },
15455            lsp::SignatureInformation {
15456                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
15457                documentation: None,
15458                parameters: Some(vec![
15459                    lsp::ParameterInformation {
15460                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
15461                        documentation: None,
15462                    },
15463                    lsp::ParameterInformation {
15464                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
15465                        documentation: None,
15466                    },
15467                    lsp::ParameterInformation {
15468                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
15469                        documentation: None,
15470                    },
15471                ]),
15472                active_parameter: None,
15473            },
15474        ],
15475        active_signature: Some(1),
15476        active_parameter: Some(0),
15477    };
15478    handle_signature_help_request(&mut cx, mocked_response).await;
15479
15480    cx.condition(|editor, _| editor.signature_help_state.is_shown())
15481        .await;
15482
15483    // Verify we have multiple signatures and the right one is selected
15484    cx.editor(|editor, _, _| {
15485        let popover = editor.signature_help_state.popover().cloned().unwrap();
15486        assert_eq!(popover.signatures.len(), 3);
15487        // active_signature was 1, so that should be the current
15488        assert_eq!(popover.current_signature, 1);
15489        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
15490        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
15491        assert_eq!(
15492            popover.signatures[2].label,
15493            "fn overloaded(x: i32, y: i32, z: i32)"
15494        );
15495    });
15496
15497    // Test navigation functionality
15498    cx.update_editor(|editor, window, cx| {
15499        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
15500    });
15501
15502    cx.editor(|editor, _, _| {
15503        let popover = editor.signature_help_state.popover().cloned().unwrap();
15504        assert_eq!(popover.current_signature, 2);
15505    });
15506
15507    // Test wrap around
15508    cx.update_editor(|editor, window, cx| {
15509        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
15510    });
15511
15512    cx.editor(|editor, _, _| {
15513        let popover = editor.signature_help_state.popover().cloned().unwrap();
15514        assert_eq!(popover.current_signature, 0);
15515    });
15516
15517    // Test previous navigation
15518    cx.update_editor(|editor, window, cx| {
15519        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
15520    });
15521
15522    cx.editor(|editor, _, _| {
15523        let popover = editor.signature_help_state.popover().cloned().unwrap();
15524        assert_eq!(popover.current_signature, 2);
15525    });
15526}
15527
15528#[gpui::test]
15529async fn test_completion_mode(cx: &mut TestAppContext) {
15530    init_test(cx, |_| {});
15531    let mut cx = EditorLspTestContext::new_rust(
15532        lsp::ServerCapabilities {
15533            completion_provider: Some(lsp::CompletionOptions {
15534                resolve_provider: Some(true),
15535                ..Default::default()
15536            }),
15537            ..Default::default()
15538        },
15539        cx,
15540    )
15541    .await;
15542
15543    struct Run {
15544        run_description: &'static str,
15545        initial_state: String,
15546        buffer_marked_text: String,
15547        completion_label: &'static str,
15548        completion_text: &'static str,
15549        expected_with_insert_mode: String,
15550        expected_with_replace_mode: String,
15551        expected_with_replace_subsequence_mode: String,
15552        expected_with_replace_suffix_mode: String,
15553    }
15554
15555    let runs = [
15556        Run {
15557            run_description: "Start of word matches completion text",
15558            initial_state: "before ediˇ after".into(),
15559            buffer_marked_text: "before <edi|> after".into(),
15560            completion_label: "editor",
15561            completion_text: "editor",
15562            expected_with_insert_mode: "before editorˇ after".into(),
15563            expected_with_replace_mode: "before editorˇ after".into(),
15564            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
15565            expected_with_replace_suffix_mode: "before editorˇ after".into(),
15566        },
15567        Run {
15568            run_description: "Accept same text at the middle of the word",
15569            initial_state: "before ediˇtor after".into(),
15570            buffer_marked_text: "before <edi|tor> after".into(),
15571            completion_label: "editor",
15572            completion_text: "editor",
15573            expected_with_insert_mode: "before editorˇtor after".into(),
15574            expected_with_replace_mode: "before editorˇ after".into(),
15575            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
15576            expected_with_replace_suffix_mode: "before editorˇ after".into(),
15577        },
15578        Run {
15579            run_description: "End of word matches completion text -- cursor at end",
15580            initial_state: "before torˇ after".into(),
15581            buffer_marked_text: "before <tor|> after".into(),
15582            completion_label: "editor",
15583            completion_text: "editor",
15584            expected_with_insert_mode: "before editorˇ after".into(),
15585            expected_with_replace_mode: "before editorˇ after".into(),
15586            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
15587            expected_with_replace_suffix_mode: "before editorˇ after".into(),
15588        },
15589        Run {
15590            run_description: "End of word matches completion text -- cursor at start",
15591            initial_state: "before ˇtor after".into(),
15592            buffer_marked_text: "before <|tor> after".into(),
15593            completion_label: "editor",
15594            completion_text: "editor",
15595            expected_with_insert_mode: "before editorˇtor after".into(),
15596            expected_with_replace_mode: "before editorˇ after".into(),
15597            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
15598            expected_with_replace_suffix_mode: "before editorˇ after".into(),
15599        },
15600        Run {
15601            run_description: "Prepend text containing whitespace",
15602            initial_state: "pˇfield: bool".into(),
15603            buffer_marked_text: "<p|field>: bool".into(),
15604            completion_label: "pub ",
15605            completion_text: "pub ",
15606            expected_with_insert_mode: "pub ˇfield: bool".into(),
15607            expected_with_replace_mode: "pub ˇ: bool".into(),
15608            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
15609            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
15610        },
15611        Run {
15612            run_description: "Add element to start of list",
15613            initial_state: "[element_ˇelement_2]".into(),
15614            buffer_marked_text: "[<element_|element_2>]".into(),
15615            completion_label: "element_1",
15616            completion_text: "element_1",
15617            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
15618            expected_with_replace_mode: "[element_1ˇ]".into(),
15619            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
15620            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
15621        },
15622        Run {
15623            run_description: "Add element to start of list -- first and second elements are equal",
15624            initial_state: "[elˇelement]".into(),
15625            buffer_marked_text: "[<el|element>]".into(),
15626            completion_label: "element",
15627            completion_text: "element",
15628            expected_with_insert_mode: "[elementˇelement]".into(),
15629            expected_with_replace_mode: "[elementˇ]".into(),
15630            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
15631            expected_with_replace_suffix_mode: "[elementˇ]".into(),
15632        },
15633        Run {
15634            run_description: "Ends with matching suffix",
15635            initial_state: "SubˇError".into(),
15636            buffer_marked_text: "<Sub|Error>".into(),
15637            completion_label: "SubscriptionError",
15638            completion_text: "SubscriptionError",
15639            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
15640            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
15641            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
15642            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
15643        },
15644        Run {
15645            run_description: "Suffix is a subsequence -- contiguous",
15646            initial_state: "SubˇErr".into(),
15647            buffer_marked_text: "<Sub|Err>".into(),
15648            completion_label: "SubscriptionError",
15649            completion_text: "SubscriptionError",
15650            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
15651            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
15652            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
15653            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
15654        },
15655        Run {
15656            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
15657            initial_state: "Suˇscrirr".into(),
15658            buffer_marked_text: "<Su|scrirr>".into(),
15659            completion_label: "SubscriptionError",
15660            completion_text: "SubscriptionError",
15661            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
15662            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
15663            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
15664            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
15665        },
15666        Run {
15667            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
15668            initial_state: "foo(indˇix)".into(),
15669            buffer_marked_text: "foo(<ind|ix>)".into(),
15670            completion_label: "node_index",
15671            completion_text: "node_index",
15672            expected_with_insert_mode: "foo(node_indexˇix)".into(),
15673            expected_with_replace_mode: "foo(node_indexˇ)".into(),
15674            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
15675            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
15676        },
15677        Run {
15678            run_description: "Replace range ends before cursor - should extend to cursor",
15679            initial_state: "before editˇo after".into(),
15680            buffer_marked_text: "before <{ed}>it|o after".into(),
15681            completion_label: "editor",
15682            completion_text: "editor",
15683            expected_with_insert_mode: "before editorˇo after".into(),
15684            expected_with_replace_mode: "before editorˇo after".into(),
15685            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
15686            expected_with_replace_suffix_mode: "before editorˇo after".into(),
15687        },
15688        Run {
15689            run_description: "Uses label for suffix matching",
15690            initial_state: "before ediˇtor after".into(),
15691            buffer_marked_text: "before <edi|tor> after".into(),
15692            completion_label: "editor",
15693            completion_text: "editor()",
15694            expected_with_insert_mode: "before editor()ˇtor after".into(),
15695            expected_with_replace_mode: "before editor()ˇ after".into(),
15696            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
15697            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
15698        },
15699        Run {
15700            run_description: "Case insensitive subsequence and suffix matching",
15701            initial_state: "before EDiˇtoR after".into(),
15702            buffer_marked_text: "before <EDi|toR> after".into(),
15703            completion_label: "editor",
15704            completion_text: "editor",
15705            expected_with_insert_mode: "before editorˇtoR after".into(),
15706            expected_with_replace_mode: "before editorˇ after".into(),
15707            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
15708            expected_with_replace_suffix_mode: "before editorˇ after".into(),
15709        },
15710    ];
15711
15712    for run in runs {
15713        let run_variations = [
15714            (LspInsertMode::Insert, run.expected_with_insert_mode),
15715            (LspInsertMode::Replace, run.expected_with_replace_mode),
15716            (
15717                LspInsertMode::ReplaceSubsequence,
15718                run.expected_with_replace_subsequence_mode,
15719            ),
15720            (
15721                LspInsertMode::ReplaceSuffix,
15722                run.expected_with_replace_suffix_mode,
15723            ),
15724        ];
15725
15726        for (lsp_insert_mode, expected_text) in run_variations {
15727            eprintln!(
15728                "run = {:?}, mode = {lsp_insert_mode:.?}",
15729                run.run_description,
15730            );
15731
15732            update_test_language_settings(&mut cx, &|settings| {
15733                settings.defaults.completions = Some(CompletionSettingsContent {
15734                    lsp_insert_mode: Some(lsp_insert_mode),
15735                    words: Some(WordsCompletionMode::Disabled),
15736                    words_min_length: Some(0),
15737                    ..Default::default()
15738                });
15739            });
15740
15741            cx.set_state(&run.initial_state);
15742
15743            // Set up resolve handler before showing completions, since resolve may be
15744            // triggered when menu becomes visible (for documentation), not just on confirm.
15745            cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(
15746                move |_, _, _| async move {
15747                    Ok(lsp::CompletionItem {
15748                        additional_text_edits: None,
15749                        ..Default::default()
15750                    })
15751                },
15752            );
15753
15754            cx.update_editor(|editor, window, cx| {
15755                editor.show_completions(&ShowCompletions, window, cx);
15756            });
15757
15758            let counter = Arc::new(AtomicUsize::new(0));
15759            handle_completion_request_with_insert_and_replace(
15760                &mut cx,
15761                &run.buffer_marked_text,
15762                vec![(run.completion_label, run.completion_text)],
15763                counter.clone(),
15764            )
15765            .await;
15766            cx.condition(|editor, _| editor.context_menu_visible())
15767                .await;
15768            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15769
15770            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15771                editor
15772                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
15773                    .unwrap()
15774            });
15775            cx.assert_editor_state(&expected_text);
15776            apply_additional_edits.await.unwrap();
15777        }
15778    }
15779}
15780
15781#[gpui::test]
15782async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
15783    init_test(cx, |_| {});
15784    let mut cx = EditorLspTestContext::new_rust(
15785        lsp::ServerCapabilities {
15786            completion_provider: Some(lsp::CompletionOptions {
15787                resolve_provider: Some(true),
15788                ..Default::default()
15789            }),
15790            ..Default::default()
15791        },
15792        cx,
15793    )
15794    .await;
15795
15796    let initial_state = "SubˇError";
15797    let buffer_marked_text = "<Sub|Error>";
15798    let completion_text = "SubscriptionError";
15799    let expected_with_insert_mode = "SubscriptionErrorˇError";
15800    let expected_with_replace_mode = "SubscriptionErrorˇ";
15801
15802    update_test_language_settings(&mut cx, &|settings| {
15803        settings.defaults.completions = Some(CompletionSettingsContent {
15804            words: Some(WordsCompletionMode::Disabled),
15805            words_min_length: Some(0),
15806            // set the opposite here to ensure that the action is overriding the default behavior
15807            lsp_insert_mode: Some(LspInsertMode::Insert),
15808            ..Default::default()
15809        });
15810    });
15811
15812    cx.set_state(initial_state);
15813    cx.update_editor(|editor, window, cx| {
15814        editor.show_completions(&ShowCompletions, window, cx);
15815    });
15816
15817    let counter = Arc::new(AtomicUsize::new(0));
15818    handle_completion_request_with_insert_and_replace(
15819        &mut cx,
15820        buffer_marked_text,
15821        vec![(completion_text, completion_text)],
15822        counter.clone(),
15823    )
15824    .await;
15825    cx.condition(|editor, _| editor.context_menu_visible())
15826        .await;
15827    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15828
15829    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15830        editor
15831            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15832            .unwrap()
15833    });
15834    cx.assert_editor_state(expected_with_replace_mode);
15835    handle_resolve_completion_request(&mut cx, None).await;
15836    apply_additional_edits.await.unwrap();
15837
15838    update_test_language_settings(&mut cx, &|settings| {
15839        settings.defaults.completions = Some(CompletionSettingsContent {
15840            words: Some(WordsCompletionMode::Disabled),
15841            words_min_length: Some(0),
15842            // set the opposite here to ensure that the action is overriding the default behavior
15843            lsp_insert_mode: Some(LspInsertMode::Replace),
15844            ..Default::default()
15845        });
15846    });
15847
15848    cx.set_state(initial_state);
15849    cx.update_editor(|editor, window, cx| {
15850        editor.show_completions(&ShowCompletions, window, cx);
15851    });
15852    handle_completion_request_with_insert_and_replace(
15853        &mut cx,
15854        buffer_marked_text,
15855        vec![(completion_text, completion_text)],
15856        counter.clone(),
15857    )
15858    .await;
15859    cx.condition(|editor, _| editor.context_menu_visible())
15860        .await;
15861    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15862
15863    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15864        editor
15865            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
15866            .unwrap()
15867    });
15868    cx.assert_editor_state(expected_with_insert_mode);
15869    handle_resolve_completion_request(&mut cx, None).await;
15870    apply_additional_edits.await.unwrap();
15871}
15872
15873#[gpui::test]
15874async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
15875    init_test(cx, |_| {});
15876    let mut cx = EditorLspTestContext::new_rust(
15877        lsp::ServerCapabilities {
15878            completion_provider: Some(lsp::CompletionOptions {
15879                resolve_provider: Some(true),
15880                ..Default::default()
15881            }),
15882            ..Default::default()
15883        },
15884        cx,
15885    )
15886    .await;
15887
15888    // scenario: surrounding text matches completion text
15889    let completion_text = "to_offset";
15890    let initial_state = indoc! {"
15891        1. buf.to_offˇsuffix
15892        2. buf.to_offˇsuf
15893        3. buf.to_offˇfix
15894        4. buf.to_offˇ
15895        5. into_offˇensive
15896        6. ˇsuffix
15897        7. let ˇ //
15898        8. aaˇzz
15899        9. buf.to_off«zzzzzˇ»suffix
15900        10. buf.«ˇzzzzz»suffix
15901        11. to_off«ˇzzzzz»
15902
15903        buf.to_offˇsuffix  // newest cursor
15904    "};
15905    let completion_marked_buffer = indoc! {"
15906        1. buf.to_offsuffix
15907        2. buf.to_offsuf
15908        3. buf.to_offfix
15909        4. buf.to_off
15910        5. into_offensive
15911        6. suffix
15912        7. let  //
15913        8. aazz
15914        9. buf.to_offzzzzzsuffix
15915        10. buf.zzzzzsuffix
15916        11. to_offzzzzz
15917
15918        buf.<to_off|suffix>  // newest cursor
15919    "};
15920    let expected = indoc! {"
15921        1. buf.to_offsetˇ
15922        2. buf.to_offsetˇsuf
15923        3. buf.to_offsetˇfix
15924        4. buf.to_offsetˇ
15925        5. into_offsetˇensive
15926        6. to_offsetˇsuffix
15927        7. let to_offsetˇ //
15928        8. aato_offsetˇzz
15929        9. buf.to_offsetˇ
15930        10. buf.to_offsetˇsuffix
15931        11. to_offsetˇ
15932
15933        buf.to_offsetˇ  // newest cursor
15934    "};
15935    cx.set_state(initial_state);
15936    cx.update_editor(|editor, window, cx| {
15937        editor.show_completions(&ShowCompletions, window, cx);
15938    });
15939    handle_completion_request_with_insert_and_replace(
15940        &mut cx,
15941        completion_marked_buffer,
15942        vec![(completion_text, completion_text)],
15943        Arc::new(AtomicUsize::new(0)),
15944    )
15945    .await;
15946    cx.condition(|editor, _| editor.context_menu_visible())
15947        .await;
15948    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
15949        editor
15950            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
15951            .unwrap()
15952    });
15953    cx.assert_editor_state(expected);
15954    handle_resolve_completion_request(&mut cx, None).await;
15955    apply_additional_edits.await.unwrap();
15956
15957    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
15958    let completion_text = "foo_and_bar";
15959    let initial_state = indoc! {"
15960        1. ooanbˇ
15961        2. zooanbˇ
15962        3. ooanbˇz
15963        4. zooanbˇz
15964        5. ooanˇ
15965        6. oanbˇ
15966
15967        ooanbˇ
15968    "};
15969    let completion_marked_buffer = indoc! {"
15970        1. ooanb
15971        2. zooanb
15972        3. ooanbz
15973        4. zooanbz
15974        5. ooan
15975        6. oanb
15976
15977        <ooanb|>
15978    "};
15979    let expected = indoc! {"
15980        1. foo_and_barˇ
15981        2. zfoo_and_barˇ
15982        3. foo_and_barˇz
15983        4. zfoo_and_barˇz
15984        5. ooanfoo_and_barˇ
15985        6. oanbfoo_and_barˇ
15986
15987        foo_and_barˇ
15988    "};
15989    cx.set_state(initial_state);
15990    cx.update_editor(|editor, window, cx| {
15991        editor.show_completions(&ShowCompletions, window, cx);
15992    });
15993    handle_completion_request_with_insert_and_replace(
15994        &mut cx,
15995        completion_marked_buffer,
15996        vec![(completion_text, completion_text)],
15997        Arc::new(AtomicUsize::new(0)),
15998    )
15999    .await;
16000    cx.condition(|editor, _| editor.context_menu_visible())
16001        .await;
16002    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16003        editor
16004            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
16005            .unwrap()
16006    });
16007    cx.assert_editor_state(expected);
16008    handle_resolve_completion_request(&mut cx, None).await;
16009    apply_additional_edits.await.unwrap();
16010
16011    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
16012    // (expects the same as if it was inserted at the end)
16013    let completion_text = "foo_and_bar";
16014    let initial_state = indoc! {"
16015        1. ooˇanb
16016        2. zooˇanb
16017        3. ooˇanbz
16018        4. zooˇanbz
16019
16020        ooˇanb
16021    "};
16022    let completion_marked_buffer = indoc! {"
16023        1. ooanb
16024        2. zooanb
16025        3. ooanbz
16026        4. zooanbz
16027
16028        <oo|anb>
16029    "};
16030    let expected = indoc! {"
16031        1. foo_and_barˇ
16032        2. zfoo_and_barˇ
16033        3. foo_and_barˇz
16034        4. zfoo_and_barˇz
16035
16036        foo_and_barˇ
16037    "};
16038    cx.set_state(initial_state);
16039    cx.update_editor(|editor, window, cx| {
16040        editor.show_completions(&ShowCompletions, window, cx);
16041    });
16042    handle_completion_request_with_insert_and_replace(
16043        &mut cx,
16044        completion_marked_buffer,
16045        vec![(completion_text, completion_text)],
16046        Arc::new(AtomicUsize::new(0)),
16047    )
16048    .await;
16049    cx.condition(|editor, _| editor.context_menu_visible())
16050        .await;
16051    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16052        editor
16053            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
16054            .unwrap()
16055    });
16056    cx.assert_editor_state(expected);
16057    handle_resolve_completion_request(&mut cx, None).await;
16058    apply_additional_edits.await.unwrap();
16059}
16060
16061// This used to crash
16062#[gpui::test]
16063async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
16064    init_test(cx, |_| {});
16065
16066    let buffer_text = indoc! {"
16067        fn main() {
16068            10.satu;
16069
16070            //
16071            // separate1
16072            // separate2
16073            // separate3
16074            //
16075
16076            10.satu20;
16077        }
16078    "};
16079    let multibuffer_text_with_selections = indoc! {"
16080        fn main() {
16081            10.satuˇ;
16082
16083            //
16084
16085            10.satuˇ20;
16086        }
16087    "};
16088    let expected_multibuffer = indoc! {"
16089        fn main() {
16090            10.saturating_sub()ˇ;
16091
16092            //
16093
16094            10.saturating_sub()ˇ;
16095        }
16096    "};
16097
16098    let fs = FakeFs::new(cx.executor());
16099    fs.insert_tree(
16100        path!("/a"),
16101        json!({
16102            "main.rs": buffer_text,
16103        }),
16104    )
16105    .await;
16106
16107    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16108    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16109    language_registry.add(rust_lang());
16110    let mut fake_servers = language_registry.register_fake_lsp(
16111        "Rust",
16112        FakeLspAdapter {
16113            capabilities: lsp::ServerCapabilities {
16114                completion_provider: Some(lsp::CompletionOptions {
16115                    resolve_provider: None,
16116                    ..lsp::CompletionOptions::default()
16117                }),
16118                ..lsp::ServerCapabilities::default()
16119            },
16120            ..FakeLspAdapter::default()
16121        },
16122    );
16123    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
16124    let workspace = window
16125        .read_with(cx, |mw, _| mw.workspace().clone())
16126        .unwrap();
16127    let cx = &mut VisualTestContext::from_window(*window, cx);
16128    let buffer = project
16129        .update(cx, |project, cx| {
16130            project.open_local_buffer(path!("/a/main.rs"), cx)
16131        })
16132        .await
16133        .unwrap();
16134
16135    let multi_buffer = cx.new(|cx| {
16136        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
16137        multi_buffer.set_excerpts_for_path(
16138            PathKey::sorted(0),
16139            buffer.clone(),
16140            [
16141                Point::zero()..Point::new(2, 0),
16142                Point::new(7, 0)..buffer.read(cx).max_point(),
16143            ],
16144            0,
16145            cx,
16146        );
16147        multi_buffer
16148    });
16149
16150    let editor = workspace.update_in(cx, |_, window, cx| {
16151        cx.new(|cx| {
16152            Editor::new(
16153                EditorMode::Full {
16154                    scale_ui_elements_with_buffer_font_size: false,
16155                    show_active_line_background: false,
16156                    sizing_behavior: SizingBehavior::Default,
16157                },
16158                multi_buffer.clone(),
16159                Some(project.clone()),
16160                window,
16161                cx,
16162            )
16163        })
16164    });
16165
16166    let pane = workspace.update_in(cx, |workspace, _, _| workspace.active_pane().clone());
16167    pane.update_in(cx, |pane, window, cx| {
16168        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
16169    });
16170
16171    let fake_server = fake_servers.next().await.unwrap();
16172    cx.run_until_parked();
16173
16174    editor.update_in(cx, |editor, window, cx| {
16175        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16176            s.select_ranges([
16177                Point::new(1, 11)..Point::new(1, 11),
16178                Point::new(5, 11)..Point::new(5, 11),
16179            ])
16180        });
16181
16182        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
16183    });
16184
16185    editor.update_in(cx, |editor, window, cx| {
16186        editor.show_completions(&ShowCompletions, window, cx);
16187    });
16188
16189    fake_server
16190        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16191            let completion_item = lsp::CompletionItem {
16192                label: "saturating_sub()".into(),
16193                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16194                    lsp::InsertReplaceEdit {
16195                        new_text: "saturating_sub()".to_owned(),
16196                        insert: lsp::Range::new(
16197                            lsp::Position::new(9, 7),
16198                            lsp::Position::new(9, 11),
16199                        ),
16200                        replace: lsp::Range::new(
16201                            lsp::Position::new(9, 7),
16202                            lsp::Position::new(9, 13),
16203                        ),
16204                    },
16205                )),
16206                ..lsp::CompletionItem::default()
16207            };
16208
16209            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
16210        })
16211        .next()
16212        .await
16213        .unwrap();
16214
16215    cx.condition(&editor, |editor, _| editor.context_menu_visible())
16216        .await;
16217
16218    editor
16219        .update_in(cx, |editor, window, cx| {
16220            editor
16221                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
16222                .unwrap()
16223        })
16224        .await
16225        .unwrap();
16226
16227    editor.update(cx, |editor, cx| {
16228        assert_text_with_selections(editor, expected_multibuffer, cx);
16229    })
16230}
16231
16232#[gpui::test]
16233async fn test_completion(cx: &mut TestAppContext) {
16234    init_test(cx, |_| {});
16235
16236    let mut cx = EditorLspTestContext::new_rust(
16237        lsp::ServerCapabilities {
16238            completion_provider: Some(lsp::CompletionOptions {
16239                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16240                resolve_provider: Some(true),
16241                ..Default::default()
16242            }),
16243            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16244            ..Default::default()
16245        },
16246        cx,
16247    )
16248    .await;
16249    let counter = Arc::new(AtomicUsize::new(0));
16250
16251    cx.set_state(indoc! {"
16252        oneˇ
16253        two
16254        three
16255    "});
16256    cx.simulate_keystroke(".");
16257    handle_completion_request(
16258        indoc! {"
16259            one.|<>
16260            two
16261            three
16262        "},
16263        vec!["first_completion", "second_completion"],
16264        true,
16265        counter.clone(),
16266        &mut cx,
16267    )
16268    .await;
16269    cx.condition(|editor, _| editor.context_menu_visible())
16270        .await;
16271    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16272
16273    let _handler = handle_signature_help_request(
16274        &mut cx,
16275        lsp::SignatureHelp {
16276            signatures: vec![lsp::SignatureInformation {
16277                label: "test signature".to_string(),
16278                documentation: None,
16279                parameters: Some(vec![lsp::ParameterInformation {
16280                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
16281                    documentation: None,
16282                }]),
16283                active_parameter: None,
16284            }],
16285            active_signature: None,
16286            active_parameter: None,
16287        },
16288    );
16289    cx.update_editor(|editor, window, cx| {
16290        assert!(
16291            !editor.signature_help_state.is_shown(),
16292            "No signature help was called for"
16293        );
16294        editor.show_signature_help(&ShowSignatureHelp, window, cx);
16295    });
16296    cx.run_until_parked();
16297    cx.update_editor(|editor, _, _| {
16298        assert!(
16299            !editor.signature_help_state.is_shown(),
16300            "No signature help should be shown when completions menu is open"
16301        );
16302    });
16303
16304    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16305        editor.context_menu_next(&Default::default(), window, cx);
16306        editor
16307            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16308            .unwrap()
16309    });
16310    cx.assert_editor_state(indoc! {"
16311        one.second_completionˇ
16312        two
16313        three
16314    "});
16315
16316    handle_resolve_completion_request(
16317        &mut cx,
16318        Some(vec![
16319            (
16320                //This overlaps with the primary completion edit which is
16321                //misbehavior from the LSP spec, test that we filter it out
16322                indoc! {"
16323                    one.second_ˇcompletion
16324                    two
16325                    threeˇ
16326                "},
16327                "overlapping additional edit",
16328            ),
16329            (
16330                indoc! {"
16331                    one.second_completion
16332                    two
16333                    threeˇ
16334                "},
16335                "\nadditional edit",
16336            ),
16337        ]),
16338    )
16339    .await;
16340    apply_additional_edits.await.unwrap();
16341    cx.assert_editor_state(indoc! {"
16342        one.second_completionˇ
16343        two
16344        three
16345        additional edit
16346    "});
16347
16348    cx.set_state(indoc! {"
16349        one.second_completion
16350        twoˇ
16351        threeˇ
16352        additional edit
16353    "});
16354    cx.simulate_keystroke(" ");
16355    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
16356    cx.simulate_keystroke("s");
16357    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
16358
16359    cx.assert_editor_state(indoc! {"
16360        one.second_completion
16361        two sˇ
16362        three sˇ
16363        additional edit
16364    "});
16365    handle_completion_request(
16366        indoc! {"
16367            one.second_completion
16368            two s
16369            three <s|>
16370            additional edit
16371        "},
16372        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
16373        true,
16374        counter.clone(),
16375        &mut cx,
16376    )
16377    .await;
16378    cx.condition(|editor, _| editor.context_menu_visible())
16379        .await;
16380    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
16381
16382    cx.simulate_keystroke("i");
16383
16384    handle_completion_request(
16385        indoc! {"
16386            one.second_completion
16387            two si
16388            three <si|>
16389            additional edit
16390        "},
16391        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
16392        true,
16393        counter.clone(),
16394        &mut cx,
16395    )
16396    .await;
16397    cx.condition(|editor, _| editor.context_menu_visible())
16398        .await;
16399    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
16400
16401    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16402        editor
16403            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16404            .unwrap()
16405    });
16406    cx.assert_editor_state(indoc! {"
16407        one.second_completion
16408        two sixth_completionˇ
16409        three sixth_completionˇ
16410        additional edit
16411    "});
16412
16413    apply_additional_edits.await.unwrap();
16414
16415    update_test_language_settings(&mut cx, &|settings| {
16416        settings.defaults.show_completions_on_input = Some(false);
16417    });
16418    cx.set_state("editorˇ");
16419    cx.simulate_keystroke(".");
16420    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
16421    cx.simulate_keystrokes("c l o");
16422    cx.assert_editor_state("editor.cloˇ");
16423    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
16424    cx.update_editor(|editor, window, cx| {
16425        editor.show_completions(&ShowCompletions, window, cx);
16426    });
16427    handle_completion_request(
16428        "editor.<clo|>",
16429        vec!["close", "clobber"],
16430        true,
16431        counter.clone(),
16432        &mut cx,
16433    )
16434    .await;
16435    cx.condition(|editor, _| editor.context_menu_visible())
16436        .await;
16437    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
16438
16439    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16440        editor
16441            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16442            .unwrap()
16443    });
16444    cx.assert_editor_state("editor.clobberˇ");
16445    handle_resolve_completion_request(&mut cx, None).await;
16446    apply_additional_edits.await.unwrap();
16447}
16448
16449#[gpui::test]
16450async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
16451    init_test(cx, |_| {});
16452
16453    let fs = FakeFs::new(cx.executor());
16454    fs.insert_tree(
16455        path!("/a"),
16456        json!({
16457            "main.rs": "",
16458        }),
16459    )
16460    .await;
16461
16462    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16463    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16464    language_registry.add(rust_lang());
16465    let command_calls = Arc::new(AtomicUsize::new(0));
16466    let registered_command = "_the/command";
16467
16468    let closure_command_calls = command_calls.clone();
16469    let mut fake_servers = language_registry.register_fake_lsp(
16470        "Rust",
16471        FakeLspAdapter {
16472            capabilities: lsp::ServerCapabilities {
16473                completion_provider: Some(lsp::CompletionOptions {
16474                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16475                    ..lsp::CompletionOptions::default()
16476                }),
16477                execute_command_provider: Some(lsp::ExecuteCommandOptions {
16478                    commands: vec![registered_command.to_owned()],
16479                    ..lsp::ExecuteCommandOptions::default()
16480                }),
16481                ..lsp::ServerCapabilities::default()
16482            },
16483            initializer: Some(Box::new(move |fake_server| {
16484                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
16485                    move |params, _| async move {
16486                        Ok(Some(lsp::CompletionResponse::Array(vec![
16487                            lsp::CompletionItem {
16488                                label: "registered_command".to_owned(),
16489                                text_edit: gen_text_edit(&params, ""),
16490                                command: Some(lsp::Command {
16491                                    title: registered_command.to_owned(),
16492                                    command: "_the/command".to_owned(),
16493                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
16494                                }),
16495                                ..lsp::CompletionItem::default()
16496                            },
16497                            lsp::CompletionItem {
16498                                label: "unregistered_command".to_owned(),
16499                                text_edit: gen_text_edit(&params, ""),
16500                                command: Some(lsp::Command {
16501                                    title: "????????????".to_owned(),
16502                                    command: "????????????".to_owned(),
16503                                    arguments: Some(vec![serde_json::Value::Null]),
16504                                }),
16505                                ..lsp::CompletionItem::default()
16506                            },
16507                        ])))
16508                    },
16509                );
16510                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
16511                    let command_calls = closure_command_calls.clone();
16512                    move |params, _| {
16513                        assert_eq!(params.command, registered_command);
16514                        let command_calls = command_calls.clone();
16515                        async move {
16516                            command_calls.fetch_add(1, atomic::Ordering::Release);
16517                            Ok(Some(json!(null)))
16518                        }
16519                    }
16520                });
16521            })),
16522            ..FakeLspAdapter::default()
16523        },
16524    );
16525    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
16526    let workspace = window
16527        .read_with(cx, |mw, _| mw.workspace().clone())
16528        .unwrap();
16529    let cx = &mut VisualTestContext::from_window(*window, cx);
16530    let editor = workspace
16531        .update_in(cx, |workspace, window, cx| {
16532            workspace.open_abs_path(
16533                PathBuf::from(path!("/a/main.rs")),
16534                OpenOptions::default(),
16535                window,
16536                cx,
16537            )
16538        })
16539        .await
16540        .unwrap()
16541        .downcast::<Editor>()
16542        .unwrap();
16543    let _fake_server = fake_servers.next().await.unwrap();
16544    cx.run_until_parked();
16545
16546    editor.update_in(cx, |editor, window, cx| {
16547        cx.focus_self(window);
16548        editor.move_to_end(&MoveToEnd, window, cx);
16549        editor.handle_input(".", window, cx);
16550    });
16551    cx.run_until_parked();
16552    editor.update(cx, |editor, _| {
16553        assert!(editor.context_menu_visible());
16554        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16555        {
16556            let completion_labels = menu
16557                .completions
16558                .borrow()
16559                .iter()
16560                .map(|c| c.label.text.clone())
16561                .collect::<Vec<_>>();
16562            assert_eq!(
16563                completion_labels,
16564                &["registered_command", "unregistered_command",],
16565            );
16566        } else {
16567            panic!("expected completion menu to be open");
16568        }
16569    });
16570
16571    editor
16572        .update_in(cx, |editor, window, cx| {
16573            editor
16574                .confirm_completion(&ConfirmCompletion::default(), window, cx)
16575                .unwrap()
16576        })
16577        .await
16578        .unwrap();
16579    cx.run_until_parked();
16580    assert_eq!(
16581        command_calls.load(atomic::Ordering::Acquire),
16582        1,
16583        "For completion with a registered command, Zed should send a command execution request",
16584    );
16585
16586    editor.update_in(cx, |editor, window, cx| {
16587        cx.focus_self(window);
16588        editor.handle_input(".", window, cx);
16589    });
16590    cx.run_until_parked();
16591    editor.update(cx, |editor, _| {
16592        assert!(editor.context_menu_visible());
16593        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16594        {
16595            let completion_labels = menu
16596                .completions
16597                .borrow()
16598                .iter()
16599                .map(|c| c.label.text.clone())
16600                .collect::<Vec<_>>();
16601            assert_eq!(
16602                completion_labels,
16603                &["registered_command", "unregistered_command",],
16604            );
16605        } else {
16606            panic!("expected completion menu to be open");
16607        }
16608    });
16609    editor
16610        .update_in(cx, |editor, window, cx| {
16611            editor.context_menu_next(&Default::default(), window, cx);
16612            editor
16613                .confirm_completion(&ConfirmCompletion::default(), window, cx)
16614                .unwrap()
16615        })
16616        .await
16617        .unwrap();
16618    cx.run_until_parked();
16619    assert_eq!(
16620        command_calls.load(atomic::Ordering::Acquire),
16621        1,
16622        "For completion with an unregistered command, Zed should not send a command execution request",
16623    );
16624}
16625
16626#[gpui::test]
16627async fn test_completion_reuse(cx: &mut TestAppContext) {
16628    init_test(cx, |_| {});
16629
16630    let mut cx = EditorLspTestContext::new_rust(
16631        lsp::ServerCapabilities {
16632            completion_provider: Some(lsp::CompletionOptions {
16633                trigger_characters: Some(vec![".".to_string()]),
16634                ..Default::default()
16635            }),
16636            ..Default::default()
16637        },
16638        cx,
16639    )
16640    .await;
16641
16642    let counter = Arc::new(AtomicUsize::new(0));
16643    cx.set_state("objˇ");
16644    cx.simulate_keystroke(".");
16645
16646    // Initial completion request returns complete results
16647    let is_incomplete = false;
16648    handle_completion_request(
16649        "obj.|<>",
16650        vec!["a", "ab", "abc"],
16651        is_incomplete,
16652        counter.clone(),
16653        &mut cx,
16654    )
16655    .await;
16656    cx.run_until_parked();
16657    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16658    cx.assert_editor_state("obj.ˇ");
16659    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
16660
16661    // Type "a" - filters existing completions
16662    cx.simulate_keystroke("a");
16663    cx.run_until_parked();
16664    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16665    cx.assert_editor_state("obj.aˇ");
16666    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
16667
16668    // Type "b" - filters existing completions
16669    cx.simulate_keystroke("b");
16670    cx.run_until_parked();
16671    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16672    cx.assert_editor_state("obj.abˇ");
16673    check_displayed_completions(vec!["ab", "abc"], &mut cx);
16674
16675    // Type "c" - filters existing completions
16676    cx.simulate_keystroke("c");
16677    cx.run_until_parked();
16678    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16679    cx.assert_editor_state("obj.abcˇ");
16680    check_displayed_completions(vec!["abc"], &mut cx);
16681
16682    // Backspace to delete "c" - filters existing completions
16683    cx.update_editor(|editor, window, cx| {
16684        editor.backspace(&Backspace, window, cx);
16685    });
16686    cx.run_until_parked();
16687    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16688    cx.assert_editor_state("obj.abˇ");
16689    check_displayed_completions(vec!["ab", "abc"], &mut cx);
16690
16691    // Moving cursor to the left dismisses menu.
16692    cx.update_editor(|editor, window, cx| {
16693        editor.move_left(&MoveLeft, window, cx);
16694    });
16695    cx.run_until_parked();
16696    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16697    cx.assert_editor_state("obj.aˇb");
16698    cx.update_editor(|editor, _, _| {
16699        assert_eq!(editor.context_menu_visible(), false);
16700    });
16701
16702    // Type "b" - new request
16703    cx.simulate_keystroke("b");
16704    let is_incomplete = false;
16705    handle_completion_request(
16706        "obj.<ab|>a",
16707        vec!["ab", "abc"],
16708        is_incomplete,
16709        counter.clone(),
16710        &mut cx,
16711    )
16712    .await;
16713    cx.run_until_parked();
16714    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
16715    cx.assert_editor_state("obj.abˇb");
16716    check_displayed_completions(vec!["ab", "abc"], &mut cx);
16717
16718    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
16719    cx.update_editor(|editor, window, cx| {
16720        editor.backspace(&Backspace, window, cx);
16721    });
16722    let is_incomplete = false;
16723    handle_completion_request(
16724        "obj.<a|>b",
16725        vec!["a", "ab", "abc"],
16726        is_incomplete,
16727        counter.clone(),
16728        &mut cx,
16729    )
16730    .await;
16731    cx.run_until_parked();
16732    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
16733    cx.assert_editor_state("obj.aˇb");
16734    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
16735
16736    // Backspace to delete "a" - dismisses menu.
16737    cx.update_editor(|editor, window, cx| {
16738        editor.backspace(&Backspace, window, cx);
16739    });
16740    cx.run_until_parked();
16741    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
16742    cx.assert_editor_state("obj.ˇb");
16743    cx.update_editor(|editor, _, _| {
16744        assert_eq!(editor.context_menu_visible(), false);
16745    });
16746}
16747
16748#[gpui::test]
16749async fn test_word_completion(cx: &mut TestAppContext) {
16750    let lsp_fetch_timeout_ms = 10;
16751    init_test(cx, |language_settings| {
16752        language_settings.defaults.completions = Some(CompletionSettingsContent {
16753            words_min_length: Some(0),
16754            lsp_fetch_timeout_ms: Some(10),
16755            lsp_insert_mode: Some(LspInsertMode::Insert),
16756            ..Default::default()
16757        });
16758    });
16759
16760    let mut cx = EditorLspTestContext::new_rust(
16761        lsp::ServerCapabilities {
16762            completion_provider: Some(lsp::CompletionOptions {
16763                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16764                ..lsp::CompletionOptions::default()
16765            }),
16766            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16767            ..lsp::ServerCapabilities::default()
16768        },
16769        cx,
16770    )
16771    .await;
16772
16773    let throttle_completions = Arc::new(AtomicBool::new(false));
16774
16775    let lsp_throttle_completions = throttle_completions.clone();
16776    let _completion_requests_handler =
16777        cx.lsp
16778            .server
16779            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
16780                let lsp_throttle_completions = lsp_throttle_completions.clone();
16781                let cx = cx.clone();
16782                async move {
16783                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
16784                        cx.background_executor()
16785                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
16786                            .await;
16787                    }
16788                    Ok(Some(lsp::CompletionResponse::Array(vec![
16789                        lsp::CompletionItem {
16790                            label: "first".into(),
16791                            ..lsp::CompletionItem::default()
16792                        },
16793                        lsp::CompletionItem {
16794                            label: "last".into(),
16795                            ..lsp::CompletionItem::default()
16796                        },
16797                    ])))
16798                }
16799            });
16800
16801    cx.set_state(indoc! {"
16802        oneˇ
16803        two
16804        three
16805    "});
16806    cx.simulate_keystroke(".");
16807    cx.executor().run_until_parked();
16808    cx.condition(|editor, _| editor.context_menu_visible())
16809        .await;
16810    cx.update_editor(|editor, window, cx| {
16811        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16812        {
16813            assert_eq!(
16814                completion_menu_entries(menu),
16815                &["first", "last"],
16816                "When LSP server is fast to reply, no fallback word completions are used"
16817            );
16818        } else {
16819            panic!("expected completion menu to be open");
16820        }
16821        editor.cancel(&Cancel, window, cx);
16822    });
16823    cx.executor().run_until_parked();
16824    cx.condition(|editor, _| !editor.context_menu_visible())
16825        .await;
16826
16827    throttle_completions.store(true, atomic::Ordering::Release);
16828    cx.simulate_keystroke(".");
16829    cx.executor()
16830        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
16831    cx.executor().run_until_parked();
16832    cx.condition(|editor, _| editor.context_menu_visible())
16833        .await;
16834    cx.update_editor(|editor, _, _| {
16835        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16836        {
16837            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
16838                "When LSP server is slow, document words can be shown instead, if configured accordingly");
16839        } else {
16840            panic!("expected completion menu to be open");
16841        }
16842    });
16843}
16844
16845#[gpui::test]
16846async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
16847    init_test(cx, |language_settings| {
16848        language_settings.defaults.completions = Some(CompletionSettingsContent {
16849            words: Some(WordsCompletionMode::Enabled),
16850            words_min_length: Some(0),
16851            lsp_insert_mode: Some(LspInsertMode::Insert),
16852            ..Default::default()
16853        });
16854    });
16855
16856    let mut cx = EditorLspTestContext::new_rust(
16857        lsp::ServerCapabilities {
16858            completion_provider: Some(lsp::CompletionOptions {
16859                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16860                ..lsp::CompletionOptions::default()
16861            }),
16862            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16863            ..lsp::ServerCapabilities::default()
16864        },
16865        cx,
16866    )
16867    .await;
16868
16869    let _completion_requests_handler =
16870        cx.lsp
16871            .server
16872            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
16873                Ok(Some(lsp::CompletionResponse::Array(vec![
16874                    lsp::CompletionItem {
16875                        label: "first".into(),
16876                        ..lsp::CompletionItem::default()
16877                    },
16878                    lsp::CompletionItem {
16879                        label: "last".into(),
16880                        ..lsp::CompletionItem::default()
16881                    },
16882                ])))
16883            });
16884
16885    cx.set_state(indoc! {"ˇ
16886        first
16887        last
16888        second
16889    "});
16890    cx.simulate_keystroke(".");
16891    cx.executor().run_until_parked();
16892    cx.condition(|editor, _| editor.context_menu_visible())
16893        .await;
16894    cx.update_editor(|editor, _, _| {
16895        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16896        {
16897            assert_eq!(
16898                completion_menu_entries(menu),
16899                &["first", "last", "second"],
16900                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
16901            );
16902        } else {
16903            panic!("expected completion menu to be open");
16904        }
16905    });
16906}
16907
16908#[gpui::test]
16909async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
16910    init_test(cx, |language_settings| {
16911        language_settings.defaults.completions = Some(CompletionSettingsContent {
16912            words: Some(WordsCompletionMode::Disabled),
16913            words_min_length: Some(0),
16914            lsp_insert_mode: Some(LspInsertMode::Insert),
16915            ..Default::default()
16916        });
16917    });
16918
16919    let mut cx = EditorLspTestContext::new_rust(
16920        lsp::ServerCapabilities {
16921            completion_provider: Some(lsp::CompletionOptions {
16922                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
16923                ..lsp::CompletionOptions::default()
16924            }),
16925            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
16926            ..lsp::ServerCapabilities::default()
16927        },
16928        cx,
16929    )
16930    .await;
16931
16932    let _completion_requests_handler =
16933        cx.lsp
16934            .server
16935            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
16936                panic!("LSP completions should not be queried when dealing with word completions")
16937            });
16938
16939    cx.set_state(indoc! {"ˇ
16940        first
16941        last
16942        second
16943    "});
16944    cx.update_editor(|editor, window, cx| {
16945        editor.show_word_completions(&ShowWordCompletions, window, cx);
16946    });
16947    cx.executor().run_until_parked();
16948    cx.condition(|editor, _| editor.context_menu_visible())
16949        .await;
16950    cx.update_editor(|editor, _, _| {
16951        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16952        {
16953            assert_eq!(
16954                completion_menu_entries(menu),
16955                &["first", "last", "second"],
16956                "`ShowWordCompletions` action should show word completions"
16957            );
16958        } else {
16959            panic!("expected completion menu to be open");
16960        }
16961    });
16962
16963    cx.simulate_keystroke("l");
16964    cx.executor().run_until_parked();
16965    cx.condition(|editor, _| editor.context_menu_visible())
16966        .await;
16967    cx.update_editor(|editor, _, _| {
16968        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16969        {
16970            assert_eq!(
16971                completion_menu_entries(menu),
16972                &["last"],
16973                "After showing word completions, further editing should filter them and not query the LSP"
16974            );
16975        } else {
16976            panic!("expected completion menu to be open");
16977        }
16978    });
16979}
16980
16981#[gpui::test]
16982async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
16983    init_test(cx, |language_settings| {
16984        language_settings.defaults.completions = Some(CompletionSettingsContent {
16985            words_min_length: Some(0),
16986            lsp: Some(false),
16987            lsp_insert_mode: Some(LspInsertMode::Insert),
16988            ..Default::default()
16989        });
16990    });
16991
16992    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
16993
16994    cx.set_state(indoc! {"ˇ
16995        0_usize
16996        let
16997        33
16998        4.5f32
16999    "});
17000    cx.update_editor(|editor, window, cx| {
17001        editor.show_completions(&ShowCompletions, window, cx);
17002    });
17003    cx.executor().run_until_parked();
17004    cx.condition(|editor, _| editor.context_menu_visible())
17005        .await;
17006    cx.update_editor(|editor, window, cx| {
17007        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17008        {
17009            assert_eq!(
17010                completion_menu_entries(menu),
17011                &["let"],
17012                "With no digits in the completion query, no digits should be in the word completions"
17013            );
17014        } else {
17015            panic!("expected completion menu to be open");
17016        }
17017        editor.cancel(&Cancel, window, cx);
17018    });
17019
17020    cx.set_state(indoc! {"17021        0_usize
17022        let
17023        3
17024        33.35f32
17025    "});
17026    cx.update_editor(|editor, window, cx| {
17027        editor.show_completions(&ShowCompletions, window, cx);
17028    });
17029    cx.executor().run_until_parked();
17030    cx.condition(|editor, _| editor.context_menu_visible())
17031        .await;
17032    cx.update_editor(|editor, _, _| {
17033        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17034        {
17035            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
17036                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
17037        } else {
17038            panic!("expected completion menu to be open");
17039        }
17040    });
17041}
17042
17043#[gpui::test]
17044async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
17045    init_test(cx, |language_settings| {
17046        language_settings.defaults.completions = Some(CompletionSettingsContent {
17047            words: Some(WordsCompletionMode::Enabled),
17048            words_min_length: Some(3),
17049            lsp_insert_mode: Some(LspInsertMode::Insert),
17050            ..Default::default()
17051        });
17052    });
17053
17054    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17055    cx.set_state(indoc! {"ˇ
17056        wow
17057        wowen
17058        wowser
17059    "});
17060    cx.simulate_keystroke("w");
17061    cx.executor().run_until_parked();
17062    cx.update_editor(|editor, _, _| {
17063        if editor.context_menu.borrow_mut().is_some() {
17064            panic!(
17065                "expected completion menu to be hidden, as words completion threshold is not met"
17066            );
17067        }
17068    });
17069
17070    cx.update_editor(|editor, window, cx| {
17071        editor.show_word_completions(&ShowWordCompletions, window, cx);
17072    });
17073    cx.executor().run_until_parked();
17074    cx.update_editor(|editor, window, cx| {
17075        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17076        {
17077            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");
17078        } else {
17079            panic!("expected completion menu to be open after the word completions are called with an action");
17080        }
17081
17082        editor.cancel(&Cancel, window, cx);
17083    });
17084    cx.update_editor(|editor, _, _| {
17085        if editor.context_menu.borrow_mut().is_some() {
17086            panic!("expected completion menu to be hidden after canceling");
17087        }
17088    });
17089
17090    cx.simulate_keystroke("o");
17091    cx.executor().run_until_parked();
17092    cx.update_editor(|editor, _, _| {
17093        if editor.context_menu.borrow_mut().is_some() {
17094            panic!(
17095                "expected completion menu to be hidden, as words completion threshold is not met still"
17096            );
17097        }
17098    });
17099
17100    cx.simulate_keystroke("w");
17101    cx.executor().run_until_parked();
17102    cx.update_editor(|editor, _, _| {
17103        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17104        {
17105            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
17106        } else {
17107            panic!("expected completion menu to be open after the word completions threshold is met");
17108        }
17109    });
17110}
17111
17112#[gpui::test]
17113async fn test_word_completions_disabled(cx: &mut TestAppContext) {
17114    init_test(cx, |language_settings| {
17115        language_settings.defaults.completions = Some(CompletionSettingsContent {
17116            words: Some(WordsCompletionMode::Enabled),
17117            words_min_length: Some(0),
17118            lsp_insert_mode: Some(LspInsertMode::Insert),
17119            ..Default::default()
17120        });
17121    });
17122
17123    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17124    cx.update_editor(|editor, _, _| {
17125        editor.disable_word_completions();
17126    });
17127    cx.set_state(indoc! {"ˇ
17128        wow
17129        wowen
17130        wowser
17131    "});
17132    cx.simulate_keystroke("w");
17133    cx.executor().run_until_parked();
17134    cx.update_editor(|editor, _, _| {
17135        if editor.context_menu.borrow_mut().is_some() {
17136            panic!(
17137                "expected completion menu to be hidden, as words completion are disabled for this editor"
17138            );
17139        }
17140    });
17141
17142    cx.update_editor(|editor, window, cx| {
17143        editor.show_word_completions(&ShowWordCompletions, window, cx);
17144    });
17145    cx.executor().run_until_parked();
17146    cx.update_editor(|editor, _, _| {
17147        if editor.context_menu.borrow_mut().is_some() {
17148            panic!(
17149                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
17150            );
17151        }
17152    });
17153}
17154
17155#[gpui::test]
17156async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
17157    init_test(cx, |language_settings| {
17158        language_settings.defaults.completions = Some(CompletionSettingsContent {
17159            words: Some(WordsCompletionMode::Disabled),
17160            words_min_length: Some(0),
17161            lsp_insert_mode: Some(LspInsertMode::Insert),
17162            ..Default::default()
17163        });
17164    });
17165
17166    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17167    cx.update_editor(|editor, _, _| {
17168        editor.set_completion_provider(None);
17169    });
17170    cx.set_state(indoc! {"ˇ
17171        wow
17172        wowen
17173        wowser
17174    "});
17175    cx.simulate_keystroke("w");
17176    cx.executor().run_until_parked();
17177    cx.update_editor(|editor, _, _| {
17178        if editor.context_menu.borrow_mut().is_some() {
17179            panic!("expected completion menu to be hidden, as disabled in settings");
17180        }
17181    });
17182}
17183
17184fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
17185    let position = || lsp::Position {
17186        line: params.text_document_position.position.line,
17187        character: params.text_document_position.position.character,
17188    };
17189    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17190        range: lsp::Range {
17191            start: position(),
17192            end: position(),
17193        },
17194        new_text: text.to_string(),
17195    }))
17196}
17197
17198#[gpui::test]
17199async fn test_multiline_completion(cx: &mut TestAppContext) {
17200    init_test(cx, |_| {});
17201
17202    let fs = FakeFs::new(cx.executor());
17203    fs.insert_tree(
17204        path!("/a"),
17205        json!({
17206            "main.ts": "a",
17207        }),
17208    )
17209    .await;
17210
17211    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17212    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17213    let typescript_language = Arc::new(Language::new(
17214        LanguageConfig {
17215            name: "TypeScript".into(),
17216            matcher: LanguageMatcher {
17217                path_suffixes: vec!["ts".to_string()],
17218                ..LanguageMatcher::default()
17219            },
17220            line_comments: vec!["// ".into()],
17221            ..LanguageConfig::default()
17222        },
17223        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17224    ));
17225    language_registry.add(typescript_language.clone());
17226    let mut fake_servers = language_registry.register_fake_lsp(
17227        "TypeScript",
17228        FakeLspAdapter {
17229            capabilities: lsp::ServerCapabilities {
17230                completion_provider: Some(lsp::CompletionOptions {
17231                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
17232                    ..lsp::CompletionOptions::default()
17233                }),
17234                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
17235                ..lsp::ServerCapabilities::default()
17236            },
17237            // Emulate vtsls label generation
17238            label_for_completion: Some(Box::new(|item, _| {
17239                let text = if let Some(description) = item
17240                    .label_details
17241                    .as_ref()
17242                    .and_then(|label_details| label_details.description.as_ref())
17243                {
17244                    format!("{} {}", item.label, description)
17245                } else if let Some(detail) = &item.detail {
17246                    format!("{} {}", item.label, detail)
17247                } else {
17248                    item.label.clone()
17249                };
17250                Some(language::CodeLabel::plain(text, None))
17251            })),
17252            ..FakeLspAdapter::default()
17253        },
17254    );
17255    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
17256    let workspace = window
17257        .read_with(cx, |mw, _| mw.workspace().clone())
17258        .unwrap();
17259    let cx = &mut VisualTestContext::from_window(*window, cx);
17260    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
17261        workspace.project().update(cx, |project, cx| {
17262            project.worktrees(cx).next().unwrap().read(cx).id()
17263        })
17264    });
17265
17266    let _buffer = project
17267        .update(cx, |project, cx| {
17268            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
17269        })
17270        .await
17271        .unwrap();
17272    let editor = workspace
17273        .update_in(cx, |workspace, window, cx| {
17274            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
17275        })
17276        .await
17277        .unwrap()
17278        .downcast::<Editor>()
17279        .unwrap();
17280    let fake_server = fake_servers.next().await.unwrap();
17281    cx.run_until_parked();
17282
17283    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
17284    let multiline_label_2 = "a\nb\nc\n";
17285    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
17286    let multiline_description = "d\ne\nf\n";
17287    let multiline_detail_2 = "g\nh\ni\n";
17288
17289    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
17290        move |params, _| async move {
17291            Ok(Some(lsp::CompletionResponse::Array(vec![
17292                lsp::CompletionItem {
17293                    label: multiline_label.to_string(),
17294                    text_edit: gen_text_edit(&params, "new_text_1"),
17295                    ..lsp::CompletionItem::default()
17296                },
17297                lsp::CompletionItem {
17298                    label: "single line label 1".to_string(),
17299                    detail: Some(multiline_detail.to_string()),
17300                    text_edit: gen_text_edit(&params, "new_text_2"),
17301                    ..lsp::CompletionItem::default()
17302                },
17303                lsp::CompletionItem {
17304                    label: "single line label 2".to_string(),
17305                    label_details: Some(lsp::CompletionItemLabelDetails {
17306                        description: Some(multiline_description.to_string()),
17307                        detail: None,
17308                    }),
17309                    text_edit: gen_text_edit(&params, "new_text_2"),
17310                    ..lsp::CompletionItem::default()
17311                },
17312                lsp::CompletionItem {
17313                    label: multiline_label_2.to_string(),
17314                    detail: Some(multiline_detail_2.to_string()),
17315                    text_edit: gen_text_edit(&params, "new_text_3"),
17316                    ..lsp::CompletionItem::default()
17317                },
17318                lsp::CompletionItem {
17319                    label: "Label with many     spaces and \t but without newlines".to_string(),
17320                    detail: Some(
17321                        "Details with many     spaces and \t but without newlines".to_string(),
17322                    ),
17323                    text_edit: gen_text_edit(&params, "new_text_4"),
17324                    ..lsp::CompletionItem::default()
17325                },
17326            ])))
17327        },
17328    );
17329
17330    editor.update_in(cx, |editor, window, cx| {
17331        cx.focus_self(window);
17332        editor.move_to_end(&MoveToEnd, window, cx);
17333        editor.handle_input(".", window, cx);
17334    });
17335    cx.run_until_parked();
17336    completion_handle.next().await.unwrap();
17337
17338    editor.update(cx, |editor, _| {
17339        assert!(editor.context_menu_visible());
17340        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17341        {
17342            let completion_labels = menu
17343                .completions
17344                .borrow()
17345                .iter()
17346                .map(|c| c.label.text.clone())
17347                .collect::<Vec<_>>();
17348            assert_eq!(
17349                completion_labels,
17350                &[
17351                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
17352                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
17353                    "single line label 2 d e f ",
17354                    "a b c g h i ",
17355                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
17356                ],
17357                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
17358            );
17359
17360            for completion in menu
17361                .completions
17362                .borrow()
17363                .iter() {
17364                    assert_eq!(
17365                        completion.label.filter_range,
17366                        0..completion.label.text.len(),
17367                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
17368                    );
17369                }
17370        } else {
17371            panic!("expected completion menu to be open");
17372        }
17373    });
17374}
17375
17376#[gpui::test]
17377async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
17378    init_test(cx, |_| {});
17379    let mut cx = EditorLspTestContext::new_rust(
17380        lsp::ServerCapabilities {
17381            completion_provider: Some(lsp::CompletionOptions {
17382                trigger_characters: Some(vec![".".to_string()]),
17383                ..Default::default()
17384            }),
17385            ..Default::default()
17386        },
17387        cx,
17388    )
17389    .await;
17390    cx.lsp
17391        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17392            Ok(Some(lsp::CompletionResponse::Array(vec![
17393                lsp::CompletionItem {
17394                    label: "first".into(),
17395                    ..Default::default()
17396                },
17397                lsp::CompletionItem {
17398                    label: "last".into(),
17399                    ..Default::default()
17400                },
17401            ])))
17402        });
17403    cx.set_state("variableˇ");
17404    cx.simulate_keystroke(".");
17405    cx.executor().run_until_parked();
17406
17407    cx.update_editor(|editor, _, _| {
17408        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17409        {
17410            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
17411        } else {
17412            panic!("expected completion menu to be open");
17413        }
17414    });
17415
17416    cx.update_editor(|editor, window, cx| {
17417        editor.move_page_down(&MovePageDown::default(), window, cx);
17418        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17419        {
17420            assert!(
17421                menu.selected_item == 1,
17422                "expected PageDown to select the last item from the context menu"
17423            );
17424        } else {
17425            panic!("expected completion menu to stay open after PageDown");
17426        }
17427    });
17428
17429    cx.update_editor(|editor, window, cx| {
17430        editor.move_page_up(&MovePageUp::default(), window, cx);
17431        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17432        {
17433            assert!(
17434                menu.selected_item == 0,
17435                "expected PageUp to select the first item from the context menu"
17436            );
17437        } else {
17438            panic!("expected completion menu to stay open after PageUp");
17439        }
17440    });
17441}
17442
17443#[gpui::test]
17444async fn test_as_is_completions(cx: &mut TestAppContext) {
17445    init_test(cx, |_| {});
17446    let mut cx = EditorLspTestContext::new_rust(
17447        lsp::ServerCapabilities {
17448            completion_provider: Some(lsp::CompletionOptions {
17449                ..Default::default()
17450            }),
17451            ..Default::default()
17452        },
17453        cx,
17454    )
17455    .await;
17456    cx.lsp
17457        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17458            Ok(Some(lsp::CompletionResponse::Array(vec![
17459                lsp::CompletionItem {
17460                    label: "unsafe".into(),
17461                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17462                        range: lsp::Range {
17463                            start: lsp::Position {
17464                                line: 1,
17465                                character: 2,
17466                            },
17467                            end: lsp::Position {
17468                                line: 1,
17469                                character: 3,
17470                            },
17471                        },
17472                        new_text: "unsafe".to_string(),
17473                    })),
17474                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
17475                    ..Default::default()
17476                },
17477            ])))
17478        });
17479    cx.set_state("fn a() {}\n");
17480    cx.executor().run_until_parked();
17481    cx.update_editor(|editor, window, cx| {
17482        editor.trigger_completion_on_input("n", true, window, cx)
17483    });
17484    cx.executor().run_until_parked();
17485
17486    cx.update_editor(|editor, window, cx| {
17487        editor.confirm_completion(&Default::default(), window, cx)
17488    });
17489    cx.executor().run_until_parked();
17490    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
17491}
17492
17493#[gpui::test]
17494async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
17495    init_test(cx, |_| {});
17496    let language =
17497        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
17498    let mut cx = EditorLspTestContext::new(
17499        language,
17500        lsp::ServerCapabilities {
17501            completion_provider: Some(lsp::CompletionOptions {
17502                ..lsp::CompletionOptions::default()
17503            }),
17504            ..lsp::ServerCapabilities::default()
17505        },
17506        cx,
17507    )
17508    .await;
17509
17510    cx.set_state(
17511        "#ifndef BAR_H
17512#define BAR_H
17513
17514#include <stdbool.h>
17515
17516int fn_branch(bool do_branch1, bool do_branch2);
17517
17518#endif // BAR_H
17519ˇ",
17520    );
17521    cx.executor().run_until_parked();
17522    cx.update_editor(|editor, window, cx| {
17523        editor.handle_input("#", window, cx);
17524    });
17525    cx.executor().run_until_parked();
17526    cx.update_editor(|editor, window, cx| {
17527        editor.handle_input("i", window, cx);
17528    });
17529    cx.executor().run_until_parked();
17530    cx.update_editor(|editor, window, cx| {
17531        editor.handle_input("n", window, cx);
17532    });
17533    cx.executor().run_until_parked();
17534    cx.assert_editor_state(
17535        "#ifndef BAR_H
17536#define BAR_H
17537
17538#include <stdbool.h>
17539
17540int fn_branch(bool do_branch1, bool do_branch2);
17541
17542#endif // BAR_H
17543#inˇ",
17544    );
17545
17546    cx.lsp
17547        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17548            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17549                is_incomplete: false,
17550                item_defaults: None,
17551                items: vec![lsp::CompletionItem {
17552                    kind: Some(lsp::CompletionItemKind::SNIPPET),
17553                    label_details: Some(lsp::CompletionItemLabelDetails {
17554                        detail: Some("header".to_string()),
17555                        description: None,
17556                    }),
17557                    label: " include".to_string(),
17558                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17559                        range: lsp::Range {
17560                            start: lsp::Position {
17561                                line: 8,
17562                                character: 1,
17563                            },
17564                            end: lsp::Position {
17565                                line: 8,
17566                                character: 1,
17567                            },
17568                        },
17569                        new_text: "include \"$0\"".to_string(),
17570                    })),
17571                    sort_text: Some("40b67681include".to_string()),
17572                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17573                    filter_text: Some("include".to_string()),
17574                    insert_text: Some("include \"$0\"".to_string()),
17575                    ..lsp::CompletionItem::default()
17576                }],
17577            })))
17578        });
17579    cx.update_editor(|editor, window, cx| {
17580        editor.show_completions(&ShowCompletions, window, cx);
17581    });
17582    cx.executor().run_until_parked();
17583    cx.update_editor(|editor, window, cx| {
17584        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
17585    });
17586    cx.executor().run_until_parked();
17587    cx.assert_editor_state(
17588        "#ifndef BAR_H
17589#define BAR_H
17590
17591#include <stdbool.h>
17592
17593int fn_branch(bool do_branch1, bool do_branch2);
17594
17595#endif // BAR_H
17596#include \"ˇ\"",
17597    );
17598
17599    cx.lsp
17600        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17601            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17602                is_incomplete: true,
17603                item_defaults: None,
17604                items: vec![lsp::CompletionItem {
17605                    kind: Some(lsp::CompletionItemKind::FILE),
17606                    label: "AGL/".to_string(),
17607                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17608                        range: lsp::Range {
17609                            start: lsp::Position {
17610                                line: 8,
17611                                character: 10,
17612                            },
17613                            end: lsp::Position {
17614                                line: 8,
17615                                character: 11,
17616                            },
17617                        },
17618                        new_text: "AGL/".to_string(),
17619                    })),
17620                    sort_text: Some("40b67681AGL/".to_string()),
17621                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17622                    filter_text: Some("AGL/".to_string()),
17623                    insert_text: Some("AGL/".to_string()),
17624                    ..lsp::CompletionItem::default()
17625                }],
17626            })))
17627        });
17628    cx.update_editor(|editor, window, cx| {
17629        editor.show_completions(&ShowCompletions, window, cx);
17630    });
17631    cx.executor().run_until_parked();
17632    cx.update_editor(|editor, window, cx| {
17633        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
17634    });
17635    cx.executor().run_until_parked();
17636    cx.assert_editor_state(
17637        r##"#ifndef BAR_H
17638#define BAR_H
17639
17640#include <stdbool.h>
17641
17642int fn_branch(bool do_branch1, bool do_branch2);
17643
17644#endif // BAR_H
17645#include "AGL/ˇ"##,
17646    );
17647
17648    cx.update_editor(|editor, window, cx| {
17649        editor.handle_input("\"", window, cx);
17650    });
17651    cx.executor().run_until_parked();
17652    cx.assert_editor_state(
17653        r##"#ifndef BAR_H
17654#define BAR_H
17655
17656#include <stdbool.h>
17657
17658int fn_branch(bool do_branch1, bool do_branch2);
17659
17660#endif // BAR_H
17661#include "AGL/"ˇ"##,
17662    );
17663}
17664
17665#[gpui::test]
17666async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
17667    init_test(cx, |_| {});
17668
17669    let mut cx = EditorLspTestContext::new_rust(
17670        lsp::ServerCapabilities {
17671            completion_provider: Some(lsp::CompletionOptions {
17672                trigger_characters: Some(vec![".".to_string()]),
17673                resolve_provider: Some(false),
17674                ..lsp::CompletionOptions::default()
17675            }),
17676            ..lsp::ServerCapabilities::default()
17677        },
17678        cx,
17679    )
17680    .await;
17681
17682    cx.set_state("fn main() { let a = 2ˇ; }");
17683    cx.simulate_keystroke(".");
17684    let completion_item = lsp::CompletionItem {
17685        label: "Some".into(),
17686        kind: Some(lsp::CompletionItemKind::SNIPPET),
17687        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17688        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17689            kind: lsp::MarkupKind::Markdown,
17690            value: "```rust\nSome(2)\n```".to_string(),
17691        })),
17692        deprecated: Some(false),
17693        sort_text: Some("Some".to_string()),
17694        filter_text: Some("Some".to_string()),
17695        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17696        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17697            range: lsp::Range {
17698                start: lsp::Position {
17699                    line: 0,
17700                    character: 22,
17701                },
17702                end: lsp::Position {
17703                    line: 0,
17704                    character: 22,
17705                },
17706            },
17707            new_text: "Some(2)".to_string(),
17708        })),
17709        additional_text_edits: Some(vec![lsp::TextEdit {
17710            range: lsp::Range {
17711                start: lsp::Position {
17712                    line: 0,
17713                    character: 20,
17714                },
17715                end: lsp::Position {
17716                    line: 0,
17717                    character: 22,
17718                },
17719            },
17720            new_text: "".to_string(),
17721        }]),
17722        ..Default::default()
17723    };
17724
17725    let closure_completion_item = completion_item.clone();
17726    let counter = Arc::new(AtomicUsize::new(0));
17727    let counter_clone = counter.clone();
17728    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17729        let task_completion_item = closure_completion_item.clone();
17730        counter_clone.fetch_add(1, atomic::Ordering::Release);
17731        async move {
17732            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17733                is_incomplete: true,
17734                item_defaults: None,
17735                items: vec![task_completion_item],
17736            })))
17737        }
17738    });
17739
17740    cx.executor().run_until_parked();
17741    cx.condition(|editor, _| editor.context_menu_visible())
17742        .await;
17743    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
17744    assert!(request.next().await.is_some());
17745    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
17746
17747    cx.simulate_keystrokes("S o m");
17748    cx.condition(|editor, _| editor.context_menu_visible())
17749        .await;
17750    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
17751    assert!(request.next().await.is_some());
17752    assert!(request.next().await.is_some());
17753    assert!(request.next().await.is_some());
17754    request.close();
17755    assert!(request.next().await.is_none());
17756    assert_eq!(
17757        counter.load(atomic::Ordering::Acquire),
17758        4,
17759        "With the completions menu open, only one LSP request should happen per input"
17760    );
17761}
17762
17763#[gpui::test]
17764async fn test_toggle_comment(cx: &mut TestAppContext) {
17765    init_test(cx, |_| {});
17766    let mut cx = EditorTestContext::new(cx).await;
17767    let language = Arc::new(Language::new(
17768        LanguageConfig {
17769            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
17770            ..Default::default()
17771        },
17772        Some(tree_sitter_rust::LANGUAGE.into()),
17773    ));
17774    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17775
17776    // If multiple selections intersect a line, the line is only toggled once.
17777    cx.set_state(indoc! {"
17778        fn a() {
17779            «//b();
17780            ˇ»// «c();
17781            //ˇ»  d();
17782        }
17783    "});
17784
17785    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17786
17787    cx.assert_editor_state(indoc! {"
17788        fn a() {
17789            «b();
17790            ˇ»«c();
17791            ˇ» d();
17792        }
17793    "});
17794
17795    // The comment prefix is inserted at the same column for every line in a
17796    // selection.
17797    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17798
17799    cx.assert_editor_state(indoc! {"
17800        fn a() {
17801            // «b();
17802            ˇ»// «c();
17803            ˇ» // d();
17804        }
17805    "});
17806
17807    // If a selection ends at the beginning of a line, that line is not toggled.
17808    cx.set_selections_state(indoc! {"
17809        fn a() {
17810            // b();
17811            «// c();
17812        ˇ»     // d();
17813        }
17814    "});
17815
17816    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17817
17818    cx.assert_editor_state(indoc! {"
17819        fn a() {
17820            // b();
17821            «c();
17822        ˇ»     // d();
17823        }
17824    "});
17825
17826    // If a selection span a single line and is empty, the line is toggled.
17827    cx.set_state(indoc! {"
17828        fn a() {
17829            a();
17830            b();
17831        ˇ
17832        }
17833    "});
17834
17835    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17836
17837    cx.assert_editor_state(indoc! {"
17838        fn a() {
17839            a();
17840            b();
17841        //•ˇ
17842        }
17843    "});
17844
17845    // If a selection span multiple lines, empty lines are not toggled.
17846    cx.set_state(indoc! {"
17847        fn a() {
17848            «a();
17849
17850            c();ˇ»
17851        }
17852    "});
17853
17854    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17855
17856    cx.assert_editor_state(indoc! {"
17857        fn a() {
17858            // «a();
17859
17860            // c();ˇ»
17861        }
17862    "});
17863
17864    // If a selection includes multiple comment prefixes, all lines are uncommented.
17865    cx.set_state(indoc! {"
17866        fn a() {
17867            «// a();
17868            /// b();
17869            //! c();ˇ»
17870        }
17871    "});
17872
17873    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
17874
17875    cx.assert_editor_state(indoc! {"
17876        fn a() {
17877            «a();
17878            b();
17879            c();ˇ»
17880        }
17881    "});
17882}
17883
17884#[gpui::test]
17885async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
17886    init_test(cx, |_| {});
17887    let mut cx = EditorTestContext::new(cx).await;
17888    let language = Arc::new(Language::new(
17889        LanguageConfig {
17890            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
17891            ..Default::default()
17892        },
17893        Some(tree_sitter_rust::LANGUAGE.into()),
17894    ));
17895    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17896
17897    let toggle_comments = &ToggleComments {
17898        advance_downwards: false,
17899        ignore_indent: true,
17900    };
17901
17902    // If multiple selections intersect a line, the line is only toggled once.
17903    cx.set_state(indoc! {"
17904        fn a() {
17905        //    «b();
17906        //    c();
17907        //    ˇ» d();
17908        }
17909    "});
17910
17911    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17912
17913    cx.assert_editor_state(indoc! {"
17914        fn a() {
17915            «b();
17916            c();
17917            ˇ» d();
17918        }
17919    "});
17920
17921    // The comment prefix is inserted at the beginning of each line
17922    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17923
17924    cx.assert_editor_state(indoc! {"
17925        fn a() {
17926        //    «b();
17927        //    c();
17928        //    ˇ» d();
17929        }
17930    "});
17931
17932    // If a selection ends at the beginning of a line, that line is not toggled.
17933    cx.set_selections_state(indoc! {"
17934        fn a() {
17935        //    b();
17936        //    «c();
17937        ˇ»//     d();
17938        }
17939    "});
17940
17941    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17942
17943    cx.assert_editor_state(indoc! {"
17944        fn a() {
17945        //    b();
17946            «c();
17947        ˇ»//     d();
17948        }
17949    "});
17950
17951    // If a selection span a single line and is empty, the line is toggled.
17952    cx.set_state(indoc! {"
17953        fn a() {
17954            a();
17955            b();
17956        ˇ
17957        }
17958    "});
17959
17960    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17961
17962    cx.assert_editor_state(indoc! {"
17963        fn a() {
17964            a();
17965            b();
17966        //ˇ
17967        }
17968    "});
17969
17970    // If a selection span multiple lines, empty lines are not toggled.
17971    cx.set_state(indoc! {"
17972        fn a() {
17973            «a();
17974
17975            c();ˇ»
17976        }
17977    "});
17978
17979    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17980
17981    cx.assert_editor_state(indoc! {"
17982        fn a() {
17983        //    «a();
17984
17985        //    c();ˇ»
17986        }
17987    "});
17988
17989    // If a selection includes multiple comment prefixes, all lines are uncommented.
17990    cx.set_state(indoc! {"
17991        fn a() {
17992        //    «a();
17993        ///    b();
17994        //!    c();ˇ»
17995        }
17996    "});
17997
17998    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
17999
18000    cx.assert_editor_state(indoc! {"
18001        fn a() {
18002            «a();
18003            b();
18004            c();ˇ»
18005        }
18006    "});
18007}
18008
18009#[gpui::test]
18010async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
18011    init_test(cx, |_| {});
18012
18013    let language = Arc::new(Language::new(
18014        LanguageConfig {
18015            line_comments: vec!["// ".into()],
18016            ..Default::default()
18017        },
18018        Some(tree_sitter_rust::LANGUAGE.into()),
18019    ));
18020
18021    let mut cx = EditorTestContext::new(cx).await;
18022
18023    cx.language_registry().add(language.clone());
18024    cx.update_buffer(|buffer, cx| {
18025        buffer.set_language(Some(language), cx);
18026    });
18027
18028    let toggle_comments = &ToggleComments {
18029        advance_downwards: true,
18030        ignore_indent: false,
18031    };
18032
18033    // Single cursor on one line -> advance
18034    // Cursor moves horizontally 3 characters as well on non-blank line
18035    cx.set_state(indoc!(
18036        "fn a() {
18037             ˇdog();
18038             cat();
18039        }"
18040    ));
18041    cx.update_editor(|editor, window, cx| {
18042        editor.toggle_comments(toggle_comments, window, cx);
18043    });
18044    cx.assert_editor_state(indoc!(
18045        "fn a() {
18046             // dog();
18047             catˇ();
18048        }"
18049    ));
18050
18051    // Single selection on one line -> don't advance
18052    cx.set_state(indoc!(
18053        "fn a() {
18054             «dog()ˇ»;
18055             cat();
18056        }"
18057    ));
18058    cx.update_editor(|editor, window, cx| {
18059        editor.toggle_comments(toggle_comments, window, cx);
18060    });
18061    cx.assert_editor_state(indoc!(
18062        "fn a() {
18063             // «dog()ˇ»;
18064             cat();
18065        }"
18066    ));
18067
18068    // Multiple cursors on one line -> advance
18069    cx.set_state(indoc!(
18070        "fn a() {
18071             ˇdˇog();
18072             cat();
18073        }"
18074    ));
18075    cx.update_editor(|editor, window, cx| {
18076        editor.toggle_comments(toggle_comments, window, cx);
18077    });
18078    cx.assert_editor_state(indoc!(
18079        "fn a() {
18080             // dog();
18081             catˇ(ˇ);
18082        }"
18083    ));
18084
18085    // Multiple cursors on one line, with selection -> don't advance
18086    cx.set_state(indoc!(
18087        "fn a() {
18088             ˇdˇog«()ˇ»;
18089             cat();
18090        }"
18091    ));
18092    cx.update_editor(|editor, window, cx| {
18093        editor.toggle_comments(toggle_comments, window, cx);
18094    });
18095    cx.assert_editor_state(indoc!(
18096        "fn a() {
18097             // ˇdˇog«()ˇ»;
18098             cat();
18099        }"
18100    ));
18101
18102    // Single cursor on one line -> advance
18103    // Cursor moves to column 0 on blank line
18104    cx.set_state(indoc!(
18105        "fn a() {
18106             ˇdog();
18107
18108             cat();
18109        }"
18110    ));
18111    cx.update_editor(|editor, window, cx| {
18112        editor.toggle_comments(toggle_comments, window, cx);
18113    });
18114    cx.assert_editor_state(indoc!(
18115        "fn a() {
18116             // dog();
18117        ˇ
18118             cat();
18119        }"
18120    ));
18121
18122    // Single cursor on one line -> advance
18123    // Cursor starts and ends at column 0
18124    cx.set_state(indoc!(
18125        "fn a() {
18126         ˇ    dog();
18127             cat();
18128        }"
18129    ));
18130    cx.update_editor(|editor, window, cx| {
18131        editor.toggle_comments(toggle_comments, window, cx);
18132    });
18133    cx.assert_editor_state(indoc!(
18134        "fn a() {
18135             // dog();
18136         ˇ    cat();
18137        }"
18138    ));
18139}
18140
18141#[gpui::test]
18142async fn test_toggle_block_comment(cx: &mut TestAppContext) {
18143    init_test(cx, |_| {});
18144
18145    let mut cx = EditorTestContext::new(cx).await;
18146
18147    let html_language = Arc::new(
18148        Language::new(
18149            LanguageConfig {
18150                name: "HTML".into(),
18151                block_comment: Some(BlockCommentConfig {
18152                    start: "<!-- ".into(),
18153                    prefix: "".into(),
18154                    end: " -->".into(),
18155                    tab_size: 0,
18156                }),
18157                ..Default::default()
18158            },
18159            Some(tree_sitter_html::LANGUAGE.into()),
18160        )
18161        .with_injection_query(
18162            r#"
18163            (script_element
18164                (raw_text) @injection.content
18165                (#set! injection.language "javascript"))
18166            "#,
18167        )
18168        .unwrap(),
18169    );
18170
18171    let javascript_language = Arc::new(Language::new(
18172        LanguageConfig {
18173            name: "JavaScript".into(),
18174            line_comments: vec!["// ".into()],
18175            ..Default::default()
18176        },
18177        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18178    ));
18179
18180    cx.language_registry().add(html_language.clone());
18181    cx.language_registry().add(javascript_language);
18182    cx.update_buffer(|buffer, cx| {
18183        buffer.set_language(Some(html_language), cx);
18184    });
18185
18186    // Toggle comments for empty selections
18187    cx.set_state(
18188        &r#"
18189            <p>A</p>ˇ
18190            <p>B</p>ˇ
18191            <p>C</p>ˇ
18192        "#
18193        .unindent(),
18194    );
18195    cx.update_editor(|editor, window, cx| {
18196        editor.toggle_comments(&ToggleComments::default(), window, cx)
18197    });
18198    cx.assert_editor_state(
18199        &r#"
18200            <!-- <p>A</p>ˇ -->
18201            <!-- <p>B</p>ˇ -->
18202            <!-- <p>C</p>ˇ -->
18203        "#
18204        .unindent(),
18205    );
18206    cx.update_editor(|editor, window, cx| {
18207        editor.toggle_comments(&ToggleComments::default(), window, cx)
18208    });
18209    cx.assert_editor_state(
18210        &r#"
18211            <p>A</p>ˇ
18212            <p>B</p>ˇ
18213            <p>C</p>ˇ
18214        "#
18215        .unindent(),
18216    );
18217
18218    // Toggle comments for mixture of empty and non-empty selections, where
18219    // multiple selections occupy a given line.
18220    cx.set_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    cx.update_editor(|editor, window, cx| {
18231        editor.toggle_comments(&ToggleComments::default(), window, cx)
18232    });
18233    cx.assert_editor_state(
18234        &r#"
18235            <!-- <p>A«</p>
18236            <p>ˇ»B</p>ˇ -->
18237            <!-- <p>C«</p>
18238            <p>ˇ»D</p>ˇ -->
18239        "#
18240        .unindent(),
18241    );
18242    cx.update_editor(|editor, window, cx| {
18243        editor.toggle_comments(&ToggleComments::default(), window, cx)
18244    });
18245    cx.assert_editor_state(
18246        &r#"
18247            <p>A«</p>
18248            <p>ˇ»B</p>ˇ
18249            <p>C«</p>
18250            <p>ˇ»D</p>ˇ
18251        "#
18252        .unindent(),
18253    );
18254
18255    // Toggle comments when different languages are active for different
18256    // selections.
18257    cx.set_state(
18258        &r#"
18259            ˇ<script>
18260                ˇvar x = new Y();
18261            ˇ</script>
18262        "#
18263        .unindent(),
18264    );
18265    cx.executor().run_until_parked();
18266    cx.update_editor(|editor, window, cx| {
18267        editor.toggle_comments(&ToggleComments::default(), window, cx)
18268    });
18269    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
18270    // Uncommenting and commenting from this position brings in even more wrong artifacts.
18271    cx.assert_editor_state(
18272        &r#"
18273            <!-- ˇ<script> -->
18274                // ˇvar x = new Y();
18275            <!-- ˇ</script> -->
18276        "#
18277        .unindent(),
18278    );
18279}
18280
18281#[gpui::test]
18282fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
18283    init_test(cx, |_| {});
18284
18285    let buffer = cx.new(|cx| Buffer::local(sample_text(6, 4, 'a'), cx));
18286    let multibuffer = cx.new(|cx| {
18287        let mut multibuffer = MultiBuffer::new(ReadWrite);
18288        multibuffer.set_excerpts_for_path(
18289            PathKey::sorted(0),
18290            buffer.clone(),
18291            [
18292                Point::new(0, 0)..Point::new(0, 4),
18293                Point::new(5, 0)..Point::new(5, 4),
18294            ],
18295            0,
18296            cx,
18297        );
18298        assert_eq!(multibuffer.read(cx).text(), "aaaa\nffff");
18299        multibuffer
18300    });
18301
18302    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
18303    editor.update_in(cx, |editor, window, cx| {
18304        assert_eq!(editor.text(cx), "aaaa\nffff");
18305        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18306            s.select_ranges([
18307                Point::new(0, 0)..Point::new(0, 0),
18308                Point::new(1, 0)..Point::new(1, 0),
18309            ])
18310        });
18311
18312        editor.handle_input("X", window, cx);
18313        assert_eq!(editor.text(cx), "Xaaaa\nXffff");
18314        assert_eq!(
18315            editor.selections.ranges(&editor.display_snapshot(cx)),
18316            [
18317                Point::new(0, 1)..Point::new(0, 1),
18318                Point::new(1, 1)..Point::new(1, 1),
18319            ]
18320        );
18321
18322        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
18323        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18324            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
18325        });
18326        editor.backspace(&Default::default(), window, cx);
18327        assert_eq!(editor.text(cx), "Xa\nfff");
18328        assert_eq!(
18329            editor.selections.ranges(&editor.display_snapshot(cx)),
18330            [Point::new(1, 0)..Point::new(1, 0)]
18331        );
18332
18333        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18334            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
18335        });
18336        editor.backspace(&Default::default(), window, cx);
18337        assert_eq!(editor.text(cx), "X\nff");
18338        assert_eq!(
18339            editor.selections.ranges(&editor.display_snapshot(cx)),
18340            [Point::new(0, 1)..Point::new(0, 1)]
18341        );
18342    });
18343}
18344
18345#[gpui::test]
18346fn test_refresh_selections(cx: &mut TestAppContext) {
18347    init_test(cx, |_| {});
18348
18349    let buffer = cx.new(|cx| Buffer::local(sample_text(5, 4, 'a'), cx));
18350    let multibuffer = cx.new(|cx| {
18351        let mut multibuffer = MultiBuffer::new(ReadWrite);
18352        multibuffer.set_excerpts_for_path(
18353            PathKey::sorted(0),
18354            buffer.clone(),
18355            [
18356                Point::new(0, 0)..Point::new(1, 4),
18357                Point::new(3, 0)..Point::new(4, 4),
18358            ],
18359            0,
18360            cx,
18361        );
18362        multibuffer
18363    });
18364
18365    let editor = cx.add_window(|window, cx| {
18366        let mut editor = build_editor(multibuffer.clone(), window, cx);
18367        let snapshot = editor.snapshot(window, cx);
18368        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18369            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
18370        });
18371        editor.begin_selection(
18372            Point::new(2, 1).to_display_point(&snapshot),
18373            true,
18374            1,
18375            window,
18376            cx,
18377        );
18378        assert_eq!(
18379            editor.selections.ranges(&editor.display_snapshot(cx)),
18380            [
18381                Point::new(1, 3)..Point::new(1, 3),
18382                Point::new(2, 1)..Point::new(2, 1),
18383            ]
18384        );
18385        editor
18386    });
18387
18388    // Refreshing selections is a no-op when excerpts haven't changed.
18389    _ = editor.update(cx, |editor, window, cx| {
18390        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
18391        assert_eq!(
18392            editor.selections.ranges(&editor.display_snapshot(cx)),
18393            [
18394                Point::new(1, 3)..Point::new(1, 3),
18395                Point::new(2, 1)..Point::new(2, 1),
18396            ]
18397        );
18398    });
18399
18400    multibuffer.update(cx, |multibuffer, cx| {
18401        multibuffer.set_excerpts_for_path(
18402            PathKey::sorted(0),
18403            buffer.clone(),
18404            [Point::new(3, 0)..Point::new(4, 4)],
18405            0,
18406            cx,
18407        );
18408    });
18409    _ = editor.update(cx, |editor, window, cx| {
18410        // Removing an excerpt causes the first selection to become degenerate.
18411        assert_eq!(
18412            editor.selections.ranges(&editor.display_snapshot(cx)),
18413            [
18414                Point::new(0, 0)..Point::new(0, 0),
18415                Point::new(0, 1)..Point::new(0, 1)
18416            ]
18417        );
18418
18419        // Refreshing selections will relocate the first selection to the original buffer
18420        // location.
18421        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
18422        assert_eq!(
18423            editor.selections.ranges(&editor.display_snapshot(cx)),
18424            [
18425                Point::new(0, 0)..Point::new(0, 0),
18426                Point::new(0, 1)..Point::new(0, 1),
18427            ]
18428        );
18429        assert!(editor.selections.pending_anchor().is_some());
18430    });
18431}
18432
18433#[gpui::test]
18434fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
18435    init_test(cx, |_| {});
18436
18437    let buffer = cx.new(|cx| Buffer::local(sample_text(5, 4, 'a'), cx));
18438    let multibuffer = cx.new(|cx| {
18439        let mut multibuffer = MultiBuffer::new(ReadWrite);
18440        multibuffer.set_excerpts_for_path(
18441            PathKey::sorted(0),
18442            buffer.clone(),
18443            [
18444                Point::new(0, 0)..Point::new(1, 4),
18445                Point::new(3, 0)..Point::new(4, 4),
18446            ],
18447            0,
18448            cx,
18449        );
18450        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\ndddd\neeee");
18451        multibuffer
18452    });
18453
18454    let editor = cx.add_window(|window, cx| {
18455        let mut editor = build_editor(multibuffer.clone(), window, cx);
18456        let snapshot = editor.snapshot(window, cx);
18457        editor.begin_selection(
18458            Point::new(1, 3).to_display_point(&snapshot),
18459            false,
18460            1,
18461            window,
18462            cx,
18463        );
18464        assert_eq!(
18465            editor.selections.ranges(&editor.display_snapshot(cx)),
18466            [Point::new(1, 3)..Point::new(1, 3)]
18467        );
18468        editor
18469    });
18470
18471    multibuffer.update(cx, |multibuffer, cx| {
18472        multibuffer.set_excerpts_for_path(
18473            PathKey::sorted(0),
18474            buffer.clone(),
18475            [Point::new(3, 0)..Point::new(4, 4)],
18476            0,
18477            cx,
18478        );
18479    });
18480    _ = editor.update(cx, |editor, window, cx| {
18481        assert_eq!(
18482            editor.selections.ranges(&editor.display_snapshot(cx)),
18483            [Point::new(0, 0)..Point::new(0, 0)]
18484        );
18485
18486        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
18487        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
18488        assert_eq!(
18489            editor.selections.ranges(&editor.display_snapshot(cx)),
18490            [Point::new(0, 0)..Point::new(0, 0)]
18491        );
18492        assert!(editor.selections.pending_anchor().is_some());
18493    });
18494}
18495
18496#[gpui::test]
18497async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
18498    init_test(cx, |_| {});
18499
18500    let language = Arc::new(
18501        Language::new(
18502            LanguageConfig {
18503                brackets: BracketPairConfig {
18504                    pairs: vec![
18505                        BracketPair {
18506                            start: "{".to_string(),
18507                            end: "}".to_string(),
18508                            close: true,
18509                            surround: true,
18510                            newline: true,
18511                        },
18512                        BracketPair {
18513                            start: "/* ".to_string(),
18514                            end: " */".to_string(),
18515                            close: true,
18516                            surround: true,
18517                            newline: true,
18518                        },
18519                    ],
18520                    ..Default::default()
18521                },
18522                ..Default::default()
18523            },
18524            Some(tree_sitter_rust::LANGUAGE.into()),
18525        )
18526        .with_indents_query("")
18527        .unwrap(),
18528    );
18529
18530    let text = concat!(
18531        "{   }\n",     //
18532        "  x\n",       //
18533        "  /*   */\n", //
18534        "x\n",         //
18535        "{{} }\n",     //
18536    );
18537
18538    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
18539    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18540    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18541    editor
18542        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
18543        .await;
18544
18545    editor.update_in(cx, |editor, window, cx| {
18546        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18547            s.select_display_ranges([
18548                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
18549                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
18550                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
18551            ])
18552        });
18553        editor.newline(&Newline, window, cx);
18554
18555        assert_eq!(
18556            editor.buffer().read(cx).read(cx).text(),
18557            concat!(
18558                "{ \n",    // Suppress rustfmt
18559                "\n",      //
18560                "}\n",     //
18561                "  x\n",   //
18562                "  /* \n", //
18563                "  \n",    //
18564                "  */\n",  //
18565                "x\n",     //
18566                "{{} \n",  //
18567                "}\n",     //
18568            )
18569        );
18570    });
18571}
18572
18573#[gpui::test]
18574fn test_highlighted_ranges(cx: &mut TestAppContext) {
18575    init_test(cx, |_| {});
18576
18577    let editor = cx.add_window(|window, cx| {
18578        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
18579        build_editor(buffer, window, cx)
18580    });
18581
18582    _ = editor.update(cx, |editor, window, cx| {
18583        let buffer = editor.buffer.read(cx).snapshot(cx);
18584
18585        let anchor_range =
18586            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
18587
18588        editor.highlight_background(
18589            HighlightKey::ColorizeBracket(0),
18590            &[
18591                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
18592                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
18593                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
18594                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
18595            ],
18596            |_, _| Hsla::red(),
18597            cx,
18598        );
18599        editor.highlight_background(
18600            HighlightKey::ColorizeBracket(1),
18601            &[
18602                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
18603                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
18604                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
18605                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
18606            ],
18607            |_, _| Hsla::green(),
18608            cx,
18609        );
18610
18611        let snapshot = editor.snapshot(window, cx);
18612        let highlighted_ranges = editor.sorted_background_highlights_in_range(
18613            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
18614            &snapshot,
18615            cx.theme(),
18616        );
18617        assert_eq!(
18618            highlighted_ranges,
18619            &[
18620                (
18621                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
18622                    Hsla::green(),
18623                ),
18624                (
18625                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
18626                    Hsla::red(),
18627                ),
18628                (
18629                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
18630                    Hsla::green(),
18631                ),
18632                (
18633                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
18634                    Hsla::red(),
18635                ),
18636            ]
18637        );
18638        assert_eq!(
18639            editor.sorted_background_highlights_in_range(
18640                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
18641                &snapshot,
18642                cx.theme(),
18643            ),
18644            &[(
18645                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
18646                Hsla::red(),
18647            )]
18648        );
18649    });
18650}
18651
18652#[gpui::test]
18653async fn test_following(cx: &mut TestAppContext) {
18654    init_test(cx, |_| {});
18655
18656    let fs = FakeFs::new(cx.executor());
18657    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
18658
18659    let buffer = project.update(cx, |project, cx| {
18660        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
18661        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
18662    });
18663    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18664    let follower = cx.update(|cx| {
18665        cx.open_window(
18666            WindowOptions {
18667                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
18668                    gpui::Point::new(px(0.), px(0.)),
18669                    gpui::Point::new(px(10.), px(80.)),
18670                ))),
18671                ..Default::default()
18672            },
18673            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
18674        )
18675        .unwrap()
18676    });
18677
18678    let is_still_following = Rc::new(RefCell::new(true));
18679    let follower_edit_event_count = Rc::new(RefCell::new(0));
18680    let pending_update = Rc::new(RefCell::new(None));
18681    let leader_entity = leader.root(cx).unwrap();
18682    let follower_entity = follower.root(cx).unwrap();
18683    _ = follower.update(cx, {
18684        let update = pending_update.clone();
18685        let is_still_following = is_still_following.clone();
18686        let follower_edit_event_count = follower_edit_event_count.clone();
18687        |_, window, cx| {
18688            cx.subscribe_in(
18689                &leader_entity,
18690                window,
18691                move |_, leader, event, window, cx| {
18692                    leader.update(cx, |leader, cx| {
18693                        leader.add_event_to_update_proto(
18694                            event,
18695                            &mut update.borrow_mut(),
18696                            window,
18697                            cx,
18698                        );
18699                    });
18700                },
18701            )
18702            .detach();
18703
18704            cx.subscribe_in(
18705                &follower_entity,
18706                window,
18707                move |_, _, event: &EditorEvent, _window, _cx| {
18708                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
18709                        *is_still_following.borrow_mut() = false;
18710                    }
18711
18712                    if let EditorEvent::BufferEdited = event {
18713                        *follower_edit_event_count.borrow_mut() += 1;
18714                    }
18715                },
18716            )
18717            .detach();
18718        }
18719    });
18720
18721    // Update the selections only
18722    _ = leader.update(cx, |leader, window, cx| {
18723        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18724            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
18725        });
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    _ = follower.update(cx, |follower, _, cx| {
18740        assert_eq!(
18741            follower.selections.ranges(&follower.display_snapshot(cx)),
18742            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
18743        );
18744    });
18745    assert!(*is_still_following.borrow());
18746    assert_eq!(*follower_edit_event_count.borrow(), 0);
18747
18748    // Update the scroll position only
18749    _ = leader.update(cx, |leader, window, cx| {
18750        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
18751    });
18752    follower
18753        .update(cx, |follower, window, cx| {
18754            follower.apply_update_proto(
18755                &project,
18756                pending_update.borrow_mut().take().unwrap(),
18757                window,
18758                cx,
18759            )
18760        })
18761        .unwrap()
18762        .await
18763        .unwrap();
18764    assert_eq!(
18765        follower
18766            .update(cx, |follower, _, cx| follower.scroll_position(cx))
18767            .unwrap(),
18768        gpui::Point::new(1.5, 3.5)
18769    );
18770    assert!(*is_still_following.borrow());
18771    assert_eq!(*follower_edit_event_count.borrow(), 0);
18772
18773    // Update the selections and scroll position. The follower's scroll position is updated
18774    // via autoscroll, not via the leader's exact scroll position.
18775    _ = leader.update(cx, |leader, window, cx| {
18776        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18777            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
18778        });
18779        leader.request_autoscroll(Autoscroll::newest(), cx);
18780        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
18781    });
18782    follower
18783        .update(cx, |follower, window, cx| {
18784            follower.apply_update_proto(
18785                &project,
18786                pending_update.borrow_mut().take().unwrap(),
18787                window,
18788                cx,
18789            )
18790        })
18791        .unwrap()
18792        .await
18793        .unwrap();
18794    _ = follower.update(cx, |follower, _, cx| {
18795        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
18796        assert_eq!(
18797            follower.selections.ranges(&follower.display_snapshot(cx)),
18798            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
18799        );
18800    });
18801    assert!(*is_still_following.borrow());
18802
18803    // Creating a pending selection that precedes another selection
18804    _ = leader.update(cx, |leader, window, cx| {
18805        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18806            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
18807        });
18808        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
18809    });
18810    follower
18811        .update(cx, |follower, window, cx| {
18812            follower.apply_update_proto(
18813                &project,
18814                pending_update.borrow_mut().take().unwrap(),
18815                window,
18816                cx,
18817            )
18818        })
18819        .unwrap()
18820        .await
18821        .unwrap();
18822    _ = follower.update(cx, |follower, _, cx| {
18823        assert_eq!(
18824            follower.selections.ranges(&follower.display_snapshot(cx)),
18825            vec![
18826                MultiBufferOffset(0)..MultiBufferOffset(0),
18827                MultiBufferOffset(1)..MultiBufferOffset(1)
18828            ]
18829        );
18830    });
18831    assert!(*is_still_following.borrow());
18832
18833    // Extend the pending selection so that it surrounds another selection
18834    _ = leader.update(cx, |leader, window, cx| {
18835        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
18836    });
18837    follower
18838        .update(cx, |follower, window, cx| {
18839            follower.apply_update_proto(
18840                &project,
18841                pending_update.borrow_mut().take().unwrap(),
18842                window,
18843                cx,
18844            )
18845        })
18846        .unwrap()
18847        .await
18848        .unwrap();
18849    _ = follower.update(cx, |follower, _, cx| {
18850        assert_eq!(
18851            follower.selections.ranges(&follower.display_snapshot(cx)),
18852            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
18853        );
18854    });
18855
18856    // Scrolling locally breaks the follow
18857    _ = follower.update(cx, |follower, window, cx| {
18858        let top_anchor = follower
18859            .buffer()
18860            .read(cx)
18861            .read(cx)
18862            .anchor_after(MultiBufferOffset(0));
18863        follower.set_scroll_anchor(
18864            ScrollAnchor {
18865                anchor: top_anchor,
18866                offset: gpui::Point::new(0.0, 0.5),
18867            },
18868            window,
18869            cx,
18870        );
18871    });
18872    assert!(!(*is_still_following.borrow()));
18873}
18874
18875#[gpui::test]
18876async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
18877    init_test(cx, |_| {});
18878
18879    let fs = FakeFs::new(cx.executor());
18880    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
18881    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
18882    let workspace = window
18883        .read_with(cx, |mw, _| mw.workspace().clone())
18884        .unwrap();
18885    let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone());
18886
18887    let cx = &mut VisualTestContext::from_window(*window, cx);
18888
18889    let leader = pane.update_in(cx, |_, window, cx| {
18890        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
18891        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
18892    });
18893
18894    // Start following the editor when it has no excerpts.
18895    let mut state_message =
18896        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
18897    let workspace_entity = workspace.clone();
18898    let follower_1 = cx
18899        .update_window(*window, |_, window, cx| {
18900            Editor::from_state_proto(
18901                workspace_entity,
18902                ViewId {
18903                    creator: CollaboratorId::PeerId(PeerId::default()),
18904                    id: 0,
18905                },
18906                &mut state_message,
18907                window,
18908                cx,
18909            )
18910        })
18911        .unwrap()
18912        .unwrap()
18913        .await
18914        .unwrap();
18915
18916    let update_message = Rc::new(RefCell::new(None));
18917    follower_1.update_in(cx, {
18918        let update = update_message.clone();
18919        |_, window, cx| {
18920            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
18921                leader.update(cx, |leader, cx| {
18922                    leader.add_event_to_update_proto(event, &mut update.borrow_mut(), window, cx);
18923                });
18924            })
18925            .detach();
18926        }
18927    });
18928
18929    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
18930        (
18931            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
18932            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
18933        )
18934    });
18935
18936    // Insert some excerpts.
18937    leader.update(cx, |leader, cx| {
18938        leader.buffer.update(cx, |multibuffer, cx| {
18939            multibuffer.set_excerpts_for_path(
18940                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
18941                buffer_1.clone(),
18942                vec![
18943                    Point::row_range(0..3),
18944                    Point::row_range(1..6),
18945                    Point::row_range(12..15),
18946                ],
18947                0,
18948                cx,
18949            );
18950            multibuffer.set_excerpts_for_path(
18951                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
18952                buffer_2.clone(),
18953                vec![Point::row_range(0..6), Point::row_range(8..12)],
18954                0,
18955                cx,
18956            );
18957        });
18958    });
18959
18960    // Apply the update of adding the excerpts.
18961    follower_1
18962        .update_in(cx, |follower, window, cx| {
18963            follower.apply_update_proto(
18964                &project,
18965                update_message.borrow().clone().unwrap(),
18966                window,
18967                cx,
18968            )
18969        })
18970        .await
18971        .unwrap();
18972    assert_eq!(
18973        follower_1.update(cx, |editor, cx| editor.text(cx)),
18974        leader.update(cx, |editor, cx| editor.text(cx))
18975    );
18976    update_message.borrow_mut().take();
18977
18978    // Start following separately after it already has excerpts.
18979    let mut state_message =
18980        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
18981    let workspace_entity = workspace.clone();
18982    let follower_2 = cx
18983        .update_window(*window, |_, window, cx| {
18984            Editor::from_state_proto(
18985                workspace_entity,
18986                ViewId {
18987                    creator: CollaboratorId::PeerId(PeerId::default()),
18988                    id: 0,
18989                },
18990                &mut state_message,
18991                window,
18992                cx,
18993            )
18994        })
18995        .unwrap()
18996        .unwrap()
18997        .await
18998        .unwrap();
18999    assert_eq!(
19000        follower_2.update(cx, |editor, cx| editor.text(cx)),
19001        leader.update(cx, |editor, cx| editor.text(cx))
19002    );
19003
19004    // Remove some excerpts.
19005    leader.update(cx, |leader, cx| {
19006        leader.buffer.update(cx, |multibuffer, cx| {
19007            multibuffer.remove_excerpts_for_path(
19008                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
19009                cx,
19010            );
19011        });
19012    });
19013
19014    // Apply the update of removing the excerpts.
19015    follower_1
19016        .update_in(cx, |follower, window, cx| {
19017            follower.apply_update_proto(
19018                &project,
19019                update_message.borrow().clone().unwrap(),
19020                window,
19021                cx,
19022            )
19023        })
19024        .await
19025        .unwrap();
19026    follower_2
19027        .update_in(cx, |follower, window, cx| {
19028            follower.apply_update_proto(
19029                &project,
19030                update_message.borrow().clone().unwrap(),
19031                window,
19032                cx,
19033            )
19034        })
19035        .await
19036        .unwrap();
19037    update_message.borrow_mut().take();
19038    assert_eq!(
19039        follower_1.update(cx, |editor, cx| editor.text(cx)),
19040        leader.update(cx, |editor, cx| editor.text(cx))
19041    );
19042}
19043
19044#[gpui::test]
19045async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19046    init_test(cx, |_| {});
19047
19048    let mut cx = EditorTestContext::new(cx).await;
19049    let lsp_store =
19050        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
19051
19052    cx.set_state(indoc! {"
19053        ˇfn func(abc def: i32) -> u32 {
19054        }
19055    "});
19056
19057    cx.update(|_, cx| {
19058        lsp_store.update(cx, |lsp_store, cx| {
19059            lsp_store
19060                .update_diagnostics(
19061                    LanguageServerId(0),
19062                    lsp::PublishDiagnosticsParams {
19063                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
19064                        version: None,
19065                        diagnostics: vec![
19066                            lsp::Diagnostic {
19067                                range: lsp::Range::new(
19068                                    lsp::Position::new(0, 11),
19069                                    lsp::Position::new(0, 12),
19070                                ),
19071                                severity: Some(lsp::DiagnosticSeverity::ERROR),
19072                                ..Default::default()
19073                            },
19074                            lsp::Diagnostic {
19075                                range: lsp::Range::new(
19076                                    lsp::Position::new(0, 12),
19077                                    lsp::Position::new(0, 15),
19078                                ),
19079                                severity: Some(lsp::DiagnosticSeverity::ERROR),
19080                                ..Default::default()
19081                            },
19082                            lsp::Diagnostic {
19083                                range: lsp::Range::new(
19084                                    lsp::Position::new(0, 25),
19085                                    lsp::Position::new(0, 28),
19086                                ),
19087                                severity: Some(lsp::DiagnosticSeverity::ERROR),
19088                                ..Default::default()
19089                            },
19090                        ],
19091                    },
19092                    None,
19093                    DiagnosticSourceKind::Pushed,
19094                    &[],
19095                    cx,
19096                )
19097                .unwrap()
19098        });
19099    });
19100
19101    executor.run_until_parked();
19102
19103    cx.update_editor(|editor, window, cx| {
19104        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19105    });
19106
19107    cx.assert_editor_state(indoc! {"
19108        fn func(abc def: i32) -> ˇu32 {
19109        }
19110    "});
19111
19112    cx.update_editor(|editor, window, cx| {
19113        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19114    });
19115
19116    cx.assert_editor_state(indoc! {"
19117        fn func(abc ˇdef: i32) -> u32 {
19118        }
19119    "});
19120
19121    cx.update_editor(|editor, window, cx| {
19122        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19123    });
19124
19125    cx.assert_editor_state(indoc! {"
19126        fn func(abcˇ def: i32) -> u32 {
19127        }
19128    "});
19129
19130    cx.update_editor(|editor, window, cx| {
19131        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
19132    });
19133
19134    cx.assert_editor_state(indoc! {"
19135        fn func(abc def: i32) -> ˇu32 {
19136        }
19137    "});
19138}
19139
19140#[gpui::test]
19141async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19142    init_test(cx, |_| {});
19143
19144    let mut cx = EditorTestContext::new(cx).await;
19145
19146    let diff_base = r#"
19147        use some::mod;
19148
19149        const A: u32 = 42;
19150
19151        fn main() {
19152            println!("hello");
19153
19154            println!("world");
19155        }
19156        "#
19157    .unindent();
19158
19159    // Edits are modified, removed, modified, added
19160    cx.set_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.set_head_text(&diff_base);
19176    executor.run_until_parked();
19177
19178    cx.update_editor(|editor, window, cx| {
19179        //Wrap around the bottom of the buffer
19180        for _ in 0..3 {
19181            editor.go_to_next_hunk(&GoToHunk, window, cx);
19182        }
19183    });
19184
19185    cx.assert_editor_state(
19186        &r#"
19187        ˇuse some::modified;
19188
19189
19190        fn main() {
19191            println!("hello there");
19192
19193            println!("around the");
19194            println!("world");
19195        }
19196        "#
19197        .unindent(),
19198    );
19199
19200    cx.update_editor(|editor, window, cx| {
19201        //Wrap around the top of the buffer
19202        for _ in 0..2 {
19203            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19204        }
19205    });
19206
19207    cx.assert_editor_state(
19208        &r#"
19209        use some::modified;
19210
19211
19212        fn main() {
19213        ˇ    println!("hello there");
19214
19215            println!("around the");
19216            println!("world");
19217        }
19218        "#
19219        .unindent(),
19220    );
19221
19222    cx.update_editor(|editor, window, cx| {
19223        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19224    });
19225
19226    cx.assert_editor_state(
19227        &r#"
19228        use some::modified;
19229
19230        ˇ
19231        fn main() {
19232            println!("hello there");
19233
19234            println!("around the");
19235            println!("world");
19236        }
19237        "#
19238        .unindent(),
19239    );
19240
19241    cx.update_editor(|editor, window, cx| {
19242        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19243    });
19244
19245    cx.assert_editor_state(
19246        &r#"
19247        ˇuse some::modified;
19248
19249
19250        fn main() {
19251            println!("hello there");
19252
19253            println!("around the");
19254            println!("world");
19255        }
19256        "#
19257        .unindent(),
19258    );
19259
19260    cx.update_editor(|editor, window, cx| {
19261        for _ in 0..2 {
19262            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
19263        }
19264    });
19265
19266    cx.assert_editor_state(
19267        &r#"
19268        use some::modified;
19269
19270
19271        fn main() {
19272        ˇ    println!("hello there");
19273
19274            println!("around the");
19275            println!("world");
19276        }
19277        "#
19278        .unindent(),
19279    );
19280
19281    cx.update_editor(|editor, window, cx| {
19282        editor.fold(&Fold, window, cx);
19283    });
19284
19285    cx.update_editor(|editor, window, cx| {
19286        editor.go_to_next_hunk(&GoToHunk, window, cx);
19287    });
19288
19289    cx.assert_editor_state(
19290        &r#"
19291        ˇuse some::modified;
19292
19293
19294        fn main() {
19295            println!("hello there");
19296
19297            println!("around the");
19298            println!("world");
19299        }
19300        "#
19301        .unindent(),
19302    );
19303}
19304
19305#[test]
19306fn test_split_words() {
19307    fn split(text: &str) -> Vec<&str> {
19308        split_words(text).collect()
19309    }
19310
19311    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
19312    assert_eq!(split("hello_world"), &["hello_", "world"]);
19313    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
19314    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
19315    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
19316    assert_eq!(split("helloworld"), &["helloworld"]);
19317
19318    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
19319}
19320
19321#[test]
19322fn test_split_words_for_snippet_prefix() {
19323    fn split(text: &str) -> Vec<&str> {
19324        snippet_candidate_suffixes(text, &|c| c.is_alphanumeric() || c == '_').collect()
19325    }
19326
19327    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
19328    assert_eq!(split("hello_world"), &["hello_world"]);
19329    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
19330    assert_eq!(split("Hello_World"), &["Hello_World"]);
19331    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
19332    assert_eq!(split("helloworld"), &["helloworld"]);
19333    assert_eq!(
19334        split("this@is!@#$^many   . symbols"),
19335        &[
19336            "symbols",
19337            " symbols",
19338            ". symbols",
19339            " . symbols",
19340            "  . symbols",
19341            "   . symbols",
19342            "many   . symbols",
19343            "^many   . symbols",
19344            "$^many   . symbols",
19345            "#$^many   . symbols",
19346            "@#$^many   . symbols",
19347            "!@#$^many   . symbols",
19348            "is!@#$^many   . symbols",
19349            "@is!@#$^many   . symbols",
19350            "this@is!@#$^many   . symbols",
19351        ],
19352    );
19353    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
19354}
19355
19356#[gpui::test]
19357async fn test_move_to_syntax_node_relative_jumps(tcx: &mut TestAppContext) {
19358    init_test(tcx, |_| {});
19359
19360    let mut cx = EditorLspTestContext::new(
19361        Arc::into_inner(markdown_lang()).unwrap(),
19362        Default::default(),
19363        tcx,
19364    )
19365    .await;
19366
19367    async fn assert(offset: i8, before: &str, after: &str, cx: &mut EditorLspTestContext) {
19368        let _state_context = cx.set_state(before);
19369        cx.run_until_parked();
19370        cx.update_editor(|editor, window, cx| editor.go_to_symbol_by_offset(window, cx, offset))
19371            .await
19372            .unwrap();
19373        cx.run_until_parked();
19374        cx.assert_editor_state(after);
19375    }
19376
19377    const ABOVE: i8 = -1;
19378    const BELOW: i8 = 1;
19379
19380    assert(
19381        ABOVE,
19382        indoc! {"
19383        # Foo
19384
19385        ˇFoo foo foo
19386
19387        # Bar
19388
19389        Bar bar bar
19390    "},
19391        indoc! {"
19392        ˇ# Foo
19393
19394        Foo foo foo
19395
19396        # Bar
19397
19398        Bar bar bar
19399    "},
19400        &mut cx,
19401    )
19402    .await;
19403
19404    assert(
19405        ABOVE,
19406        indoc! {"
19407        ˇ# Foo
19408
19409        Foo foo foo
19410
19411        # Bar
19412
19413        Bar bar bar
19414    "},
19415        indoc! {"
19416        ˇ# Foo
19417
19418        Foo foo foo
19419
19420        # Bar
19421
19422        Bar bar bar
19423    "},
19424        &mut cx,
19425    )
19426    .await;
19427
19428    assert(
19429        BELOW,
19430        indoc! {"
19431        ˇ# Foo
19432
19433        Foo foo foo
19434
19435        # Bar
19436
19437        Bar bar bar
19438    "},
19439        indoc! {"
19440        # Foo
19441
19442        Foo foo foo
19443
19444        ˇ# Bar
19445
19446        Bar bar bar
19447    "},
19448        &mut cx,
19449    )
19450    .await;
19451
19452    assert(
19453        BELOW,
19454        indoc! {"
19455        # Foo
19456
19457        ˇFoo foo foo
19458
19459        # Bar
19460
19461        Bar bar bar
19462    "},
19463        indoc! {"
19464        # Foo
19465
19466        Foo foo foo
19467
19468        ˇ# Bar
19469
19470        Bar bar bar
19471    "},
19472        &mut cx,
19473    )
19474    .await;
19475
19476    assert(
19477        BELOW,
19478        indoc! {"
19479        # Foo
19480
19481        Foo foo foo
19482
19483        ˇ# Bar
19484
19485        Bar bar bar
19486    "},
19487        indoc! {"
19488        # Foo
19489
19490        Foo foo foo
19491
19492        ˇ# Bar
19493
19494        Bar bar bar
19495    "},
19496        &mut cx,
19497    )
19498    .await;
19499
19500    assert(
19501        BELOW,
19502        indoc! {"
19503        # Foo
19504
19505        Foo foo foo
19506
19507        # Bar
19508        ˇ
19509        Bar bar bar
19510    "},
19511        indoc! {"
19512        # Foo
19513
19514        Foo foo foo
19515
19516        # Bar
19517        ˇ
19518        Bar bar bar
19519    "},
19520        &mut cx,
19521    )
19522    .await;
19523}
19524
19525#[gpui::test]
19526async fn test_move_to_syntax_node_relative_dead_zone(tcx: &mut TestAppContext) {
19527    init_test(tcx, |_| {});
19528
19529    let mut cx = EditorLspTestContext::new(
19530        Arc::into_inner(rust_lang()).unwrap(),
19531        Default::default(),
19532        tcx,
19533    )
19534    .await;
19535
19536    async fn assert(offset: i8, before: &str, after: &str, cx: &mut EditorLspTestContext) {
19537        let _state_context = cx.set_state(before);
19538        cx.run_until_parked();
19539        cx.update_editor(|editor, window, cx| editor.go_to_symbol_by_offset(window, cx, offset))
19540            .await
19541            .unwrap();
19542        cx.run_until_parked();
19543        cx.assert_editor_state(after);
19544    }
19545
19546    const ABOVE: i8 = -1;
19547    const BELOW: i8 = 1;
19548
19549    assert(
19550        ABOVE,
19551        indoc! {"
19552        fn foo() {
19553            // foo fn
19554        }
19555
19556        ˇ// this zone is not inside any top level outline node
19557
19558        fn bar() {
19559            // bar fn
19560            let _ = 2;
19561        }
19562    "},
19563        indoc! {"
19564        ˇfn foo() {
19565            // foo fn
19566        }
19567
19568        // this zone is not inside any top level outline node
19569
19570        fn bar() {
19571            // bar fn
19572            let _ = 2;
19573        }
19574    "},
19575        &mut cx,
19576    )
19577    .await;
19578
19579    assert(
19580        BELOW,
19581        indoc! {"
19582        fn foo() {
19583            // foo fn
19584        }
19585
19586        ˇ// this zone is not inside any top level outline node
19587
19588        fn bar() {
19589            // bar fn
19590            let _ = 2;
19591        }
19592    "},
19593        indoc! {"
19594        fn foo() {
19595            // foo fn
19596        }
19597
19598        // this zone is not inside any top level outline node
19599
19600        ˇfn bar() {
19601            // bar fn
19602            let _ = 2;
19603        }
19604    "},
19605        &mut cx,
19606    )
19607    .await;
19608}
19609
19610#[gpui::test]
19611async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
19612    init_test(cx, |_| {});
19613
19614    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
19615
19616    #[track_caller]
19617    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
19618        let _state_context = cx.set_state(before);
19619        cx.run_until_parked();
19620        cx.update_editor(|editor, window, cx| {
19621            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
19622        });
19623        cx.run_until_parked();
19624        cx.assert_editor_state(after);
19625    }
19626
19627    // Outside bracket jumps to outside of matching bracket
19628    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
19629    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
19630
19631    // Inside bracket jumps to inside of matching bracket
19632    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
19633    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
19634
19635    // When outside a bracket and inside, favor jumping to the inside bracket
19636    assert(
19637        "console.log('foo', [1, 2, 3]ˇ);",
19638        "console.log('foo', ˇ[1, 2, 3]);",
19639        &mut cx,
19640    );
19641    assert(
19642        "console.log(ˇ'foo', [1, 2, 3]);",
19643        "console.log('foo'ˇ, [1, 2, 3]);",
19644        &mut cx,
19645    );
19646
19647    // Bias forward if two options are equally likely
19648    assert(
19649        "let result = curried_fun()ˇ();",
19650        "let result = curried_fun()()ˇ;",
19651        &mut cx,
19652    );
19653
19654    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
19655    assert(
19656        indoc! {"
19657            function test() {
19658                console.log('test')ˇ
19659            }"},
19660        indoc! {"
19661            function test() {
19662                console.logˇ('test')
19663            }"},
19664        &mut cx,
19665    );
19666}
19667
19668#[gpui::test]
19669async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
19670    init_test(cx, |_| {});
19671    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
19672    language_registry.add(markdown_lang());
19673    language_registry.add(rust_lang());
19674    let buffer = cx.new(|cx| {
19675        let mut buffer = language::Buffer::local(
19676            indoc! {"
19677            ```rs
19678            impl Worktree {
19679                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
19680                }
19681            }
19682            ```
19683        "},
19684            cx,
19685        );
19686        buffer.set_language_registry(language_registry.clone());
19687        buffer.set_language(Some(markdown_lang()), cx);
19688        buffer
19689    });
19690    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19691    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
19692    cx.executor().run_until_parked();
19693    _ = editor.update(cx, |editor, window, cx| {
19694        // Case 1: Test outer enclosing brackets
19695        select_ranges(
19696            editor,
19697            &indoc! {"
19698                ```rs
19699                impl Worktree {
19700                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
19701                    }
1970219703                ```
19704            "},
19705            window,
19706            cx,
19707        );
19708        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
19709        assert_text_with_selections(
19710            editor,
19711            &indoc! {"
19712                ```rs
19713                impl Worktree ˇ{
19714                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
19715                    }
19716                }
19717                ```
19718            "},
19719            cx,
19720        );
19721        // Case 2: Test inner enclosing brackets
19722        select_ranges(
19723            editor,
19724            &indoc! {"
19725                ```rs
19726                impl Worktree {
19727                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1972819729                }
19730                ```
19731            "},
19732            window,
19733            cx,
19734        );
19735        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
19736        assert_text_with_selections(
19737            editor,
19738            &indoc! {"
19739                ```rs
19740                impl Worktree {
19741                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
19742                    }
19743                }
19744                ```
19745            "},
19746            cx,
19747        );
19748    });
19749}
19750
19751#[gpui::test]
19752async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
19753    init_test(cx, |_| {});
19754
19755    let fs = FakeFs::new(cx.executor());
19756    fs.insert_tree(
19757        path!("/a"),
19758        json!({
19759            "main.rs": "fn main() { let a = 5; }",
19760            "other.rs": "// Test file",
19761        }),
19762    )
19763    .await;
19764    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19765
19766    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19767    language_registry.add(Arc::new(Language::new(
19768        LanguageConfig {
19769            name: "Rust".into(),
19770            matcher: LanguageMatcher {
19771                path_suffixes: vec!["rs".to_string()],
19772                ..Default::default()
19773            },
19774            brackets: BracketPairConfig {
19775                pairs: vec![BracketPair {
19776                    start: "{".to_string(),
19777                    end: "}".to_string(),
19778                    close: true,
19779                    surround: true,
19780                    newline: true,
19781                }],
19782                disabled_scopes_by_bracket_ix: Vec::new(),
19783            },
19784            ..Default::default()
19785        },
19786        Some(tree_sitter_rust::LANGUAGE.into()),
19787    )));
19788    let mut fake_servers = language_registry.register_fake_lsp(
19789        "Rust",
19790        FakeLspAdapter {
19791            capabilities: lsp::ServerCapabilities {
19792                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
19793                    first_trigger_character: "{".to_string(),
19794                    more_trigger_character: None,
19795                }),
19796                ..Default::default()
19797            },
19798            ..Default::default()
19799        },
19800    );
19801
19802    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
19803    let workspace = window
19804        .read_with(cx, |mw, _| mw.workspace().clone())
19805        .unwrap();
19806
19807    let cx = &mut VisualTestContext::from_window(*window, cx);
19808
19809    let worktree_id = workspace.update_in(cx, |workspace, _, cx| {
19810        workspace.project().update(cx, |project, cx| {
19811            project.worktrees(cx).next().unwrap().read(cx).id()
19812        })
19813    });
19814
19815    let buffer = project
19816        .update(cx, |project, cx| {
19817            project.open_local_buffer(path!("/a/main.rs"), cx)
19818        })
19819        .await
19820        .unwrap();
19821    let editor_handle = workspace
19822        .update_in(cx, |workspace, window, cx| {
19823            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
19824        })
19825        .await
19826        .unwrap()
19827        .downcast::<Editor>()
19828        .unwrap();
19829
19830    let fake_server = fake_servers.next().await.unwrap();
19831
19832    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
19833        |params, _| async move {
19834            assert_eq!(
19835                params.text_document_position.text_document.uri,
19836                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
19837            );
19838            assert_eq!(
19839                params.text_document_position.position,
19840                lsp::Position::new(0, 21),
19841            );
19842
19843            Ok(Some(vec![lsp::TextEdit {
19844                new_text: "]".to_string(),
19845                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
19846            }]))
19847        },
19848    );
19849
19850    editor_handle.update_in(cx, |editor, window, cx| {
19851        window.focus(&editor.focus_handle(cx), cx);
19852        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19853            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
19854        });
19855        editor.handle_input("{", window, cx);
19856    });
19857
19858    cx.executor().run_until_parked();
19859
19860    buffer.update(cx, |buffer, _| {
19861        assert_eq!(
19862            buffer.text(),
19863            "fn main() { let a = {5}; }",
19864            "No extra braces from on type formatting should appear in the buffer"
19865        )
19866    });
19867}
19868
19869#[gpui::test(iterations = 20, seeds(31))]
19870async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
19871    init_test(cx, |_| {});
19872
19873    let mut cx = EditorLspTestContext::new_rust(
19874        lsp::ServerCapabilities {
19875            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
19876                first_trigger_character: ".".to_string(),
19877                more_trigger_character: None,
19878            }),
19879            ..Default::default()
19880        },
19881        cx,
19882    )
19883    .await;
19884
19885    cx.update_buffer(|buffer, _| {
19886        // This causes autoindent to be async.
19887        buffer.set_sync_parse_timeout(None)
19888    });
19889
19890    cx.set_state("fn c() {\n    d()ˇ\n}\n");
19891    cx.simulate_keystroke("\n");
19892    cx.run_until_parked();
19893
19894    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
19895    let mut request =
19896        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
19897            let buffer_cloned = buffer_cloned.clone();
19898            async move {
19899                buffer_cloned.update(&mut cx, |buffer, _| {
19900                    assert_eq!(
19901                        buffer.text(),
19902                        "fn c() {\n    d()\n        .\n}\n",
19903                        "OnTypeFormatting should triggered after autoindent applied"
19904                    )
19905                });
19906
19907                Ok(Some(vec![]))
19908            }
19909        });
19910
19911    cx.simulate_keystroke(".");
19912    cx.run_until_parked();
19913
19914    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
19915    assert!(request.next().await.is_some());
19916    request.close();
19917    assert!(request.next().await.is_none());
19918}
19919
19920#[gpui::test]
19921async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
19922    init_test(cx, |_| {});
19923
19924    let fs = FakeFs::new(cx.executor());
19925    fs.insert_tree(
19926        path!("/a"),
19927        json!({
19928            "main.rs": "fn main() { let a = 5; }",
19929            "other.rs": "// Test file",
19930        }),
19931    )
19932    .await;
19933
19934    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
19935
19936    let server_restarts = Arc::new(AtomicUsize::new(0));
19937    let closure_restarts = Arc::clone(&server_restarts);
19938    let language_server_name = "test language server";
19939    let language_name: LanguageName = "Rust".into();
19940
19941    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19942    language_registry.add(Arc::new(Language::new(
19943        LanguageConfig {
19944            name: language_name.clone(),
19945            matcher: LanguageMatcher {
19946                path_suffixes: vec!["rs".to_string()],
19947                ..Default::default()
19948            },
19949            ..Default::default()
19950        },
19951        Some(tree_sitter_rust::LANGUAGE.into()),
19952    )));
19953    let mut fake_servers = language_registry.register_fake_lsp(
19954        "Rust",
19955        FakeLspAdapter {
19956            name: language_server_name,
19957            initialization_options: Some(json!({
19958                "testOptionValue": true
19959            })),
19960            initializer: Some(Box::new(move |fake_server| {
19961                let task_restarts = Arc::clone(&closure_restarts);
19962                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
19963                    task_restarts.fetch_add(1, atomic::Ordering::Release);
19964                    futures::future::ready(Ok(()))
19965                });
19966            })),
19967            ..Default::default()
19968        },
19969    );
19970
19971    let _window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
19972    let _buffer = project
19973        .update(cx, |project, cx| {
19974            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
19975        })
19976        .await
19977        .unwrap();
19978    let _fake_server = fake_servers.next().await.unwrap();
19979    update_test_language_settings(cx, &|language_settings| {
19980        language_settings.languages.0.insert(
19981            language_name.clone().0.to_string(),
19982            LanguageSettingsContent {
19983                tab_size: NonZeroU32::new(8),
19984                ..Default::default()
19985            },
19986        );
19987    });
19988    cx.executor().run_until_parked();
19989    assert_eq!(
19990        server_restarts.load(atomic::Ordering::Acquire),
19991        0,
19992        "Should not restart LSP server on an unrelated change"
19993    );
19994
19995    update_test_project_settings(cx, &|project_settings| {
19996        project_settings.lsp.0.insert(
19997            "Some other server name".into(),
19998            LspSettings {
19999                binary: None,
20000                settings: None,
20001                initialization_options: Some(json!({
20002                    "some other init value": false
20003                })),
20004                enable_lsp_tasks: false,
20005                fetch: None,
20006            },
20007        );
20008    });
20009    cx.executor().run_until_parked();
20010    assert_eq!(
20011        server_restarts.load(atomic::Ordering::Acquire),
20012        0,
20013        "Should not restart LSP server on an unrelated LSP settings change"
20014    );
20015
20016    update_test_project_settings(cx, &|project_settings| {
20017        project_settings.lsp.0.insert(
20018            language_server_name.into(),
20019            LspSettings {
20020                binary: None,
20021                settings: None,
20022                initialization_options: Some(json!({
20023                    "anotherInitValue": false
20024                })),
20025                enable_lsp_tasks: false,
20026                fetch: None,
20027            },
20028        );
20029    });
20030    cx.executor().run_until_parked();
20031    assert_eq!(
20032        server_restarts.load(atomic::Ordering::Acquire),
20033        1,
20034        "Should restart LSP server on a related LSP settings change"
20035    );
20036
20037    update_test_project_settings(cx, &|project_settings| {
20038        project_settings.lsp.0.insert(
20039            language_server_name.into(),
20040            LspSettings {
20041                binary: None,
20042                settings: None,
20043                initialization_options: Some(json!({
20044                    "anotherInitValue": false
20045                })),
20046                enable_lsp_tasks: false,
20047                fetch: None,
20048            },
20049        );
20050    });
20051    cx.executor().run_until_parked();
20052    assert_eq!(
20053        server_restarts.load(atomic::Ordering::Acquire),
20054        1,
20055        "Should not restart LSP server on a related LSP settings change that is the same"
20056    );
20057
20058    update_test_project_settings(cx, &|project_settings| {
20059        project_settings.lsp.0.insert(
20060            language_server_name.into(),
20061            LspSettings {
20062                binary: None,
20063                settings: None,
20064                initialization_options: None,
20065                enable_lsp_tasks: false,
20066                fetch: None,
20067            },
20068        );
20069    });
20070    cx.executor().run_until_parked();
20071    assert_eq!(
20072        server_restarts.load(atomic::Ordering::Acquire),
20073        2,
20074        "Should restart LSP server on another related LSP settings change"
20075    );
20076}
20077
20078#[gpui::test]
20079async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
20080    init_test(cx, |_| {});
20081
20082    let mut cx = EditorLspTestContext::new_rust(
20083        lsp::ServerCapabilities {
20084            completion_provider: Some(lsp::CompletionOptions {
20085                trigger_characters: Some(vec![".".to_string()]),
20086                resolve_provider: Some(true),
20087                ..Default::default()
20088            }),
20089            ..Default::default()
20090        },
20091        cx,
20092    )
20093    .await;
20094
20095    cx.set_state("fn main() { let a = 2ˇ; }");
20096    cx.simulate_keystroke(".");
20097    let completion_item = lsp::CompletionItem {
20098        label: "some".into(),
20099        kind: Some(lsp::CompletionItemKind::SNIPPET),
20100        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
20101        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
20102            kind: lsp::MarkupKind::Markdown,
20103            value: "```rust\nSome(2)\n```".to_string(),
20104        })),
20105        deprecated: Some(false),
20106        sort_text: Some("fffffff2".to_string()),
20107        filter_text: Some("some".to_string()),
20108        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
20109        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20110            range: lsp::Range {
20111                start: lsp::Position {
20112                    line: 0,
20113                    character: 22,
20114                },
20115                end: lsp::Position {
20116                    line: 0,
20117                    character: 22,
20118                },
20119            },
20120            new_text: "Some(2)".to_string(),
20121        })),
20122        additional_text_edits: Some(vec![lsp::TextEdit {
20123            range: lsp::Range {
20124                start: lsp::Position {
20125                    line: 0,
20126                    character: 20,
20127                },
20128                end: lsp::Position {
20129                    line: 0,
20130                    character: 22,
20131                },
20132            },
20133            new_text: "".to_string(),
20134        }]),
20135        ..Default::default()
20136    };
20137
20138    let closure_completion_item = completion_item.clone();
20139    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20140        let task_completion_item = closure_completion_item.clone();
20141        async move {
20142            Ok(Some(lsp::CompletionResponse::Array(vec![
20143                task_completion_item,
20144            ])))
20145        }
20146    });
20147
20148    request.next().await;
20149
20150    cx.condition(|editor, _| editor.context_menu_visible())
20151        .await;
20152    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
20153        editor
20154            .confirm_completion(&ConfirmCompletion::default(), window, cx)
20155            .unwrap()
20156    });
20157    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
20158
20159    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
20160        let task_completion_item = completion_item.clone();
20161        async move { Ok(task_completion_item) }
20162    })
20163    .next()
20164    .await
20165    .unwrap();
20166    apply_additional_edits.await.unwrap();
20167    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
20168}
20169
20170#[gpui::test]
20171async fn test_completions_with_additional_edits_and_multiple_cursors(cx: &mut TestAppContext) {
20172    init_test(cx, |_| {});
20173
20174    let mut cx = EditorLspTestContext::new_typescript(
20175        lsp::ServerCapabilities {
20176            completion_provider: Some(lsp::CompletionOptions {
20177                resolve_provider: Some(true),
20178                ..Default::default()
20179            }),
20180            ..Default::default()
20181        },
20182        cx,
20183    )
20184    .await;
20185
20186    cx.set_state(
20187        "import { «Fooˇ» } from './types';\n\nclass Bar {\n    method(): «Fooˇ» { return new Foo(); }\n}",
20188    );
20189
20190    cx.simulate_keystroke("F");
20191    cx.simulate_keystroke("o");
20192
20193    let completion_item = lsp::CompletionItem {
20194        label: "FooBar".into(),
20195        kind: Some(lsp::CompletionItemKind::CLASS),
20196        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20197            range: lsp::Range {
20198                start: lsp::Position {
20199                    line: 3,
20200                    character: 14,
20201                },
20202                end: lsp::Position {
20203                    line: 3,
20204                    character: 16,
20205                },
20206            },
20207            new_text: "FooBar".to_string(),
20208        })),
20209        additional_text_edits: Some(vec![lsp::TextEdit {
20210            range: lsp::Range {
20211                start: lsp::Position {
20212                    line: 0,
20213                    character: 9,
20214                },
20215                end: lsp::Position {
20216                    line: 0,
20217                    character: 11,
20218                },
20219            },
20220            new_text: "FooBar".to_string(),
20221        }]),
20222        ..Default::default()
20223    };
20224
20225    let closure_completion_item = completion_item.clone();
20226    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20227        let task_completion_item = closure_completion_item.clone();
20228        async move {
20229            Ok(Some(lsp::CompletionResponse::Array(vec![
20230                task_completion_item,
20231            ])))
20232        }
20233    });
20234
20235    request.next().await;
20236
20237    cx.condition(|editor, _| editor.context_menu_visible())
20238        .await;
20239    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
20240        editor
20241            .confirm_completion(&ConfirmCompletion::default(), window, cx)
20242            .unwrap()
20243    });
20244
20245    cx.assert_editor_state(
20246        "import { FooBarˇ } from './types';\n\nclass Bar {\n    method(): FooBarˇ { return new Foo(); }\n}",
20247    );
20248
20249    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
20250        let task_completion_item = completion_item.clone();
20251        async move { Ok(task_completion_item) }
20252    })
20253    .next()
20254    .await
20255    .unwrap();
20256
20257    apply_additional_edits.await.unwrap();
20258
20259    cx.assert_editor_state(
20260        "import { FooBarˇ } from './types';\n\nclass Bar {\n    method(): FooBarˇ { return new Foo(); }\n}",
20261    );
20262}
20263
20264#[gpui::test]
20265async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
20266    init_test(cx, |_| {});
20267
20268    let mut cx = EditorLspTestContext::new_rust(
20269        lsp::ServerCapabilities {
20270            completion_provider: Some(lsp::CompletionOptions {
20271                trigger_characters: Some(vec![".".to_string()]),
20272                resolve_provider: Some(true),
20273                ..Default::default()
20274            }),
20275            ..Default::default()
20276        },
20277        cx,
20278    )
20279    .await;
20280
20281    cx.set_state("fn main() { let a = 2ˇ; }");
20282    cx.simulate_keystroke(".");
20283
20284    let item1 = lsp::CompletionItem {
20285        label: "method id()".to_string(),
20286        filter_text: Some("id".to_string()),
20287        detail: None,
20288        documentation: None,
20289        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20290            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
20291            new_text: ".id".to_string(),
20292        })),
20293        ..lsp::CompletionItem::default()
20294    };
20295
20296    let item2 = lsp::CompletionItem {
20297        label: "other".to_string(),
20298        filter_text: Some("other".to_string()),
20299        detail: None,
20300        documentation: None,
20301        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20302            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
20303            new_text: ".other".to_string(),
20304        })),
20305        ..lsp::CompletionItem::default()
20306    };
20307
20308    let item1 = item1.clone();
20309    cx.set_request_handler::<lsp::request::Completion, _, _>({
20310        let item1 = item1.clone();
20311        move |_, _, _| {
20312            let item1 = item1.clone();
20313            let item2 = item2.clone();
20314            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
20315        }
20316    })
20317    .next()
20318    .await;
20319
20320    cx.condition(|editor, _| editor.context_menu_visible())
20321        .await;
20322    cx.update_editor(|editor, _, _| {
20323        let context_menu = editor.context_menu.borrow_mut();
20324        let context_menu = context_menu
20325            .as_ref()
20326            .expect("Should have the context menu deployed");
20327        match context_menu {
20328            CodeContextMenu::Completions(completions_menu) => {
20329                let completions = completions_menu.completions.borrow_mut();
20330                assert_eq!(
20331                    completions
20332                        .iter()
20333                        .map(|completion| &completion.label.text)
20334                        .collect::<Vec<_>>(),
20335                    vec!["method id()", "other"]
20336                )
20337            }
20338            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
20339        }
20340    });
20341
20342    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
20343        let item1 = item1.clone();
20344        move |_, item_to_resolve, _| {
20345            let item1 = item1.clone();
20346            async move {
20347                if item1 == item_to_resolve {
20348                    Ok(lsp::CompletionItem {
20349                        label: "method id()".to_string(),
20350                        filter_text: Some("id".to_string()),
20351                        detail: Some("Now resolved!".to_string()),
20352                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
20353                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20354                            range: lsp::Range::new(
20355                                lsp::Position::new(0, 22),
20356                                lsp::Position::new(0, 22),
20357                            ),
20358                            new_text: ".id".to_string(),
20359                        })),
20360                        ..lsp::CompletionItem::default()
20361                    })
20362                } else {
20363                    Ok(item_to_resolve)
20364                }
20365            }
20366        }
20367    })
20368    .next()
20369    .await
20370    .unwrap();
20371    cx.run_until_parked();
20372
20373    cx.update_editor(|editor, window, cx| {
20374        editor.context_menu_next(&Default::default(), window, cx);
20375    });
20376    cx.run_until_parked();
20377
20378    cx.update_editor(|editor, _, _| {
20379        let context_menu = editor.context_menu.borrow_mut();
20380        let context_menu = context_menu
20381            .as_ref()
20382            .expect("Should have the context menu deployed");
20383        match context_menu {
20384            CodeContextMenu::Completions(completions_menu) => {
20385                let completions = completions_menu.completions.borrow_mut();
20386                assert_eq!(
20387                    completions
20388                        .iter()
20389                        .map(|completion| &completion.label.text)
20390                        .collect::<Vec<_>>(),
20391                    vec!["method id() Now resolved!", "other"],
20392                    "Should update first completion label, but not second as the filter text did not match."
20393                );
20394            }
20395            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
20396        }
20397    });
20398}
20399
20400#[gpui::test]
20401async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
20402    init_test(cx, |_| {});
20403    let mut cx = EditorLspTestContext::new_rust(
20404        lsp::ServerCapabilities {
20405            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
20406            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
20407            completion_provider: Some(lsp::CompletionOptions {
20408                resolve_provider: Some(true),
20409                ..Default::default()
20410            }),
20411            ..Default::default()
20412        },
20413        cx,
20414    )
20415    .await;
20416    cx.set_state(indoc! {"
20417        struct TestStruct {
20418            field: i32
20419        }
20420
20421        fn mainˇ() {
20422            let unused_var = 42;
20423            let test_struct = TestStruct { field: 42 };
20424        }
20425    "});
20426    let symbol_range = cx.lsp_range(indoc! {"
20427        struct TestStruct {
20428            field: i32
20429        }
20430
20431        «fn main»() {
20432            let unused_var = 42;
20433            let test_struct = TestStruct { field: 42 };
20434        }
20435    "});
20436    let mut hover_requests =
20437        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
20438            Ok(Some(lsp::Hover {
20439                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
20440                    kind: lsp::MarkupKind::Markdown,
20441                    value: "Function documentation".to_string(),
20442                }),
20443                range: Some(symbol_range),
20444            }))
20445        });
20446
20447    // Case 1: Test that code action menu hide hover popover
20448    cx.dispatch_action(Hover);
20449    hover_requests.next().await;
20450    cx.condition(|editor, _| editor.hover_state.visible()).await;
20451    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
20452        move |_, _, _| async move {
20453            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
20454                lsp::CodeAction {
20455                    title: "Remove unused variable".to_string(),
20456                    kind: Some(CodeActionKind::QUICKFIX),
20457                    edit: Some(lsp::WorkspaceEdit {
20458                        changes: Some(
20459                            [(
20460                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
20461                                vec![lsp::TextEdit {
20462                                    range: lsp::Range::new(
20463                                        lsp::Position::new(5, 4),
20464                                        lsp::Position::new(5, 27),
20465                                    ),
20466                                    new_text: "".to_string(),
20467                                }],
20468                            )]
20469                            .into_iter()
20470                            .collect(),
20471                        ),
20472                        ..Default::default()
20473                    }),
20474                    ..Default::default()
20475                },
20476            )]))
20477        },
20478    );
20479    cx.update_editor(|editor, window, cx| {
20480        editor.toggle_code_actions(
20481            &ToggleCodeActions {
20482                deployed_from: None,
20483                quick_launch: false,
20484            },
20485            window,
20486            cx,
20487        );
20488    });
20489    code_action_requests.next().await;
20490    cx.run_until_parked();
20491    cx.condition(|editor, _| editor.context_menu_visible())
20492        .await;
20493    cx.update_editor(|editor, _, _| {
20494        assert!(
20495            !editor.hover_state.visible(),
20496            "Hover popover should be hidden when code action menu is shown"
20497        );
20498        // Hide code actions
20499        editor.context_menu.take();
20500    });
20501
20502    // Case 2: Test that code completions hide hover popover
20503    cx.dispatch_action(Hover);
20504    hover_requests.next().await;
20505    cx.condition(|editor, _| editor.hover_state.visible()).await;
20506    let counter = Arc::new(AtomicUsize::new(0));
20507    let mut completion_requests =
20508        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20509            let counter = counter.clone();
20510            async move {
20511                counter.fetch_add(1, atomic::Ordering::Release);
20512                Ok(Some(lsp::CompletionResponse::Array(vec![
20513                    lsp::CompletionItem {
20514                        label: "main".into(),
20515                        kind: Some(lsp::CompletionItemKind::FUNCTION),
20516                        detail: Some("() -> ()".to_string()),
20517                        ..Default::default()
20518                    },
20519                    lsp::CompletionItem {
20520                        label: "TestStruct".into(),
20521                        kind: Some(lsp::CompletionItemKind::STRUCT),
20522                        detail: Some("struct TestStruct".to_string()),
20523                        ..Default::default()
20524                    },
20525                ])))
20526            }
20527        });
20528    cx.update_editor(|editor, window, cx| {
20529        editor.show_completions(&ShowCompletions, window, cx);
20530    });
20531    completion_requests.next().await;
20532    cx.condition(|editor, _| editor.context_menu_visible())
20533        .await;
20534    cx.update_editor(|editor, _, _| {
20535        assert!(
20536            !editor.hover_state.visible(),
20537            "Hover popover should be hidden when completion menu is shown"
20538        );
20539    });
20540}
20541
20542#[gpui::test]
20543async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
20544    init_test(cx, |_| {});
20545
20546    let mut cx = EditorLspTestContext::new_rust(
20547        lsp::ServerCapabilities {
20548            completion_provider: Some(lsp::CompletionOptions {
20549                trigger_characters: Some(vec![".".to_string()]),
20550                resolve_provider: Some(true),
20551                ..Default::default()
20552            }),
20553            ..Default::default()
20554        },
20555        cx,
20556    )
20557    .await;
20558
20559    cx.set_state("fn main() { let a = 2ˇ; }");
20560    cx.simulate_keystroke(".");
20561
20562    let unresolved_item_1 = lsp::CompletionItem {
20563        label: "id".to_string(),
20564        filter_text: Some("id".to_string()),
20565        detail: None,
20566        documentation: None,
20567        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20568            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
20569            new_text: ".id".to_string(),
20570        })),
20571        ..lsp::CompletionItem::default()
20572    };
20573    let resolved_item_1 = lsp::CompletionItem {
20574        additional_text_edits: Some(vec![lsp::TextEdit {
20575            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
20576            new_text: "!!".to_string(),
20577        }]),
20578        ..unresolved_item_1.clone()
20579    };
20580    let unresolved_item_2 = lsp::CompletionItem {
20581        label: "other".to_string(),
20582        filter_text: Some("other".to_string()),
20583        detail: None,
20584        documentation: None,
20585        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
20586            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
20587            new_text: ".other".to_string(),
20588        })),
20589        ..lsp::CompletionItem::default()
20590    };
20591    let resolved_item_2 = lsp::CompletionItem {
20592        additional_text_edits: Some(vec![lsp::TextEdit {
20593            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
20594            new_text: "??".to_string(),
20595        }]),
20596        ..unresolved_item_2.clone()
20597    };
20598
20599    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
20600    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
20601    cx.lsp
20602        .server
20603        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
20604            let unresolved_item_1 = unresolved_item_1.clone();
20605            let resolved_item_1 = resolved_item_1.clone();
20606            let unresolved_item_2 = unresolved_item_2.clone();
20607            let resolved_item_2 = resolved_item_2.clone();
20608            let resolve_requests_1 = resolve_requests_1.clone();
20609            let resolve_requests_2 = resolve_requests_2.clone();
20610            move |unresolved_request, _| {
20611                let unresolved_item_1 = unresolved_item_1.clone();
20612                let resolved_item_1 = resolved_item_1.clone();
20613                let unresolved_item_2 = unresolved_item_2.clone();
20614                let resolved_item_2 = resolved_item_2.clone();
20615                let resolve_requests_1 = resolve_requests_1.clone();
20616                let resolve_requests_2 = resolve_requests_2.clone();
20617                async move {
20618                    if unresolved_request == unresolved_item_1 {
20619                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
20620                        Ok(resolved_item_1.clone())
20621                    } else if unresolved_request == unresolved_item_2 {
20622                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
20623                        Ok(resolved_item_2.clone())
20624                    } else {
20625                        panic!("Unexpected completion item {unresolved_request:?}")
20626                    }
20627                }
20628            }
20629        })
20630        .detach();
20631
20632    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20633        let unresolved_item_1 = unresolved_item_1.clone();
20634        let unresolved_item_2 = unresolved_item_2.clone();
20635        async move {
20636            Ok(Some(lsp::CompletionResponse::Array(vec![
20637                unresolved_item_1,
20638                unresolved_item_2,
20639            ])))
20640        }
20641    })
20642    .next()
20643    .await;
20644
20645    cx.condition(|editor, _| editor.context_menu_visible())
20646        .await;
20647    cx.update_editor(|editor, _, _| {
20648        let context_menu = editor.context_menu.borrow_mut();
20649        let context_menu = context_menu
20650            .as_ref()
20651            .expect("Should have the context menu deployed");
20652        match context_menu {
20653            CodeContextMenu::Completions(completions_menu) => {
20654                let completions = completions_menu.completions.borrow_mut();
20655                assert_eq!(
20656                    completions
20657                        .iter()
20658                        .map(|completion| &completion.label.text)
20659                        .collect::<Vec<_>>(),
20660                    vec!["id", "other"]
20661                )
20662            }
20663            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
20664        }
20665    });
20666    cx.run_until_parked();
20667
20668    cx.update_editor(|editor, window, cx| {
20669        editor.context_menu_next(&ContextMenuNext, window, cx);
20670    });
20671    cx.run_until_parked();
20672    cx.update_editor(|editor, window, cx| {
20673        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
20674    });
20675    cx.run_until_parked();
20676    cx.update_editor(|editor, window, cx| {
20677        editor.context_menu_next(&ContextMenuNext, window, cx);
20678    });
20679    cx.run_until_parked();
20680    cx.update_editor(|editor, window, cx| {
20681        editor
20682            .compose_completion(&ComposeCompletion::default(), window, cx)
20683            .expect("No task returned")
20684    })
20685    .await
20686    .expect("Completion failed");
20687    cx.run_until_parked();
20688
20689    cx.update_editor(|editor, _, cx| {
20690        assert_eq!(
20691            resolve_requests_1.load(atomic::Ordering::Acquire),
20692            1,
20693            "Should always resolve once despite multiple selections"
20694        );
20695        assert_eq!(
20696            resolve_requests_2.load(atomic::Ordering::Acquire),
20697            1,
20698            "Should always resolve once after multiple selections and applying the completion"
20699        );
20700        assert_eq!(
20701            editor.text(cx),
20702            "fn main() { let a = ??.other; }",
20703            "Should use resolved data when applying the completion"
20704        );
20705    });
20706}
20707
20708#[gpui::test]
20709async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
20710    init_test(cx, |_| {});
20711
20712    let item_0 = lsp::CompletionItem {
20713        label: "abs".into(),
20714        insert_text: Some("abs".into()),
20715        data: Some(json!({ "very": "special"})),
20716        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
20717        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
20718            lsp::InsertReplaceEdit {
20719                new_text: "abs".to_string(),
20720                insert: lsp::Range::default(),
20721                replace: lsp::Range::default(),
20722            },
20723        )),
20724        ..lsp::CompletionItem::default()
20725    };
20726    let items = iter::once(item_0.clone())
20727        .chain((11..51).map(|i| lsp::CompletionItem {
20728            label: format!("item_{}", i),
20729            insert_text: Some(format!("item_{}", i)),
20730            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
20731            ..lsp::CompletionItem::default()
20732        }))
20733        .collect::<Vec<_>>();
20734
20735    let default_commit_characters = vec!["?".to_string()];
20736    let default_data = json!({ "default": "data"});
20737    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
20738    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
20739    let default_edit_range = lsp::Range {
20740        start: lsp::Position {
20741            line: 0,
20742            character: 5,
20743        },
20744        end: lsp::Position {
20745            line: 0,
20746            character: 5,
20747        },
20748    };
20749
20750    let mut cx = EditorLspTestContext::new_rust(
20751        lsp::ServerCapabilities {
20752            completion_provider: Some(lsp::CompletionOptions {
20753                trigger_characters: Some(vec![".".to_string()]),
20754                resolve_provider: Some(true),
20755                ..Default::default()
20756            }),
20757            ..Default::default()
20758        },
20759        cx,
20760    )
20761    .await;
20762
20763    cx.set_state("fn main() { let a = 2ˇ; }");
20764    cx.simulate_keystroke(".");
20765
20766    let completion_data = default_data.clone();
20767    let completion_characters = default_commit_characters.clone();
20768    let completion_items = items.clone();
20769    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
20770        let default_data = completion_data.clone();
20771        let default_commit_characters = completion_characters.clone();
20772        let items = completion_items.clone();
20773        async move {
20774            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
20775                items,
20776                item_defaults: Some(lsp::CompletionListItemDefaults {
20777                    data: Some(default_data.clone()),
20778                    commit_characters: Some(default_commit_characters.clone()),
20779                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
20780                        default_edit_range,
20781                    )),
20782                    insert_text_format: Some(default_insert_text_format),
20783                    insert_text_mode: Some(default_insert_text_mode),
20784                }),
20785                ..lsp::CompletionList::default()
20786            })))
20787        }
20788    })
20789    .next()
20790    .await;
20791
20792    let resolved_items = Arc::new(Mutex::new(Vec::new()));
20793    cx.lsp
20794        .server
20795        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
20796            let closure_resolved_items = resolved_items.clone();
20797            move |item_to_resolve, _| {
20798                let closure_resolved_items = closure_resolved_items.clone();
20799                async move {
20800                    closure_resolved_items.lock().push(item_to_resolve.clone());
20801                    Ok(item_to_resolve)
20802                }
20803            }
20804        })
20805        .detach();
20806
20807    cx.condition(|editor, _| editor.context_menu_visible())
20808        .await;
20809    cx.run_until_parked();
20810    cx.update_editor(|editor, _, _| {
20811        let menu = editor.context_menu.borrow_mut();
20812        match menu.as_ref().expect("should have the completions menu") {
20813            CodeContextMenu::Completions(completions_menu) => {
20814                assert_eq!(
20815                    completions_menu
20816                        .entries
20817                        .borrow()
20818                        .iter()
20819                        .map(|mat| mat.string.clone())
20820                        .collect::<Vec<String>>(),
20821                    items
20822                        .iter()
20823                        .map(|completion| completion.label.clone())
20824                        .collect::<Vec<String>>()
20825                );
20826            }
20827            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
20828        }
20829    });
20830    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
20831    // with 4 from the end.
20832    assert_eq!(
20833        *resolved_items.lock(),
20834        [&items[0..16], &items[items.len() - 4..items.len()]]
20835            .concat()
20836            .iter()
20837            .cloned()
20838            .map(|mut item| {
20839                if item.data.is_none() {
20840                    item.data = Some(default_data.clone());
20841                }
20842                item
20843            })
20844            .collect::<Vec<lsp::CompletionItem>>(),
20845        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
20846    );
20847    resolved_items.lock().clear();
20848
20849    cx.update_editor(|editor, window, cx| {
20850        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
20851    });
20852    cx.run_until_parked();
20853    // Completions that have already been resolved are skipped.
20854    assert_eq!(
20855        *resolved_items.lock(),
20856        items[items.len() - 17..items.len() - 4]
20857            .iter()
20858            .cloned()
20859            .map(|mut item| {
20860                if item.data.is_none() {
20861                    item.data = Some(default_data.clone());
20862                }
20863                item
20864            })
20865            .collect::<Vec<lsp::CompletionItem>>()
20866    );
20867    resolved_items.lock().clear();
20868}
20869
20870#[gpui::test]
20871async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
20872    init_test(cx, |_| {});
20873
20874    let mut cx = EditorLspTestContext::new(
20875        Language::new(
20876            LanguageConfig {
20877                matcher: LanguageMatcher {
20878                    path_suffixes: vec!["jsx".into()],
20879                    ..Default::default()
20880                },
20881                overrides: [(
20882                    "element".into(),
20883                    LanguageConfigOverride {
20884                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
20885                        ..Default::default()
20886                    },
20887                )]
20888                .into_iter()
20889                .collect(),
20890                ..Default::default()
20891            },
20892            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
20893        )
20894        .with_override_query("(jsx_self_closing_element) @element")
20895        .unwrap(),
20896        lsp::ServerCapabilities {
20897            completion_provider: Some(lsp::CompletionOptions {
20898                trigger_characters: Some(vec![":".to_string()]),
20899                ..Default::default()
20900            }),
20901            ..Default::default()
20902        },
20903        cx,
20904    )
20905    .await;
20906
20907    cx.lsp
20908        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
20909            Ok(Some(lsp::CompletionResponse::Array(vec![
20910                lsp::CompletionItem {
20911                    label: "bg-blue".into(),
20912                    ..Default::default()
20913                },
20914                lsp::CompletionItem {
20915                    label: "bg-red".into(),
20916                    ..Default::default()
20917                },
20918                lsp::CompletionItem {
20919                    label: "bg-yellow".into(),
20920                    ..Default::default()
20921                },
20922            ])))
20923        });
20924
20925    cx.set_state(r#"<p class="bgˇ" />"#);
20926
20927    // Trigger completion when typing a dash, because the dash is an extra
20928    // word character in the 'element' scope, which contains the cursor.
20929    cx.simulate_keystroke("-");
20930    cx.executor().run_until_parked();
20931    cx.update_editor(|editor, _, _| {
20932        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
20933        {
20934            assert_eq!(
20935                completion_menu_entries(menu),
20936                &["bg-blue", "bg-red", "bg-yellow"]
20937            );
20938        } else {
20939            panic!("expected completion menu to be open");
20940        }
20941    });
20942
20943    cx.simulate_keystroke("l");
20944    cx.executor().run_until_parked();
20945    cx.update_editor(|editor, _, _| {
20946        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
20947        {
20948            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
20949        } else {
20950            panic!("expected completion menu to be open");
20951        }
20952    });
20953
20954    // When filtering completions, consider the character after the '-' to
20955    // be the start of a subword.
20956    cx.set_state(r#"<p class="yelˇ" />"#);
20957    cx.simulate_keystroke("l");
20958    cx.executor().run_until_parked();
20959    cx.update_editor(|editor, _, _| {
20960        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
20961        {
20962            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
20963        } else {
20964            panic!("expected completion menu to be open");
20965        }
20966    });
20967}
20968
20969fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
20970    let entries = menu.entries.borrow();
20971    entries.iter().map(|mat| mat.string.clone()).collect()
20972}
20973
20974#[gpui::test]
20975async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
20976    init_test(cx, |settings| {
20977        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
20978    });
20979
20980    let fs = FakeFs::new(cx.executor());
20981    fs.insert_file(path!("/file.ts"), Default::default()).await;
20982
20983    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
20984    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
20985
20986    language_registry.add(Arc::new(Language::new(
20987        LanguageConfig {
20988            name: "TypeScript".into(),
20989            matcher: LanguageMatcher {
20990                path_suffixes: vec!["ts".to_string()],
20991                ..Default::default()
20992            },
20993            ..Default::default()
20994        },
20995        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
20996    )));
20997    update_test_language_settings(cx, &|settings| {
20998        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
20999    });
21000
21001    let test_plugin = "test_plugin";
21002    let _ = language_registry.register_fake_lsp(
21003        "TypeScript",
21004        FakeLspAdapter {
21005            prettier_plugins: vec![test_plugin],
21006            ..Default::default()
21007        },
21008    );
21009
21010    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
21011    let buffer = project
21012        .update(cx, |project, cx| {
21013            project.open_local_buffer(path!("/file.ts"), cx)
21014        })
21015        .await
21016        .unwrap();
21017
21018    let buffer_text = "one\ntwo\nthree\n";
21019    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21020    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
21021    editor.update_in(cx, |editor, window, cx| {
21022        editor.set_text(buffer_text, window, cx)
21023    });
21024
21025    editor
21026        .update_in(cx, |editor, window, cx| {
21027            editor.perform_format(
21028                project.clone(),
21029                FormatTrigger::Manual,
21030                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
21031                window,
21032                cx,
21033            )
21034        })
21035        .unwrap()
21036        .await;
21037    assert_eq!(
21038        editor.update(cx, |editor, cx| editor.text(cx)),
21039        buffer_text.to_string() + prettier_format_suffix,
21040        "Test prettier formatting was not applied to the original buffer text",
21041    );
21042
21043    update_test_language_settings(cx, &|settings| {
21044        settings.defaults.formatter = Some(FormatterList::default())
21045    });
21046    let format = editor.update_in(cx, |editor, window, cx| {
21047        editor.perform_format(
21048            project.clone(),
21049            FormatTrigger::Manual,
21050            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
21051            window,
21052            cx,
21053        )
21054    });
21055    format.await.unwrap();
21056    assert_eq!(
21057        editor.update(cx, |editor, cx| editor.text(cx)),
21058        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
21059        "Autoformatting (via test prettier) was not applied to the original buffer text",
21060    );
21061}
21062
21063#[gpui::test]
21064async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
21065    init_test(cx, |settings| {
21066        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
21067    });
21068
21069    let fs = FakeFs::new(cx.executor());
21070    fs.insert_file(path!("/file.settings"), Default::default())
21071        .await;
21072
21073    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
21074    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21075
21076    let ts_lang = Arc::new(Language::new(
21077        LanguageConfig {
21078            name: "TypeScript".into(),
21079            matcher: LanguageMatcher {
21080                path_suffixes: vec!["ts".to_string()],
21081                ..LanguageMatcher::default()
21082            },
21083            prettier_parser_name: Some("typescript".to_string()),
21084            ..LanguageConfig::default()
21085        },
21086        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21087    ));
21088
21089    language_registry.add(ts_lang.clone());
21090
21091    update_test_language_settings(cx, &|settings| {
21092        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
21093    });
21094
21095    let test_plugin = "test_plugin";
21096    let _ = language_registry.register_fake_lsp(
21097        "TypeScript",
21098        FakeLspAdapter {
21099            prettier_plugins: vec![test_plugin],
21100            ..Default::default()
21101        },
21102    );
21103
21104    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
21105    let buffer = project
21106        .update(cx, |project, cx| {
21107            project.open_local_buffer(path!("/file.settings"), cx)
21108        })
21109        .await
21110        .unwrap();
21111
21112    project.update(cx, |project, cx| {
21113        project.set_language_for_buffer(&buffer, ts_lang, cx)
21114    });
21115
21116    let buffer_text = "one\ntwo\nthree\n";
21117    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
21118    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
21119    editor.update_in(cx, |editor, window, cx| {
21120        editor.set_text(buffer_text, window, cx)
21121    });
21122
21123    editor
21124        .update_in(cx, |editor, window, cx| {
21125            editor.perform_format(
21126                project.clone(),
21127                FormatTrigger::Manual,
21128                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
21129                window,
21130                cx,
21131            )
21132        })
21133        .unwrap()
21134        .await;
21135    assert_eq!(
21136        editor.update(cx, |editor, cx| editor.text(cx)),
21137        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
21138        "Test prettier formatting was not applied to the original buffer text",
21139    );
21140
21141    update_test_language_settings(cx, &|settings| {
21142        settings.defaults.formatter = Some(FormatterList::default())
21143    });
21144    let format = editor.update_in(cx, |editor, window, cx| {
21145        editor.perform_format(
21146            project.clone(),
21147            FormatTrigger::Manual,
21148            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
21149            window,
21150            cx,
21151        )
21152    });
21153    format.await.unwrap();
21154
21155    assert_eq!(
21156        editor.update(cx, |editor, cx| editor.text(cx)),
21157        buffer_text.to_string()
21158            + prettier_format_suffix
21159            + "\ntypescript\n"
21160            + prettier_format_suffix
21161            + "\ntypescript",
21162        "Autoformatting (via test prettier) was not applied to the original buffer text",
21163    );
21164}
21165
21166#[gpui::test]
21167async fn test_addition_reverts(cx: &mut TestAppContext) {
21168    init_test(cx, |_| {});
21169    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
21170    let base_text = indoc! {r#"
21171        struct Row;
21172        struct Row1;
21173        struct Row2;
21174
21175        struct Row4;
21176        struct Row5;
21177        struct Row6;
21178
21179        struct Row8;
21180        struct Row9;
21181        struct Row10;"#};
21182
21183    // When addition hunks are not adjacent to carets, no hunk revert is performed
21184    assert_hunk_revert(
21185        indoc! {r#"struct Row;
21186                   struct Row1;
21187                   struct Row1.1;
21188                   struct Row1.2;
21189                   struct Row2;ˇ
21190
21191                   struct Row4;
21192                   struct Row5;
21193                   struct Row6;
21194
21195                   struct Row8;
21196                   ˇstruct Row9;
21197                   struct Row9.1;
21198                   struct Row9.2;
21199                   struct Row9.3;
21200                   struct Row10;"#},
21201        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
21202        indoc! {r#"struct Row;
21203                   struct Row1;
21204                   struct Row1.1;
21205                   struct Row1.2;
21206                   struct Row2;ˇ
21207
21208                   struct Row4;
21209                   struct Row5;
21210                   struct Row6;
21211
21212                   struct Row8;
21213                   ˇstruct Row9;
21214                   struct Row9.1;
21215                   struct Row9.2;
21216                   struct Row9.3;
21217                   struct Row10;"#},
21218        base_text,
21219        &mut cx,
21220    );
21221    // Same for selections
21222    assert_hunk_revert(
21223        indoc! {r#"struct Row;
21224                   struct Row1;
21225                   struct Row2;
21226                   struct Row2.1;
21227                   struct Row2.2;
21228                   «ˇ
21229                   struct Row4;
21230                   struct» Row5;
21231                   «struct Row6;
21232                   ˇ»
21233                   struct Row9.1;
21234                   struct Row9.2;
21235                   struct Row9.3;
21236                   struct Row8;
21237                   struct Row9;
21238                   struct Row10;"#},
21239        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
21240        indoc! {r#"struct Row;
21241                   struct Row1;
21242                   struct Row2;
21243                   struct Row2.1;
21244                   struct Row2.2;
21245                   «ˇ
21246                   struct Row4;
21247                   struct» Row5;
21248                   «struct Row6;
21249                   ˇ»
21250                   struct Row9.1;
21251                   struct Row9.2;
21252                   struct Row9.3;
21253                   struct Row8;
21254                   struct Row9;
21255                   struct Row10;"#},
21256        base_text,
21257        &mut cx,
21258    );
21259
21260    // When carets and selections intersect the addition hunks, those are reverted.
21261    // Adjacent carets got merged.
21262    assert_hunk_revert(
21263        indoc! {r#"struct Row;
21264                   ˇ// something on the top
21265                   struct Row1;
21266                   struct Row2;
21267                   struct Roˇw3.1;
21268                   struct Row2.2;
21269                   struct Row2.3;ˇ
21270
21271                   struct Row4;
21272                   struct ˇRow5.1;
21273                   struct Row5.2;
21274                   struct «Rowˇ»5.3;
21275                   struct Row5;
21276                   struct Row6;
21277                   ˇ
21278                   struct Row9.1;
21279                   struct «Rowˇ»9.2;
21280                   struct «ˇRow»9.3;
21281                   struct Row8;
21282                   struct Row9;
21283                   «ˇ// something on bottom»
21284                   struct Row10;"#},
21285        vec![
21286            DiffHunkStatusKind::Added,
21287            DiffHunkStatusKind::Added,
21288            DiffHunkStatusKind::Added,
21289            DiffHunkStatusKind::Added,
21290            DiffHunkStatusKind::Added,
21291        ],
21292        indoc! {r#"struct Row;
21293                   ˇstruct Row1;
21294                   struct Row2;
21295                   ˇ
21296                   struct Row4;
21297                   ˇstruct Row5;
21298                   struct Row6;
21299                   ˇ
21300                   ˇstruct Row8;
21301                   struct Row9;
21302                   ˇstruct Row10;"#},
21303        base_text,
21304        &mut cx,
21305    );
21306}
21307
21308#[gpui::test]
21309async fn test_modification_reverts(cx: &mut TestAppContext) {
21310    init_test(cx, |_| {});
21311    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
21312    let base_text = indoc! {r#"
21313        struct Row;
21314        struct Row1;
21315        struct Row2;
21316
21317        struct Row4;
21318        struct Row5;
21319        struct Row6;
21320
21321        struct Row8;
21322        struct Row9;
21323        struct Row10;"#};
21324
21325    // Modification hunks behave the same as the addition ones.
21326    assert_hunk_revert(
21327        indoc! {r#"struct Row;
21328                   struct Row1;
21329                   struct Row33;
21330                   ˇ
21331                   struct Row4;
21332                   struct Row5;
21333                   struct Row6;
21334                   ˇ
21335                   struct Row99;
21336                   struct Row9;
21337                   struct Row10;"#},
21338        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
21339        indoc! {r#"struct Row;
21340                   struct Row1;
21341                   struct Row33;
21342                   ˇ
21343                   struct Row4;
21344                   struct Row5;
21345                   struct Row6;
21346                   ˇ
21347                   struct Row99;
21348                   struct Row9;
21349                   struct Row10;"#},
21350        base_text,
21351        &mut cx,
21352    );
21353    assert_hunk_revert(
21354        indoc! {r#"struct Row;
21355                   struct Row1;
21356                   struct Row33;
21357                   «ˇ
21358                   struct Row4;
21359                   struct» Row5;
21360                   «struct Row6;
21361                   ˇ»
21362                   struct Row99;
21363                   struct Row9;
21364                   struct Row10;"#},
21365        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
21366        indoc! {r#"struct Row;
21367                   struct Row1;
21368                   struct Row33;
21369                   «ˇ
21370                   struct Row4;
21371                   struct» Row5;
21372                   «struct Row6;
21373                   ˇ»
21374                   struct Row99;
21375                   struct Row9;
21376                   struct Row10;"#},
21377        base_text,
21378        &mut cx,
21379    );
21380
21381    assert_hunk_revert(
21382        indoc! {r#"ˇstruct Row1.1;
21383                   struct Row1;
21384                   «ˇstr»uct Row22;
21385
21386                   struct ˇRow44;
21387                   struct Row5;
21388                   struct «Rˇ»ow66;ˇ
21389
21390                   «struˇ»ct Row88;
21391                   struct Row9;
21392                   struct Row1011;ˇ"#},
21393        vec![
21394            DiffHunkStatusKind::Modified,
21395            DiffHunkStatusKind::Modified,
21396            DiffHunkStatusKind::Modified,
21397            DiffHunkStatusKind::Modified,
21398            DiffHunkStatusKind::Modified,
21399            DiffHunkStatusKind::Modified,
21400        ],
21401        indoc! {r#"struct Row;
21402                   ˇstruct Row1;
21403                   struct Row2;
21404                   ˇ
21405                   struct Row4;
21406                   ˇstruct Row5;
21407                   struct Row6;
21408                   ˇ
21409                   struct Row8;
21410                   ˇstruct Row9;
21411                   struct Row10;ˇ"#},
21412        base_text,
21413        &mut cx,
21414    );
21415}
21416
21417#[gpui::test]
21418async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
21419    init_test(cx, |_| {});
21420    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
21421    let base_text = indoc! {r#"
21422        one
21423
21424        two
21425        three
21426        "#};
21427
21428    cx.set_head_text(base_text);
21429    cx.set_state("\nˇ\n");
21430    cx.executor().run_until_parked();
21431    cx.update_editor(|editor, _window, cx| {
21432        editor.expand_selected_diff_hunks(cx);
21433    });
21434    cx.executor().run_until_parked();
21435    cx.update_editor(|editor, window, cx| {
21436        editor.backspace(&Default::default(), window, cx);
21437    });
21438    cx.run_until_parked();
21439    cx.assert_state_with_diff(
21440        indoc! {r#"
21441
21442        - two
21443        - threeˇ
21444        +
21445        "#}
21446        .to_string(),
21447    );
21448}
21449
21450#[gpui::test]
21451async fn test_deletion_reverts(cx: &mut TestAppContext) {
21452    init_test(cx, |_| {});
21453    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
21454    let base_text = indoc! {r#"struct Row;
21455struct Row1;
21456struct Row2;
21457
21458struct Row4;
21459struct Row5;
21460struct Row6;
21461
21462struct Row8;
21463struct Row9;
21464struct Row10;"#};
21465
21466    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
21467    assert_hunk_revert(
21468        indoc! {r#"struct Row;
21469                   struct Row2;
21470
21471                   ˇstruct Row4;
21472                   struct Row5;
21473                   struct Row6;
21474                   ˇ
21475                   struct Row8;
21476                   struct Row10;"#},
21477        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
21478        indoc! {r#"struct Row;
21479                   struct Row2;
21480
21481                   ˇstruct Row4;
21482                   struct Row5;
21483                   struct Row6;
21484                   ˇ
21485                   struct Row8;
21486                   struct Row10;"#},
21487        base_text,
21488        &mut cx,
21489    );
21490    assert_hunk_revert(
21491        indoc! {r#"struct Row;
21492                   struct Row2;
21493
21494                   «ˇstruct Row4;
21495                   struct» Row5;
21496                   «struct Row6;
21497                   ˇ»
21498                   struct Row8;
21499                   struct Row10;"#},
21500        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
21501        indoc! {r#"struct Row;
21502                   struct Row2;
21503
21504                   «ˇstruct Row4;
21505                   struct» Row5;
21506                   «struct Row6;
21507                   ˇ»
21508                   struct Row8;
21509                   struct Row10;"#},
21510        base_text,
21511        &mut cx,
21512    );
21513
21514    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
21515    assert_hunk_revert(
21516        indoc! {r#"struct Row;
21517                   ˇstruct Row2;
21518
21519                   struct Row4;
21520                   struct Row5;
21521                   struct Row6;
21522
21523                   struct Row8;ˇ
21524                   struct Row10;"#},
21525        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
21526        indoc! {r#"struct Row;
21527                   struct Row1;
21528                   ˇstruct Row2;
21529
21530                   struct Row4;
21531                   struct Row5;
21532                   struct Row6;
21533
21534                   struct Row8;ˇ
21535                   struct Row9;
21536                   struct Row10;"#},
21537        base_text,
21538        &mut cx,
21539    );
21540    assert_hunk_revert(
21541        indoc! {r#"struct Row;
21542                   struct Row2«ˇ;
21543                   struct Row4;
21544                   struct» Row5;
21545                   «struct Row6;
21546
21547                   struct Row8;ˇ»
21548                   struct Row10;"#},
21549        vec![
21550            DiffHunkStatusKind::Deleted,
21551            DiffHunkStatusKind::Deleted,
21552            DiffHunkStatusKind::Deleted,
21553        ],
21554        indoc! {r#"struct Row;
21555                   struct Row1;
21556                   struct Row2«ˇ;
21557
21558                   struct Row4;
21559                   struct» Row5;
21560                   «struct Row6;
21561
21562                   struct Row8;ˇ»
21563                   struct Row9;
21564                   struct Row10;"#},
21565        base_text,
21566        &mut cx,
21567    );
21568}
21569
21570#[gpui::test]
21571async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
21572    init_test(cx, |_| {});
21573
21574    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
21575    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
21576    let base_text_3 =
21577        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
21578
21579    let text_1 = edit_first_char_of_every_line(base_text_1);
21580    let text_2 = edit_first_char_of_every_line(base_text_2);
21581    let text_3 = edit_first_char_of_every_line(base_text_3);
21582
21583    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
21584    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
21585    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
21586
21587    let multibuffer = cx.new(|cx| {
21588        let mut multibuffer = MultiBuffer::new(ReadWrite);
21589        multibuffer.set_excerpts_for_path(
21590            PathKey::sorted(0),
21591            buffer_1.clone(),
21592            [
21593                Point::new(0, 0)..Point::new(2, 0),
21594                Point::new(5, 0)..Point::new(6, 0),
21595                Point::new(9, 0)..Point::new(9, 4),
21596            ],
21597            0,
21598            cx,
21599        );
21600        multibuffer.set_excerpts_for_path(
21601            PathKey::sorted(1),
21602            buffer_2.clone(),
21603            [
21604                Point::new(0, 0)..Point::new(2, 0),
21605                Point::new(5, 0)..Point::new(6, 0),
21606                Point::new(9, 0)..Point::new(9, 4),
21607            ],
21608            0,
21609            cx,
21610        );
21611        multibuffer.set_excerpts_for_path(
21612            PathKey::sorted(2),
21613            buffer_3.clone(),
21614            [
21615                Point::new(0, 0)..Point::new(2, 0),
21616                Point::new(5, 0)..Point::new(6, 0),
21617                Point::new(9, 0)..Point::new(9, 4),
21618            ],
21619            0,
21620            cx,
21621        );
21622        multibuffer
21623    });
21624
21625    let fs = FakeFs::new(cx.executor());
21626    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
21627    let (editor, cx) = cx
21628        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
21629    editor.update_in(cx, |editor, _window, cx| {
21630        for (buffer, diff_base) in [
21631            (buffer_1.clone(), base_text_1),
21632            (buffer_2.clone(), base_text_2),
21633            (buffer_3.clone(), base_text_3),
21634        ] {
21635            let diff = cx.new(|cx| {
21636                BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
21637            });
21638            editor
21639                .buffer
21640                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
21641        }
21642    });
21643    cx.executor().run_until_parked();
21644
21645    editor.update_in(cx, |editor, window, cx| {
21646        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}");
21647        editor.select_all(&SelectAll, window, cx);
21648        editor.git_restore(&Default::default(), window, cx);
21649    });
21650    cx.executor().run_until_parked();
21651
21652    // When all ranges are selected, all buffer hunks are reverted.
21653    editor.update(cx, |editor, cx| {
21654        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");
21655    });
21656    buffer_1.update(cx, |buffer, _| {
21657        assert_eq!(buffer.text(), base_text_1);
21658    });
21659    buffer_2.update(cx, |buffer, _| {
21660        assert_eq!(buffer.text(), base_text_2);
21661    });
21662    buffer_3.update(cx, |buffer, _| {
21663        assert_eq!(buffer.text(), base_text_3);
21664    });
21665
21666    editor.update_in(cx, |editor, window, cx| {
21667        editor.undo(&Default::default(), window, cx);
21668    });
21669
21670    editor.update_in(cx, |editor, window, cx| {
21671        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21672            s.select_ranges(Some(Point::new(0, 0)..Point::new(5, 0)));
21673        });
21674        editor.git_restore(&Default::default(), window, cx);
21675    });
21676
21677    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
21678    // but not affect buffer_2 and its related excerpts.
21679    editor.update(cx, |editor, cx| {
21680        assert_eq!(
21681            editor.display_text(cx),
21682            "\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}"
21683        );
21684    });
21685    buffer_1.update(cx, |buffer, _| {
21686        assert_eq!(buffer.text(), base_text_1);
21687    });
21688    buffer_2.update(cx, |buffer, _| {
21689        assert_eq!(
21690            buffer.text(),
21691            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
21692        );
21693    });
21694    buffer_3.update(cx, |buffer, _| {
21695        assert_eq!(
21696            buffer.text(),
21697            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
21698        );
21699    });
21700
21701    fn edit_first_char_of_every_line(text: &str) -> String {
21702        text.split('\n')
21703            .map(|line| format!("X{}", &line[1..]))
21704            .collect::<Vec<_>>()
21705            .join("\n")
21706    }
21707}
21708
21709#[gpui::test]
21710async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
21711    init_test(cx, |_| {});
21712
21713    let cols = 4;
21714    let rows = 10;
21715    let sample_text_1 = sample_text(rows, cols, 'a');
21716    assert_eq!(
21717        sample_text_1,
21718        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
21719    );
21720    let sample_text_2 = sample_text(rows, cols, 'l');
21721    assert_eq!(
21722        sample_text_2,
21723        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
21724    );
21725    let sample_text_3 = sample_text(rows, cols, 'v');
21726    assert_eq!(
21727        sample_text_3,
21728        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
21729    );
21730
21731    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
21732    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
21733    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
21734
21735    let multi_buffer = cx.new(|cx| {
21736        let mut multibuffer = MultiBuffer::new(ReadWrite);
21737        multibuffer.set_excerpts_for_path(
21738            PathKey::sorted(0),
21739            buffer_1.clone(),
21740            [
21741                Point::new(0, 0)..Point::new(2, 0),
21742                Point::new(5, 0)..Point::new(6, 0),
21743                Point::new(9, 0)..Point::new(9, 4),
21744            ],
21745            0,
21746            cx,
21747        );
21748        multibuffer.set_excerpts_for_path(
21749            PathKey::sorted(1),
21750            buffer_2.clone(),
21751            [
21752                Point::new(0, 0)..Point::new(2, 0),
21753                Point::new(5, 0)..Point::new(6, 0),
21754                Point::new(9, 0)..Point::new(9, 4),
21755            ],
21756            0,
21757            cx,
21758        );
21759        multibuffer.set_excerpts_for_path(
21760            PathKey::sorted(2),
21761            buffer_3.clone(),
21762            [
21763                Point::new(0, 0)..Point::new(2, 0),
21764                Point::new(5, 0)..Point::new(6, 0),
21765                Point::new(9, 0)..Point::new(9, 4),
21766            ],
21767            0,
21768            cx,
21769        );
21770        multibuffer
21771    });
21772
21773    let fs = FakeFs::new(cx.executor());
21774    fs.insert_tree(
21775        "/a",
21776        json!({
21777            "main.rs": sample_text_1,
21778            "other.rs": sample_text_2,
21779            "lib.rs": sample_text_3,
21780        }),
21781    )
21782    .await;
21783    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21784    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
21785    let workspace = window
21786        .read_with(cx, |mw, _| mw.workspace().clone())
21787        .unwrap();
21788    let cx = &mut VisualTestContext::from_window(*window, cx);
21789    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21790        Editor::new(
21791            EditorMode::full(),
21792            multi_buffer,
21793            Some(project.clone()),
21794            window,
21795            cx,
21796        )
21797    });
21798    let multibuffer_item_id = workspace.update_in(cx, |workspace, window, cx| {
21799        assert!(
21800            workspace.active_item(cx).is_none(),
21801            "active item should be None before the first item is added"
21802        );
21803        workspace.add_item_to_active_pane(
21804            Box::new(multi_buffer_editor.clone()),
21805            None,
21806            true,
21807            window,
21808            cx,
21809        );
21810        let active_item = workspace
21811            .active_item(cx)
21812            .expect("should have an active item after adding the multi buffer");
21813        assert_eq!(
21814            active_item.buffer_kind(cx),
21815            ItemBufferKind::Multibuffer,
21816            "A multi buffer was expected to active after adding"
21817        );
21818        active_item.item_id()
21819    });
21820
21821    cx.executor().run_until_parked();
21822
21823    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21824        editor.change_selections(
21825            SelectionEffects::scroll(Autoscroll::Next),
21826            window,
21827            cx,
21828            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
21829        );
21830        editor.open_excerpts(&OpenExcerpts, window, cx);
21831    });
21832    cx.executor().run_until_parked();
21833    let first_item_id = workspace.update_in(cx, |workspace, window, cx| {
21834        let active_item = workspace
21835            .active_item(cx)
21836            .expect("should have an active item after navigating into the 1st buffer");
21837        let first_item_id = active_item.item_id();
21838        assert_ne!(
21839            first_item_id, multibuffer_item_id,
21840            "Should navigate into the 1st buffer and activate it"
21841        );
21842        assert_eq!(
21843            active_item.buffer_kind(cx),
21844            ItemBufferKind::Singleton,
21845            "New active item should be a singleton buffer"
21846        );
21847        assert_eq!(
21848            active_item
21849                .act_as::<Editor>(cx)
21850                .expect("should have navigated into an editor for the 1st buffer")
21851                .read(cx)
21852                .text(cx),
21853            sample_text_1
21854        );
21855
21856        workspace
21857            .go_back(workspace.active_pane().downgrade(), window, cx)
21858            .detach_and_log_err(cx);
21859
21860        first_item_id
21861    });
21862
21863    cx.executor().run_until_parked();
21864    workspace.update_in(cx, |workspace, _, cx| {
21865        let active_item = workspace
21866            .active_item(cx)
21867            .expect("should have an active item after navigating back");
21868        assert_eq!(
21869            active_item.item_id(),
21870            multibuffer_item_id,
21871            "Should navigate back to the multi buffer"
21872        );
21873        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
21874    });
21875
21876    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21877        editor.change_selections(
21878            SelectionEffects::scroll(Autoscroll::Next),
21879            window,
21880            cx,
21881            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
21882        );
21883        editor.open_excerpts(&OpenExcerpts, window, cx);
21884    });
21885    cx.executor().run_until_parked();
21886    let second_item_id = workspace.update_in(cx, |workspace, window, cx| {
21887        let active_item = workspace
21888            .active_item(cx)
21889            .expect("should have an active item after navigating into the 2nd buffer");
21890        let second_item_id = active_item.item_id();
21891        assert_ne!(
21892            second_item_id, multibuffer_item_id,
21893            "Should navigate away from the multibuffer"
21894        );
21895        assert_ne!(
21896            second_item_id, first_item_id,
21897            "Should navigate into the 2nd buffer and activate it"
21898        );
21899        assert_eq!(
21900            active_item.buffer_kind(cx),
21901            ItemBufferKind::Singleton,
21902            "New active item should be a singleton buffer"
21903        );
21904        assert_eq!(
21905            active_item
21906                .act_as::<Editor>(cx)
21907                .expect("should have navigated into an editor")
21908                .read(cx)
21909                .text(cx),
21910            sample_text_2
21911        );
21912
21913        workspace
21914            .go_back(workspace.active_pane().downgrade(), window, cx)
21915            .detach_and_log_err(cx);
21916
21917        second_item_id
21918    });
21919
21920    cx.executor().run_until_parked();
21921    workspace.update_in(cx, |workspace, _, cx| {
21922        let active_item = workspace
21923            .active_item(cx)
21924            .expect("should have an active item after navigating back from the 2nd buffer");
21925        assert_eq!(
21926            active_item.item_id(),
21927            multibuffer_item_id,
21928            "Should navigate back from the 2nd buffer to the multi buffer"
21929        );
21930        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
21931    });
21932
21933    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21934        editor.change_selections(
21935            SelectionEffects::scroll(Autoscroll::Next),
21936            window,
21937            cx,
21938            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
21939        );
21940        editor.open_excerpts(&OpenExcerpts, window, cx);
21941    });
21942    cx.executor().run_until_parked();
21943    workspace.update_in(cx, |workspace, window, cx| {
21944        let active_item = workspace
21945            .active_item(cx)
21946            .expect("should have an active item after navigating into the 3rd buffer");
21947        let third_item_id = active_item.item_id();
21948        assert_ne!(
21949            third_item_id, multibuffer_item_id,
21950            "Should navigate into the 3rd buffer and activate it"
21951        );
21952        assert_ne!(third_item_id, first_item_id);
21953        assert_ne!(third_item_id, second_item_id);
21954        assert_eq!(
21955            active_item.buffer_kind(cx),
21956            ItemBufferKind::Singleton,
21957            "New active item should be a singleton buffer"
21958        );
21959        assert_eq!(
21960            active_item
21961                .act_as::<Editor>(cx)
21962                .expect("should have navigated into an editor")
21963                .read(cx)
21964                .text(cx),
21965            sample_text_3
21966        );
21967
21968        workspace
21969            .go_back(workspace.active_pane().downgrade(), window, cx)
21970            .detach_and_log_err(cx);
21971    });
21972
21973    cx.executor().run_until_parked();
21974    workspace.update_in(cx, |workspace, _, cx| {
21975        let active_item = workspace
21976            .active_item(cx)
21977            .expect("should have an active item after navigating back from the 3rd buffer");
21978        assert_eq!(
21979            active_item.item_id(),
21980            multibuffer_item_id,
21981            "Should navigate back from the 3rd buffer to the multi buffer"
21982        );
21983        assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
21984    });
21985}
21986
21987#[gpui::test]
21988async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21989    init_test(cx, |_| {});
21990
21991    let mut cx = EditorTestContext::new(cx).await;
21992
21993    let diff_base = r#"
21994        use some::mod;
21995
21996        const A: u32 = 42;
21997
21998        fn main() {
21999            println!("hello");
22000
22001            println!("world");
22002        }
22003        "#
22004    .unindent();
22005
22006    cx.set_state(
22007        &r#"
22008        use some::modified;
22009
22010        ˇ
22011        fn main() {
22012            println!("hello there");
22013
22014            println!("around the");
22015            println!("world");
22016        }
22017        "#
22018        .unindent(),
22019    );
22020
22021    cx.set_head_text(&diff_base);
22022    executor.run_until_parked();
22023
22024    cx.update_editor(|editor, window, cx| {
22025        editor.go_to_next_hunk(&GoToHunk, window, cx);
22026        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22027    });
22028    executor.run_until_parked();
22029    cx.assert_state_with_diff(
22030        r#"
22031          use some::modified;
22032
22033
22034          fn main() {
22035        -     println!("hello");
22036        + ˇ    println!("hello there");
22037
22038              println!("around the");
22039              println!("world");
22040          }
22041        "#
22042        .unindent(),
22043    );
22044
22045    cx.update_editor(|editor, window, cx| {
22046        for _ in 0..2 {
22047            editor.go_to_next_hunk(&GoToHunk, window, cx);
22048            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22049        }
22050    });
22051    executor.run_until_parked();
22052    cx.assert_state_with_diff(
22053        r#"
22054        - use some::mod;
22055        + ˇuse some::modified;
22056
22057
22058          fn main() {
22059        -     println!("hello");
22060        +     println!("hello there");
22061
22062        +     println!("around the");
22063              println!("world");
22064          }
22065        "#
22066        .unindent(),
22067    );
22068
22069    cx.update_editor(|editor, window, cx| {
22070        editor.go_to_next_hunk(&GoToHunk, window, cx);
22071        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22072    });
22073    executor.run_until_parked();
22074    cx.assert_state_with_diff(
22075        r#"
22076        - use some::mod;
22077        + use some::modified;
22078
22079        - const A: u32 = 42;
22080          ˇ
22081          fn main() {
22082        -     println!("hello");
22083        +     println!("hello there");
22084
22085        +     println!("around the");
22086              println!("world");
22087          }
22088        "#
22089        .unindent(),
22090    );
22091
22092    cx.update_editor(|editor, window, cx| {
22093        editor.cancel(&Cancel, window, cx);
22094    });
22095
22096    cx.assert_state_with_diff(
22097        r#"
22098          use some::modified;
22099
22100          ˇ
22101          fn main() {
22102              println!("hello there");
22103
22104              println!("around the");
22105              println!("world");
22106          }
22107        "#
22108        .unindent(),
22109    );
22110}
22111
22112#[gpui::test]
22113async fn test_diff_base_change_with_expanded_diff_hunks(
22114    executor: BackgroundExecutor,
22115    cx: &mut TestAppContext,
22116) {
22117    init_test(cx, |_| {});
22118
22119    let mut cx = EditorTestContext::new(cx).await;
22120
22121    let diff_base = r#"
22122        use some::mod1;
22123        use some::mod2;
22124
22125        const A: u32 = 42;
22126        const B: u32 = 42;
22127        const C: u32 = 42;
22128
22129        fn main() {
22130            println!("hello");
22131
22132            println!("world");
22133        }
22134        "#
22135    .unindent();
22136
22137    cx.set_state(
22138        &r#"
22139        use some::mod2;
22140
22141        const A: u32 = 42;
22142        const C: u32 = 42;
22143
22144        fn main(ˇ) {
22145            //println!("hello");
22146
22147            println!("world");
22148            //
22149            //
22150        }
22151        "#
22152        .unindent(),
22153    );
22154
22155    cx.set_head_text(&diff_base);
22156    executor.run_until_parked();
22157
22158    cx.update_editor(|editor, window, cx| {
22159        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22160    });
22161    executor.run_until_parked();
22162    cx.assert_state_with_diff(
22163        r#"
22164        - use some::mod1;
22165          use some::mod2;
22166
22167          const A: u32 = 42;
22168        - const B: u32 = 42;
22169          const C: u32 = 42;
22170
22171          fn main(ˇ) {
22172        -     println!("hello");
22173        +     //println!("hello");
22174
22175              println!("world");
22176        +     //
22177        +     //
22178          }
22179        "#
22180        .unindent(),
22181    );
22182
22183    cx.set_head_text("new diff base!");
22184    executor.run_until_parked();
22185    cx.assert_state_with_diff(
22186        r#"
22187        - new diff base!
22188        + use some::mod2;
22189        +
22190        + const A: u32 = 42;
22191        + const C: u32 = 42;
22192        +
22193        + fn main(ˇ) {
22194        +     //println!("hello");
22195        +
22196        +     println!("world");
22197        +     //
22198        +     //
22199        + }
22200        "#
22201        .unindent(),
22202    );
22203}
22204
22205#[gpui::test]
22206async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
22207    init_test(cx, |_| {});
22208
22209    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
22210    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
22211    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
22212    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
22213    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
22214    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
22215
22216    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
22217    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
22218    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
22219
22220    let multi_buffer = cx.new(|cx| {
22221        let mut multibuffer = MultiBuffer::new(ReadWrite);
22222        multibuffer.set_excerpts_for_path(
22223            PathKey::sorted(0),
22224            buffer_1.clone(),
22225            [
22226                Point::new(0, 0)..Point::new(2, 3),
22227                Point::new(5, 0)..Point::new(6, 3),
22228                Point::new(9, 0)..Point::new(10, 3),
22229            ],
22230            0,
22231            cx,
22232        );
22233        multibuffer.set_excerpts_for_path(
22234            PathKey::sorted(1),
22235            buffer_2.clone(),
22236            [
22237                Point::new(0, 0)..Point::new(2, 3),
22238                Point::new(5, 0)..Point::new(6, 3),
22239                Point::new(9, 0)..Point::new(10, 3),
22240            ],
22241            0,
22242            cx,
22243        );
22244        multibuffer.set_excerpts_for_path(
22245            PathKey::sorted(2),
22246            buffer_3.clone(),
22247            [
22248                Point::new(0, 0)..Point::new(2, 3),
22249                Point::new(5, 0)..Point::new(6, 3),
22250                Point::new(9, 0)..Point::new(10, 3),
22251            ],
22252            0,
22253            cx,
22254        );
22255        assert_eq!(multibuffer.excerpt_ids().len(), 9);
22256        multibuffer
22257    });
22258
22259    let editor =
22260        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
22261    editor
22262        .update(cx, |editor, _window, cx| {
22263            for (buffer, diff_base) in [
22264                (buffer_1.clone(), file_1_old),
22265                (buffer_2.clone(), file_2_old),
22266                (buffer_3.clone(), file_3_old),
22267            ] {
22268                let diff = cx.new(|cx| {
22269                    BufferDiff::new_with_base_text(diff_base, &buffer.read(cx).text_snapshot(), cx)
22270                });
22271                editor
22272                    .buffer
22273                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
22274            }
22275        })
22276        .unwrap();
22277
22278    let mut cx = EditorTestContext::for_editor(editor, cx).await;
22279    cx.run_until_parked();
22280
22281    cx.assert_editor_state(
22282        &"
22283            ˇaaa
22284            ccc
22285            ddd
22286            ggg
22287            hhh
22288
22289            lll
22290            mmm
22291            NNN
22292            qqq
22293            rrr
22294            uuu
22295            111
22296            222
22297            333
22298            666
22299            777
22300            000
22301            !!!"
22302        .unindent(),
22303    );
22304
22305    cx.update_editor(|editor, window, cx| {
22306        editor.select_all(&SelectAll, window, cx);
22307        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22308    });
22309    cx.executor().run_until_parked();
22310
22311    cx.assert_state_with_diff(
22312        "
22313            «aaa
22314          - bbb
22315            ccc
22316            ddd
22317            ggg
22318            hhh
22319
22320            lll
22321            mmm
22322          - nnn
22323          + NNN
22324            qqq
22325            rrr
22326            uuu
22327            111
22328            222
22329            333
22330          + 666
22331            777
22332            000
22333            !!!ˇ»"
22334            .unindent(),
22335    );
22336}
22337
22338#[gpui::test]
22339async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
22340    init_test(cx, |_| {});
22341
22342    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
22343    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
22344
22345    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
22346    let multi_buffer = cx.new(|cx| {
22347        let mut multibuffer = MultiBuffer::new(ReadWrite);
22348        multibuffer.set_excerpts_for_path(
22349            PathKey::sorted(0),
22350            buffer.clone(),
22351            [
22352                Point::new(0, 0)..Point::new(1, 3),
22353                Point::new(4, 0)..Point::new(6, 3),
22354                Point::new(9, 0)..Point::new(9, 3),
22355            ],
22356            0,
22357            cx,
22358        );
22359        assert_eq!(multibuffer.excerpt_ids().len(), 3);
22360        multibuffer
22361    });
22362
22363    let editor =
22364        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
22365    editor
22366        .update(cx, |editor, _window, cx| {
22367            let diff = cx.new(|cx| {
22368                BufferDiff::new_with_base_text(base, &buffer.read(cx).text_snapshot(), cx)
22369            });
22370            editor
22371                .buffer
22372                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
22373        })
22374        .unwrap();
22375
22376    let mut cx = EditorTestContext::for_editor(editor, cx).await;
22377    cx.run_until_parked();
22378
22379    cx.update_editor(|editor, window, cx| {
22380        editor.expand_all_diff_hunks(&Default::default(), window, cx)
22381    });
22382    cx.executor().run_until_parked();
22383
22384    // When the start of a hunk coincides with the start of its excerpt,
22385    // the hunk is expanded. When the start of a hunk is earlier than
22386    // the start of its excerpt, the hunk is not expanded.
22387    cx.assert_state_with_diff(
22388        "
22389            ˇaaa
22390          - bbb
22391          + BBB
22392          - ddd
22393          - eee
22394          + DDD
22395          + EEE
22396            fff
22397            iii"
22398        .unindent(),
22399    );
22400}
22401
22402#[gpui::test]
22403async fn test_edits_around_expanded_insertion_hunks(
22404    executor: BackgroundExecutor,
22405    cx: &mut TestAppContext,
22406) {
22407    init_test(cx, |_| {});
22408
22409    let mut cx = EditorTestContext::new(cx).await;
22410
22411    let diff_base = r#"
22412        use some::mod1;
22413        use some::mod2;
22414
22415        const A: u32 = 42;
22416
22417        fn main() {
22418            println!("hello");
22419
22420            println!("world");
22421        }
22422        "#
22423    .unindent();
22424    executor.run_until_parked();
22425    cx.set_state(
22426        &r#"
22427        use some::mod1;
22428        use some::mod2;
22429
22430        const A: u32 = 42;
22431        const B: u32 = 42;
22432        const C: u32 = 42;
22433        ˇ
22434
22435        fn main() {
22436            println!("hello");
22437
22438            println!("world");
22439        }
22440        "#
22441        .unindent(),
22442    );
22443
22444    cx.set_head_text(&diff_base);
22445    executor.run_until_parked();
22446
22447    cx.update_editor(|editor, window, cx| {
22448        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22449    });
22450    executor.run_until_parked();
22451
22452    cx.assert_state_with_diff(
22453        r#"
22454        use some::mod1;
22455        use some::mod2;
22456
22457        const A: u32 = 42;
22458      + const B: u32 = 42;
22459      + const C: u32 = 42;
22460      + ˇ
22461
22462        fn main() {
22463            println!("hello");
22464
22465            println!("world");
22466        }
22467      "#
22468        .unindent(),
22469    );
22470
22471    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
22472    executor.run_until_parked();
22473
22474    cx.assert_state_with_diff(
22475        r#"
22476        use some::mod1;
22477        use some::mod2;
22478
22479        const A: u32 = 42;
22480      + const B: u32 = 42;
22481      + const C: u32 = 42;
22482      + const D: u32 = 42;
22483      + ˇ
22484
22485        fn main() {
22486            println!("hello");
22487
22488            println!("world");
22489        }
22490      "#
22491        .unindent(),
22492    );
22493
22494    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
22495    executor.run_until_parked();
22496
22497    cx.assert_state_with_diff(
22498        r#"
22499        use some::mod1;
22500        use some::mod2;
22501
22502        const A: u32 = 42;
22503      + const B: u32 = 42;
22504      + const C: u32 = 42;
22505      + const D: u32 = 42;
22506      + const E: u32 = 42;
22507      + ˇ
22508
22509        fn main() {
22510            println!("hello");
22511
22512            println!("world");
22513        }
22514      "#
22515        .unindent(),
22516    );
22517
22518    cx.update_editor(|editor, window, cx| {
22519        editor.delete_line(&DeleteLine, window, cx);
22520    });
22521    executor.run_until_parked();
22522
22523    cx.assert_state_with_diff(
22524        r#"
22525        use some::mod1;
22526        use some::mod2;
22527
22528        const A: u32 = 42;
22529      + const B: u32 = 42;
22530      + const C: u32 = 42;
22531      + const D: u32 = 42;
22532      + const E: u32 = 42;
22533        ˇ
22534        fn main() {
22535            println!("hello");
22536
22537            println!("world");
22538        }
22539      "#
22540        .unindent(),
22541    );
22542
22543    cx.update_editor(|editor, window, cx| {
22544        editor.move_up(&MoveUp, window, cx);
22545        editor.delete_line(&DeleteLine, window, cx);
22546        editor.move_up(&MoveUp, window, cx);
22547        editor.delete_line(&DeleteLine, window, cx);
22548        editor.move_up(&MoveUp, window, cx);
22549        editor.delete_line(&DeleteLine, window, cx);
22550    });
22551    executor.run_until_parked();
22552    cx.assert_state_with_diff(
22553        r#"
22554        use some::mod1;
22555        use some::mod2;
22556
22557        const A: u32 = 42;
22558      + const B: u32 = 42;
22559        ˇ
22560        fn main() {
22561            println!("hello");
22562
22563            println!("world");
22564        }
22565      "#
22566        .unindent(),
22567    );
22568
22569    cx.update_editor(|editor, window, cx| {
22570        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
22571        editor.delete_line(&DeleteLine, window, cx);
22572    });
22573    executor.run_until_parked();
22574    cx.assert_state_with_diff(
22575        r#"
22576        ˇ
22577        fn main() {
22578            println!("hello");
22579
22580            println!("world");
22581        }
22582      "#
22583        .unindent(),
22584    );
22585}
22586
22587#[gpui::test]
22588async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
22589    init_test(cx, |_| {});
22590
22591    let mut cx = EditorTestContext::new(cx).await;
22592    cx.set_head_text(indoc! { "
22593        one
22594        two
22595        three
22596        four
22597        five
22598        "
22599    });
22600    cx.set_state(indoc! { "
22601        one
22602        ˇthree
22603        five
22604    "});
22605    cx.run_until_parked();
22606    cx.update_editor(|editor, window, cx| {
22607        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
22608    });
22609    cx.assert_state_with_diff(
22610        indoc! { "
22611        one
22612      - two
22613        ˇthree
22614      - four
22615        five
22616    "}
22617        .to_string(),
22618    );
22619    cx.update_editor(|editor, window, cx| {
22620        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
22621    });
22622
22623    cx.assert_state_with_diff(
22624        indoc! { "
22625        one
22626        ˇthree
22627        five
22628    "}
22629        .to_string(),
22630    );
22631
22632    cx.update_editor(|editor, window, cx| {
22633        editor.move_up(&MoveUp, window, cx);
22634        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
22635    });
22636    cx.assert_state_with_diff(
22637        indoc! { "
22638        ˇone
22639      - two
22640        three
22641        five
22642    "}
22643        .to_string(),
22644    );
22645
22646    cx.update_editor(|editor, window, cx| {
22647        editor.move_down(&MoveDown, window, cx);
22648        editor.move_down(&MoveDown, window, cx);
22649        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
22650    });
22651    cx.assert_state_with_diff(
22652        indoc! { "
22653        one
22654      - two
22655        ˇthree
22656      - four
22657        five
22658    "}
22659        .to_string(),
22660    );
22661
22662    cx.set_state(indoc! { "
22663        one
22664        ˇTWO
22665        three
22666        four
22667        five
22668    "});
22669    cx.run_until_parked();
22670    cx.update_editor(|editor, window, cx| {
22671        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
22672    });
22673
22674    cx.assert_state_with_diff(
22675        indoc! { "
22676            one
22677          - two
22678          + ˇTWO
22679            three
22680            four
22681            five
22682        "}
22683        .to_string(),
22684    );
22685    cx.update_editor(|editor, window, cx| {
22686        editor.move_up(&Default::default(), window, cx);
22687        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
22688    });
22689    cx.assert_state_with_diff(
22690        indoc! { "
22691            one
22692            ˇTWO
22693            three
22694            four
22695            five
22696        "}
22697        .to_string(),
22698    );
22699}
22700
22701#[gpui::test]
22702async fn test_toggling_adjacent_diff_hunks_2(
22703    executor: BackgroundExecutor,
22704    cx: &mut TestAppContext,
22705) {
22706    init_test(cx, |_| {});
22707
22708    let mut cx = EditorTestContext::new(cx).await;
22709
22710    let diff_base = r#"
22711        lineA
22712        lineB
22713        lineC
22714        lineD
22715        "#
22716    .unindent();
22717
22718    cx.set_state(
22719        &r#"
22720        ˇlineA1
22721        lineB
22722        lineD
22723        "#
22724        .unindent(),
22725    );
22726    cx.set_head_text(&diff_base);
22727    executor.run_until_parked();
22728
22729    cx.update_editor(|editor, window, cx| {
22730        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22731    });
22732    executor.run_until_parked();
22733    cx.assert_state_with_diff(
22734        r#"
22735        - lineA
22736        + ˇlineA1
22737          lineB
22738          lineD
22739        "#
22740        .unindent(),
22741    );
22742
22743    cx.update_editor(|editor, window, cx| {
22744        editor.move_down(&MoveDown, window, cx);
22745        editor.move_right(&MoveRight, window, cx);
22746        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
22747    });
22748    executor.run_until_parked();
22749    cx.assert_state_with_diff(
22750        r#"
22751        - lineA
22752        + lineA1
22753          lˇineB
22754        - lineC
22755          lineD
22756        "#
22757        .unindent(),
22758    );
22759}
22760
22761#[gpui::test]
22762async fn test_edits_around_expanded_deletion_hunks(
22763    executor: BackgroundExecutor,
22764    cx: &mut TestAppContext,
22765) {
22766    init_test(cx, |_| {});
22767
22768    let mut cx = EditorTestContext::new(cx).await;
22769
22770    let diff_base = r#"
22771        use some::mod1;
22772        use some::mod2;
22773
22774        const A: u32 = 42;
22775        const B: u32 = 42;
22776        const C: u32 = 42;
22777
22778
22779        fn main() {
22780            println!("hello");
22781
22782            println!("world");
22783        }
22784    "#
22785    .unindent();
22786    executor.run_until_parked();
22787    cx.set_state(
22788        &r#"
22789        use some::mod1;
22790        use some::mod2;
22791
22792        ˇconst B: u32 = 42;
22793        const C: u32 = 42;
22794
22795
22796        fn main() {
22797            println!("hello");
22798
22799            println!("world");
22800        }
22801        "#
22802        .unindent(),
22803    );
22804
22805    cx.set_head_text(&diff_base);
22806    executor.run_until_parked();
22807
22808    cx.update_editor(|editor, window, cx| {
22809        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22810    });
22811    executor.run_until_parked();
22812
22813    cx.assert_state_with_diff(
22814        r#"
22815        use some::mod1;
22816        use some::mod2;
22817
22818      - const A: u32 = 42;
22819        ˇconst B: u32 = 42;
22820        const C: u32 = 42;
22821
22822
22823        fn main() {
22824            println!("hello");
22825
22826            println!("world");
22827        }
22828      "#
22829        .unindent(),
22830    );
22831
22832    cx.update_editor(|editor, window, cx| {
22833        editor.delete_line(&DeleteLine, window, cx);
22834    });
22835    executor.run_until_parked();
22836    cx.assert_state_with_diff(
22837        r#"
22838        use some::mod1;
22839        use some::mod2;
22840
22841      - const A: u32 = 42;
22842      - const B: u32 = 42;
22843        ˇconst C: u32 = 42;
22844
22845
22846        fn main() {
22847            println!("hello");
22848
22849            println!("world");
22850        }
22851      "#
22852        .unindent(),
22853    );
22854
22855    cx.update_editor(|editor, window, cx| {
22856        editor.delete_line(&DeleteLine, window, cx);
22857    });
22858    executor.run_until_parked();
22859    cx.assert_state_with_diff(
22860        r#"
22861        use some::mod1;
22862        use some::mod2;
22863
22864      - const A: u32 = 42;
22865      - const B: u32 = 42;
22866      - const C: u32 = 42;
22867        ˇ
22868
22869        fn main() {
22870            println!("hello");
22871
22872            println!("world");
22873        }
22874      "#
22875        .unindent(),
22876    );
22877
22878    cx.update_editor(|editor, window, cx| {
22879        editor.handle_input("replacement", window, cx);
22880    });
22881    executor.run_until_parked();
22882    cx.assert_state_with_diff(
22883        r#"
22884        use some::mod1;
22885        use some::mod2;
22886
22887      - const A: u32 = 42;
22888      - const B: u32 = 42;
22889      - const C: u32 = 42;
22890      -
22891      + replacementˇ
22892
22893        fn main() {
22894            println!("hello");
22895
22896            println!("world");
22897        }
22898      "#
22899        .unindent(),
22900    );
22901}
22902
22903#[gpui::test]
22904async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22905    init_test(cx, |_| {});
22906
22907    let mut cx = EditorTestContext::new(cx).await;
22908
22909    let base_text = r#"
22910        one
22911        two
22912        three
22913        four
22914        five
22915    "#
22916    .unindent();
22917    executor.run_until_parked();
22918    cx.set_state(
22919        &r#"
22920        one
22921        two
22922        fˇour
22923        five
22924        "#
22925        .unindent(),
22926    );
22927
22928    cx.set_head_text(&base_text);
22929    executor.run_until_parked();
22930
22931    cx.update_editor(|editor, window, cx| {
22932        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22933    });
22934    executor.run_until_parked();
22935
22936    cx.assert_state_with_diff(
22937        r#"
22938          one
22939          two
22940        - three
22941          fˇour
22942          five
22943        "#
22944        .unindent(),
22945    );
22946
22947    cx.update_editor(|editor, window, cx| {
22948        editor.backspace(&Backspace, window, cx);
22949        editor.backspace(&Backspace, window, cx);
22950    });
22951    executor.run_until_parked();
22952    cx.assert_state_with_diff(
22953        r#"
22954          one
22955          two
22956        - threeˇ
22957        - four
22958        + our
22959          five
22960        "#
22961        .unindent(),
22962    );
22963}
22964
22965#[gpui::test]
22966async fn test_edit_after_expanded_modification_hunk(
22967    executor: BackgroundExecutor,
22968    cx: &mut TestAppContext,
22969) {
22970    init_test(cx, |_| {});
22971
22972    let mut cx = EditorTestContext::new(cx).await;
22973
22974    let diff_base = r#"
22975        use some::mod1;
22976        use some::mod2;
22977
22978        const A: u32 = 42;
22979        const B: u32 = 42;
22980        const C: u32 = 42;
22981        const D: u32 = 42;
22982
22983
22984        fn main() {
22985            println!("hello");
22986
22987            println!("world");
22988        }"#
22989    .unindent();
22990
22991    cx.set_state(
22992        &r#"
22993        use some::mod1;
22994        use some::mod2;
22995
22996        const A: u32 = 42;
22997        const B: u32 = 42;
22998        const C: u32 = 43ˇ
22999        const D: u32 = 42;
23000
23001
23002        fn main() {
23003            println!("hello");
23004
23005            println!("world");
23006        }"#
23007        .unindent(),
23008    );
23009
23010    cx.set_head_text(&diff_base);
23011    executor.run_until_parked();
23012    cx.update_editor(|editor, window, cx| {
23013        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23014    });
23015    executor.run_until_parked();
23016
23017    cx.assert_state_with_diff(
23018        r#"
23019        use some::mod1;
23020        use some::mod2;
23021
23022        const A: u32 = 42;
23023        const B: u32 = 42;
23024      - const C: u32 = 42;
23025      + const C: u32 = 43ˇ
23026        const D: u32 = 42;
23027
23028
23029        fn main() {
23030            println!("hello");
23031
23032            println!("world");
23033        }"#
23034        .unindent(),
23035    );
23036
23037    cx.update_editor(|editor, window, cx| {
23038        editor.handle_input("\nnew_line\n", window, cx);
23039    });
23040    executor.run_until_parked();
23041
23042    cx.assert_state_with_diff(
23043        r#"
23044        use some::mod1;
23045        use some::mod2;
23046
23047        const A: u32 = 42;
23048        const B: u32 = 42;
23049      - const C: u32 = 42;
23050      + const C: u32 = 43
23051      + new_line
23052      + ˇ
23053        const D: u32 = 42;
23054
23055
23056        fn main() {
23057            println!("hello");
23058
23059            println!("world");
23060        }"#
23061        .unindent(),
23062    );
23063}
23064
23065#[gpui::test]
23066async fn test_stage_and_unstage_added_file_hunk(
23067    executor: BackgroundExecutor,
23068    cx: &mut TestAppContext,
23069) {
23070    init_test(cx, |_| {});
23071
23072    let mut cx = EditorTestContext::new(cx).await;
23073    cx.update_editor(|editor, _, cx| {
23074        editor.set_expand_all_diff_hunks(cx);
23075    });
23076
23077    let working_copy = r#"
23078            ˇfn main() {
23079                println!("hello, world!");
23080            }
23081        "#
23082    .unindent();
23083
23084    cx.set_state(&working_copy);
23085    executor.run_until_parked();
23086
23087    cx.assert_state_with_diff(
23088        r#"
23089            + ˇfn main() {
23090            +     println!("hello, world!");
23091            + }
23092        "#
23093        .unindent(),
23094    );
23095    cx.assert_index_text(None);
23096
23097    cx.update_editor(|editor, window, cx| {
23098        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
23099    });
23100    executor.run_until_parked();
23101    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
23102    cx.assert_state_with_diff(
23103        r#"
23104            + ˇfn main() {
23105            +     println!("hello, world!");
23106            + }
23107        "#
23108        .unindent(),
23109    );
23110
23111    cx.update_editor(|editor, window, cx| {
23112        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
23113    });
23114    executor.run_until_parked();
23115    cx.assert_index_text(None);
23116}
23117
23118async fn setup_indent_guides_editor(
23119    text: &str,
23120    cx: &mut TestAppContext,
23121) -> (BufferId, EditorTestContext) {
23122    init_test(cx, |_| {});
23123
23124    let mut cx = EditorTestContext::new(cx).await;
23125
23126    let buffer_id = cx.update_editor(|editor, window, cx| {
23127        editor.set_text(text, window, cx);
23128        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
23129
23130        buffer_ids[0]
23131    });
23132
23133    (buffer_id, cx)
23134}
23135
23136fn assert_indent_guides(
23137    range: Range<u32>,
23138    expected: Vec<IndentGuide>,
23139    active_indices: Option<Vec<usize>>,
23140    cx: &mut EditorTestContext,
23141) {
23142    let indent_guides = cx.update_editor(|editor, window, cx| {
23143        let snapshot = editor.snapshot(window, cx).display_snapshot;
23144        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
23145            editor,
23146            MultiBufferRow(range.start)..MultiBufferRow(range.end),
23147            true,
23148            &snapshot,
23149            cx,
23150        );
23151
23152        indent_guides.sort_by(|a, b| {
23153            a.depth.cmp(&b.depth).then(
23154                a.start_row
23155                    .cmp(&b.start_row)
23156                    .then(a.end_row.cmp(&b.end_row)),
23157            )
23158        });
23159        indent_guides
23160    });
23161
23162    if let Some(expected) = active_indices {
23163        let active_indices = cx.update_editor(|editor, window, cx| {
23164            let snapshot = editor.snapshot(window, cx).display_snapshot;
23165            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
23166        });
23167
23168        assert_eq!(
23169            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
23170            expected,
23171            "Active indent guide indices do not match"
23172        );
23173    }
23174
23175    assert_eq!(indent_guides, expected, "Indent guides do not match");
23176}
23177
23178fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
23179    IndentGuide {
23180        buffer_id,
23181        start_row: MultiBufferRow(start_row),
23182        end_row: MultiBufferRow(end_row),
23183        depth,
23184        tab_size: 4,
23185        settings: IndentGuideSettings {
23186            enabled: true,
23187            line_width: 1,
23188            active_line_width: 1,
23189            coloring: IndentGuideColoring::default(),
23190            background_coloring: IndentGuideBackgroundColoring::default(),
23191        },
23192    }
23193}
23194
23195#[gpui::test]
23196async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
23197    let (buffer_id, mut cx) = setup_indent_guides_editor(
23198        &"
23199        fn main() {
23200            let a = 1;
23201        }"
23202        .unindent(),
23203        cx,
23204    )
23205    .await;
23206
23207    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
23208}
23209
23210#[gpui::test]
23211async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
23212    let (buffer_id, mut cx) = setup_indent_guides_editor(
23213        &"
23214        fn main() {
23215            let a = 1;
23216            let b = 2;
23217        }"
23218        .unindent(),
23219        cx,
23220    )
23221    .await;
23222
23223    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
23224}
23225
23226#[gpui::test]
23227async fn test_indent_guide_nested(cx: &mut TestAppContext) {
23228    let (buffer_id, mut cx) = setup_indent_guides_editor(
23229        &"
23230        fn main() {
23231            let a = 1;
23232            if a == 3 {
23233                let b = 2;
23234            } else {
23235                let c = 3;
23236            }
23237        }"
23238        .unindent(),
23239        cx,
23240    )
23241    .await;
23242
23243    assert_indent_guides(
23244        0..8,
23245        vec![
23246            indent_guide(buffer_id, 1, 6, 0),
23247            indent_guide(buffer_id, 3, 3, 1),
23248            indent_guide(buffer_id, 5, 5, 1),
23249        ],
23250        None,
23251        &mut cx,
23252    );
23253}
23254
23255#[gpui::test]
23256async fn test_indent_guide_tab(cx: &mut TestAppContext) {
23257    let (buffer_id, mut cx) = setup_indent_guides_editor(
23258        &"
23259        fn main() {
23260            let a = 1;
23261                let b = 2;
23262            let c = 3;
23263        }"
23264        .unindent(),
23265        cx,
23266    )
23267    .await;
23268
23269    assert_indent_guides(
23270        0..5,
23271        vec![
23272            indent_guide(buffer_id, 1, 3, 0),
23273            indent_guide(buffer_id, 2, 2, 1),
23274        ],
23275        None,
23276        &mut cx,
23277    );
23278}
23279
23280#[gpui::test]
23281async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
23282    let (buffer_id, mut cx) = setup_indent_guides_editor(
23283        &"
23284        fn main() {
23285            let a = 1;
23286
23287            let c = 3;
23288        }"
23289        .unindent(),
23290        cx,
23291    )
23292    .await;
23293
23294    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
23295}
23296
23297#[gpui::test]
23298async fn test_indent_guide_complex(cx: &mut TestAppContext) {
23299    let (buffer_id, mut cx) = setup_indent_guides_editor(
23300        &"
23301        fn main() {
23302            let a = 1;
23303
23304            let c = 3;
23305
23306            if a == 3 {
23307                let b = 2;
23308            } else {
23309                let c = 3;
23310            }
23311        }"
23312        .unindent(),
23313        cx,
23314    )
23315    .await;
23316
23317    assert_indent_guides(
23318        0..11,
23319        vec![
23320            indent_guide(buffer_id, 1, 9, 0),
23321            indent_guide(buffer_id, 6, 6, 1),
23322            indent_guide(buffer_id, 8, 8, 1),
23323        ],
23324        None,
23325        &mut cx,
23326    );
23327}
23328
23329#[gpui::test]
23330async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
23331    let (buffer_id, mut cx) = setup_indent_guides_editor(
23332        &"
23333        fn main() {
23334            let a = 1;
23335
23336            let c = 3;
23337
23338            if a == 3 {
23339                let b = 2;
23340            } else {
23341                let c = 3;
23342            }
23343        }"
23344        .unindent(),
23345        cx,
23346    )
23347    .await;
23348
23349    assert_indent_guides(
23350        1..11,
23351        vec![
23352            indent_guide(buffer_id, 1, 9, 0),
23353            indent_guide(buffer_id, 6, 6, 1),
23354            indent_guide(buffer_id, 8, 8, 1),
23355        ],
23356        None,
23357        &mut cx,
23358    );
23359}
23360
23361#[gpui::test]
23362async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
23363    let (buffer_id, mut cx) = setup_indent_guides_editor(
23364        &"
23365        fn main() {
23366            let a = 1;
23367
23368            let c = 3;
23369
23370            if a == 3 {
23371                let b = 2;
23372            } else {
23373                let c = 3;
23374            }
23375        }"
23376        .unindent(),
23377        cx,
23378    )
23379    .await;
23380
23381    assert_indent_guides(
23382        1..10,
23383        vec![
23384            indent_guide(buffer_id, 1, 9, 0),
23385            indent_guide(buffer_id, 6, 6, 1),
23386            indent_guide(buffer_id, 8, 8, 1),
23387        ],
23388        None,
23389        &mut cx,
23390    );
23391}
23392
23393#[gpui::test]
23394async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
23395    let (buffer_id, mut cx) = setup_indent_guides_editor(
23396        &"
23397        fn main() {
23398            if a {
23399                b(
23400                    c,
23401                    d,
23402                )
23403            } else {
23404                e(
23405                    f
23406                )
23407            }
23408        }"
23409        .unindent(),
23410        cx,
23411    )
23412    .await;
23413
23414    assert_indent_guides(
23415        0..11,
23416        vec![
23417            indent_guide(buffer_id, 1, 10, 0),
23418            indent_guide(buffer_id, 2, 5, 1),
23419            indent_guide(buffer_id, 7, 9, 1),
23420            indent_guide(buffer_id, 3, 4, 2),
23421            indent_guide(buffer_id, 8, 8, 2),
23422        ],
23423        None,
23424        &mut cx,
23425    );
23426
23427    cx.update_editor(|editor, window, cx| {
23428        editor.fold_at(MultiBufferRow(2), window, cx);
23429        assert_eq!(
23430            editor.display_text(cx),
23431            "
23432            fn main() {
23433                if a {
23434                    b(⋯
23435                    )
23436                } else {
23437                    e(
23438                        f
23439                    )
23440                }
23441            }"
23442            .unindent()
23443        );
23444    });
23445
23446    assert_indent_guides(
23447        0..11,
23448        vec![
23449            indent_guide(buffer_id, 1, 10, 0),
23450            indent_guide(buffer_id, 2, 5, 1),
23451            indent_guide(buffer_id, 7, 9, 1),
23452            indent_guide(buffer_id, 8, 8, 2),
23453        ],
23454        None,
23455        &mut cx,
23456    );
23457}
23458
23459#[gpui::test]
23460async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
23461    let (buffer_id, mut cx) = setup_indent_guides_editor(
23462        &"
23463        block1
23464            block2
23465                block3
23466                    block4
23467            block2
23468        block1
23469        block1"
23470            .unindent(),
23471        cx,
23472    )
23473    .await;
23474
23475    assert_indent_guides(
23476        1..10,
23477        vec![
23478            indent_guide(buffer_id, 1, 4, 0),
23479            indent_guide(buffer_id, 2, 3, 1),
23480            indent_guide(buffer_id, 3, 3, 2),
23481        ],
23482        None,
23483        &mut cx,
23484    );
23485}
23486
23487#[gpui::test]
23488async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
23489    let (buffer_id, mut cx) = setup_indent_guides_editor(
23490        &"
23491        block1
23492            block2
23493                block3
23494
23495        block1
23496        block1"
23497            .unindent(),
23498        cx,
23499    )
23500    .await;
23501
23502    assert_indent_guides(
23503        0..6,
23504        vec![
23505            indent_guide(buffer_id, 1, 2, 0),
23506            indent_guide(buffer_id, 2, 2, 1),
23507        ],
23508        None,
23509        &mut cx,
23510    );
23511}
23512
23513#[gpui::test]
23514async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
23515    let (buffer_id, mut cx) = setup_indent_guides_editor(
23516        &"
23517        function component() {
23518        \treturn (
23519        \t\t\t
23520        \t\t<div>
23521        \t\t\t<abc></abc>
23522        \t\t</div>
23523        \t)
23524        }"
23525        .unindent(),
23526        cx,
23527    )
23528    .await;
23529
23530    assert_indent_guides(
23531        0..8,
23532        vec![
23533            indent_guide(buffer_id, 1, 6, 0),
23534            indent_guide(buffer_id, 2, 5, 1),
23535            indent_guide(buffer_id, 4, 4, 2),
23536        ],
23537        None,
23538        &mut cx,
23539    );
23540}
23541
23542#[gpui::test]
23543async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
23544    let (buffer_id, mut cx) = setup_indent_guides_editor(
23545        &"
23546        function component() {
23547        \treturn (
23548        \t
23549        \t\t<div>
23550        \t\t\t<abc></abc>
23551        \t\t</div>
23552        \t)
23553        }"
23554        .unindent(),
23555        cx,
23556    )
23557    .await;
23558
23559    assert_indent_guides(
23560        0..8,
23561        vec![
23562            indent_guide(buffer_id, 1, 6, 0),
23563            indent_guide(buffer_id, 2, 5, 1),
23564            indent_guide(buffer_id, 4, 4, 2),
23565        ],
23566        None,
23567        &mut cx,
23568    );
23569}
23570
23571#[gpui::test]
23572async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
23573    let (buffer_id, mut cx) = setup_indent_guides_editor(
23574        &"
23575        block1
23576
23577
23578
23579            block2
23580        "
23581        .unindent(),
23582        cx,
23583    )
23584    .await;
23585
23586    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
23587}
23588
23589#[gpui::test]
23590async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
23591    let (buffer_id, mut cx) = setup_indent_guides_editor(
23592        &"
23593        def a:
23594        \tb = 3
23595        \tif True:
23596        \t\tc = 4
23597        \t\td = 5
23598        \tprint(b)
23599        "
23600        .unindent(),
23601        cx,
23602    )
23603    .await;
23604
23605    assert_indent_guides(
23606        0..6,
23607        vec![
23608            indent_guide(buffer_id, 1, 5, 0),
23609            indent_guide(buffer_id, 3, 4, 1),
23610        ],
23611        None,
23612        &mut cx,
23613    );
23614}
23615
23616#[gpui::test]
23617async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
23618    let (buffer_id, mut cx) = setup_indent_guides_editor(
23619        &"
23620    fn main() {
23621        let a = 1;
23622    }"
23623        .unindent(),
23624        cx,
23625    )
23626    .await;
23627
23628    cx.update_editor(|editor, window, cx| {
23629        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23630            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
23631        });
23632    });
23633
23634    assert_indent_guides(
23635        0..3,
23636        vec![indent_guide(buffer_id, 1, 1, 0)],
23637        Some(vec![0]),
23638        &mut cx,
23639    );
23640}
23641
23642#[gpui::test]
23643async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
23644    let (buffer_id, mut cx) = setup_indent_guides_editor(
23645        &"
23646    fn main() {
23647        if 1 == 2 {
23648            let a = 1;
23649        }
23650    }"
23651        .unindent(),
23652        cx,
23653    )
23654    .await;
23655
23656    cx.update_editor(|editor, window, cx| {
23657        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23658            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
23659        });
23660    });
23661    cx.run_until_parked();
23662
23663    assert_indent_guides(
23664        0..4,
23665        vec![
23666            indent_guide(buffer_id, 1, 3, 0),
23667            indent_guide(buffer_id, 2, 2, 1),
23668        ],
23669        Some(vec![1]),
23670        &mut cx,
23671    );
23672
23673    cx.update_editor(|editor, window, cx| {
23674        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23675            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
23676        });
23677    });
23678    cx.run_until_parked();
23679
23680    assert_indent_guides(
23681        0..4,
23682        vec![
23683            indent_guide(buffer_id, 1, 3, 0),
23684            indent_guide(buffer_id, 2, 2, 1),
23685        ],
23686        Some(vec![1]),
23687        &mut cx,
23688    );
23689
23690    cx.update_editor(|editor, window, cx| {
23691        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23692            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
23693        });
23694    });
23695    cx.run_until_parked();
23696
23697    assert_indent_guides(
23698        0..4,
23699        vec![
23700            indent_guide(buffer_id, 1, 3, 0),
23701            indent_guide(buffer_id, 2, 2, 1),
23702        ],
23703        Some(vec![0]),
23704        &mut cx,
23705    );
23706}
23707
23708#[gpui::test]
23709async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
23710    let (buffer_id, mut cx) = setup_indent_guides_editor(
23711        &"
23712    fn main() {
23713        let a = 1;
23714
23715        let b = 2;
23716    }"
23717        .unindent(),
23718        cx,
23719    )
23720    .await;
23721
23722    cx.update_editor(|editor, window, cx| {
23723        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23724            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
23725        });
23726    });
23727
23728    assert_indent_guides(
23729        0..5,
23730        vec![indent_guide(buffer_id, 1, 3, 0)],
23731        Some(vec![0]),
23732        &mut cx,
23733    );
23734}
23735
23736#[gpui::test]
23737async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
23738    let (buffer_id, mut cx) = setup_indent_guides_editor(
23739        &"
23740    def m:
23741        a = 1
23742        pass"
23743            .unindent(),
23744        cx,
23745    )
23746    .await;
23747
23748    cx.update_editor(|editor, window, cx| {
23749        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23750            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
23751        });
23752    });
23753
23754    assert_indent_guides(
23755        0..3,
23756        vec![indent_guide(buffer_id, 1, 2, 0)],
23757        Some(vec![0]),
23758        &mut cx,
23759    );
23760}
23761
23762#[gpui::test]
23763async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
23764    init_test(cx, |_| {});
23765    let mut cx = EditorTestContext::new(cx).await;
23766    let text = indoc! {
23767        "
23768        impl A {
23769            fn b() {
23770                0;
23771                3;
23772                5;
23773                6;
23774                7;
23775            }
23776        }
23777        "
23778    };
23779    let base_text = indoc! {
23780        "
23781        impl A {
23782            fn b() {
23783                0;
23784                1;
23785                2;
23786                3;
23787                4;
23788            }
23789            fn c() {
23790                5;
23791                6;
23792                7;
23793            }
23794        }
23795        "
23796    };
23797
23798    cx.update_editor(|editor, window, cx| {
23799        editor.set_text(text, window, cx);
23800
23801        editor.buffer().update(cx, |multibuffer, cx| {
23802            let buffer = multibuffer.as_singleton().unwrap();
23803            let diff = cx.new(|cx| {
23804                BufferDiff::new_with_base_text(base_text, &buffer.read(cx).text_snapshot(), cx)
23805            });
23806
23807            multibuffer.set_all_diff_hunks_expanded(cx);
23808            multibuffer.add_diff(diff, cx);
23809
23810            buffer.read(cx).remote_id()
23811        })
23812    });
23813    cx.run_until_parked();
23814
23815    cx.assert_state_with_diff(
23816        indoc! { "
23817          impl A {
23818              fn b() {
23819                  0;
23820        -         1;
23821        -         2;
23822                  3;
23823        -         4;
23824        -     }
23825        -     fn c() {
23826                  5;
23827                  6;
23828                  7;
23829              }
23830          }
23831          ˇ"
23832        }
23833        .to_string(),
23834    );
23835
23836    let mut actual_guides = cx.update_editor(|editor, window, cx| {
23837        editor
23838            .snapshot(window, cx)
23839            .buffer_snapshot()
23840            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
23841            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
23842            .collect::<Vec<_>>()
23843    });
23844    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
23845    assert_eq!(
23846        actual_guides,
23847        vec![
23848            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
23849            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
23850            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
23851        ]
23852    );
23853}
23854
23855#[gpui::test]
23856async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
23857    init_test(cx, |_| {});
23858    let mut cx = EditorTestContext::new(cx).await;
23859
23860    let diff_base = r#"
23861        a
23862        b
23863        c
23864        "#
23865    .unindent();
23866
23867    cx.set_state(
23868        &r#"
23869        ˇA
23870        b
23871        C
23872        "#
23873        .unindent(),
23874    );
23875    cx.set_head_text(&diff_base);
23876    cx.update_editor(|editor, window, cx| {
23877        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23878    });
23879    executor.run_until_parked();
23880
23881    let both_hunks_expanded = r#"
23882        - a
23883        + ˇA
23884          b
23885        - c
23886        + C
23887        "#
23888    .unindent();
23889
23890    cx.assert_state_with_diff(both_hunks_expanded.clone());
23891
23892    let hunk_ranges = cx.update_editor(|editor, window, cx| {
23893        let snapshot = editor.snapshot(window, cx);
23894        let hunks = editor
23895            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
23896            .collect::<Vec<_>>();
23897        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
23898        hunks
23899            .into_iter()
23900            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
23901            .collect::<Vec<_>>()
23902    });
23903    assert_eq!(hunk_ranges.len(), 2);
23904
23905    cx.update_editor(|editor, _, cx| {
23906        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
23907    });
23908    executor.run_until_parked();
23909
23910    let second_hunk_expanded = r#"
23911          ˇA
23912          b
23913        - c
23914        + C
23915        "#
23916    .unindent();
23917
23918    cx.assert_state_with_diff(second_hunk_expanded);
23919
23920    cx.update_editor(|editor, _, cx| {
23921        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
23922    });
23923    executor.run_until_parked();
23924
23925    cx.assert_state_with_diff(both_hunks_expanded.clone());
23926
23927    cx.update_editor(|editor, _, cx| {
23928        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
23929    });
23930    executor.run_until_parked();
23931
23932    let first_hunk_expanded = r#"
23933        - a
23934        + ˇA
23935          b
23936          C
23937        "#
23938    .unindent();
23939
23940    cx.assert_state_with_diff(first_hunk_expanded);
23941
23942    cx.update_editor(|editor, _, cx| {
23943        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
23944    });
23945    executor.run_until_parked();
23946
23947    cx.assert_state_with_diff(both_hunks_expanded);
23948
23949    cx.set_state(
23950        &r#"
23951        ˇA
23952        b
23953        "#
23954        .unindent(),
23955    );
23956    cx.run_until_parked();
23957
23958    // TODO this cursor position seems bad
23959    cx.assert_state_with_diff(
23960        r#"
23961        - ˇa
23962        + A
23963          b
23964        "#
23965        .unindent(),
23966    );
23967
23968    cx.update_editor(|editor, window, cx| {
23969        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
23970    });
23971
23972    cx.assert_state_with_diff(
23973        r#"
23974            - ˇa
23975            + A
23976              b
23977            - c
23978            "#
23979        .unindent(),
23980    );
23981
23982    let hunk_ranges = cx.update_editor(|editor, window, cx| {
23983        let snapshot = editor.snapshot(window, cx);
23984        let hunks = editor
23985            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
23986            .collect::<Vec<_>>();
23987        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
23988        hunks
23989            .into_iter()
23990            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
23991            .collect::<Vec<_>>()
23992    });
23993    assert_eq!(hunk_ranges.len(), 2);
23994
23995    cx.update_editor(|editor, _, cx| {
23996        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
23997    });
23998    executor.run_until_parked();
23999
24000    cx.assert_state_with_diff(
24001        r#"
24002        - ˇa
24003        + A
24004          b
24005        "#
24006        .unindent(),
24007    );
24008}
24009
24010#[gpui::test]
24011async fn test_toggle_deletion_hunk_at_start_of_file(
24012    executor: BackgroundExecutor,
24013    cx: &mut TestAppContext,
24014) {
24015    init_test(cx, |_| {});
24016    let mut cx = EditorTestContext::new(cx).await;
24017
24018    let diff_base = r#"
24019        a
24020        b
24021        c
24022        "#
24023    .unindent();
24024
24025    cx.set_state(
24026        &r#"
24027        ˇb
24028        c
24029        "#
24030        .unindent(),
24031    );
24032    cx.set_head_text(&diff_base);
24033    cx.update_editor(|editor, window, cx| {
24034        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
24035    });
24036    executor.run_until_parked();
24037
24038    let hunk_expanded = r#"
24039        - a
24040          ˇb
24041          c
24042        "#
24043    .unindent();
24044
24045    cx.assert_state_with_diff(hunk_expanded.clone());
24046
24047    let hunk_ranges = cx.update_editor(|editor, window, cx| {
24048        let snapshot = editor.snapshot(window, cx);
24049        let hunks = editor
24050            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
24051            .collect::<Vec<_>>();
24052        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
24053        hunks
24054            .into_iter()
24055            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
24056            .collect::<Vec<_>>()
24057    });
24058    assert_eq!(hunk_ranges.len(), 1);
24059
24060    cx.update_editor(|editor, _, cx| {
24061        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
24062    });
24063    executor.run_until_parked();
24064
24065    let hunk_collapsed = r#"
24066          ˇb
24067          c
24068        "#
24069    .unindent();
24070
24071    cx.assert_state_with_diff(hunk_collapsed);
24072
24073    cx.update_editor(|editor, _, cx| {
24074        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
24075    });
24076    executor.run_until_parked();
24077
24078    cx.assert_state_with_diff(hunk_expanded);
24079}
24080
24081#[gpui::test]
24082async fn test_select_smaller_syntax_node_after_diff_hunk_collapse(
24083    executor: BackgroundExecutor,
24084    cx: &mut TestAppContext,
24085) {
24086    init_test(cx, |_| {});
24087
24088    let mut cx = EditorTestContext::new(cx).await;
24089    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
24090
24091    cx.set_state(
24092        &r#"
24093        fn main() {
24094            let x = ˇ1;
24095        }
24096        "#
24097        .unindent(),
24098    );
24099
24100    let diff_base = r#"
24101        fn removed_one() {
24102            println!("this function was deleted");
24103        }
24104
24105        fn removed_two() {
24106            println!("this function was also deleted");
24107        }
24108
24109        fn main() {
24110            let x = 1;
24111        }
24112        "#
24113    .unindent();
24114    cx.set_head_text(&diff_base);
24115    executor.run_until_parked();
24116
24117    cx.update_editor(|editor, window, cx| {
24118        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
24119    });
24120    executor.run_until_parked();
24121
24122    cx.update_editor(|editor, window, cx| {
24123        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
24124    });
24125
24126    cx.update_editor(|editor, window, cx| {
24127        editor.collapse_all_diff_hunks(&CollapseAllDiffHunks, window, cx);
24128    });
24129    executor.run_until_parked();
24130
24131    cx.update_editor(|editor, window, cx| {
24132        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
24133    });
24134}
24135
24136#[gpui::test]
24137async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
24138    executor: BackgroundExecutor,
24139    cx: &mut TestAppContext,
24140) {
24141    init_test(cx, |_| {});
24142    let mut cx = EditorTestContext::new(cx).await;
24143
24144    cx.set_state("ˇnew\nsecond\nthird\n");
24145    cx.set_head_text("old\nsecond\nthird\n");
24146    cx.update_editor(|editor, window, cx| {
24147        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
24148    });
24149    executor.run_until_parked();
24150    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
24151
24152    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
24153    cx.update_editor(|editor, window, cx| {
24154        let snapshot = editor.snapshot(window, cx);
24155        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
24156        let hunks = editor
24157            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
24158            .collect::<Vec<_>>();
24159        assert_eq!(hunks.len(), 1);
24160        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
24161        editor.toggle_single_diff_hunk(hunk_range, cx)
24162    });
24163    executor.run_until_parked();
24164    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
24165
24166    // Keep the editor scrolled to the top so the full hunk remains visible.
24167    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
24168}
24169
24170#[gpui::test]
24171async fn test_display_diff_hunks(cx: &mut TestAppContext) {
24172    init_test(cx, |_| {});
24173
24174    let fs = FakeFs::new(cx.executor());
24175    fs.insert_tree(
24176        path!("/test"),
24177        json!({
24178            ".git": {},
24179            "file-1": "ONE\n",
24180            "file-2": "TWO\n",
24181            "file-3": "THREE\n",
24182        }),
24183    )
24184    .await;
24185
24186    fs.set_head_for_repo(
24187        path!("/test/.git").as_ref(),
24188        &[
24189            ("file-1", "one\n".into()),
24190            ("file-2", "two\n".into()),
24191            ("file-3", "three\n".into()),
24192        ],
24193        "deadbeef",
24194    );
24195
24196    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
24197    let mut buffers = vec![];
24198    for i in 1..=3 {
24199        let buffer = project
24200            .update(cx, |project, cx| {
24201                let path = format!(path!("/test/file-{}"), i);
24202                project.open_local_buffer(path, cx)
24203            })
24204            .await
24205            .unwrap();
24206        buffers.push(buffer);
24207    }
24208
24209    let multibuffer = cx.new(|cx| {
24210        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
24211        multibuffer.set_all_diff_hunks_expanded(cx);
24212        for buffer in &buffers {
24213            let snapshot = buffer.read(cx).snapshot();
24214            multibuffer.set_excerpts_for_path(
24215                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
24216                buffer.clone(),
24217                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
24218                2,
24219                cx,
24220            );
24221        }
24222        multibuffer
24223    });
24224
24225    let editor = cx.add_window(|window, cx| {
24226        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
24227    });
24228    cx.run_until_parked();
24229
24230    let snapshot = editor
24231        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
24232        .unwrap();
24233    let hunks = snapshot
24234        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
24235        .map(|hunk| match hunk {
24236            DisplayDiffHunk::Unfolded {
24237                display_row_range, ..
24238            } => display_row_range,
24239            DisplayDiffHunk::Folded { .. } => unreachable!(),
24240        })
24241        .collect::<Vec<_>>();
24242    assert_eq!(
24243        hunks,
24244        [
24245            DisplayRow(2)..DisplayRow(4),
24246            DisplayRow(7)..DisplayRow(9),
24247            DisplayRow(12)..DisplayRow(14),
24248        ]
24249    );
24250}
24251
24252#[gpui::test]
24253async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
24254    init_test(cx, |_| {});
24255
24256    let mut cx = EditorTestContext::new(cx).await;
24257    cx.set_head_text(indoc! { "
24258        one
24259        two
24260        three
24261        four
24262        five
24263        "
24264    });
24265    cx.set_index_text(indoc! { "
24266        one
24267        two
24268        three
24269        four
24270        five
24271        "
24272    });
24273    cx.set_state(indoc! {"
24274        one
24275        TWO
24276        ˇTHREE
24277        FOUR
24278        five
24279    "});
24280    cx.run_until_parked();
24281    cx.update_editor(|editor, window, cx| {
24282        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
24283    });
24284    cx.run_until_parked();
24285    cx.assert_index_text(Some(indoc! {"
24286        one
24287        TWO
24288        THREE
24289        FOUR
24290        five
24291    "}));
24292    cx.set_state(indoc! { "
24293        one
24294        TWO
24295        ˇTHREE-HUNDRED
24296        FOUR
24297        five
24298    "});
24299    cx.run_until_parked();
24300    cx.update_editor(|editor, window, cx| {
24301        let snapshot = editor.snapshot(window, cx);
24302        let hunks = editor
24303            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
24304            .collect::<Vec<_>>();
24305        assert_eq!(hunks.len(), 1);
24306        assert_eq!(
24307            hunks[0].status(),
24308            DiffHunkStatus {
24309                kind: DiffHunkStatusKind::Modified,
24310                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
24311            }
24312        );
24313
24314        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
24315    });
24316    cx.run_until_parked();
24317    cx.assert_index_text(Some(indoc! {"
24318        one
24319        TWO
24320        THREE-HUNDRED
24321        FOUR
24322        five
24323    "}));
24324}
24325
24326#[gpui::test]
24327fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
24328    init_test(cx, |_| {});
24329
24330    let editor = cx.add_window(|window, cx| {
24331        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
24332        build_editor(buffer, window, cx)
24333    });
24334
24335    let render_args = Arc::new(Mutex::new(None));
24336    let snapshot = editor
24337        .update(cx, |editor, window, cx| {
24338            let snapshot = editor.buffer().read(cx).snapshot(cx);
24339            let range =
24340                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
24341
24342            struct RenderArgs {
24343                row: MultiBufferRow,
24344                folded: bool,
24345                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
24346            }
24347
24348            let crease = Crease::inline(
24349                range,
24350                FoldPlaceholder::test(),
24351                {
24352                    let toggle_callback = render_args.clone();
24353                    move |row, folded, callback, _window, _cx| {
24354                        *toggle_callback.lock() = Some(RenderArgs {
24355                            row,
24356                            folded,
24357                            callback,
24358                        });
24359                        div()
24360                    }
24361                },
24362                |_row, _folded, _window, _cx| div(),
24363            );
24364
24365            editor.insert_creases(Some(crease), cx);
24366            let snapshot = editor.snapshot(window, cx);
24367            let _div =
24368                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
24369            snapshot
24370        })
24371        .unwrap();
24372
24373    let render_args = render_args.lock().take().unwrap();
24374    assert_eq!(render_args.row, MultiBufferRow(1));
24375    assert!(!render_args.folded);
24376    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
24377
24378    cx.update_window(*editor, |_, window, cx| {
24379        (render_args.callback)(true, window, cx)
24380    })
24381    .unwrap();
24382    let snapshot = editor
24383        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
24384        .unwrap();
24385    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
24386
24387    cx.update_window(*editor, |_, window, cx| {
24388        (render_args.callback)(false, window, cx)
24389    })
24390    .unwrap();
24391    let snapshot = editor
24392        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
24393        .unwrap();
24394    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
24395}
24396
24397#[gpui::test]
24398async fn test_input_text(cx: &mut TestAppContext) {
24399    init_test(cx, |_| {});
24400    let mut cx = EditorTestContext::new(cx).await;
24401
24402    cx.set_state(
24403        &r#"ˇone
24404        two
24405
24406        three
24407        fourˇ
24408        five
24409
24410        siˇx"#
24411            .unindent(),
24412    );
24413
24414    cx.dispatch_action(HandleInput(String::new()));
24415    cx.assert_editor_state(
24416        &r#"ˇone
24417        two
24418
24419        three
24420        fourˇ
24421        five
24422
24423        siˇx"#
24424            .unindent(),
24425    );
24426
24427    cx.dispatch_action(HandleInput("AAAA".to_string()));
24428    cx.assert_editor_state(
24429        &r#"AAAAˇone
24430        two
24431
24432        three
24433        fourAAAAˇ
24434        five
24435
24436        siAAAAˇx"#
24437            .unindent(),
24438    );
24439}
24440
24441#[gpui::test]
24442async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
24443    init_test(cx, |_| {});
24444
24445    let mut cx = EditorTestContext::new(cx).await;
24446    cx.set_state(
24447        r#"let foo = 1;
24448let foo = 2;
24449let foo = 3;
24450let fooˇ = 4;
24451let foo = 5;
24452let foo = 6;
24453let foo = 7;
24454let foo = 8;
24455let foo = 9;
24456let foo = 10;
24457let foo = 11;
24458let foo = 12;
24459let foo = 13;
24460let foo = 14;
24461let foo = 15;"#,
24462    );
24463
24464    cx.update_editor(|e, window, cx| {
24465        assert_eq!(
24466            e.next_scroll_position,
24467            NextScrollCursorCenterTopBottom::Center,
24468            "Default next scroll direction is center",
24469        );
24470
24471        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
24472        assert_eq!(
24473            e.next_scroll_position,
24474            NextScrollCursorCenterTopBottom::Top,
24475            "After center, next scroll direction should be top",
24476        );
24477
24478        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
24479        assert_eq!(
24480            e.next_scroll_position,
24481            NextScrollCursorCenterTopBottom::Bottom,
24482            "After top, next scroll direction should be bottom",
24483        );
24484
24485        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
24486        assert_eq!(
24487            e.next_scroll_position,
24488            NextScrollCursorCenterTopBottom::Center,
24489            "After bottom, scrolling should start over",
24490        );
24491
24492        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
24493        assert_eq!(
24494            e.next_scroll_position,
24495            NextScrollCursorCenterTopBottom::Top,
24496            "Scrolling continues if retriggered fast enough"
24497        );
24498    });
24499
24500    cx.executor()
24501        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
24502    cx.executor().run_until_parked();
24503    cx.update_editor(|e, _, _| {
24504        assert_eq!(
24505            e.next_scroll_position,
24506            NextScrollCursorCenterTopBottom::Center,
24507            "If scrolling is not triggered fast enough, it should reset"
24508        );
24509    });
24510}
24511
24512#[gpui::test]
24513async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
24514    init_test(cx, |_| {});
24515    let mut cx = EditorLspTestContext::new_rust(
24516        lsp::ServerCapabilities {
24517            definition_provider: Some(lsp::OneOf::Left(true)),
24518            references_provider: Some(lsp::OneOf::Left(true)),
24519            ..lsp::ServerCapabilities::default()
24520        },
24521        cx,
24522    )
24523    .await;
24524
24525    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
24526        let go_to_definition = cx
24527            .lsp
24528            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
24529                move |params, _| async move {
24530                    if empty_go_to_definition {
24531                        Ok(None)
24532                    } else {
24533                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
24534                            uri: params.text_document_position_params.text_document.uri,
24535                            range: lsp::Range::new(
24536                                lsp::Position::new(4, 3),
24537                                lsp::Position::new(4, 6),
24538                            ),
24539                        })))
24540                    }
24541                },
24542            );
24543        let references = cx
24544            .lsp
24545            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
24546                Ok(Some(vec![lsp::Location {
24547                    uri: params.text_document_position.text_document.uri,
24548                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
24549                }]))
24550            });
24551        (go_to_definition, references)
24552    };
24553
24554    cx.set_state(
24555        &r#"fn one() {
24556            let mut a = ˇtwo();
24557        }
24558
24559        fn two() {}"#
24560            .unindent(),
24561    );
24562    set_up_lsp_handlers(false, &mut cx);
24563    let navigated = cx
24564        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
24565        .await
24566        .expect("Failed to navigate to definition");
24567    assert_eq!(
24568        navigated,
24569        Navigated::Yes,
24570        "Should have navigated to definition from the GetDefinition response"
24571    );
24572    cx.assert_editor_state(
24573        &r#"fn one() {
24574            let mut a = two();
24575        }
24576
24577        fn «twoˇ»() {}"#
24578            .unindent(),
24579    );
24580
24581    let editors = cx.update_workspace(|workspace, _, cx| {
24582        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
24583    });
24584    cx.update_editor(|_, _, test_editor_cx| {
24585        assert_eq!(
24586            editors.len(),
24587            1,
24588            "Initially, only one, test, editor should be open in the workspace"
24589        );
24590        assert_eq!(
24591            test_editor_cx.entity(),
24592            editors.last().expect("Asserted len is 1").clone()
24593        );
24594    });
24595
24596    set_up_lsp_handlers(true, &mut cx);
24597    let navigated = cx
24598        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
24599        .await
24600        .expect("Failed to navigate to lookup references");
24601    assert_eq!(
24602        navigated,
24603        Navigated::Yes,
24604        "Should have navigated to references as a fallback after empty GoToDefinition response"
24605    );
24606    // We should not change the selections in the existing file,
24607    // if opening another milti buffer with the references
24608    cx.assert_editor_state(
24609        &r#"fn one() {
24610            let mut a = two();
24611        }
24612
24613        fn «twoˇ»() {}"#
24614            .unindent(),
24615    );
24616    let editors = cx.update_workspace(|workspace, _, cx| {
24617        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
24618    });
24619    cx.update_editor(|_, _, test_editor_cx| {
24620        assert_eq!(
24621            editors.len(),
24622            2,
24623            "After falling back to references search, we open a new editor with the results"
24624        );
24625        let references_fallback_text = editors
24626            .into_iter()
24627            .find(|new_editor| *new_editor != test_editor_cx.entity())
24628            .expect("Should have one non-test editor now")
24629            .read(test_editor_cx)
24630            .text(test_editor_cx);
24631        assert_eq!(
24632            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
24633            "Should use the range from the references response and not the GoToDefinition one"
24634        );
24635    });
24636}
24637
24638#[gpui::test]
24639async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
24640    init_test(cx, |_| {});
24641    cx.update(|cx| {
24642        let mut editor_settings = EditorSettings::get_global(cx).clone();
24643        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
24644        EditorSettings::override_global(editor_settings, cx);
24645    });
24646    let mut cx = EditorLspTestContext::new_rust(
24647        lsp::ServerCapabilities {
24648            definition_provider: Some(lsp::OneOf::Left(true)),
24649            references_provider: Some(lsp::OneOf::Left(true)),
24650            ..lsp::ServerCapabilities::default()
24651        },
24652        cx,
24653    )
24654    .await;
24655    let original_state = r#"fn one() {
24656        let mut a = ˇtwo();
24657    }
24658
24659    fn two() {}"#
24660        .unindent();
24661    cx.set_state(&original_state);
24662
24663    let mut go_to_definition = cx
24664        .lsp
24665        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
24666            move |_, _| async move { Ok(None) },
24667        );
24668    let _references = cx
24669        .lsp
24670        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
24671            panic!("Should not call for references with no go to definition fallback")
24672        });
24673
24674    let navigated = cx
24675        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
24676        .await
24677        .expect("Failed to navigate to lookup references");
24678    go_to_definition
24679        .next()
24680        .await
24681        .expect("Should have called the go_to_definition handler");
24682
24683    assert_eq!(
24684        navigated,
24685        Navigated::No,
24686        "Should have navigated to references as a fallback after empty GoToDefinition response"
24687    );
24688    cx.assert_editor_state(&original_state);
24689    let editors = cx.update_workspace(|workspace, _, cx| {
24690        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
24691    });
24692    cx.update_editor(|_, _, _| {
24693        assert_eq!(
24694            editors.len(),
24695            1,
24696            "After unsuccessful fallback, no other editor should have been opened"
24697        );
24698    });
24699}
24700
24701#[gpui::test]
24702async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
24703    init_test(cx, |_| {});
24704    let mut cx = EditorLspTestContext::new_rust(
24705        lsp::ServerCapabilities {
24706            references_provider: Some(lsp::OneOf::Left(true)),
24707            ..lsp::ServerCapabilities::default()
24708        },
24709        cx,
24710    )
24711    .await;
24712
24713    cx.set_state(
24714        &r#"
24715        fn one() {
24716            let mut a = two();
24717        }
24718
24719        fn ˇtwo() {}"#
24720            .unindent(),
24721    );
24722    cx.lsp
24723        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
24724            Ok(Some(vec![
24725                lsp::Location {
24726                    uri: params.text_document_position.text_document.uri.clone(),
24727                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
24728                },
24729                lsp::Location {
24730                    uri: params.text_document_position.text_document.uri,
24731                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
24732                },
24733            ]))
24734        });
24735    let navigated = cx
24736        .update_editor(|editor, window, cx| {
24737            editor.find_all_references(&FindAllReferences::default(), window, cx)
24738        })
24739        .unwrap()
24740        .await
24741        .expect("Failed to navigate to references");
24742    assert_eq!(
24743        navigated,
24744        Navigated::Yes,
24745        "Should have navigated to references from the FindAllReferences response"
24746    );
24747    cx.assert_editor_state(
24748        &r#"fn one() {
24749            let mut a = two();
24750        }
24751
24752        fn ˇtwo() {}"#
24753            .unindent(),
24754    );
24755
24756    let editors = cx.update_workspace(|workspace, _, cx| {
24757        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
24758    });
24759    cx.update_editor(|_, _, _| {
24760        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
24761    });
24762
24763    cx.set_state(
24764        &r#"fn one() {
24765            let mut a = ˇtwo();
24766        }
24767
24768        fn two() {}"#
24769            .unindent(),
24770    );
24771    let navigated = cx
24772        .update_editor(|editor, window, cx| {
24773            editor.find_all_references(&FindAllReferences::default(), window, cx)
24774        })
24775        .unwrap()
24776        .await
24777        .expect("Failed to navigate to references");
24778    assert_eq!(
24779        navigated,
24780        Navigated::Yes,
24781        "Should have navigated to references from the FindAllReferences response"
24782    );
24783    cx.assert_editor_state(
24784        &r#"fn one() {
24785            let mut a = ˇtwo();
24786        }
24787
24788        fn two() {}"#
24789            .unindent(),
24790    );
24791    let editors = cx.update_workspace(|workspace, _, cx| {
24792        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
24793    });
24794    cx.update_editor(|_, _, _| {
24795        assert_eq!(
24796            editors.len(),
24797            2,
24798            "should have re-used the previous multibuffer"
24799        );
24800    });
24801
24802    cx.set_state(
24803        &r#"fn one() {
24804            let mut a = ˇtwo();
24805        }
24806        fn three() {}
24807        fn two() {}"#
24808            .unindent(),
24809    );
24810    cx.lsp
24811        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
24812            Ok(Some(vec![
24813                lsp::Location {
24814                    uri: params.text_document_position.text_document.uri.clone(),
24815                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
24816                },
24817                lsp::Location {
24818                    uri: params.text_document_position.text_document.uri,
24819                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
24820                },
24821            ]))
24822        });
24823    let navigated = cx
24824        .update_editor(|editor, window, cx| {
24825            editor.find_all_references(&FindAllReferences::default(), window, cx)
24826        })
24827        .unwrap()
24828        .await
24829        .expect("Failed to navigate to references");
24830    assert_eq!(
24831        navigated,
24832        Navigated::Yes,
24833        "Should have navigated to references from the FindAllReferences response"
24834    );
24835    cx.assert_editor_state(
24836        &r#"fn one() {
24837                let mut a = ˇtwo();
24838            }
24839            fn three() {}
24840            fn two() {}"#
24841            .unindent(),
24842    );
24843    let editors = cx.update_workspace(|workspace, _, cx| {
24844        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
24845    });
24846    cx.update_editor(|_, _, _| {
24847        assert_eq!(
24848            editors.len(),
24849            3,
24850            "should have used a new multibuffer as offsets changed"
24851        );
24852    });
24853}
24854#[gpui::test]
24855async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
24856    init_test(cx, |_| {});
24857
24858    let language = Arc::new(Language::new(
24859        LanguageConfig::default(),
24860        Some(tree_sitter_rust::LANGUAGE.into()),
24861    ));
24862
24863    let text = r#"
24864        #[cfg(test)]
24865        mod tests() {
24866            #[test]
24867            fn runnable_1() {
24868                let a = 1;
24869            }
24870
24871            #[test]
24872            fn runnable_2() {
24873                let a = 1;
24874                let b = 2;
24875            }
24876        }
24877    "#
24878    .unindent();
24879
24880    let fs = FakeFs::new(cx.executor());
24881    fs.insert_file("/file.rs", Default::default()).await;
24882
24883    let project = Project::test(fs, ["/a".as_ref()], cx).await;
24884    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
24885    let cx = &mut VisualTestContext::from_window(*window, cx);
24886    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
24887    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
24888
24889    let editor = cx.new_window_entity(|window, cx| {
24890        Editor::new(
24891            EditorMode::full(),
24892            multi_buffer,
24893            Some(project.clone()),
24894            window,
24895            cx,
24896        )
24897    });
24898
24899    editor.update_in(cx, |editor, window, cx| {
24900        let snapshot = editor.buffer().read(cx).snapshot(cx);
24901        editor.runnables.insert(
24902            buffer.read(cx).remote_id(),
24903            3,
24904            buffer.read(cx).version(),
24905            RunnableTasks {
24906                templates: Vec::new(),
24907                offset: snapshot.anchor_before(MultiBufferOffset(43)),
24908                column: 0,
24909                extra_variables: HashMap::default(),
24910                context_range: BufferOffset(43)..BufferOffset(85),
24911            },
24912        );
24913        editor.runnables.insert(
24914            buffer.read(cx).remote_id(),
24915            8,
24916            buffer.read(cx).version(),
24917            RunnableTasks {
24918                templates: Vec::new(),
24919                offset: snapshot.anchor_before(MultiBufferOffset(86)),
24920                column: 0,
24921                extra_variables: HashMap::default(),
24922                context_range: BufferOffset(86)..BufferOffset(191),
24923            },
24924        );
24925
24926        // Test finding task when cursor is inside function body
24927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24928            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
24929        });
24930        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
24931        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
24932
24933        // Test finding task when cursor is on function name
24934        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24935            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
24936        });
24937        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
24938        assert_eq!(row, 8, "Should find task when cursor is on function name");
24939    });
24940}
24941
24942#[gpui::test]
24943async fn test_folding_buffers(cx: &mut TestAppContext) {
24944    init_test(cx, |_| {});
24945
24946    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
24947    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
24948    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
24949
24950    let fs = FakeFs::new(cx.executor());
24951    fs.insert_tree(
24952        path!("/a"),
24953        json!({
24954            "first.rs": sample_text_1,
24955            "second.rs": sample_text_2,
24956            "third.rs": sample_text_3,
24957        }),
24958    )
24959    .await;
24960    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24961    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
24962    let cx = &mut VisualTestContext::from_window(*window, cx);
24963    let worktree = project.update(cx, |project, cx| {
24964        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
24965        assert_eq!(worktrees.len(), 1);
24966        worktrees.pop().unwrap()
24967    });
24968    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
24969
24970    let buffer_1 = project
24971        .update(cx, |project, cx| {
24972            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
24973        })
24974        .await
24975        .unwrap();
24976    let buffer_2 = project
24977        .update(cx, |project, cx| {
24978            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
24979        })
24980        .await
24981        .unwrap();
24982    let buffer_3 = project
24983        .update(cx, |project, cx| {
24984            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
24985        })
24986        .await
24987        .unwrap();
24988
24989    let multi_buffer = cx.new(|cx| {
24990        let mut multi_buffer = MultiBuffer::new(ReadWrite);
24991        multi_buffer.set_excerpts_for_path(
24992            PathKey::sorted(0),
24993            buffer_1.clone(),
24994            [
24995                Point::new(0, 0)..Point::new(2, 0),
24996                Point::new(5, 0)..Point::new(6, 0),
24997                Point::new(9, 0)..Point::new(10, 4),
24998            ],
24999            0,
25000            cx,
25001        );
25002        multi_buffer.set_excerpts_for_path(
25003            PathKey::sorted(1),
25004            buffer_2.clone(),
25005            [
25006                Point::new(0, 0)..Point::new(2, 0),
25007                Point::new(5, 0)..Point::new(6, 0),
25008                Point::new(9, 0)..Point::new(10, 4),
25009            ],
25010            0,
25011            cx,
25012        );
25013        multi_buffer.set_excerpts_for_path(
25014            PathKey::sorted(2),
25015            buffer_3.clone(),
25016            [
25017                Point::new(0, 0)..Point::new(2, 0),
25018                Point::new(5, 0)..Point::new(6, 0),
25019                Point::new(9, 0)..Point::new(10, 4),
25020            ],
25021            0,
25022            cx,
25023        );
25024        multi_buffer
25025    });
25026    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
25027        Editor::new(
25028            EditorMode::full(),
25029            multi_buffer.clone(),
25030            Some(project.clone()),
25031            window,
25032            cx,
25033        )
25034    });
25035
25036    assert_eq!(
25037        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25038        "\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",
25039    );
25040
25041    multi_buffer_editor.update(cx, |editor, cx| {
25042        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
25043    });
25044    assert_eq!(
25045        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25046        "\n\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n1111\n2222\n\n5555",
25047        "After folding the first buffer, its text should not be displayed"
25048    );
25049
25050    multi_buffer_editor.update(cx, |editor, cx| {
25051        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
25052    });
25053    assert_eq!(
25054        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25055        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n1111\n2222\n\n5555",
25056        "After folding the second buffer, its text should not be displayed"
25057    );
25058
25059    multi_buffer_editor.update(cx, |editor, cx| {
25060        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
25061    });
25062    assert_eq!(
25063        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25064        "\n\n\n\n\n",
25065        "After folding the third buffer, its text should not be displayed"
25066    );
25067
25068    // Emulate selection inside the fold logic, that should work
25069    multi_buffer_editor.update_in(cx, |editor, window, cx| {
25070        editor
25071            .snapshot(window, cx)
25072            .next_line_boundary(Point::new(0, 4));
25073    });
25074
25075    multi_buffer_editor.update(cx, |editor, cx| {
25076        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
25077    });
25078    assert_eq!(
25079        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25080        "\n\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n",
25081        "After unfolding the second buffer, its text should be displayed"
25082    );
25083
25084    // Typing inside of buffer 1 causes that buffer to be unfolded.
25085    multi_buffer_editor.update_in(cx, |editor, window, cx| {
25086        assert_eq!(
25087            multi_buffer
25088                .read(cx)
25089                .snapshot(cx)
25090                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
25091                .collect::<String>(),
25092            "bbbb"
25093        );
25094        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25095            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
25096        });
25097        editor.handle_input("B", window, cx);
25098    });
25099
25100    assert_eq!(
25101        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25102        "\n\naaaa\nBbbbb\ncccc\n\nffff\ngggg\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\nqqqq\nrrrr\n\nuuuu\n\n",
25103        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
25104    );
25105
25106    multi_buffer_editor.update(cx, |editor, cx| {
25107        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
25108    });
25109    assert_eq!(
25110        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25111        "\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",
25112        "After unfolding the all buffers, all original text should be displayed"
25113    );
25114}
25115
25116#[gpui::test]
25117async fn test_folded_buffers_cleared_on_excerpts_removed(cx: &mut TestAppContext) {
25118    init_test(cx, |_| {});
25119
25120    let fs = FakeFs::new(cx.executor());
25121    fs.insert_tree(
25122        path!("/root"),
25123        json!({
25124            "file_a.txt": "File A\nFile A\nFile A",
25125            "file_b.txt": "File B\nFile B\nFile B",
25126        }),
25127    )
25128    .await;
25129
25130    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
25131    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25132    let cx = &mut VisualTestContext::from_window(*window, cx);
25133    let worktree = project.update(cx, |project, cx| {
25134        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
25135        assert_eq!(worktrees.len(), 1);
25136        worktrees.pop().unwrap()
25137    });
25138    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
25139
25140    let buffer_a = project
25141        .update(cx, |project, cx| {
25142            project.open_buffer((worktree_id, rel_path("file_a.txt")), cx)
25143        })
25144        .await
25145        .unwrap();
25146    let buffer_b = project
25147        .update(cx, |project, cx| {
25148            project.open_buffer((worktree_id, rel_path("file_b.txt")), cx)
25149        })
25150        .await
25151        .unwrap();
25152
25153    let multi_buffer = cx.new(|cx| {
25154        let mut multi_buffer = MultiBuffer::new(ReadWrite);
25155        let range_a = Point::new(0, 0)..Point::new(2, 4);
25156        let range_b = Point::new(0, 0)..Point::new(2, 4);
25157
25158        multi_buffer.set_excerpts_for_path(PathKey::sorted(0), buffer_a.clone(), [range_a], 0, cx);
25159        multi_buffer.set_excerpts_for_path(PathKey::sorted(1), buffer_b.clone(), [range_b], 0, cx);
25160        multi_buffer
25161    });
25162
25163    let editor = cx.new_window_entity(|window, cx| {
25164        Editor::new(
25165            EditorMode::full(),
25166            multi_buffer.clone(),
25167            Some(project.clone()),
25168            window,
25169            cx,
25170        )
25171    });
25172
25173    editor.update(cx, |editor, cx| {
25174        editor.fold_buffer(buffer_a.read(cx).remote_id(), cx);
25175    });
25176    assert!(editor.update(cx, |editor, cx| editor.has_any_buffer_folded(cx)));
25177
25178    // When the excerpts for `buffer_a` are removed, a
25179    // `multi_buffer::Event::ExcerptsRemoved` event is emitted, which should be
25180    // picked up by the editor and update the display map accordingly.
25181    multi_buffer.update(cx, |multi_buffer, cx| {
25182        multi_buffer.remove_excerpts_for_path(PathKey::sorted(0), cx)
25183    });
25184    assert!(!editor.update(cx, |editor, cx| editor.has_any_buffer_folded(cx)));
25185}
25186
25187#[gpui::test]
25188async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
25189    init_test(cx, |_| {});
25190
25191    let sample_text_1 = "1111\n2222\n3333".to_string();
25192    let sample_text_2 = "4444\n5555\n6666".to_string();
25193    let sample_text_3 = "7777\n8888\n9999".to_string();
25194
25195    let fs = FakeFs::new(cx.executor());
25196    fs.insert_tree(
25197        path!("/a"),
25198        json!({
25199            "first.rs": sample_text_1,
25200            "second.rs": sample_text_2,
25201            "third.rs": sample_text_3,
25202        }),
25203    )
25204    .await;
25205    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25206    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25207    let cx = &mut VisualTestContext::from_window(*window, cx);
25208    let worktree = project.update(cx, |project, cx| {
25209        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
25210        assert_eq!(worktrees.len(), 1);
25211        worktrees.pop().unwrap()
25212    });
25213    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
25214
25215    let buffer_1 = project
25216        .update(cx, |project, cx| {
25217            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
25218        })
25219        .await
25220        .unwrap();
25221    let buffer_2 = project
25222        .update(cx, |project, cx| {
25223            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
25224        })
25225        .await
25226        .unwrap();
25227    let buffer_3 = project
25228        .update(cx, |project, cx| {
25229            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
25230        })
25231        .await
25232        .unwrap();
25233
25234    let multi_buffer = cx.new(|cx| {
25235        let mut multi_buffer = MultiBuffer::new(ReadWrite);
25236        multi_buffer.set_excerpts_for_path(
25237            PathKey::sorted(0),
25238            buffer_1.clone(),
25239            [Point::new(0, 0)..Point::new(3, 0)],
25240            0,
25241            cx,
25242        );
25243        multi_buffer.set_excerpts_for_path(
25244            PathKey::sorted(1),
25245            buffer_2.clone(),
25246            [Point::new(0, 0)..Point::new(3, 0)],
25247            0,
25248            cx,
25249        );
25250        multi_buffer.set_excerpts_for_path(
25251            PathKey::sorted(2),
25252            buffer_3.clone(),
25253            [Point::new(0, 0)..Point::new(3, 0)],
25254            0,
25255            cx,
25256        );
25257        multi_buffer
25258    });
25259
25260    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
25261        Editor::new(
25262            EditorMode::full(),
25263            multi_buffer,
25264            Some(project.clone()),
25265            window,
25266            cx,
25267        )
25268    });
25269
25270    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
25271    assert_eq!(
25272        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25273        full_text,
25274    );
25275
25276    multi_buffer_editor.update(cx, |editor, cx| {
25277        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
25278    });
25279    assert_eq!(
25280        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25281        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
25282        "After folding the first buffer, its text should not be displayed"
25283    );
25284
25285    multi_buffer_editor.update(cx, |editor, cx| {
25286        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
25287    });
25288
25289    assert_eq!(
25290        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25291        "\n\n\n\n\n\n7777\n8888\n9999",
25292        "After folding the second buffer, its text should not be displayed"
25293    );
25294
25295    multi_buffer_editor.update(cx, |editor, cx| {
25296        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
25297    });
25298    assert_eq!(
25299        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25300        "\n\n\n\n\n",
25301        "After folding the third buffer, its text should not be displayed"
25302    );
25303
25304    multi_buffer_editor.update(cx, |editor, cx| {
25305        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
25306    });
25307    assert_eq!(
25308        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25309        "\n\n\n\n4444\n5555\n6666\n\n",
25310        "After unfolding the second buffer, its text should be displayed"
25311    );
25312
25313    multi_buffer_editor.update(cx, |editor, cx| {
25314        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
25315    });
25316    assert_eq!(
25317        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25318        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
25319        "After unfolding the first buffer, its text should be displayed"
25320    );
25321
25322    multi_buffer_editor.update(cx, |editor, cx| {
25323        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
25324    });
25325    assert_eq!(
25326        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25327        full_text,
25328        "After unfolding all buffers, all original text should be displayed"
25329    );
25330}
25331
25332#[gpui::test]
25333async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
25334    init_test(cx, |_| {});
25335
25336    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
25337
25338    let fs = FakeFs::new(cx.executor());
25339    fs.insert_tree(
25340        path!("/a"),
25341        json!({
25342            "main.rs": sample_text,
25343        }),
25344    )
25345    .await;
25346    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25347    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25348    let cx = &mut VisualTestContext::from_window(*window, cx);
25349    let worktree = project.update(cx, |project, cx| {
25350        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
25351        assert_eq!(worktrees.len(), 1);
25352        worktrees.pop().unwrap()
25353    });
25354    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
25355
25356    let buffer_1 = project
25357        .update(cx, |project, cx| {
25358            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
25359        })
25360        .await
25361        .unwrap();
25362
25363    let multi_buffer = cx.new(|cx| {
25364        let mut multi_buffer = MultiBuffer::new(ReadWrite);
25365        multi_buffer.set_excerpts_for_path(
25366            PathKey::sorted(0),
25367            buffer_1.clone(),
25368            [Point::new(0, 0)
25369                ..Point::new(
25370                    sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
25371                    0,
25372                )],
25373            0,
25374            cx,
25375        );
25376        multi_buffer
25377    });
25378    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
25379        Editor::new(
25380            EditorMode::full(),
25381            multi_buffer,
25382            Some(project.clone()),
25383            window,
25384            cx,
25385        )
25386    });
25387
25388    let selection_range = Point::new(1, 0)..Point::new(2, 0);
25389    multi_buffer_editor.update_in(cx, |editor, window, cx| {
25390        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25391        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
25392        editor.highlight_text(
25393            HighlightKey::Editor,
25394            vec![highlight_range.clone()],
25395            HighlightStyle::color(Hsla::green()),
25396            cx,
25397        );
25398        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25399            s.select_ranges(Some(highlight_range))
25400        });
25401    });
25402
25403    let full_text = format!("\n\n{sample_text}");
25404    assert_eq!(
25405        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
25406        full_text,
25407    );
25408}
25409
25410#[gpui::test]
25411async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
25412    init_test(cx, |_| {});
25413    cx.update(|cx| {
25414        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
25415            "keymaps/default-linux.json",
25416            cx,
25417        )
25418        .unwrap();
25419        cx.bind_keys(default_key_bindings);
25420    });
25421
25422    let (editor, cx) = cx.add_window_view(|window, cx| {
25423        let multi_buffer = MultiBuffer::build_multi(
25424            [
25425                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
25426                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
25427                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
25428                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
25429            ],
25430            cx,
25431        );
25432        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
25433
25434        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
25435        // fold all but the second buffer, so that we test navigating between two
25436        // adjacent folded buffers, as well as folded buffers at the start and
25437        // end the multibuffer
25438        editor.fold_buffer(buffer_ids[0], cx);
25439        editor.fold_buffer(buffer_ids[2], cx);
25440        editor.fold_buffer(buffer_ids[3], cx);
25441
25442        editor
25443    });
25444    cx.simulate_resize(size(px(1000.), px(1000.)));
25445
25446    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
25447    cx.assert_excerpts_with_selections(indoc! {"
25448        [EXCERPT]
25449        ˇ[FOLDED]
25450        [EXCERPT]
25451        a1
25452        b1
25453        [EXCERPT]
25454        [FOLDED]
25455        [EXCERPT]
25456        [FOLDED]
25457        "
25458    });
25459    cx.simulate_keystroke("down");
25460    cx.assert_excerpts_with_selections(indoc! {"
25461        [EXCERPT]
25462        [FOLDED]
25463        [EXCERPT]
25464        ˇa1
25465        b1
25466        [EXCERPT]
25467        [FOLDED]
25468        [EXCERPT]
25469        [FOLDED]
25470        "
25471    });
25472    cx.simulate_keystroke("down");
25473    cx.assert_excerpts_with_selections(indoc! {"
25474        [EXCERPT]
25475        [FOLDED]
25476        [EXCERPT]
25477        a1
25478        ˇb1
25479        [EXCERPT]
25480        [FOLDED]
25481        [EXCERPT]
25482        [FOLDED]
25483        "
25484    });
25485    cx.simulate_keystroke("down");
25486    cx.assert_excerpts_with_selections(indoc! {"
25487        [EXCERPT]
25488        [FOLDED]
25489        [EXCERPT]
25490        a1
25491        b1
25492        ˇ[EXCERPT]
25493        [FOLDED]
25494        [EXCERPT]
25495        [FOLDED]
25496        "
25497    });
25498    cx.simulate_keystroke("down");
25499    cx.assert_excerpts_with_selections(indoc! {"
25500        [EXCERPT]
25501        [FOLDED]
25502        [EXCERPT]
25503        a1
25504        b1
25505        [EXCERPT]
25506        ˇ[FOLDED]
25507        [EXCERPT]
25508        [FOLDED]
25509        "
25510    });
25511    for _ in 0..5 {
25512        cx.simulate_keystroke("down");
25513        cx.assert_excerpts_with_selections(indoc! {"
25514            [EXCERPT]
25515            [FOLDED]
25516            [EXCERPT]
25517            a1
25518            b1
25519            [EXCERPT]
25520            [FOLDED]
25521            [EXCERPT]
25522            ˇ[FOLDED]
25523            "
25524        });
25525    }
25526
25527    cx.simulate_keystroke("up");
25528    cx.assert_excerpts_with_selections(indoc! {"
25529        [EXCERPT]
25530        [FOLDED]
25531        [EXCERPT]
25532        a1
25533        b1
25534        [EXCERPT]
25535        ˇ[FOLDED]
25536        [EXCERPT]
25537        [FOLDED]
25538        "
25539    });
25540    cx.simulate_keystroke("up");
25541    cx.assert_excerpts_with_selections(indoc! {"
25542        [EXCERPT]
25543        [FOLDED]
25544        [EXCERPT]
25545        a1
25546        b1
25547        ˇ[EXCERPT]
25548        [FOLDED]
25549        [EXCERPT]
25550        [FOLDED]
25551        "
25552    });
25553    cx.simulate_keystroke("up");
25554    cx.assert_excerpts_with_selections(indoc! {"
25555        [EXCERPT]
25556        [FOLDED]
25557        [EXCERPT]
25558        a1
25559        ˇb1
25560        [EXCERPT]
25561        [FOLDED]
25562        [EXCERPT]
25563        [FOLDED]
25564        "
25565    });
25566    cx.simulate_keystroke("up");
25567    cx.assert_excerpts_with_selections(indoc! {"
25568        [EXCERPT]
25569        [FOLDED]
25570        [EXCERPT]
25571        ˇa1
25572        b1
25573        [EXCERPT]
25574        [FOLDED]
25575        [EXCERPT]
25576        [FOLDED]
25577        "
25578    });
25579    for _ in 0..5 {
25580        cx.simulate_keystroke("up");
25581        cx.assert_excerpts_with_selections(indoc! {"
25582            [EXCERPT]
25583            ˇ[FOLDED]
25584            [EXCERPT]
25585            a1
25586            b1
25587            [EXCERPT]
25588            [FOLDED]
25589            [EXCERPT]
25590            [FOLDED]
25591            "
25592        });
25593    }
25594}
25595
25596#[gpui::test]
25597async fn test_edit_prediction_text(cx: &mut TestAppContext) {
25598    init_test(cx, |_| {});
25599
25600    // Simple insertion
25601    assert_highlighted_edits(
25602        "Hello, world!",
25603        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
25604        true,
25605        cx,
25606        &|highlighted_edits, cx| {
25607            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
25608            assert_eq!(highlighted_edits.highlights.len(), 1);
25609            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
25610            assert_eq!(
25611                highlighted_edits.highlights[0].1.background_color,
25612                Some(cx.theme().status().created_background)
25613            );
25614        },
25615    )
25616    .await;
25617
25618    // Replacement
25619    assert_highlighted_edits(
25620        "This is a test.",
25621        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
25622        false,
25623        cx,
25624        &|highlighted_edits, cx| {
25625            assert_eq!(highlighted_edits.text, "That is a test.");
25626            assert_eq!(highlighted_edits.highlights.len(), 1);
25627            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
25628            assert_eq!(
25629                highlighted_edits.highlights[0].1.background_color,
25630                Some(cx.theme().status().created_background)
25631            );
25632        },
25633    )
25634    .await;
25635
25636    // Multiple edits
25637    assert_highlighted_edits(
25638        "Hello, world!",
25639        vec![
25640            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
25641            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
25642        ],
25643        false,
25644        cx,
25645        &|highlighted_edits, cx| {
25646            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
25647            assert_eq!(highlighted_edits.highlights.len(), 2);
25648            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
25649            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
25650            assert_eq!(
25651                highlighted_edits.highlights[0].1.background_color,
25652                Some(cx.theme().status().created_background)
25653            );
25654            assert_eq!(
25655                highlighted_edits.highlights[1].1.background_color,
25656                Some(cx.theme().status().created_background)
25657            );
25658        },
25659    )
25660    .await;
25661
25662    // Multiple lines with edits
25663    assert_highlighted_edits(
25664        "First line\nSecond line\nThird line\nFourth line",
25665        vec![
25666            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
25667            (
25668                Point::new(2, 0)..Point::new(2, 10),
25669                "New third line".to_string(),
25670            ),
25671            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
25672        ],
25673        false,
25674        cx,
25675        &|highlighted_edits, cx| {
25676            assert_eq!(
25677                highlighted_edits.text,
25678                "Second modified\nNew third line\nFourth updated line"
25679            );
25680            assert_eq!(highlighted_edits.highlights.len(), 3);
25681            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
25682            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
25683            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
25684            for highlight in &highlighted_edits.highlights {
25685                assert_eq!(
25686                    highlight.1.background_color,
25687                    Some(cx.theme().status().created_background)
25688                );
25689            }
25690        },
25691    )
25692    .await;
25693}
25694
25695#[gpui::test]
25696async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
25697    init_test(cx, |_| {});
25698
25699    // Deletion
25700    assert_highlighted_edits(
25701        "Hello, world!",
25702        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
25703        true,
25704        cx,
25705        &|highlighted_edits, cx| {
25706            assert_eq!(highlighted_edits.text, "Hello, world!");
25707            assert_eq!(highlighted_edits.highlights.len(), 1);
25708            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
25709            assert_eq!(
25710                highlighted_edits.highlights[0].1.background_color,
25711                Some(cx.theme().status().deleted_background)
25712            );
25713        },
25714    )
25715    .await;
25716
25717    // Insertion
25718    assert_highlighted_edits(
25719        "Hello, world!",
25720        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
25721        true,
25722        cx,
25723        &|highlighted_edits, cx| {
25724            assert_eq!(highlighted_edits.highlights.len(), 1);
25725            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
25726            assert_eq!(
25727                highlighted_edits.highlights[0].1.background_color,
25728                Some(cx.theme().status().created_background)
25729            );
25730        },
25731    )
25732    .await;
25733}
25734
25735async fn assert_highlighted_edits(
25736    text: &str,
25737    edits: Vec<(Range<Point>, String)>,
25738    include_deletions: bool,
25739    cx: &mut TestAppContext,
25740    assertion_fn: &dyn Fn(HighlightedText, &App),
25741) {
25742    let window = cx.add_window(|window, cx| {
25743        let buffer = MultiBuffer::build_simple(text, cx);
25744        Editor::new(EditorMode::full(), buffer, None, window, cx)
25745    });
25746    let cx = &mut VisualTestContext::from_window(*window, cx);
25747
25748    let (buffer, snapshot) = window
25749        .update(cx, |editor, _window, cx| {
25750            (
25751                editor.buffer().clone(),
25752                editor.buffer().read(cx).snapshot(cx),
25753            )
25754        })
25755        .unwrap();
25756
25757    let edits = edits
25758        .into_iter()
25759        .map(|(range, edit)| {
25760            (
25761                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
25762                edit,
25763            )
25764        })
25765        .collect::<Vec<_>>();
25766
25767    let text_anchor_edits = edits
25768        .clone()
25769        .into_iter()
25770        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
25771        .collect::<Vec<_>>();
25772
25773    let edit_preview = window
25774        .update(cx, |_, _window, cx| {
25775            buffer
25776                .read(cx)
25777                .as_singleton()
25778                .unwrap()
25779                .read(cx)
25780                .preview_edits(text_anchor_edits.into(), cx)
25781        })
25782        .unwrap()
25783        .await;
25784
25785    cx.update(|_window, cx| {
25786        let highlighted_edits = edit_prediction_edit_text(
25787            snapshot.as_singleton().unwrap().2,
25788            &edits,
25789            &edit_preview,
25790            include_deletions,
25791            cx,
25792        );
25793        assertion_fn(highlighted_edits, cx)
25794    });
25795}
25796
25797#[track_caller]
25798fn assert_breakpoint(
25799    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
25800    path: &Arc<Path>,
25801    expected: Vec<(u32, Breakpoint)>,
25802) {
25803    if expected.is_empty() {
25804        assert!(!breakpoints.contains_key(path), "{}", path.display());
25805    } else {
25806        let mut breakpoint = breakpoints
25807            .get(path)
25808            .unwrap()
25809            .iter()
25810            .map(|breakpoint| {
25811                (
25812                    breakpoint.row,
25813                    Breakpoint {
25814                        message: breakpoint.message.clone(),
25815                        state: breakpoint.state,
25816                        condition: breakpoint.condition.clone(),
25817                        hit_condition: breakpoint.hit_condition.clone(),
25818                    },
25819                )
25820            })
25821            .collect::<Vec<_>>();
25822
25823        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
25824
25825        assert_eq!(expected, breakpoint);
25826    }
25827}
25828
25829fn add_log_breakpoint_at_cursor(
25830    editor: &mut Editor,
25831    log_message: &str,
25832    window: &mut Window,
25833    cx: &mut Context<Editor>,
25834) {
25835    let (anchor, bp) = editor
25836        .breakpoints_at_cursors(window, cx)
25837        .first()
25838        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
25839        .unwrap_or_else(|| {
25840            let snapshot = editor.snapshot(window, cx);
25841            let cursor_position: Point =
25842                editor.selections.newest(&snapshot.display_snapshot).head();
25843
25844            let breakpoint_position = snapshot
25845                .buffer_snapshot()
25846                .anchor_before(Point::new(cursor_position.row, 0));
25847
25848            (breakpoint_position, Breakpoint::new_log(log_message))
25849        });
25850
25851    editor.edit_breakpoint_at_anchor(
25852        anchor,
25853        bp,
25854        BreakpointEditAction::EditLogMessage(log_message.into()),
25855        cx,
25856    );
25857}
25858
25859#[gpui::test]
25860async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
25861    init_test(cx, |_| {});
25862
25863    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
25864    let fs = FakeFs::new(cx.executor());
25865    fs.insert_tree(
25866        path!("/a"),
25867        json!({
25868            "main.rs": sample_text,
25869        }),
25870    )
25871    .await;
25872    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25873    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25874    let cx = &mut VisualTestContext::from_window(*window, cx);
25875
25876    let fs = FakeFs::new(cx.executor());
25877    fs.insert_tree(
25878        path!("/a"),
25879        json!({
25880            "main.rs": sample_text,
25881        }),
25882    )
25883    .await;
25884    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25885    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
25886    let workspace = window
25887        .read_with(cx, |mw, _| mw.workspace().clone())
25888        .unwrap();
25889    let cx = &mut VisualTestContext::from_window(*window, cx);
25890    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
25891        workspace.project().update(cx, |project, cx| {
25892            project.worktrees(cx).next().unwrap().read(cx).id()
25893        })
25894    });
25895
25896    let buffer = project
25897        .update(cx, |project, cx| {
25898            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
25899        })
25900        .await
25901        .unwrap();
25902
25903    let (editor, cx) = cx.add_window_view(|window, cx| {
25904        Editor::new(
25905            EditorMode::full(),
25906            MultiBuffer::build_from_buffer(buffer, cx),
25907            Some(project.clone()),
25908            window,
25909            cx,
25910        )
25911    });
25912
25913    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
25914    let abs_path = project.read_with(cx, |project, cx| {
25915        project
25916            .absolute_path(&project_path, cx)
25917            .map(Arc::from)
25918            .unwrap()
25919    });
25920
25921    // assert we can add breakpoint on the first line
25922    editor.update_in(cx, |editor, window, cx| {
25923        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25924        editor.move_to_end(&MoveToEnd, window, cx);
25925        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25926    });
25927
25928    let breakpoints = editor.update(cx, |editor, cx| {
25929        editor
25930            .breakpoint_store()
25931            .as_ref()
25932            .unwrap()
25933            .read(cx)
25934            .all_source_breakpoints(cx)
25935    });
25936
25937    assert_eq!(1, breakpoints.len());
25938    assert_breakpoint(
25939        &breakpoints,
25940        &abs_path,
25941        vec![
25942            (0, Breakpoint::new_standard()),
25943            (3, Breakpoint::new_standard()),
25944        ],
25945    );
25946
25947    editor.update_in(cx, |editor, window, cx| {
25948        editor.move_to_beginning(&MoveToBeginning, window, cx);
25949        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25950    });
25951
25952    let breakpoints = editor.update(cx, |editor, cx| {
25953        editor
25954            .breakpoint_store()
25955            .as_ref()
25956            .unwrap()
25957            .read(cx)
25958            .all_source_breakpoints(cx)
25959    });
25960
25961    assert_eq!(1, breakpoints.len());
25962    assert_breakpoint(
25963        &breakpoints,
25964        &abs_path,
25965        vec![(3, Breakpoint::new_standard())],
25966    );
25967
25968    editor.update_in(cx, |editor, window, cx| {
25969        editor.move_to_end(&MoveToEnd, window, cx);
25970        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
25971    });
25972
25973    let breakpoints = editor.update(cx, |editor, cx| {
25974        editor
25975            .breakpoint_store()
25976            .as_ref()
25977            .unwrap()
25978            .read(cx)
25979            .all_source_breakpoints(cx)
25980    });
25981
25982    assert_eq!(0, breakpoints.len());
25983    assert_breakpoint(&breakpoints, &abs_path, vec![]);
25984}
25985
25986#[gpui::test]
25987async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
25988    init_test(cx, |_| {});
25989
25990    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
25991
25992    let fs = FakeFs::new(cx.executor());
25993    fs.insert_tree(
25994        path!("/a"),
25995        json!({
25996            "main.rs": sample_text,
25997        }),
25998    )
25999    .await;
26000    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26001    let (multi_workspace, cx) =
26002        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26003    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
26004
26005    let worktree_id = workspace.update(cx, |workspace, cx| {
26006        workspace.project().update(cx, |project, cx| {
26007            project.worktrees(cx).next().unwrap().read(cx).id()
26008        })
26009    });
26010
26011    let buffer = project
26012        .update(cx, |project, cx| {
26013            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
26014        })
26015        .await
26016        .unwrap();
26017
26018    let (editor, cx) = cx.add_window_view(|window, cx| {
26019        Editor::new(
26020            EditorMode::full(),
26021            MultiBuffer::build_from_buffer(buffer, cx),
26022            Some(project.clone()),
26023            window,
26024            cx,
26025        )
26026    });
26027
26028    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
26029    let abs_path = project.read_with(cx, |project, cx| {
26030        project
26031            .absolute_path(&project_path, cx)
26032            .map(Arc::from)
26033            .unwrap()
26034    });
26035
26036    editor.update_in(cx, |editor, window, cx| {
26037        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
26038    });
26039
26040    let breakpoints = editor.update(cx, |editor, cx| {
26041        editor
26042            .breakpoint_store()
26043            .as_ref()
26044            .unwrap()
26045            .read(cx)
26046            .all_source_breakpoints(cx)
26047    });
26048
26049    assert_breakpoint(
26050        &breakpoints,
26051        &abs_path,
26052        vec![(0, Breakpoint::new_log("hello world"))],
26053    );
26054
26055    // Removing a log message from a log breakpoint should remove it
26056    editor.update_in(cx, |editor, window, cx| {
26057        add_log_breakpoint_at_cursor(editor, "", window, cx);
26058    });
26059
26060    let breakpoints = editor.update(cx, |editor, cx| {
26061        editor
26062            .breakpoint_store()
26063            .as_ref()
26064            .unwrap()
26065            .read(cx)
26066            .all_source_breakpoints(cx)
26067    });
26068
26069    assert_breakpoint(&breakpoints, &abs_path, vec![]);
26070
26071    editor.update_in(cx, |editor, window, cx| {
26072        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
26073        editor.move_to_end(&MoveToEnd, window, cx);
26074        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
26075        // Not adding a log message to a standard breakpoint shouldn't remove it
26076        add_log_breakpoint_at_cursor(editor, "", window, cx);
26077    });
26078
26079    let breakpoints = editor.update(cx, |editor, cx| {
26080        editor
26081            .breakpoint_store()
26082            .as_ref()
26083            .unwrap()
26084            .read(cx)
26085            .all_source_breakpoints(cx)
26086    });
26087
26088    assert_breakpoint(
26089        &breakpoints,
26090        &abs_path,
26091        vec![
26092            (0, Breakpoint::new_standard()),
26093            (3, Breakpoint::new_standard()),
26094        ],
26095    );
26096
26097    editor.update_in(cx, |editor, window, cx| {
26098        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
26099    });
26100
26101    let breakpoints = editor.update(cx, |editor, cx| {
26102        editor
26103            .breakpoint_store()
26104            .as_ref()
26105            .unwrap()
26106            .read(cx)
26107            .all_source_breakpoints(cx)
26108    });
26109
26110    assert_breakpoint(
26111        &breakpoints,
26112        &abs_path,
26113        vec![
26114            (0, Breakpoint::new_standard()),
26115            (3, Breakpoint::new_log("hello world")),
26116        ],
26117    );
26118
26119    editor.update_in(cx, |editor, window, cx| {
26120        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
26121    });
26122
26123    let breakpoints = editor.update(cx, |editor, cx| {
26124        editor
26125            .breakpoint_store()
26126            .as_ref()
26127            .unwrap()
26128            .read(cx)
26129            .all_source_breakpoints(cx)
26130    });
26131
26132    assert_breakpoint(
26133        &breakpoints,
26134        &abs_path,
26135        vec![
26136            (0, Breakpoint::new_standard()),
26137            (3, Breakpoint::new_log("hello Earth!!")),
26138        ],
26139    );
26140}
26141
26142/// This also tests that Editor::breakpoint_at_cursor_head is working properly
26143/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
26144/// or when breakpoints were placed out of order. This tests for a regression too
26145#[gpui::test]
26146async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
26147    init_test(cx, |_| {});
26148
26149    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
26150    let fs = FakeFs::new(cx.executor());
26151    fs.insert_tree(
26152        path!("/a"),
26153        json!({
26154            "main.rs": sample_text,
26155        }),
26156    )
26157    .await;
26158    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26159    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26160    let cx = &mut VisualTestContext::from_window(*window, cx);
26161
26162    let fs = FakeFs::new(cx.executor());
26163    fs.insert_tree(
26164        path!("/a"),
26165        json!({
26166            "main.rs": sample_text,
26167        }),
26168    )
26169    .await;
26170    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26171    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26172    let workspace = window
26173        .read_with(cx, |mw, _| mw.workspace().clone())
26174        .unwrap();
26175    let cx = &mut VisualTestContext::from_window(*window, cx);
26176    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
26177        workspace.project().update(cx, |project, cx| {
26178            project.worktrees(cx).next().unwrap().read(cx).id()
26179        })
26180    });
26181
26182    let buffer = project
26183        .update(cx, |project, cx| {
26184            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
26185        })
26186        .await
26187        .unwrap();
26188
26189    let (editor, cx) = cx.add_window_view(|window, cx| {
26190        Editor::new(
26191            EditorMode::full(),
26192            MultiBuffer::build_from_buffer(buffer, cx),
26193            Some(project.clone()),
26194            window,
26195            cx,
26196        )
26197    });
26198
26199    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
26200    let abs_path = project.read_with(cx, |project, cx| {
26201        project
26202            .absolute_path(&project_path, cx)
26203            .map(Arc::from)
26204            .unwrap()
26205    });
26206
26207    // assert we can add breakpoint on the first line
26208    editor.update_in(cx, |editor, window, cx| {
26209        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
26210        editor.move_to_end(&MoveToEnd, window, cx);
26211        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
26212        editor.move_up(&MoveUp, window, cx);
26213        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
26214    });
26215
26216    let breakpoints = editor.update(cx, |editor, cx| {
26217        editor
26218            .breakpoint_store()
26219            .as_ref()
26220            .unwrap()
26221            .read(cx)
26222            .all_source_breakpoints(cx)
26223    });
26224
26225    assert_eq!(1, breakpoints.len());
26226    assert_breakpoint(
26227        &breakpoints,
26228        &abs_path,
26229        vec![
26230            (0, Breakpoint::new_standard()),
26231            (2, Breakpoint::new_standard()),
26232            (3, Breakpoint::new_standard()),
26233        ],
26234    );
26235
26236    editor.update_in(cx, |editor, window, cx| {
26237        editor.move_to_beginning(&MoveToBeginning, window, cx);
26238        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
26239        editor.move_to_end(&MoveToEnd, window, cx);
26240        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
26241        // Disabling a breakpoint that doesn't exist should do nothing
26242        editor.move_up(&MoveUp, window, cx);
26243        editor.move_up(&MoveUp, window, cx);
26244        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
26245    });
26246
26247    let breakpoints = editor.update(cx, |editor, cx| {
26248        editor
26249            .breakpoint_store()
26250            .as_ref()
26251            .unwrap()
26252            .read(cx)
26253            .all_source_breakpoints(cx)
26254    });
26255
26256    let disable_breakpoint = {
26257        let mut bp = Breakpoint::new_standard();
26258        bp.state = BreakpointState::Disabled;
26259        bp
26260    };
26261
26262    assert_eq!(1, breakpoints.len());
26263    assert_breakpoint(
26264        &breakpoints,
26265        &abs_path,
26266        vec![
26267            (0, disable_breakpoint.clone()),
26268            (2, Breakpoint::new_standard()),
26269            (3, disable_breakpoint.clone()),
26270        ],
26271    );
26272
26273    editor.update_in(cx, |editor, window, cx| {
26274        editor.move_to_beginning(&MoveToBeginning, window, cx);
26275        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
26276        editor.move_to_end(&MoveToEnd, window, cx);
26277        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
26278        editor.move_up(&MoveUp, window, cx);
26279        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
26280    });
26281
26282    let breakpoints = editor.update(cx, |editor, cx| {
26283        editor
26284            .breakpoint_store()
26285            .as_ref()
26286            .unwrap()
26287            .read(cx)
26288            .all_source_breakpoints(cx)
26289    });
26290
26291    assert_eq!(1, breakpoints.len());
26292    assert_breakpoint(
26293        &breakpoints,
26294        &abs_path,
26295        vec![
26296            (0, Breakpoint::new_standard()),
26297            (2, disable_breakpoint),
26298            (3, Breakpoint::new_standard()),
26299        ],
26300    );
26301}
26302
26303#[gpui::test]
26304async fn test_breakpoint_phantom_indicator_collision_on_toggle(cx: &mut TestAppContext) {
26305    init_test(cx, |_| {});
26306
26307    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
26308    let fs = FakeFs::new(cx.executor());
26309    fs.insert_tree(
26310        path!("/a"),
26311        json!({
26312            "main.rs": sample_text,
26313        }),
26314    )
26315    .await;
26316    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26317    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26318    let workspace = window
26319        .read_with(cx, |mw, _| mw.workspace().clone())
26320        .unwrap();
26321    let cx = &mut VisualTestContext::from_window(*window, cx);
26322    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
26323        workspace.project().update(cx, |project, cx| {
26324            project.worktrees(cx).next().unwrap().read(cx).id()
26325        })
26326    });
26327
26328    let buffer = project
26329        .update(cx, |project, cx| {
26330            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
26331        })
26332        .await
26333        .unwrap();
26334
26335    let (editor, cx) = cx.add_window_view(|window, cx| {
26336        Editor::new(
26337            EditorMode::full(),
26338            MultiBuffer::build_from_buffer(buffer, cx),
26339            Some(project.clone()),
26340            window,
26341            cx,
26342        )
26343    });
26344
26345    // Simulate hovering over row 0 with no existing breakpoint.
26346    editor.update(cx, |editor, _cx| {
26347        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
26348            display_row: DisplayRow(0),
26349            is_active: true,
26350            collides_with_existing_breakpoint: false,
26351        });
26352    });
26353
26354    // Toggle breakpoint on the same row (row 0) — collision should flip to true.
26355    editor.update_in(cx, |editor, window, cx| {
26356        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
26357    });
26358    editor.update(cx, |editor, _cx| {
26359        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
26360        assert!(
26361            indicator.collides_with_existing_breakpoint,
26362            "Adding a breakpoint on the hovered row should set collision to true"
26363        );
26364    });
26365
26366    // Toggle again on the same row — breakpoint is removed, collision should flip back to false.
26367    editor.update_in(cx, |editor, window, cx| {
26368        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
26369    });
26370    editor.update(cx, |editor, _cx| {
26371        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
26372        assert!(
26373            !indicator.collides_with_existing_breakpoint,
26374            "Removing a breakpoint on the hovered row should set collision to false"
26375        );
26376    });
26377
26378    // Now move cursor to row 2 while phantom indicator stays on row 0.
26379    editor.update_in(cx, |editor, window, cx| {
26380        editor.move_down(&MoveDown, window, cx);
26381        editor.move_down(&MoveDown, window, cx);
26382    });
26383
26384    // Ensure phantom indicator is still on row 0, not colliding.
26385    editor.update(cx, |editor, _cx| {
26386        editor.gutter_breakpoint_indicator.0 = Some(PhantomBreakpointIndicator {
26387            display_row: DisplayRow(0),
26388            is_active: true,
26389            collides_with_existing_breakpoint: false,
26390        });
26391    });
26392
26393    // Toggle breakpoint on row 2 (cursor row) — phantom on row 0 should NOT be affected.
26394    editor.update_in(cx, |editor, window, cx| {
26395        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
26396    });
26397    editor.update(cx, |editor, _cx| {
26398        let indicator = editor.gutter_breakpoint_indicator.0.unwrap();
26399        assert!(
26400            !indicator.collides_with_existing_breakpoint,
26401            "Toggling a breakpoint on a different row should not affect the phantom indicator"
26402        );
26403    });
26404}
26405
26406#[gpui::test]
26407async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
26408    init_test(cx, |_| {});
26409    let capabilities = lsp::ServerCapabilities {
26410        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
26411            prepare_provider: Some(true),
26412            work_done_progress_options: Default::default(),
26413        })),
26414        ..Default::default()
26415    };
26416    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
26417
26418    cx.set_state(indoc! {"
26419        struct Fˇoo {}
26420    "});
26421
26422    cx.update_editor(|editor, _, cx| {
26423        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
26424        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
26425        editor.highlight_background(
26426            HighlightKey::DocumentHighlightRead,
26427            &[highlight_range],
26428            |_, theme| theme.colors().editor_document_highlight_read_background,
26429            cx,
26430        );
26431    });
26432
26433    let mut prepare_rename_handler = cx
26434        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
26435            move |_, _, _| async move {
26436                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
26437                    start: lsp::Position {
26438                        line: 0,
26439                        character: 7,
26440                    },
26441                    end: lsp::Position {
26442                        line: 0,
26443                        character: 10,
26444                    },
26445                })))
26446            },
26447        );
26448    let prepare_rename_task = cx
26449        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
26450        .expect("Prepare rename was not started");
26451    prepare_rename_handler.next().await.unwrap();
26452    prepare_rename_task.await.expect("Prepare rename failed");
26453
26454    let mut rename_handler =
26455        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
26456            let edit = lsp::TextEdit {
26457                range: lsp::Range {
26458                    start: lsp::Position {
26459                        line: 0,
26460                        character: 7,
26461                    },
26462                    end: lsp::Position {
26463                        line: 0,
26464                        character: 10,
26465                    },
26466                },
26467                new_text: "FooRenamed".to_string(),
26468            };
26469            Ok(Some(lsp::WorkspaceEdit::new(
26470                // Specify the same edit twice
26471                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
26472            )))
26473        });
26474    let rename_task = cx
26475        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
26476        .expect("Confirm rename was not started");
26477    rename_handler.next().await.unwrap();
26478    rename_task.await.expect("Confirm rename failed");
26479    cx.run_until_parked();
26480
26481    // Despite two edits, only one is actually applied as those are identical
26482    cx.assert_editor_state(indoc! {"
26483        struct FooRenamedˇ {}
26484    "});
26485}
26486
26487#[gpui::test]
26488async fn test_rename_without_prepare(cx: &mut TestAppContext) {
26489    init_test(cx, |_| {});
26490    // These capabilities indicate that the server does not support prepare rename.
26491    let capabilities = lsp::ServerCapabilities {
26492        rename_provider: Some(lsp::OneOf::Left(true)),
26493        ..Default::default()
26494    };
26495    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
26496
26497    cx.set_state(indoc! {"
26498        struct Fˇoo {}
26499    "});
26500
26501    cx.update_editor(|editor, _window, cx| {
26502        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
26503        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
26504        editor.highlight_background(
26505            HighlightKey::DocumentHighlightRead,
26506            &[highlight_range],
26507            |_, theme| theme.colors().editor_document_highlight_read_background,
26508            cx,
26509        );
26510    });
26511
26512    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
26513        .expect("Prepare rename was not started")
26514        .await
26515        .expect("Prepare rename failed");
26516
26517    let mut rename_handler =
26518        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
26519            let edit = lsp::TextEdit {
26520                range: lsp::Range {
26521                    start: lsp::Position {
26522                        line: 0,
26523                        character: 7,
26524                    },
26525                    end: lsp::Position {
26526                        line: 0,
26527                        character: 10,
26528                    },
26529                },
26530                new_text: "FooRenamed".to_string(),
26531            };
26532            Ok(Some(lsp::WorkspaceEdit::new(
26533                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
26534            )))
26535        });
26536    let rename_task = cx
26537        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
26538        .expect("Confirm rename was not started");
26539    rename_handler.next().await.unwrap();
26540    rename_task.await.expect("Confirm rename failed");
26541    cx.run_until_parked();
26542
26543    // Correct range is renamed, as `surrounding_word` is used to find it.
26544    cx.assert_editor_state(indoc! {"
26545        struct FooRenamedˇ {}
26546    "});
26547}
26548
26549#[gpui::test]
26550async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
26551    init_test(cx, |_| {});
26552    let mut cx = EditorTestContext::new(cx).await;
26553
26554    let language = Arc::new(
26555        Language::new(
26556            LanguageConfig::default(),
26557            Some(tree_sitter_html::LANGUAGE.into()),
26558        )
26559        .with_brackets_query(
26560            r#"
26561            ("<" @open "/>" @close)
26562            ("</" @open ">" @close)
26563            ("<" @open ">" @close)
26564            ("\"" @open "\"" @close)
26565            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
26566        "#,
26567        )
26568        .unwrap(),
26569    );
26570    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26571
26572    cx.set_state(indoc! {"
26573        <span>ˇ</span>
26574    "});
26575    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
26576    cx.assert_editor_state(indoc! {"
26577        <span>
26578        ˇ
26579        </span>
26580    "});
26581
26582    cx.set_state(indoc! {"
26583        <span><span></span>ˇ</span>
26584    "});
26585    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
26586    cx.assert_editor_state(indoc! {"
26587        <span><span></span>
26588        ˇ</span>
26589    "});
26590
26591    cx.set_state(indoc! {"
26592        <span>ˇ
26593        </span>
26594    "});
26595    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
26596    cx.assert_editor_state(indoc! {"
26597        <span>
26598        ˇ
26599        </span>
26600    "});
26601}
26602
26603#[gpui::test(iterations = 10)]
26604async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
26605    init_test(cx, |_| {});
26606
26607    let fs = FakeFs::new(cx.executor());
26608    fs.insert_tree(
26609        path!("/dir"),
26610        json!({
26611            "a.ts": "a",
26612        }),
26613    )
26614    .await;
26615
26616    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
26617    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26618    let workspace = window
26619        .read_with(cx, |mw, _| mw.workspace().clone())
26620        .unwrap();
26621    let cx = &mut VisualTestContext::from_window(*window, cx);
26622
26623    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26624    language_registry.add(Arc::new(Language::new(
26625        LanguageConfig {
26626            name: "TypeScript".into(),
26627            matcher: LanguageMatcher {
26628                path_suffixes: vec!["ts".to_string()],
26629                ..Default::default()
26630            },
26631            ..Default::default()
26632        },
26633        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
26634    )));
26635    let mut fake_language_servers = language_registry.register_fake_lsp(
26636        "TypeScript",
26637        FakeLspAdapter {
26638            capabilities: lsp::ServerCapabilities {
26639                code_lens_provider: Some(lsp::CodeLensOptions {
26640                    resolve_provider: Some(true),
26641                }),
26642                execute_command_provider: Some(lsp::ExecuteCommandOptions {
26643                    commands: vec!["_the/command".to_string()],
26644                    ..lsp::ExecuteCommandOptions::default()
26645                }),
26646                ..lsp::ServerCapabilities::default()
26647            },
26648            ..FakeLspAdapter::default()
26649        },
26650    );
26651
26652    let editor = workspace
26653        .update_in(cx, |workspace, window, cx| {
26654            workspace.open_abs_path(
26655                PathBuf::from(path!("/dir/a.ts")),
26656                OpenOptions::default(),
26657                window,
26658                cx,
26659            )
26660        })
26661        .await
26662        .unwrap()
26663        .downcast::<Editor>()
26664        .unwrap();
26665    cx.executor().run_until_parked();
26666
26667    let fake_server = fake_language_servers.next().await.unwrap();
26668
26669    let buffer = editor.update(cx, |editor, cx| {
26670        editor
26671            .buffer()
26672            .read(cx)
26673            .as_singleton()
26674            .expect("have opened a single file by path")
26675    });
26676
26677    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
26678    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
26679    drop(buffer_snapshot);
26680    let actions = cx
26681        .update_window(*window, |_, window, cx| {
26682            project.code_actions(&buffer, anchor..anchor, window, cx)
26683        })
26684        .unwrap();
26685
26686    fake_server
26687        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
26688            Ok(Some(vec![
26689                lsp::CodeLens {
26690                    range: lsp::Range::default(),
26691                    command: Some(lsp::Command {
26692                        title: "Code lens command".to_owned(),
26693                        command: "_the/command".to_owned(),
26694                        arguments: None,
26695                    }),
26696                    data: None,
26697                },
26698                lsp::CodeLens {
26699                    range: lsp::Range::default(),
26700                    command: Some(lsp::Command {
26701                        title: "Command not in capabilities".to_owned(),
26702                        command: "not in capabilities".to_owned(),
26703                        arguments: None,
26704                    }),
26705                    data: None,
26706                },
26707                lsp::CodeLens {
26708                    range: lsp::Range {
26709                        start: lsp::Position {
26710                            line: 1,
26711                            character: 1,
26712                        },
26713                        end: lsp::Position {
26714                            line: 1,
26715                            character: 1,
26716                        },
26717                    },
26718                    command: Some(lsp::Command {
26719                        title: "Command not in range".to_owned(),
26720                        command: "_the/command".to_owned(),
26721                        arguments: None,
26722                    }),
26723                    data: None,
26724                },
26725            ]))
26726        })
26727        .next()
26728        .await;
26729
26730    let actions = actions.await.unwrap();
26731    assert_eq!(
26732        actions.len(),
26733        1,
26734        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
26735    );
26736    let action = actions[0].clone();
26737    let apply = project.update(cx, |project, cx| {
26738        project.apply_code_action(buffer.clone(), action, true, cx)
26739    });
26740
26741    // Resolving the code action does not populate its edits. In absence of
26742    // edits, we must execute the given command.
26743    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
26744        |mut lens, _| async move {
26745            let lens_command = lens.command.as_mut().expect("should have a command");
26746            assert_eq!(lens_command.title, "Code lens command");
26747            lens_command.arguments = Some(vec![json!("the-argument")]);
26748            Ok(lens)
26749        },
26750    );
26751
26752    // While executing the command, the language server sends the editor
26753    // a `workspaceEdit` request.
26754    fake_server
26755        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
26756            let fake = fake_server.clone();
26757            move |params, _| {
26758                assert_eq!(params.command, "_the/command");
26759                let fake = fake.clone();
26760                async move {
26761                    fake.server
26762                        .request::<lsp::request::ApplyWorkspaceEdit>(
26763                            lsp::ApplyWorkspaceEditParams {
26764                                label: None,
26765                                edit: lsp::WorkspaceEdit {
26766                                    changes: Some(
26767                                        [(
26768                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
26769                                            vec![lsp::TextEdit {
26770                                                range: lsp::Range::new(
26771                                                    lsp::Position::new(0, 0),
26772                                                    lsp::Position::new(0, 0),
26773                                                ),
26774                                                new_text: "X".into(),
26775                                            }],
26776                                        )]
26777                                        .into_iter()
26778                                        .collect(),
26779                                    ),
26780                                    ..lsp::WorkspaceEdit::default()
26781                                },
26782                            },
26783                            DEFAULT_LSP_REQUEST_TIMEOUT,
26784                        )
26785                        .await
26786                        .into_response()
26787                        .unwrap();
26788                    Ok(Some(json!(null)))
26789                }
26790            }
26791        })
26792        .next()
26793        .await;
26794
26795    // Applying the code lens command returns a project transaction containing the edits
26796    // sent by the language server in its `workspaceEdit` request.
26797    let transaction = apply.await.unwrap();
26798    assert!(transaction.0.contains_key(&buffer));
26799    buffer.update(cx, |buffer, cx| {
26800        assert_eq!(buffer.text(), "Xa");
26801        buffer.undo(cx);
26802        assert_eq!(buffer.text(), "a");
26803    });
26804
26805    let actions_after_edits = cx
26806        .update(|window, cx| project.code_actions(&buffer, anchor..anchor, window, cx))
26807        .unwrap()
26808        .await;
26809    assert_eq!(
26810        actions, actions_after_edits,
26811        "For the same selection, same code lens actions should be returned"
26812    );
26813
26814    let _responses =
26815        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
26816            panic!("No more code lens requests are expected");
26817        });
26818    editor.update_in(cx, |editor, window, cx| {
26819        editor.select_all(&SelectAll, window, cx);
26820    });
26821    cx.executor().run_until_parked();
26822    let new_actions = cx
26823        .update(|window, cx| project.code_actions(&buffer, anchor..anchor, window, cx))
26824        .unwrap()
26825        .await;
26826    assert_eq!(
26827        actions, new_actions,
26828        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
26829    );
26830}
26831
26832#[gpui::test]
26833async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
26834    init_test(cx, |_| {});
26835
26836    let fs = FakeFs::new(cx.executor());
26837    let main_text = r#"fn main() {
26838println!("1");
26839println!("2");
26840println!("3");
26841println!("4");
26842println!("5");
26843}"#;
26844    let lib_text = "mod foo {}";
26845    fs.insert_tree(
26846        path!("/a"),
26847        json!({
26848            "lib.rs": lib_text,
26849            "main.rs": main_text,
26850        }),
26851    )
26852    .await;
26853
26854    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26855    let (multi_workspace, cx) =
26856        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
26857    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
26858    let worktree_id = workspace.update(cx, |workspace, cx| {
26859        workspace.project().update(cx, |project, cx| {
26860            project.worktrees(cx).next().unwrap().read(cx).id()
26861        })
26862    });
26863
26864    let expected_ranges = vec![
26865        Point::new(0, 0)..Point::new(0, 0),
26866        Point::new(1, 0)..Point::new(1, 1),
26867        Point::new(2, 0)..Point::new(2, 2),
26868        Point::new(3, 0)..Point::new(3, 3),
26869    ];
26870
26871    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
26872    let editor_1 = workspace
26873        .update_in(cx, |workspace, window, cx| {
26874            workspace.open_path(
26875                (worktree_id, rel_path("main.rs")),
26876                Some(pane_1.downgrade()),
26877                true,
26878                window,
26879                cx,
26880            )
26881        })
26882        .unwrap()
26883        .await
26884        .downcast::<Editor>()
26885        .unwrap();
26886    pane_1.update(cx, |pane, cx| {
26887        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26888        open_editor.update(cx, |editor, cx| {
26889            assert_eq!(
26890                editor.display_text(cx),
26891                main_text,
26892                "Original main.rs text on initial open",
26893            );
26894            assert_eq!(
26895                editor
26896                    .selections
26897                    .all::<Point>(&editor.display_snapshot(cx))
26898                    .into_iter()
26899                    .map(|s| s.range())
26900                    .collect::<Vec<_>>(),
26901                vec![Point::zero()..Point::zero()],
26902                "Default selections on initial open",
26903            );
26904        })
26905    });
26906    editor_1.update_in(cx, |editor, window, cx| {
26907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26908            s.select_ranges(expected_ranges.clone());
26909        });
26910    });
26911
26912    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
26913        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
26914    });
26915    let editor_2 = workspace
26916        .update_in(cx, |workspace, window, cx| {
26917            workspace.open_path(
26918                (worktree_id, rel_path("main.rs")),
26919                Some(pane_2.downgrade()),
26920                true,
26921                window,
26922                cx,
26923            )
26924        })
26925        .unwrap()
26926        .await
26927        .downcast::<Editor>()
26928        .unwrap();
26929    pane_2.update(cx, |pane, cx| {
26930        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
26931        open_editor.update(cx, |editor, cx| {
26932            assert_eq!(
26933                editor.display_text(cx),
26934                main_text,
26935                "Original main.rs text on initial open in another panel",
26936            );
26937            assert_eq!(
26938                editor
26939                    .selections
26940                    .all::<Point>(&editor.display_snapshot(cx))
26941                    .into_iter()
26942                    .map(|s| s.range())
26943                    .collect::<Vec<_>>(),
26944                vec![Point::zero()..Point::zero()],
26945                "Default selections on initial open in another panel",
26946            );
26947        })
26948    });
26949
26950    editor_2.update_in(cx, |editor, window, cx| {
26951        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
26952    });
26953
26954    let _other_editor_1 = workspace
26955        .update_in(cx, |workspace, window, cx| {
26956            workspace.open_path(
26957                (worktree_id, rel_path("lib.rs")),
26958                Some(pane_1.downgrade()),
26959                true,
26960                window,
26961                cx,
26962            )
26963        })
26964        .unwrap()
26965        .await
26966        .downcast::<Editor>()
26967        .unwrap();
26968    pane_1
26969        .update_in(cx, |pane, window, cx| {
26970            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
26971        })
26972        .await
26973        .unwrap();
26974    drop(editor_1);
26975    pane_1.update(cx, |pane, cx| {
26976        pane.active_item()
26977            .unwrap()
26978            .downcast::<Editor>()
26979            .unwrap()
26980            .update(cx, |editor, cx| {
26981                assert_eq!(
26982                    editor.display_text(cx),
26983                    lib_text,
26984                    "Other file should be open and active",
26985                );
26986            });
26987        assert_eq!(pane.items().count(), 1, "No other editors should be open");
26988    });
26989
26990    let _other_editor_2 = workspace
26991        .update_in(cx, |workspace, window, cx| {
26992            workspace.open_path(
26993                (worktree_id, rel_path("lib.rs")),
26994                Some(pane_2.downgrade()),
26995                true,
26996                window,
26997                cx,
26998            )
26999        })
27000        .unwrap()
27001        .await
27002        .downcast::<Editor>()
27003        .unwrap();
27004    pane_2
27005        .update_in(cx, |pane, window, cx| {
27006            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
27007        })
27008        .await
27009        .unwrap();
27010    drop(editor_2);
27011    pane_2.update(cx, |pane, cx| {
27012        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
27013        open_editor.update(cx, |editor, cx| {
27014            assert_eq!(
27015                editor.display_text(cx),
27016                lib_text,
27017                "Other file should be open and active in another panel too",
27018            );
27019        });
27020        assert_eq!(
27021            pane.items().count(),
27022            1,
27023            "No other editors should be open in another pane",
27024        );
27025    });
27026
27027    let _editor_1_reopened = workspace
27028        .update_in(cx, |workspace, window, cx| {
27029            workspace.open_path(
27030                (worktree_id, rel_path("main.rs")),
27031                Some(pane_1.downgrade()),
27032                true,
27033                window,
27034                cx,
27035            )
27036        })
27037        .unwrap()
27038        .await
27039        .downcast::<Editor>()
27040        .unwrap();
27041    let _editor_2_reopened = workspace
27042        .update_in(cx, |workspace, window, cx| {
27043            workspace.open_path(
27044                (worktree_id, rel_path("main.rs")),
27045                Some(pane_2.downgrade()),
27046                true,
27047                window,
27048                cx,
27049            )
27050        })
27051        .unwrap()
27052        .await
27053        .downcast::<Editor>()
27054        .unwrap();
27055    pane_1.update(cx, |pane, cx| {
27056        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
27057        open_editor.update(cx, |editor, cx| {
27058            assert_eq!(
27059                editor.display_text(cx),
27060                main_text,
27061                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
27062            );
27063            assert_eq!(
27064                editor
27065                    .selections
27066                    .all::<Point>(&editor.display_snapshot(cx))
27067                    .into_iter()
27068                    .map(|s| s.range())
27069                    .collect::<Vec<_>>(),
27070                expected_ranges,
27071                "Previous editor in the 1st panel had selections and should get them restored on reopen",
27072            );
27073        })
27074    });
27075    pane_2.update(cx, |pane, cx| {
27076        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
27077        open_editor.update(cx, |editor, cx| {
27078            assert_eq!(
27079                editor.display_text(cx),
27080                r#"fn main() {
27081⋯rintln!("1");
27082⋯intln!("2");
27083⋯ntln!("3");
27084println!("4");
27085println!("5");
27086}"#,
27087                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
27088            );
27089            assert_eq!(
27090                editor
27091                    .selections
27092                    .all::<Point>(&editor.display_snapshot(cx))
27093                    .into_iter()
27094                    .map(|s| s.range())
27095                    .collect::<Vec<_>>(),
27096                vec![Point::zero()..Point::zero()],
27097                "Previous editor in the 2nd pane had no selections changed hence should restore none",
27098            );
27099        })
27100    });
27101}
27102
27103#[gpui::test]
27104async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
27105    init_test(cx, |_| {});
27106
27107    let fs = FakeFs::new(cx.executor());
27108    let main_text = r#"fn main() {
27109println!("1");
27110println!("2");
27111println!("3");
27112println!("4");
27113println!("5");
27114}"#;
27115    let lib_text = "mod foo {}";
27116    fs.insert_tree(
27117        path!("/a"),
27118        json!({
27119            "lib.rs": lib_text,
27120            "main.rs": main_text,
27121        }),
27122    )
27123    .await;
27124
27125    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27126    let (multi_workspace, cx) =
27127        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27128    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
27129    let worktree_id = workspace.update(cx, |workspace, cx| {
27130        workspace.project().update(cx, |project, cx| {
27131            project.worktrees(cx).next().unwrap().read(cx).id()
27132        })
27133    });
27134
27135    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
27136    let editor = workspace
27137        .update_in(cx, |workspace, window, cx| {
27138            workspace.open_path(
27139                (worktree_id, rel_path("main.rs")),
27140                Some(pane.downgrade()),
27141                true,
27142                window,
27143                cx,
27144            )
27145        })
27146        .unwrap()
27147        .await
27148        .downcast::<Editor>()
27149        .unwrap();
27150    pane.update(cx, |pane, cx| {
27151        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
27152        open_editor.update(cx, |editor, cx| {
27153            assert_eq!(
27154                editor.display_text(cx),
27155                main_text,
27156                "Original main.rs text on initial open",
27157            );
27158        })
27159    });
27160    editor.update_in(cx, |editor, window, cx| {
27161        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
27162    });
27163
27164    cx.update_global(|store: &mut SettingsStore, cx| {
27165        store.update_user_settings(cx, |s| {
27166            s.workspace.restore_on_file_reopen = Some(false);
27167        });
27168    });
27169    editor.update_in(cx, |editor, window, cx| {
27170        editor.fold_ranges(
27171            vec![
27172                Point::new(1, 0)..Point::new(1, 1),
27173                Point::new(2, 0)..Point::new(2, 2),
27174                Point::new(3, 0)..Point::new(3, 3),
27175            ],
27176            false,
27177            window,
27178            cx,
27179        );
27180    });
27181    pane.update_in(cx, |pane, window, cx| {
27182        pane.close_all_items(&CloseAllItems::default(), window, cx)
27183    })
27184    .await
27185    .unwrap();
27186    pane.update(cx, |pane, _| {
27187        assert!(pane.active_item().is_none());
27188    });
27189    cx.update_global(|store: &mut SettingsStore, cx| {
27190        store.update_user_settings(cx, |s| {
27191            s.workspace.restore_on_file_reopen = Some(true);
27192        });
27193    });
27194
27195    let _editor_reopened = workspace
27196        .update_in(cx, |workspace, window, cx| {
27197            workspace.open_path(
27198                (worktree_id, rel_path("main.rs")),
27199                Some(pane.downgrade()),
27200                true,
27201                window,
27202                cx,
27203            )
27204        })
27205        .unwrap()
27206        .await
27207        .downcast::<Editor>()
27208        .unwrap();
27209    pane.update(cx, |pane, cx| {
27210        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
27211        open_editor.update(cx, |editor, cx| {
27212            assert_eq!(
27213                editor.display_text(cx),
27214                main_text,
27215                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
27216            );
27217        })
27218    });
27219}
27220
27221#[gpui::test]
27222async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
27223    struct EmptyModalView {
27224        focus_handle: gpui::FocusHandle,
27225    }
27226    impl EventEmitter<DismissEvent> for EmptyModalView {}
27227    impl Render for EmptyModalView {
27228        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
27229            div()
27230        }
27231    }
27232    impl Focusable for EmptyModalView {
27233        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
27234            self.focus_handle.clone()
27235        }
27236    }
27237    impl workspace::ModalView for EmptyModalView {}
27238    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
27239        EmptyModalView {
27240            focus_handle: cx.focus_handle(),
27241        }
27242    }
27243
27244    init_test(cx, |_| {});
27245
27246    let fs = FakeFs::new(cx.executor());
27247    let project = Project::test(fs, [], cx).await;
27248    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27249    let workspace = window
27250        .read_with(cx, |mw, _| mw.workspace().clone())
27251        .unwrap();
27252    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
27253    let cx = &mut VisualTestContext::from_window(*window, cx);
27254    let editor = cx.new_window_entity(|window, cx| {
27255        Editor::new(
27256            EditorMode::full(),
27257            buffer,
27258            Some(project.clone()),
27259            window,
27260            cx,
27261        )
27262    });
27263    workspace.update_in(cx, |workspace, window, cx| {
27264        workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
27265    });
27266
27267    editor.update_in(cx, |editor, window, cx| {
27268        editor.open_context_menu(&OpenContextMenu, window, cx);
27269        assert!(editor.mouse_context_menu.is_some());
27270    });
27271    workspace.update_in(cx, |workspace, window, cx| {
27272        workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
27273    });
27274
27275    cx.read(|cx| {
27276        assert!(editor.read(cx).mouse_context_menu.is_none());
27277    });
27278}
27279
27280fn set_linked_edit_ranges(
27281    opening: (Point, Point),
27282    closing: (Point, Point),
27283    editor: &mut Editor,
27284    cx: &mut Context<Editor>,
27285) {
27286    let Some((buffer, _)) = editor
27287        .buffer
27288        .read(cx)
27289        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
27290    else {
27291        panic!("Failed to get buffer for selection position");
27292    };
27293    let buffer = buffer.read(cx);
27294    let buffer_id = buffer.remote_id();
27295    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
27296    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
27297    let mut linked_ranges = HashMap::default();
27298    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
27299    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
27300}
27301
27302#[gpui::test]
27303async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
27304    init_test(cx, |_| {});
27305
27306    let fs = FakeFs::new(cx.executor());
27307    fs.insert_file(path!("/file.html"), Default::default())
27308        .await;
27309
27310    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
27311
27312    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27313    let html_language = Arc::new(Language::new(
27314        LanguageConfig {
27315            name: "HTML".into(),
27316            matcher: LanguageMatcher {
27317                path_suffixes: vec!["html".to_string()],
27318                ..LanguageMatcher::default()
27319            },
27320            brackets: BracketPairConfig {
27321                pairs: vec![BracketPair {
27322                    start: "<".into(),
27323                    end: ">".into(),
27324                    close: true,
27325                    ..Default::default()
27326                }],
27327                ..Default::default()
27328            },
27329            ..Default::default()
27330        },
27331        Some(tree_sitter_html::LANGUAGE.into()),
27332    ));
27333    language_registry.add(html_language);
27334    let mut fake_servers = language_registry.register_fake_lsp(
27335        "HTML",
27336        FakeLspAdapter {
27337            capabilities: lsp::ServerCapabilities {
27338                completion_provider: Some(lsp::CompletionOptions {
27339                    resolve_provider: Some(true),
27340                    ..Default::default()
27341                }),
27342                ..Default::default()
27343            },
27344            ..Default::default()
27345        },
27346    );
27347
27348    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27349    let workspace = window
27350        .read_with(cx, |mw, _| mw.workspace().clone())
27351        .unwrap();
27352    let cx = &mut VisualTestContext::from_window(*window, cx);
27353
27354    let worktree_id = workspace.update_in(cx, |workspace, _window, cx| {
27355        workspace.project().update(cx, |project, cx| {
27356            project.worktrees(cx).next().unwrap().read(cx).id()
27357        })
27358    });
27359
27360    project
27361        .update(cx, |project, cx| {
27362            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
27363        })
27364        .await
27365        .unwrap();
27366    let editor = workspace
27367        .update_in(cx, |workspace, window, cx| {
27368            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
27369        })
27370        .await
27371        .unwrap()
27372        .downcast::<Editor>()
27373        .unwrap();
27374
27375    let fake_server = fake_servers.next().await.unwrap();
27376    cx.run_until_parked();
27377    editor.update_in(cx, |editor, window, cx| {
27378        editor.set_text("<ad></ad>", window, cx);
27379        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
27380            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
27381        });
27382        set_linked_edit_ranges(
27383            (Point::new(0, 1), Point::new(0, 3)),
27384            (Point::new(0, 6), Point::new(0, 8)),
27385            editor,
27386            cx,
27387        );
27388    });
27389    let mut completion_handle =
27390        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
27391            Ok(Some(lsp::CompletionResponse::Array(vec![
27392                lsp::CompletionItem {
27393                    label: "head".to_string(),
27394                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
27395                        lsp::InsertReplaceEdit {
27396                            new_text: "head".to_string(),
27397                            insert: lsp::Range::new(
27398                                lsp::Position::new(0, 1),
27399                                lsp::Position::new(0, 3),
27400                            ),
27401                            replace: lsp::Range::new(
27402                                lsp::Position::new(0, 1),
27403                                lsp::Position::new(0, 3),
27404                            ),
27405                        },
27406                    )),
27407                    ..Default::default()
27408                },
27409            ])))
27410        });
27411    editor.update_in(cx, |editor, window, cx| {
27412        editor.show_completions(&ShowCompletions, window, cx);
27413    });
27414    cx.run_until_parked();
27415    completion_handle.next().await.unwrap();
27416    editor.update(cx, |editor, _| {
27417        assert!(
27418            editor.context_menu_visible(),
27419            "Completion menu should be visible"
27420        );
27421    });
27422    editor.update_in(cx, |editor, window, cx| {
27423        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
27424    });
27425    cx.executor().run_until_parked();
27426    editor.update(cx, |editor, cx| {
27427        assert_eq!(editor.text(cx), "<head></head>");
27428    });
27429}
27430
27431#[gpui::test]
27432async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
27433    init_test(cx, |_| {});
27434
27435    let mut cx = EditorTestContext::new(cx).await;
27436    let language = Arc::new(Language::new(
27437        LanguageConfig {
27438            name: "TSX".into(),
27439            matcher: LanguageMatcher {
27440                path_suffixes: vec!["tsx".to_string()],
27441                ..LanguageMatcher::default()
27442            },
27443            brackets: BracketPairConfig {
27444                pairs: vec![BracketPair {
27445                    start: "<".into(),
27446                    end: ">".into(),
27447                    close: true,
27448                    ..Default::default()
27449                }],
27450                ..Default::default()
27451            },
27452            linked_edit_characters: HashSet::from_iter(['.']),
27453            ..Default::default()
27454        },
27455        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
27456    ));
27457    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27458
27459    // Test typing > does not extend linked pair
27460    cx.set_state("<divˇ<div></div>");
27461    cx.update_editor(|editor, _, cx| {
27462        set_linked_edit_ranges(
27463            (Point::new(0, 1), Point::new(0, 4)),
27464            (Point::new(0, 11), Point::new(0, 14)),
27465            editor,
27466            cx,
27467        );
27468    });
27469    cx.update_editor(|editor, window, cx| {
27470        editor.handle_input(">", window, cx);
27471    });
27472    cx.assert_editor_state("<div>ˇ<div></div>");
27473
27474    // Test typing . do extend linked pair
27475    cx.set_state("<Animatedˇ></Animated>");
27476    cx.update_editor(|editor, _, cx| {
27477        set_linked_edit_ranges(
27478            (Point::new(0, 1), Point::new(0, 9)),
27479            (Point::new(0, 12), Point::new(0, 20)),
27480            editor,
27481            cx,
27482        );
27483    });
27484    cx.update_editor(|editor, window, cx| {
27485        editor.handle_input(".", window, cx);
27486    });
27487    cx.assert_editor_state("<Animated.ˇ></Animated.>");
27488    cx.update_editor(|editor, _, cx| {
27489        set_linked_edit_ranges(
27490            (Point::new(0, 1), Point::new(0, 10)),
27491            (Point::new(0, 13), Point::new(0, 21)),
27492            editor,
27493            cx,
27494        );
27495    });
27496    cx.update_editor(|editor, window, cx| {
27497        editor.handle_input("V", window, cx);
27498    });
27499    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
27500}
27501
27502#[gpui::test]
27503async fn test_linked_edits_on_typing_dot_without_language_override(cx: &mut TestAppContext) {
27504    init_test(cx, |_| {});
27505
27506    let mut cx = EditorTestContext::new(cx).await;
27507    let language = Arc::new(Language::new(
27508        LanguageConfig {
27509            name: "HTML".into(),
27510            matcher: LanguageMatcher {
27511                path_suffixes: vec!["html".to_string()],
27512                ..LanguageMatcher::default()
27513            },
27514            brackets: BracketPairConfig {
27515                pairs: vec![BracketPair {
27516                    start: "<".into(),
27517                    end: ">".into(),
27518                    close: true,
27519                    ..Default::default()
27520                }],
27521                ..Default::default()
27522            },
27523            ..Default::default()
27524        },
27525        Some(tree_sitter_html::LANGUAGE.into()),
27526    ));
27527    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27528
27529    cx.set_state("<Tableˇ></Table>");
27530    cx.update_editor(|editor, _, cx| {
27531        set_linked_edit_ranges(
27532            (Point::new(0, 1), Point::new(0, 6)),
27533            (Point::new(0, 9), Point::new(0, 14)),
27534            editor,
27535            cx,
27536        );
27537    });
27538    cx.update_editor(|editor, window, cx| {
27539        editor.handle_input(".", window, cx);
27540    });
27541    cx.assert_editor_state("<Table.ˇ></Table.>");
27542}
27543
27544#[gpui::test]
27545async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
27546    init_test(cx, |_| {});
27547
27548    let fs = FakeFs::new(cx.executor());
27549    fs.insert_tree(
27550        path!("/root"),
27551        json!({
27552            "a": {
27553                "main.rs": "fn main() {}",
27554            },
27555            "foo": {
27556                "bar": {
27557                    "external_file.rs": "pub mod external {}",
27558                }
27559            }
27560        }),
27561    )
27562    .await;
27563
27564    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
27565    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27566    language_registry.add(rust_lang());
27567    let _fake_servers = language_registry.register_fake_lsp(
27568        "Rust",
27569        FakeLspAdapter {
27570            ..FakeLspAdapter::default()
27571        },
27572    );
27573    let (multi_workspace, cx) =
27574        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
27575    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
27576    let worktree_id = workspace.update(cx, |workspace, cx| {
27577        workspace.project().update(cx, |project, cx| {
27578            project.worktrees(cx).next().unwrap().read(cx).id()
27579        })
27580    });
27581
27582    let assert_language_servers_count =
27583        |expected: usize, context: &str, cx: &mut VisualTestContext| {
27584            project.update(cx, |project, cx| {
27585                let current = project
27586                    .lsp_store()
27587                    .read(cx)
27588                    .as_local()
27589                    .unwrap()
27590                    .language_servers
27591                    .len();
27592                assert_eq!(expected, current, "{context}");
27593            });
27594        };
27595
27596    assert_language_servers_count(
27597        0,
27598        "No servers should be running before any file is open",
27599        cx,
27600    );
27601    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
27602    let main_editor = workspace
27603        .update_in(cx, |workspace, window, cx| {
27604            workspace.open_path(
27605                (worktree_id, rel_path("main.rs")),
27606                Some(pane.downgrade()),
27607                true,
27608                window,
27609                cx,
27610            )
27611        })
27612        .unwrap()
27613        .await
27614        .downcast::<Editor>()
27615        .unwrap();
27616    pane.update(cx, |pane, cx| {
27617        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
27618        open_editor.update(cx, |editor, cx| {
27619            assert_eq!(
27620                editor.display_text(cx),
27621                "fn main() {}",
27622                "Original main.rs text on initial open",
27623            );
27624        });
27625        assert_eq!(open_editor, main_editor);
27626    });
27627    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
27628
27629    let external_editor = workspace
27630        .update_in(cx, |workspace, window, cx| {
27631            workspace.open_abs_path(
27632                PathBuf::from("/root/foo/bar/external_file.rs"),
27633                OpenOptions::default(),
27634                window,
27635                cx,
27636            )
27637        })
27638        .await
27639        .expect("opening external file")
27640        .downcast::<Editor>()
27641        .expect("downcasted external file's open element to editor");
27642    pane.update(cx, |pane, cx| {
27643        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
27644        open_editor.update(cx, |editor, cx| {
27645            assert_eq!(
27646                editor.display_text(cx),
27647                "pub mod external {}",
27648                "External file is open now",
27649            );
27650        });
27651        assert_eq!(open_editor, external_editor);
27652    });
27653    assert_language_servers_count(
27654        1,
27655        "Second, external, *.rs file should join the existing server",
27656        cx,
27657    );
27658
27659    pane.update_in(cx, |pane, window, cx| {
27660        pane.close_active_item(&CloseActiveItem::default(), window, cx)
27661    })
27662    .await
27663    .unwrap();
27664    pane.update_in(cx, |pane, window, cx| {
27665        pane.navigate_backward(&Default::default(), window, cx);
27666    });
27667    cx.run_until_parked();
27668    pane.update(cx, |pane, cx| {
27669        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
27670        open_editor.update(cx, |editor, cx| {
27671            assert_eq!(
27672                editor.display_text(cx),
27673                "pub mod external {}",
27674                "External file is open now",
27675            );
27676        });
27677    });
27678    assert_language_servers_count(
27679        1,
27680        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
27681        cx,
27682    );
27683
27684    cx.update(|_, cx| {
27685        workspace::reload(cx);
27686    });
27687    assert_language_servers_count(
27688        1,
27689        "After reloading the worktree with local and external files opened, only one project should be started",
27690        cx,
27691    );
27692}
27693
27694#[gpui::test]
27695async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
27696    init_test(cx, |_| {});
27697
27698    let mut cx = EditorTestContext::new(cx).await;
27699    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
27700    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27701
27702    // test cursor move to start of each line on tab
27703    // for `if`, `elif`, `else`, `while`, `with` and `for`
27704    cx.set_state(indoc! {"
27705        def main():
27706        ˇ    for item in items:
27707        ˇ        while item.active:
27708        ˇ            if item.value > 10:
27709        ˇ                continue
27710        ˇ            elif item.value < 0:
27711        ˇ                break
27712        ˇ            else:
27713        ˇ                with item.context() as ctx:
27714        ˇ                    yield count
27715        ˇ        else:
27716        ˇ            log('while else')
27717        ˇ    else:
27718        ˇ        log('for else')
27719    "});
27720    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27721    cx.wait_for_autoindent_applied().await;
27722    cx.assert_editor_state(indoc! {"
27723        def main():
27724            ˇfor item in items:
27725                ˇwhile item.active:
27726                    ˇif item.value > 10:
27727                        ˇcontinue
27728                    ˇelif item.value < 0:
27729                        ˇbreak
27730                    ˇelse:
27731                        ˇwith item.context() as ctx:
27732                            ˇyield count
27733                ˇelse:
27734                    ˇlog('while else')
27735            ˇelse:
27736                ˇlog('for else')
27737    "});
27738    // test relative indent is preserved when tab
27739    // for `if`, `elif`, `else`, `while`, `with` and `for`
27740    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27741    cx.wait_for_autoindent_applied().await;
27742    cx.assert_editor_state(indoc! {"
27743        def main():
27744                ˇfor item in items:
27745                    ˇwhile item.active:
27746                        ˇif item.value > 10:
27747                            ˇcontinue
27748                        ˇelif item.value < 0:
27749                            ˇbreak
27750                        ˇelse:
27751                            ˇwith item.context() as ctx:
27752                                ˇyield count
27753                    ˇelse:
27754                        ˇlog('while else')
27755                ˇelse:
27756                    ˇlog('for else')
27757    "});
27758
27759    // test cursor move to start of each line on tab
27760    // for `try`, `except`, `else`, `finally`, `match` and `def`
27761    cx.set_state(indoc! {"
27762        def main():
27763        ˇ    try:
27764        ˇ        fetch()
27765        ˇ    except ValueError:
27766        ˇ        handle_error()
27767        ˇ    else:
27768        ˇ        match value:
27769        ˇ            case _:
27770        ˇ    finally:
27771        ˇ        def status():
27772        ˇ            return 0
27773    "});
27774    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27775    cx.wait_for_autoindent_applied().await;
27776    cx.assert_editor_state(indoc! {"
27777        def main():
27778            ˇtry:
27779                ˇfetch()
27780            ˇexcept ValueError:
27781                ˇhandle_error()
27782            ˇelse:
27783                ˇmatch value:
27784                    ˇcase _:
27785            ˇfinally:
27786                ˇdef status():
27787                    ˇreturn 0
27788    "});
27789    // test relative indent is preserved when tab
27790    // for `try`, `except`, `else`, `finally`, `match` and `def`
27791    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
27792    cx.wait_for_autoindent_applied().await;
27793    cx.assert_editor_state(indoc! {"
27794        def main():
27795                ˇtry:
27796                    ˇfetch()
27797                ˇexcept ValueError:
27798                    ˇhandle_error()
27799                ˇelse:
27800                    ˇmatch value:
27801                        ˇcase _:
27802                ˇfinally:
27803                    ˇdef status():
27804                        ˇreturn 0
27805    "});
27806}
27807
27808#[gpui::test]
27809async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
27810    init_test(cx, |_| {});
27811
27812    let mut cx = EditorTestContext::new(cx).await;
27813    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
27814    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
27815
27816    // test `else` auto outdents when typed inside `if` block
27817    cx.set_state(indoc! {"
27818        def main():
27819            if i == 2:
27820                return
27821                ˇ
27822    "});
27823    cx.update_editor(|editor, window, cx| {
27824        editor.handle_input("else:", window, cx);
27825    });
27826    cx.wait_for_autoindent_applied().await;
27827    cx.assert_editor_state(indoc! {"
27828        def main():
27829            if i == 2:
27830                return
27831            else:ˇ
27832    "});
27833
27834    // test `except` auto outdents when typed inside `try` block
27835    cx.set_state(indoc! {"
27836        def main():
27837            try:
27838                i = 2
27839                ˇ
27840    "});
27841    cx.update_editor(|editor, window, cx| {
27842        editor.handle_input("except:", window, cx);
27843    });
27844    cx.wait_for_autoindent_applied().await;
27845    cx.assert_editor_state(indoc! {"
27846        def main():
27847            try:
27848                i = 2
27849            except:ˇ
27850    "});
27851
27852    // test `else` auto outdents when typed inside `except` block
27853    cx.set_state(indoc! {"
27854        def main():
27855            try:
27856                i = 2
27857            except:
27858                j = 2
27859                ˇ
27860    "});
27861    cx.update_editor(|editor, window, cx| {
27862        editor.handle_input("else:", window, cx);
27863    });
27864    cx.wait_for_autoindent_applied().await;
27865    cx.assert_editor_state(indoc! {"
27866        def main():
27867            try:
27868                i = 2
27869            except:
27870                j = 2
27871            else:ˇ
27872    "});
27873
27874    // test `finally` auto outdents when typed inside `else` block
27875    cx.set_state(indoc! {"
27876        def main():
27877            try:
27878                i = 2
27879            except:
27880                j = 2
27881            else:
27882                k = 2
27883                ˇ
27884    "});
27885    cx.update_editor(|editor, window, cx| {
27886        editor.handle_input("finally:", window, cx);
27887    });
27888    cx.wait_for_autoindent_applied().await;
27889    cx.assert_editor_state(indoc! {"
27890        def main():
27891            try:
27892                i = 2
27893            except:
27894                j = 2
27895            else:
27896                k = 2
27897            finally:ˇ
27898    "});
27899
27900    // test `else` does not outdents when typed inside `except` block right after for block
27901    cx.set_state(indoc! {"
27902        def main():
27903            try:
27904                i = 2
27905            except:
27906                for i in range(n):
27907                    pass
27908                ˇ
27909    "});
27910    cx.update_editor(|editor, window, cx| {
27911        editor.handle_input("else:", window, cx);
27912    });
27913    cx.wait_for_autoindent_applied().await;
27914    cx.assert_editor_state(indoc! {"
27915        def main():
27916            try:
27917                i = 2
27918            except:
27919                for i in range(n):
27920                    pass
27921                else:ˇ
27922    "});
27923
27924    // test `finally` auto outdents when typed inside `else` block right after for block
27925    cx.set_state(indoc! {"
27926        def main():
27927            try:
27928                i = 2
27929            except:
27930                j = 2
27931            else:
27932                for i in range(n):
27933                    pass
27934                ˇ
27935    "});
27936    cx.update_editor(|editor, window, cx| {
27937        editor.handle_input("finally:", window, cx);
27938    });
27939    cx.wait_for_autoindent_applied().await;
27940    cx.assert_editor_state(indoc! {"
27941        def main():
27942            try:
27943                i = 2
27944            except:
27945                j = 2
27946            else:
27947                for i in range(n):
27948                    pass
27949            finally:ˇ
27950    "});
27951
27952    // test `except` outdents to inner "try" block
27953    cx.set_state(indoc! {"
27954        def main():
27955            try:
27956                i = 2
27957                if i == 2:
27958                    try:
27959                        i = 3
27960                        ˇ
27961    "});
27962    cx.update_editor(|editor, window, cx| {
27963        editor.handle_input("except:", window, cx);
27964    });
27965    cx.wait_for_autoindent_applied().await;
27966    cx.assert_editor_state(indoc! {"
27967        def main():
27968            try:
27969                i = 2
27970                if i == 2:
27971                    try:
27972                        i = 3
27973                    except:ˇ
27974    "});
27975
27976    // test `except` outdents to outer "try" block
27977    cx.set_state(indoc! {"
27978        def main():
27979            try:
27980                i = 2
27981                if i == 2:
27982                    try:
27983                        i = 3
27984                ˇ
27985    "});
27986    cx.update_editor(|editor, window, cx| {
27987        editor.handle_input("except:", window, cx);
27988    });
27989    cx.wait_for_autoindent_applied().await;
27990    cx.assert_editor_state(indoc! {"
27991        def main():
27992            try:
27993                i = 2
27994                if i == 2:
27995                    try:
27996                        i = 3
27997            except:ˇ
27998    "});
27999
28000    // test `else` stays at correct indent when typed after `for` block
28001    cx.set_state(indoc! {"
28002        def main():
28003            for i in range(10):
28004                if i == 3:
28005                    break
28006            ˇ
28007    "});
28008    cx.update_editor(|editor, window, cx| {
28009        editor.handle_input("else:", window, cx);
28010    });
28011    cx.wait_for_autoindent_applied().await;
28012    cx.assert_editor_state(indoc! {"
28013        def main():
28014            for i in range(10):
28015                if i == 3:
28016                    break
28017            else:ˇ
28018    "});
28019
28020    // test does not outdent on typing after line with square brackets
28021    cx.set_state(indoc! {"
28022        def f() -> list[str]:
28023            ˇ
28024    "});
28025    cx.update_editor(|editor, window, cx| {
28026        editor.handle_input("a", window, cx);
28027    });
28028    cx.wait_for_autoindent_applied().await;
28029    cx.assert_editor_state(indoc! {"
28030        def f() -> list[str]:
2803128032    "});
28033
28034    // test does not outdent on typing : after case keyword
28035    cx.set_state(indoc! {"
28036        match 1:
28037            caseˇ
28038    "});
28039    cx.update_editor(|editor, window, cx| {
28040        editor.handle_input(":", window, cx);
28041    });
28042    cx.wait_for_autoindent_applied().await;
28043    cx.assert_editor_state(indoc! {"
28044        match 1:
28045            case:ˇ
28046    "});
28047}
28048
28049#[gpui::test]
28050async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
28051    init_test(cx, |_| {});
28052    update_test_language_settings(cx, &|settings| {
28053        settings.defaults.extend_comment_on_newline = Some(false);
28054    });
28055    let mut cx = EditorTestContext::new(cx).await;
28056    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
28057    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28058
28059    // test correct indent after newline on comment
28060    cx.set_state(indoc! {"
28061        # COMMENT:ˇ
28062    "});
28063    cx.update_editor(|editor, window, cx| {
28064        editor.newline(&Newline, window, cx);
28065    });
28066    cx.wait_for_autoindent_applied().await;
28067    cx.assert_editor_state(indoc! {"
28068        # COMMENT:
28069        ˇ
28070    "});
28071
28072    // test correct indent after newline in brackets
28073    cx.set_state(indoc! {"
28074        {ˇ}
28075    "});
28076    cx.update_editor(|editor, window, cx| {
28077        editor.newline(&Newline, window, cx);
28078    });
28079    cx.wait_for_autoindent_applied().await;
28080    cx.assert_editor_state(indoc! {"
28081        {
28082            ˇ
28083        }
28084    "});
28085
28086    cx.set_state(indoc! {"
28087        (ˇ)
28088    "});
28089    cx.update_editor(|editor, window, cx| {
28090        editor.newline(&Newline, window, cx);
28091    });
28092    cx.run_until_parked();
28093    cx.assert_editor_state(indoc! {"
28094        (
28095            ˇ
28096        )
28097    "});
28098
28099    // do not indent after empty lists or dictionaries
28100    cx.set_state(indoc! {"
28101        a = []ˇ
28102    "});
28103    cx.update_editor(|editor, window, cx| {
28104        editor.newline(&Newline, window, cx);
28105    });
28106    cx.run_until_parked();
28107    cx.assert_editor_state(indoc! {"
28108        a = []
28109        ˇ
28110    "});
28111}
28112
28113#[gpui::test]
28114async fn test_python_indent_in_markdown(cx: &mut TestAppContext) {
28115    init_test(cx, |_| {});
28116
28117    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
28118    let python_lang = languages::language("python", tree_sitter_python::LANGUAGE.into());
28119    language_registry.add(markdown_lang());
28120    language_registry.add(python_lang);
28121
28122    let mut cx = EditorTestContext::new(cx).await;
28123    cx.update_buffer(|buffer, cx| {
28124        buffer.set_language_registry(language_registry);
28125        buffer.set_language(Some(markdown_lang()), cx);
28126    });
28127
28128    // Test that `else:` correctly outdents to match `if:` inside the Python code block
28129    cx.set_state(indoc! {"
28130        # Heading
28131
28132        ```python
28133        def main():
28134            if condition:
28135                pass
28136                ˇ
28137        ```
28138    "});
28139    cx.update_editor(|editor, window, cx| {
28140        editor.handle_input("else:", window, cx);
28141    });
28142    cx.run_until_parked();
28143    cx.assert_editor_state(indoc! {"
28144        # Heading
28145
28146        ```python
28147        def main():
28148            if condition:
28149                pass
28150            else:ˇ
28151        ```
28152    "});
28153}
28154
28155#[gpui::test]
28156async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
28157    init_test(cx, |_| {});
28158
28159    let mut cx = EditorTestContext::new(cx).await;
28160    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
28161    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28162
28163    // test cursor move to start of each line on tab
28164    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
28165    cx.set_state(indoc! {"
28166        function main() {
28167        ˇ    for item in $items; do
28168        ˇ        while [ -n \"$item\" ]; do
28169        ˇ            if [ \"$value\" -gt 10 ]; then
28170        ˇ                continue
28171        ˇ            elif [ \"$value\" -lt 0 ]; then
28172        ˇ                break
28173        ˇ            else
28174        ˇ                echo \"$item\"
28175        ˇ            fi
28176        ˇ        done
28177        ˇ    done
28178        ˇ}
28179    "});
28180    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
28181    cx.wait_for_autoindent_applied().await;
28182    cx.assert_editor_state(indoc! {"
28183        function main() {
28184            ˇfor item in $items; do
28185                ˇwhile [ -n \"$item\" ]; do
28186                    ˇif [ \"$value\" -gt 10 ]; then
28187                        ˇcontinue
28188                    ˇelif [ \"$value\" -lt 0 ]; then
28189                        ˇbreak
28190                    ˇelse
28191                        ˇecho \"$item\"
28192                    ˇfi
28193                ˇdone
28194            ˇdone
28195        ˇ}
28196    "});
28197    // test relative indent is preserved when tab
28198    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
28199    cx.wait_for_autoindent_applied().await;
28200    cx.assert_editor_state(indoc! {"
28201        function main() {
28202                ˇfor item in $items; do
28203                    ˇwhile [ -n \"$item\" ]; do
28204                        ˇif [ \"$value\" -gt 10 ]; then
28205                            ˇcontinue
28206                        ˇelif [ \"$value\" -lt 0 ]; then
28207                            ˇbreak
28208                        ˇelse
28209                            ˇecho \"$item\"
28210                        ˇfi
28211                    ˇdone
28212                ˇdone
28213            ˇ}
28214    "});
28215
28216    // test cursor move to start of each line on tab
28217    // for `case` statement with patterns
28218    cx.set_state(indoc! {"
28219        function handle() {
28220        ˇ    case \"$1\" in
28221        ˇ        start)
28222        ˇ            echo \"a\"
28223        ˇ            ;;
28224        ˇ        stop)
28225        ˇ            echo \"b\"
28226        ˇ            ;;
28227        ˇ        *)
28228        ˇ            echo \"c\"
28229        ˇ            ;;
28230        ˇ    esac
28231        ˇ}
28232    "});
28233    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
28234    cx.wait_for_autoindent_applied().await;
28235    cx.assert_editor_state(indoc! {"
28236        function handle() {
28237            ˇcase \"$1\" in
28238                ˇstart)
28239                    ˇecho \"a\"
28240                    ˇ;;
28241                ˇstop)
28242                    ˇecho \"b\"
28243                    ˇ;;
28244                ˇ*)
28245                    ˇecho \"c\"
28246                    ˇ;;
28247            ˇesac
28248        ˇ}
28249    "});
28250}
28251
28252#[gpui::test]
28253async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
28254    init_test(cx, |_| {});
28255
28256    let mut cx = EditorTestContext::new(cx).await;
28257    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
28258    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28259
28260    // test indents on comment insert
28261    cx.set_state(indoc! {"
28262        function main() {
28263        ˇ    for item in $items; do
28264        ˇ        while [ -n \"$item\" ]; do
28265        ˇ            if [ \"$value\" -gt 10 ]; then
28266        ˇ                continue
28267        ˇ            elif [ \"$value\" -lt 0 ]; then
28268        ˇ                break
28269        ˇ            else
28270        ˇ                echo \"$item\"
28271        ˇ            fi
28272        ˇ        done
28273        ˇ    done
28274        ˇ}
28275    "});
28276    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
28277    cx.wait_for_autoindent_applied().await;
28278    cx.assert_editor_state(indoc! {"
28279        function main() {
28280        #ˇ    for item in $items; do
28281        #ˇ        while [ -n \"$item\" ]; do
28282        #ˇ            if [ \"$value\" -gt 10 ]; then
28283        #ˇ                continue
28284        #ˇ            elif [ \"$value\" -lt 0 ]; then
28285        #ˇ                break
28286        #ˇ            else
28287        #ˇ                echo \"$item\"
28288        #ˇ            fi
28289        #ˇ        done
28290        #ˇ    done
28291        #ˇ}
28292    "});
28293}
28294
28295#[gpui::test]
28296async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
28297    init_test(cx, |_| {});
28298
28299    let mut cx = EditorTestContext::new(cx).await;
28300    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
28301    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28302
28303    // test `else` auto outdents when typed inside `if` block
28304    cx.set_state(indoc! {"
28305        if [ \"$1\" = \"test\" ]; then
28306            echo \"foo bar\"
28307            ˇ
28308    "});
28309    cx.update_editor(|editor, window, cx| {
28310        editor.handle_input("else", window, cx);
28311    });
28312    cx.wait_for_autoindent_applied().await;
28313    cx.assert_editor_state(indoc! {"
28314        if [ \"$1\" = \"test\" ]; then
28315            echo \"foo bar\"
28316        elseˇ
28317    "});
28318
28319    // test `elif` auto outdents when typed inside `if` block
28320    cx.set_state(indoc! {"
28321        if [ \"$1\" = \"test\" ]; then
28322            echo \"foo bar\"
28323            ˇ
28324    "});
28325    cx.update_editor(|editor, window, cx| {
28326        editor.handle_input("elif", window, cx);
28327    });
28328    cx.wait_for_autoindent_applied().await;
28329    cx.assert_editor_state(indoc! {"
28330        if [ \"$1\" = \"test\" ]; then
28331            echo \"foo bar\"
28332        elifˇ
28333    "});
28334
28335    // test `fi` auto outdents when typed inside `else` block
28336    cx.set_state(indoc! {"
28337        if [ \"$1\" = \"test\" ]; then
28338            echo \"foo bar\"
28339        else
28340            echo \"bar baz\"
28341            ˇ
28342    "});
28343    cx.update_editor(|editor, window, cx| {
28344        editor.handle_input("fi", window, cx);
28345    });
28346    cx.wait_for_autoindent_applied().await;
28347    cx.assert_editor_state(indoc! {"
28348        if [ \"$1\" = \"test\" ]; then
28349            echo \"foo bar\"
28350        else
28351            echo \"bar baz\"
28352        fiˇ
28353    "});
28354
28355    // test `done` auto outdents when typed inside `while` block
28356    cx.set_state(indoc! {"
28357        while read line; do
28358            echo \"$line\"
28359            ˇ
28360    "});
28361    cx.update_editor(|editor, window, cx| {
28362        editor.handle_input("done", window, cx);
28363    });
28364    cx.wait_for_autoindent_applied().await;
28365    cx.assert_editor_state(indoc! {"
28366        while read line; do
28367            echo \"$line\"
28368        doneˇ
28369    "});
28370
28371    // test `done` auto outdents when typed inside `for` block
28372    cx.set_state(indoc! {"
28373        for file in *.txt; do
28374            cat \"$file\"
28375            ˇ
28376    "});
28377    cx.update_editor(|editor, window, cx| {
28378        editor.handle_input("done", window, cx);
28379    });
28380    cx.wait_for_autoindent_applied().await;
28381    cx.assert_editor_state(indoc! {"
28382        for file in *.txt; do
28383            cat \"$file\"
28384        doneˇ
28385    "});
28386
28387    // test `esac` auto outdents when typed inside `case` block
28388    cx.set_state(indoc! {"
28389        case \"$1\" in
28390            start)
28391                echo \"foo bar\"
28392                ;;
28393            stop)
28394                echo \"bar baz\"
28395                ;;
28396            ˇ
28397    "});
28398    cx.update_editor(|editor, window, cx| {
28399        editor.handle_input("esac", window, cx);
28400    });
28401    cx.wait_for_autoindent_applied().await;
28402    cx.assert_editor_state(indoc! {"
28403        case \"$1\" in
28404            start)
28405                echo \"foo bar\"
28406                ;;
28407            stop)
28408                echo \"bar baz\"
28409                ;;
28410        esacˇ
28411    "});
28412
28413    // test `*)` auto outdents when typed inside `case` block
28414    cx.set_state(indoc! {"
28415        case \"$1\" in
28416            start)
28417                echo \"foo bar\"
28418                ;;
28419                ˇ
28420    "});
28421    cx.update_editor(|editor, window, cx| {
28422        editor.handle_input("*)", window, cx);
28423    });
28424    cx.wait_for_autoindent_applied().await;
28425    cx.assert_editor_state(indoc! {"
28426        case \"$1\" in
28427            start)
28428                echo \"foo bar\"
28429                ;;
28430            *)ˇ
28431    "});
28432
28433    // test `fi` outdents to correct level with nested if blocks
28434    cx.set_state(indoc! {"
28435        if [ \"$1\" = \"test\" ]; then
28436            echo \"outer if\"
28437            if [ \"$2\" = \"debug\" ]; then
28438                echo \"inner if\"
28439                ˇ
28440    "});
28441    cx.update_editor(|editor, window, cx| {
28442        editor.handle_input("fi", window, cx);
28443    });
28444    cx.wait_for_autoindent_applied().await;
28445    cx.assert_editor_state(indoc! {"
28446        if [ \"$1\" = \"test\" ]; then
28447            echo \"outer if\"
28448            if [ \"$2\" = \"debug\" ]; then
28449                echo \"inner if\"
28450            fiˇ
28451    "});
28452}
28453
28454#[gpui::test]
28455async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
28456    init_test(cx, |_| {});
28457    update_test_language_settings(cx, &|settings| {
28458        settings.defaults.extend_comment_on_newline = Some(false);
28459    });
28460    let mut cx = EditorTestContext::new(cx).await;
28461    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
28462    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
28463
28464    // test correct indent after newline on comment
28465    cx.set_state(indoc! {"
28466        # COMMENT:ˇ
28467    "});
28468    cx.update_editor(|editor, window, cx| {
28469        editor.newline(&Newline, window, cx);
28470    });
28471    cx.wait_for_autoindent_applied().await;
28472    cx.assert_editor_state(indoc! {"
28473        # COMMENT:
28474        ˇ
28475    "});
28476
28477    // test correct indent after newline after `then`
28478    cx.set_state(indoc! {"
28479
28480        if [ \"$1\" = \"test\" ]; thenˇ
28481    "});
28482    cx.update_editor(|editor, window, cx| {
28483        editor.newline(&Newline, window, cx);
28484    });
28485    cx.wait_for_autoindent_applied().await;
28486    cx.assert_editor_state(indoc! {"
28487
28488        if [ \"$1\" = \"test\" ]; then
28489            ˇ
28490    "});
28491
28492    // test correct indent after newline after `else`
28493    cx.set_state(indoc! {"
28494        if [ \"$1\" = \"test\" ]; then
28495        elseˇ
28496    "});
28497    cx.update_editor(|editor, window, cx| {
28498        editor.newline(&Newline, window, cx);
28499    });
28500    cx.wait_for_autoindent_applied().await;
28501    cx.assert_editor_state(indoc! {"
28502        if [ \"$1\" = \"test\" ]; then
28503        else
28504            ˇ
28505    "});
28506
28507    // test correct indent after newline after `elif`
28508    cx.set_state(indoc! {"
28509        if [ \"$1\" = \"test\" ]; then
28510        elifˇ
28511    "});
28512    cx.update_editor(|editor, window, cx| {
28513        editor.newline(&Newline, window, cx);
28514    });
28515    cx.wait_for_autoindent_applied().await;
28516    cx.assert_editor_state(indoc! {"
28517        if [ \"$1\" = \"test\" ]; then
28518        elif
28519            ˇ
28520    "});
28521
28522    // test correct indent after newline after `do`
28523    cx.set_state(indoc! {"
28524        for file in *.txt; doˇ
28525    "});
28526    cx.update_editor(|editor, window, cx| {
28527        editor.newline(&Newline, window, cx);
28528    });
28529    cx.wait_for_autoindent_applied().await;
28530    cx.assert_editor_state(indoc! {"
28531        for file in *.txt; do
28532            ˇ
28533    "});
28534
28535    // test correct indent after newline after case pattern
28536    cx.set_state(indoc! {"
28537        case \"$1\" in
28538            start)ˇ
28539    "});
28540    cx.update_editor(|editor, window, cx| {
28541        editor.newline(&Newline, window, cx);
28542    });
28543    cx.wait_for_autoindent_applied().await;
28544    cx.assert_editor_state(indoc! {"
28545        case \"$1\" in
28546            start)
28547                ˇ
28548    "});
28549
28550    // test correct indent after newline after case pattern
28551    cx.set_state(indoc! {"
28552        case \"$1\" in
28553            start)
28554                ;;
28555            *)ˇ
28556    "});
28557    cx.update_editor(|editor, window, cx| {
28558        editor.newline(&Newline, window, cx);
28559    });
28560    cx.wait_for_autoindent_applied().await;
28561    cx.assert_editor_state(indoc! {"
28562        case \"$1\" in
28563            start)
28564                ;;
28565            *)
28566                ˇ
28567    "});
28568
28569    // test correct indent after newline after function opening brace
28570    cx.set_state(indoc! {"
28571        function test() {ˇ}
28572    "});
28573    cx.update_editor(|editor, window, cx| {
28574        editor.newline(&Newline, window, cx);
28575    });
28576    cx.wait_for_autoindent_applied().await;
28577    cx.assert_editor_state(indoc! {"
28578        function test() {
28579            ˇ
28580        }
28581    "});
28582
28583    // test no extra indent after semicolon on same line
28584    cx.set_state(indoc! {"
28585        echo \"test\"28586    "});
28587    cx.update_editor(|editor, window, cx| {
28588        editor.newline(&Newline, window, cx);
28589    });
28590    cx.wait_for_autoindent_applied().await;
28591    cx.assert_editor_state(indoc! {"
28592        echo \"test\";
28593        ˇ
28594    "});
28595}
28596
28597fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
28598    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
28599    point..point
28600}
28601
28602#[track_caller]
28603fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
28604    let (text, ranges) = marked_text_ranges(marked_text, true);
28605    assert_eq!(editor.text(cx), text);
28606    assert_eq!(
28607        editor.selections.ranges(&editor.display_snapshot(cx)),
28608        ranges
28609            .iter()
28610            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
28611            .collect::<Vec<_>>(),
28612        "Assert selections are {}",
28613        marked_text
28614    );
28615}
28616
28617pub fn handle_signature_help_request(
28618    cx: &mut EditorLspTestContext,
28619    mocked_response: lsp::SignatureHelp,
28620) -> impl Future<Output = ()> + use<> {
28621    let mut request =
28622        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
28623            let mocked_response = mocked_response.clone();
28624            async move { Ok(Some(mocked_response)) }
28625        });
28626
28627    async move {
28628        request.next().await;
28629    }
28630}
28631
28632#[track_caller]
28633pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
28634    cx.update_editor(|editor, _, _| {
28635        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
28636            let entries = menu.entries.borrow();
28637            let entries = entries
28638                .iter()
28639                .map(|entry| entry.string.as_str())
28640                .collect::<Vec<_>>();
28641            assert_eq!(entries, expected);
28642        } else {
28643            panic!("Expected completions menu");
28644        }
28645    });
28646}
28647
28648#[gpui::test]
28649async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
28650    init_test(cx, |_| {});
28651    let mut cx = EditorLspTestContext::new_rust(
28652        lsp::ServerCapabilities {
28653            completion_provider: Some(lsp::CompletionOptions {
28654                ..Default::default()
28655            }),
28656            ..Default::default()
28657        },
28658        cx,
28659    )
28660    .await;
28661    cx.lsp
28662        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
28663            Ok(Some(lsp::CompletionResponse::Array(vec![
28664                lsp::CompletionItem {
28665                    label: "unsafe".into(),
28666                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
28667                        range: lsp::Range {
28668                            start: lsp::Position {
28669                                line: 0,
28670                                character: 9,
28671                            },
28672                            end: lsp::Position {
28673                                line: 0,
28674                                character: 11,
28675                            },
28676                        },
28677                        new_text: "unsafe".to_string(),
28678                    })),
28679                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
28680                    ..Default::default()
28681                },
28682            ])))
28683        });
28684
28685    cx.update_editor(|editor, _, cx| {
28686        editor.project().unwrap().update(cx, |project, cx| {
28687            project.snippets().update(cx, |snippets, _cx| {
28688                snippets.add_snippet_for_test(
28689                    None,
28690                    PathBuf::from("test_snippets.json"),
28691                    vec![
28692                        Arc::new(project::snippet_provider::Snippet {
28693                            prefix: vec![
28694                                "unlimited word count".to_string(),
28695                                "unlimit word count".to_string(),
28696                                "unlimited unknown".to_string(),
28697                            ],
28698                            body: "this is many words".to_string(),
28699                            description: Some("description".to_string()),
28700                            name: "multi-word snippet test".to_string(),
28701                        }),
28702                        Arc::new(project::snippet_provider::Snippet {
28703                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
28704                            body: "fewer words".to_string(),
28705                            description: Some("alt description".to_string()),
28706                            name: "other name".to_string(),
28707                        }),
28708                        Arc::new(project::snippet_provider::Snippet {
28709                            prefix: vec!["ab aa".to_string()],
28710                            body: "abcd".to_string(),
28711                            description: None,
28712                            name: "alphabet".to_string(),
28713                        }),
28714                    ],
28715                );
28716            });
28717        })
28718    });
28719
28720    let get_completions = |cx: &mut EditorLspTestContext| {
28721        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
28722            Some(CodeContextMenu::Completions(context_menu)) => {
28723                let entries = context_menu.entries.borrow();
28724                entries
28725                    .iter()
28726                    .map(|entry| entry.string.clone())
28727                    .collect_vec()
28728            }
28729            _ => vec![],
28730        })
28731    };
28732
28733    // snippets:
28734    //  @foo
28735    //  foo bar
28736    //
28737    // when typing:
28738    //
28739    // when typing:
28740    //  - if I type a symbol "open the completions with snippets only"
28741    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
28742    //
28743    // stuff we need:
28744    //  - filtering logic change?
28745    //  - remember how far back the completion started.
28746
28747    let test_cases: &[(&str, &[&str])] = &[
28748        (
28749            "un",
28750            &[
28751                "unsafe",
28752                "unlimit word count",
28753                "unlimited unknown",
28754                "unlimited word count",
28755                "unsnip",
28756            ],
28757        ),
28758        (
28759            "u ",
28760            &[
28761                "unlimit word count",
28762                "unlimited unknown",
28763                "unlimited word count",
28764            ],
28765        ),
28766        ("u a", &["ab aa", "unsafe"]), // unsAfe
28767        (
28768            "u u",
28769            &[
28770                "unsafe",
28771                "unlimit word count",
28772                "unlimited unknown", // ranked highest among snippets
28773                "unlimited word count",
28774                "unsnip",
28775            ],
28776        ),
28777        ("uw c", &["unlimit word count", "unlimited word count"]),
28778        (
28779            "u w",
28780            &[
28781                "unlimit word count",
28782                "unlimited word count",
28783                "unlimited unknown",
28784            ],
28785        ),
28786        ("u w ", &["unlimit word count", "unlimited word count"]),
28787        (
28788            "u ",
28789            &[
28790                "unlimit word count",
28791                "unlimited unknown",
28792                "unlimited word count",
28793            ],
28794        ),
28795        ("wor", &[]),
28796        ("uf", &["unsafe"]),
28797        ("af", &["unsafe"]),
28798        ("afu", &[]),
28799        (
28800            "ue",
28801            &["unsafe", "unlimited unknown", "unlimited word count"],
28802        ),
28803        ("@", &["@few"]),
28804        ("@few", &["@few"]),
28805        ("@ ", &[]),
28806        ("a@", &["@few"]),
28807        ("a@f", &["@few", "unsafe"]),
28808        ("a@fw", &["@few"]),
28809        ("a", &["ab aa", "unsafe"]),
28810        ("aa", &["ab aa"]),
28811        ("aaa", &["ab aa"]),
28812        ("ab", &["ab aa"]),
28813        ("ab ", &["ab aa"]),
28814        ("ab a", &["ab aa", "unsafe"]),
28815        ("ab ab", &["ab aa"]),
28816        ("ab ab aa", &["ab aa"]),
28817    ];
28818
28819    for &(input_to_simulate, expected_completions) in test_cases {
28820        cx.set_state("fn a() { ˇ }\n");
28821        for c in input_to_simulate.split("") {
28822            cx.simulate_input(c);
28823            cx.run_until_parked();
28824        }
28825        let expected_completions = expected_completions
28826            .iter()
28827            .map(|s| s.to_string())
28828            .collect_vec();
28829        assert_eq!(
28830            get_completions(&mut cx),
28831            expected_completions,
28832            "< actual / expected >, input = {input_to_simulate:?}",
28833        );
28834    }
28835}
28836
28837/// Handle completion request passing a marked string specifying where the completion
28838/// should be triggered from using '|' character, what range should be replaced, and what completions
28839/// should be returned using '<' and '>' to delimit the range.
28840///
28841/// Also see `handle_completion_request_with_insert_and_replace`.
28842#[track_caller]
28843pub fn handle_completion_request(
28844    marked_string: &str,
28845    completions: Vec<&'static str>,
28846    is_incomplete: bool,
28847    counter: Arc<AtomicUsize>,
28848    cx: &mut EditorLspTestContext,
28849) -> impl Future<Output = ()> {
28850    let complete_from_marker: TextRangeMarker = '|'.into();
28851    let replace_range_marker: TextRangeMarker = ('<', '>').into();
28852    let (_, mut marked_ranges) = marked_text_ranges_by(
28853        marked_string,
28854        vec![complete_from_marker.clone(), replace_range_marker.clone()],
28855    );
28856
28857    let complete_from_position = cx.to_lsp(MultiBufferOffset(
28858        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
28859    ));
28860    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
28861    let replace_range =
28862        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
28863
28864    let mut request =
28865        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
28866            let completions = completions.clone();
28867            counter.fetch_add(1, atomic::Ordering::Release);
28868            async move {
28869                assert_eq!(params.text_document_position.text_document.uri, url.clone());
28870                assert_eq!(
28871                    params.text_document_position.position,
28872                    complete_from_position
28873                );
28874                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
28875                    is_incomplete,
28876                    item_defaults: None,
28877                    items: completions
28878                        .iter()
28879                        .map(|completion_text| lsp::CompletionItem {
28880                            label: completion_text.to_string(),
28881                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
28882                                range: replace_range,
28883                                new_text: completion_text.to_string(),
28884                            })),
28885                            ..Default::default()
28886                        })
28887                        .collect(),
28888                })))
28889            }
28890        });
28891
28892    async move {
28893        request.next().await;
28894    }
28895}
28896
28897/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
28898/// given instead, which also contains an `insert` range.
28899///
28900/// This function uses markers to define ranges:
28901/// - `|` marks the cursor position
28902/// - `<>` marks the replace range
28903/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
28904pub fn handle_completion_request_with_insert_and_replace(
28905    cx: &mut EditorLspTestContext,
28906    marked_string: &str,
28907    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
28908    counter: Arc<AtomicUsize>,
28909) -> impl Future<Output = ()> {
28910    let complete_from_marker: TextRangeMarker = '|'.into();
28911    let replace_range_marker: TextRangeMarker = ('<', '>').into();
28912    let insert_range_marker: TextRangeMarker = ('{', '}').into();
28913
28914    let (_, mut marked_ranges) = marked_text_ranges_by(
28915        marked_string,
28916        vec![
28917            complete_from_marker.clone(),
28918            replace_range_marker.clone(),
28919            insert_range_marker.clone(),
28920        ],
28921    );
28922
28923    let complete_from_position = cx.to_lsp(MultiBufferOffset(
28924        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
28925    ));
28926    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
28927    let replace_range =
28928        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
28929
28930    let insert_range = match marked_ranges.remove(&insert_range_marker) {
28931        Some(ranges) if !ranges.is_empty() => {
28932            let range1 = ranges[0].clone();
28933            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
28934        }
28935        _ => lsp::Range {
28936            start: replace_range.start,
28937            end: complete_from_position,
28938        },
28939    };
28940
28941    let mut request =
28942        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
28943            let completions = completions.clone();
28944            counter.fetch_add(1, atomic::Ordering::Release);
28945            async move {
28946                assert_eq!(params.text_document_position.text_document.uri, url.clone());
28947                assert_eq!(
28948                    params.text_document_position.position, complete_from_position,
28949                    "marker `|` position doesn't match",
28950                );
28951                Ok(Some(lsp::CompletionResponse::Array(
28952                    completions
28953                        .iter()
28954                        .map(|(label, new_text)| lsp::CompletionItem {
28955                            label: label.to_string(),
28956                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
28957                                lsp::InsertReplaceEdit {
28958                                    insert: insert_range,
28959                                    replace: replace_range,
28960                                    new_text: new_text.to_string(),
28961                                },
28962                            )),
28963                            ..Default::default()
28964                        })
28965                        .collect(),
28966                )))
28967            }
28968        });
28969
28970    async move {
28971        request.next().await;
28972    }
28973}
28974
28975fn handle_resolve_completion_request(
28976    cx: &mut EditorLspTestContext,
28977    edits: Option<Vec<(&'static str, &'static str)>>,
28978) -> impl Future<Output = ()> {
28979    let edits = edits.map(|edits| {
28980        edits
28981            .iter()
28982            .map(|(marked_string, new_text)| {
28983                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
28984                let replace_range = cx.to_lsp_range(
28985                    MultiBufferOffset(marked_ranges[0].start)
28986                        ..MultiBufferOffset(marked_ranges[0].end),
28987                );
28988                lsp::TextEdit::new(replace_range, new_text.to_string())
28989            })
28990            .collect::<Vec<_>>()
28991    });
28992
28993    let mut request =
28994        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
28995            let edits = edits.clone();
28996            async move {
28997                Ok(lsp::CompletionItem {
28998                    additional_text_edits: edits,
28999                    ..Default::default()
29000                })
29001            }
29002        });
29003
29004    async move {
29005        request.next().await;
29006    }
29007}
29008
29009pub(crate) fn update_test_language_settings(
29010    cx: &mut TestAppContext,
29011    f: &dyn Fn(&mut AllLanguageSettingsContent),
29012) {
29013    cx.update(|cx| {
29014        SettingsStore::update_global(cx, |store, cx| {
29015            store.update_user_settings(cx, &|settings: &mut SettingsContent| {
29016                f(&mut settings.project.all_languages)
29017            });
29018        });
29019    });
29020}
29021
29022pub(crate) fn update_test_project_settings(
29023    cx: &mut TestAppContext,
29024    f: &dyn Fn(&mut ProjectSettingsContent),
29025) {
29026    cx.update(|cx| {
29027        SettingsStore::update_global(cx, |store, cx| {
29028            store.update_user_settings(cx, |settings| f(&mut settings.project));
29029        });
29030    });
29031}
29032
29033pub(crate) fn update_test_editor_settings(
29034    cx: &mut TestAppContext,
29035    f: &dyn Fn(&mut EditorSettingsContent),
29036) {
29037    cx.update(|cx| {
29038        SettingsStore::update_global(cx, |store, cx| {
29039            store.update_user_settings(cx, |settings| f(&mut settings.editor));
29040        })
29041    })
29042}
29043
29044pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
29045    cx.update(|cx| {
29046        assets::Assets.load_test_fonts(cx);
29047        let store = SettingsStore::test(cx);
29048        cx.set_global(store);
29049        theme::init(theme::LoadThemes::JustBase, cx);
29050        release_channel::init(semver::Version::new(0, 0, 0), cx);
29051        crate::init(cx);
29052    });
29053    zlog::init_test();
29054    update_test_language_settings(cx, &f);
29055}
29056
29057#[track_caller]
29058fn assert_hunk_revert(
29059    not_reverted_text_with_selections: &str,
29060    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
29061    expected_reverted_text_with_selections: &str,
29062    base_text: &str,
29063    cx: &mut EditorLspTestContext,
29064) {
29065    cx.set_state(not_reverted_text_with_selections);
29066    cx.set_head_text(base_text);
29067    cx.executor().run_until_parked();
29068
29069    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
29070        let snapshot = editor.snapshot(window, cx);
29071        let reverted_hunk_statuses = snapshot
29072            .buffer_snapshot()
29073            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
29074            .map(|hunk| hunk.status().kind)
29075            .collect::<Vec<_>>();
29076
29077        editor.git_restore(&Default::default(), window, cx);
29078        reverted_hunk_statuses
29079    });
29080    cx.executor().run_until_parked();
29081    cx.assert_editor_state(expected_reverted_text_with_selections);
29082    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
29083}
29084
29085#[gpui::test(iterations = 10)]
29086async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
29087    init_test(cx, |_| {});
29088
29089    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
29090    let counter = diagnostic_requests.clone();
29091
29092    let fs = FakeFs::new(cx.executor());
29093    fs.insert_tree(
29094        path!("/a"),
29095        json!({
29096            "first.rs": "fn main() { let a = 5; }",
29097            "second.rs": "// Test file",
29098        }),
29099    )
29100    .await;
29101
29102    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
29103    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
29104    let workspace = window
29105        .read_with(cx, |mw, _| mw.workspace().clone())
29106        .unwrap();
29107    let cx = &mut VisualTestContext::from_window(*window, cx);
29108
29109    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
29110    language_registry.add(rust_lang());
29111    let mut fake_servers = language_registry.register_fake_lsp(
29112        "Rust",
29113        FakeLspAdapter {
29114            capabilities: lsp::ServerCapabilities {
29115                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
29116                    lsp::DiagnosticOptions {
29117                        identifier: None,
29118                        inter_file_dependencies: true,
29119                        workspace_diagnostics: true,
29120                        work_done_progress_options: Default::default(),
29121                    },
29122                )),
29123                ..Default::default()
29124            },
29125            ..Default::default()
29126        },
29127    );
29128
29129    let editor = workspace
29130        .update_in(cx, |workspace, window, cx| {
29131            workspace.open_abs_path(
29132                PathBuf::from(path!("/a/first.rs")),
29133                OpenOptions::default(),
29134                window,
29135                cx,
29136            )
29137        })
29138        .await
29139        .unwrap()
29140        .downcast::<Editor>()
29141        .unwrap();
29142    let fake_server = fake_servers.next().await.unwrap();
29143    let server_id = fake_server.server.server_id();
29144    let mut first_request = fake_server
29145        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
29146            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
29147            let result_id = Some(new_result_id.to_string());
29148            assert_eq!(
29149                params.text_document.uri,
29150                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
29151            );
29152            async move {
29153                Ok(lsp::DocumentDiagnosticReportResult::Report(
29154                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
29155                        related_documents: None,
29156                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
29157                            items: Vec::new(),
29158                            result_id,
29159                        },
29160                    }),
29161                ))
29162            }
29163        });
29164
29165    let ensure_result_id = |expected_result_id: Option<SharedString>, cx: &mut TestAppContext| {
29166        project.update(cx, |project, cx| {
29167            let buffer_id = editor
29168                .read(cx)
29169                .buffer()
29170                .read(cx)
29171                .as_singleton()
29172                .expect("created a singleton buffer")
29173                .read(cx)
29174                .remote_id();
29175            let buffer_result_id = project
29176                .lsp_store()
29177                .read(cx)
29178                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
29179            assert_eq!(expected_result_id, buffer_result_id);
29180        });
29181    };
29182
29183    ensure_result_id(None, cx);
29184    cx.executor().advance_clock(Duration::from_millis(60));
29185    cx.executor().run_until_parked();
29186    assert_eq!(
29187        diagnostic_requests.load(atomic::Ordering::Acquire),
29188        1,
29189        "Opening file should trigger diagnostic request"
29190    );
29191    first_request
29192        .next()
29193        .await
29194        .expect("should have sent the first diagnostics pull request");
29195    ensure_result_id(Some(SharedString::new_static("1")), cx);
29196
29197    // Editing should trigger diagnostics
29198    editor.update_in(cx, |editor, window, cx| {
29199        editor.handle_input("2", window, cx)
29200    });
29201    cx.executor().advance_clock(Duration::from_millis(60));
29202    cx.executor().run_until_parked();
29203    assert_eq!(
29204        diagnostic_requests.load(atomic::Ordering::Acquire),
29205        2,
29206        "Editing should trigger diagnostic request"
29207    );
29208    ensure_result_id(Some(SharedString::new_static("2")), cx);
29209
29210    // Moving cursor should not trigger diagnostic request
29211    editor.update_in(cx, |editor, window, cx| {
29212        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
29213            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
29214        });
29215    });
29216    cx.executor().advance_clock(Duration::from_millis(60));
29217    cx.executor().run_until_parked();
29218    assert_eq!(
29219        diagnostic_requests.load(atomic::Ordering::Acquire),
29220        2,
29221        "Cursor movement should not trigger diagnostic request"
29222    );
29223    ensure_result_id(Some(SharedString::new_static("2")), cx);
29224    // Multiple rapid edits should be debounced
29225    for _ in 0..5 {
29226        editor.update_in(cx, |editor, window, cx| {
29227            editor.handle_input("x", window, cx)
29228        });
29229    }
29230    cx.executor().advance_clock(Duration::from_millis(60));
29231    cx.executor().run_until_parked();
29232
29233    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
29234    assert!(
29235        final_requests <= 4,
29236        "Multiple rapid edits should be debounced (got {final_requests} requests)",
29237    );
29238    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
29239}
29240
29241#[gpui::test]
29242async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
29243    // Regression test for issue #11671
29244    // Previously, adding a cursor after moving multiple cursors would reset
29245    // the cursor count instead of adding to the existing cursors.
29246    init_test(cx, |_| {});
29247    let mut cx = EditorTestContext::new(cx).await;
29248
29249    // Create a simple buffer with cursor at start
29250    cx.set_state(indoc! {"
29251        ˇaaaa
29252        bbbb
29253        cccc
29254        dddd
29255        eeee
29256        ffff
29257        gggg
29258        hhhh"});
29259
29260    // Add 2 cursors below (so we have 3 total)
29261    cx.update_editor(|editor, window, cx| {
29262        editor.add_selection_below(&Default::default(), window, cx);
29263        editor.add_selection_below(&Default::default(), window, cx);
29264    });
29265
29266    // Verify we have 3 cursors
29267    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
29268    assert_eq!(
29269        initial_count, 3,
29270        "Should have 3 cursors after adding 2 below"
29271    );
29272
29273    // Move down one line
29274    cx.update_editor(|editor, window, cx| {
29275        editor.move_down(&MoveDown, window, cx);
29276    });
29277
29278    // Add another cursor below
29279    cx.update_editor(|editor, window, cx| {
29280        editor.add_selection_below(&Default::default(), window, cx);
29281    });
29282
29283    // Should now have 4 cursors (3 original + 1 new)
29284    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
29285    assert_eq!(
29286        final_count, 4,
29287        "Should have 4 cursors after moving and adding another"
29288    );
29289}
29290
29291#[gpui::test]
29292async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
29293    init_test(cx, |_| {});
29294
29295    let mut cx = EditorTestContext::new(cx).await;
29296
29297    cx.set_state(indoc!(
29298        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
29299           Second line here"#
29300    ));
29301
29302    cx.update_editor(|editor, window, cx| {
29303        // Enable soft wrapping with a narrow width to force soft wrapping and
29304        // confirm that more than 2 rows are being displayed.
29305        editor.set_wrap_width(Some(100.0.into()), cx);
29306        assert!(editor.display_text(cx).lines().count() > 2);
29307
29308        editor.add_selection_below(
29309            &AddSelectionBelow {
29310                skip_soft_wrap: true,
29311            },
29312            window,
29313            cx,
29314        );
29315
29316        assert_eq!(
29317            display_ranges(editor, cx),
29318            &[
29319                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
29320                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
29321            ]
29322        );
29323
29324        editor.add_selection_above(
29325            &AddSelectionAbove {
29326                skip_soft_wrap: true,
29327            },
29328            window,
29329            cx,
29330        );
29331
29332        assert_eq!(
29333            display_ranges(editor, cx),
29334            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
29335        );
29336
29337        editor.add_selection_below(
29338            &AddSelectionBelow {
29339                skip_soft_wrap: false,
29340            },
29341            window,
29342            cx,
29343        );
29344
29345        assert_eq!(
29346            display_ranges(editor, cx),
29347            &[
29348                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
29349                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
29350            ]
29351        );
29352
29353        editor.add_selection_above(
29354            &AddSelectionAbove {
29355                skip_soft_wrap: false,
29356            },
29357            window,
29358            cx,
29359        );
29360
29361        assert_eq!(
29362            display_ranges(editor, cx),
29363            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
29364        );
29365    });
29366
29367    // Set up text where selections are in the middle of a soft-wrapped line.
29368    // When adding selection below with `skip_soft_wrap` set to `true`, the new
29369    // selection should be at the same buffer column, not the same pixel
29370    // position.
29371    cx.set_state(indoc!(
29372        r#"1. Very long line to show «howˇ» a wrapped line would look
29373           2. Very long line to show how a wrapped line would look"#
29374    ));
29375
29376    cx.update_editor(|editor, window, cx| {
29377        // Enable soft wrapping with a narrow width to force soft wrapping and
29378        // confirm that more than 2 rows are being displayed.
29379        editor.set_wrap_width(Some(100.0.into()), cx);
29380        assert!(editor.display_text(cx).lines().count() > 2);
29381
29382        editor.add_selection_below(
29383            &AddSelectionBelow {
29384                skip_soft_wrap: true,
29385            },
29386            window,
29387            cx,
29388        );
29389
29390        // Assert that there's now 2 selections, both selecting the same column
29391        // range in the buffer row.
29392        let display_map = editor.display_map.update(cx, |map, cx| map.snapshot(cx));
29393        let selections = editor.selections.all::<Point>(&display_map);
29394        assert_eq!(selections.len(), 2);
29395        assert_eq!(selections[0].start.column, selections[1].start.column);
29396        assert_eq!(selections[0].end.column, selections[1].end.column);
29397    });
29398}
29399
29400#[gpui::test]
29401async fn test_insert_snippet(cx: &mut TestAppContext) {
29402    init_test(cx, |_| {});
29403    let mut cx = EditorTestContext::new(cx).await;
29404
29405    cx.update_editor(|editor, _, cx| {
29406        editor.project().unwrap().update(cx, |project, cx| {
29407            project.snippets().update(cx, |snippets, _cx| {
29408                let snippet = project::snippet_provider::Snippet {
29409                    prefix: vec![], // no prefix needed!
29410                    body: "an Unspecified".to_string(),
29411                    description: Some("shhhh it's a secret".to_string()),
29412                    name: "super secret snippet".to_string(),
29413                };
29414                snippets.add_snippet_for_test(
29415                    None,
29416                    PathBuf::from("test_snippets.json"),
29417                    vec![Arc::new(snippet)],
29418                );
29419
29420                let snippet = project::snippet_provider::Snippet {
29421                    prefix: vec![], // no prefix needed!
29422                    body: " Location".to_string(),
29423                    description: Some("the word 'location'".to_string()),
29424                    name: "location word".to_string(),
29425                };
29426                snippets.add_snippet_for_test(
29427                    Some("Markdown".to_string()),
29428                    PathBuf::from("test_snippets.json"),
29429                    vec![Arc::new(snippet)],
29430                );
29431            });
29432        })
29433    });
29434
29435    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
29436
29437    cx.update_editor(|editor, window, cx| {
29438        editor.insert_snippet_at_selections(
29439            &InsertSnippet {
29440                language: None,
29441                name: Some("super secret snippet".to_string()),
29442                snippet: None,
29443            },
29444            window,
29445            cx,
29446        );
29447
29448        // Language is specified in the action,
29449        // so the buffer language does not need to match
29450        editor.insert_snippet_at_selections(
29451            &InsertSnippet {
29452                language: Some("Markdown".to_string()),
29453                name: Some("location word".to_string()),
29454                snippet: None,
29455            },
29456            window,
29457            cx,
29458        );
29459
29460        editor.insert_snippet_at_selections(
29461            &InsertSnippet {
29462                language: None,
29463                name: None,
29464                snippet: Some("$0 after".to_string()),
29465            },
29466            window,
29467            cx,
29468        );
29469    });
29470
29471    cx.assert_editor_state(
29472        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
29473    );
29474}
29475
29476#[gpui::test]
29477async fn test_inlay_hints_request_timeout(cx: &mut TestAppContext) {
29478    use crate::inlays::inlay_hints::InlayHintRefreshReason;
29479    use crate::inlays::inlay_hints::tests::{cached_hint_labels, init_test, visible_hint_labels};
29480    use settings::InlayHintSettingsContent;
29481    use std::sync::atomic::AtomicU32;
29482    use std::time::Duration;
29483
29484    const BASE_TIMEOUT_SECS: u64 = 1;
29485
29486    let request_count = Arc::new(AtomicU32::new(0));
29487    let closure_request_count = request_count.clone();
29488
29489    init_test(cx, &|settings| {
29490        settings.defaults.inlay_hints = Some(InlayHintSettingsContent {
29491            enabled: Some(true),
29492            ..InlayHintSettingsContent::default()
29493        })
29494    });
29495    cx.update(|cx| {
29496        SettingsStore::update_global(cx, |store, cx| {
29497            store.update_user_settings(cx, &|settings: &mut SettingsContent| {
29498                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
29499                    request_timeout: Some(BASE_TIMEOUT_SECS),
29500                    button: Some(true),
29501                    notifications: None,
29502                    semantic_token_rules: None,
29503                });
29504            });
29505        });
29506    });
29507
29508    let fs = FakeFs::new(cx.executor());
29509    fs.insert_tree(
29510        path!("/a"),
29511        json!({
29512            "main.rs": "fn main() { let a = 5; }",
29513        }),
29514    )
29515    .await;
29516
29517    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
29518    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
29519    language_registry.add(rust_lang());
29520    let mut fake_servers = language_registry.register_fake_lsp(
29521        "Rust",
29522        FakeLspAdapter {
29523            capabilities: lsp::ServerCapabilities {
29524                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
29525                ..lsp::ServerCapabilities::default()
29526            },
29527            initializer: Some(Box::new(move |fake_server| {
29528                let request_count = closure_request_count.clone();
29529                fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
29530                    move |params, cx| {
29531                        let request_count = request_count.clone();
29532                        async move {
29533                            cx.background_executor()
29534                                .timer(Duration::from_secs(BASE_TIMEOUT_SECS * 2))
29535                                .await;
29536                            let count = request_count.fetch_add(1, atomic::Ordering::Release) + 1;
29537                            assert_eq!(
29538                                params.text_document.uri,
29539                                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
29540                            );
29541                            Ok(Some(vec![lsp::InlayHint {
29542                                position: lsp::Position::new(0, 1),
29543                                label: lsp::InlayHintLabel::String(count.to_string()),
29544                                kind: None,
29545                                text_edits: None,
29546                                tooltip: None,
29547                                padding_left: None,
29548                                padding_right: None,
29549                                data: None,
29550                            }]))
29551                        }
29552                    },
29553                );
29554            })),
29555            ..FakeLspAdapter::default()
29556        },
29557    );
29558
29559    let buffer = project
29560        .update(cx, |project, cx| {
29561            project.open_local_buffer(path!("/a/main.rs"), cx)
29562        })
29563        .await
29564        .unwrap();
29565    let editor = cx.add_window(|window, cx| Editor::for_buffer(buffer, Some(project), window, cx));
29566
29567    cx.executor().run_until_parked();
29568    let fake_server = fake_servers.next().await.unwrap();
29569
29570    cx.executor()
29571        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
29572    cx.executor().run_until_parked();
29573    editor
29574        .update(cx, |editor, _window, cx| {
29575            assert!(
29576                cached_hint_labels(editor, cx).is_empty(),
29577                "First request should time out, no hints cached"
29578            );
29579        })
29580        .unwrap();
29581
29582    editor
29583        .update(cx, |editor, _window, cx| {
29584            editor.refresh_inlay_hints(
29585                InlayHintRefreshReason::RefreshRequested {
29586                    server_id: fake_server.server.server_id(),
29587                    request_id: Some(1),
29588                },
29589                cx,
29590            );
29591        })
29592        .unwrap();
29593    cx.executor()
29594        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS) + Duration::from_millis(100));
29595    cx.executor().run_until_parked();
29596    editor
29597        .update(cx, |editor, _window, cx| {
29598            assert!(
29599                cached_hint_labels(editor, cx).is_empty(),
29600                "Second request should also time out with BASE_TIMEOUT, no hints cached"
29601            );
29602        })
29603        .unwrap();
29604
29605    cx.update(|cx| {
29606        SettingsStore::update_global(cx, |store, cx| {
29607            store.update_user_settings(cx, |settings| {
29608                settings.global_lsp_settings = Some(GlobalLspSettingsContent {
29609                    request_timeout: Some(BASE_TIMEOUT_SECS * 4),
29610                    button: Some(true),
29611                    notifications: None,
29612                    semantic_token_rules: None,
29613                });
29614            });
29615        });
29616    });
29617    editor
29618        .update(cx, |editor, _window, cx| {
29619            editor.refresh_inlay_hints(
29620                InlayHintRefreshReason::RefreshRequested {
29621                    server_id: fake_server.server.server_id(),
29622                    request_id: Some(2),
29623                },
29624                cx,
29625            );
29626        })
29627        .unwrap();
29628    cx.executor()
29629        .advance_clock(Duration::from_secs(BASE_TIMEOUT_SECS * 4) + Duration::from_millis(100));
29630    cx.executor().run_until_parked();
29631    editor
29632        .update(cx, |editor, _window, cx| {
29633            assert_eq!(
29634                vec!["1".to_string()],
29635                cached_hint_labels(editor, cx),
29636                "With extended timeout (BASE * 4), hints should arrive successfully"
29637            );
29638            assert_eq!(vec!["1".to_string()], visible_hint_labels(editor, cx));
29639        })
29640        .unwrap();
29641}
29642
29643#[gpui::test]
29644async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
29645    init_test(cx, |_| {});
29646    let (editor, cx) = cx.add_window_view(Editor::single_line);
29647    editor.update_in(cx, |editor, window, cx| {
29648        editor.set_text("oops\n\nwow\n", window, cx)
29649    });
29650    cx.run_until_parked();
29651    editor.update(cx, |editor, cx| {
29652        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
29653    });
29654    editor.update(cx, |editor, cx| {
29655        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
29656    });
29657    cx.run_until_parked();
29658    editor.update(cx, |editor, cx| {
29659        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
29660    });
29661}
29662
29663#[gpui::test]
29664async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
29665    init_test(cx, |_| {});
29666
29667    cx.update(|cx| {
29668        register_project_item::<Editor>(cx);
29669    });
29670
29671    let fs = FakeFs::new(cx.executor());
29672    fs.insert_tree("/root1", json!({})).await;
29673    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
29674        .await;
29675
29676    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
29677    let (multi_workspace, cx) =
29678        cx.add_window_view(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
29679    let workspace = multi_workspace.read_with(cx, |mw, _| mw.workspace().clone());
29680
29681    let worktree_id = project.update(cx, |project, cx| {
29682        project.worktrees(cx).next().unwrap().read(cx).id()
29683    });
29684
29685    let handle = workspace
29686        .update_in(cx, |workspace, window, cx| {
29687            let project_path = (worktree_id, rel_path("one.pdf"));
29688            workspace.open_path(project_path, None, true, window, cx)
29689        })
29690        .await
29691        .unwrap();
29692    // The test file content `vec![0xff, 0xfe, ...]` starts with a UTF-16 LE BOM.
29693    // Previously, this fell back to `InvalidItemView` because it wasn't valid UTF-8.
29694    // With auto-detection enabled, this is now recognized as UTF-16 and opens in the Editor.
29695    assert_eq!(handle.to_any_view().entity_type(), TypeId::of::<Editor>());
29696}
29697
29698#[gpui::test]
29699async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
29700    init_test(cx, |_| {});
29701
29702    let language = Arc::new(Language::new(
29703        LanguageConfig::default(),
29704        Some(tree_sitter_rust::LANGUAGE.into()),
29705    ));
29706
29707    // Test hierarchical sibling navigation
29708    let text = r#"
29709        fn outer() {
29710            if condition {
29711                let a = 1;
29712            }
29713            let b = 2;
29714        }
29715
29716        fn another() {
29717            let c = 3;
29718        }
29719    "#;
29720
29721    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
29722    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
29723    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
29724
29725    // Wait for parsing to complete
29726    editor
29727        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
29728        .await;
29729
29730    editor.update_in(cx, |editor, window, cx| {
29731        // Start by selecting "let a = 1;" inside the if block
29732        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
29733            s.select_display_ranges([
29734                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
29735            ]);
29736        });
29737
29738        let initial_selection = editor
29739            .selections
29740            .display_ranges(&editor.display_snapshot(cx));
29741        assert_eq!(initial_selection.len(), 1, "Should have one selection");
29742
29743        // Test select next sibling - should move up levels to find the next sibling
29744        // Since "let a = 1;" has no siblings in the if block, it should move up
29745        // to find "let b = 2;" which is a sibling of the if block
29746        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
29747        let next_selection = editor
29748            .selections
29749            .display_ranges(&editor.display_snapshot(cx));
29750
29751        // Should have a selection and it should be different from the initial
29752        assert_eq!(
29753            next_selection.len(),
29754            1,
29755            "Should have one selection after next"
29756        );
29757        assert_ne!(
29758            next_selection[0], initial_selection[0],
29759            "Next sibling selection should be different"
29760        );
29761
29762        // Test hierarchical navigation by going to the end of the current function
29763        // and trying to navigate to the next function
29764        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
29765            s.select_display_ranges([
29766                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
29767            ]);
29768        });
29769
29770        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
29771        let function_next_selection = editor
29772            .selections
29773            .display_ranges(&editor.display_snapshot(cx));
29774
29775        // Should move to the next function
29776        assert_eq!(
29777            function_next_selection.len(),
29778            1,
29779            "Should have one selection after function next"
29780        );
29781
29782        // Test select previous sibling navigation
29783        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
29784        let prev_selection = editor
29785            .selections
29786            .display_ranges(&editor.display_snapshot(cx));
29787
29788        // Should have a selection and it should be different
29789        assert_eq!(
29790            prev_selection.len(),
29791            1,
29792            "Should have one selection after prev"
29793        );
29794        assert_ne!(
29795            prev_selection[0], function_next_selection[0],
29796            "Previous sibling selection should be different from next"
29797        );
29798    });
29799}
29800
29801#[gpui::test]
29802async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
29803    init_test(cx, |_| {});
29804
29805    let mut cx = EditorTestContext::new(cx).await;
29806    cx.set_state(
29807        "let ˇvariable = 42;
29808let another = variable + 1;
29809let result = variable * 2;",
29810    );
29811
29812    // Set up document highlights manually (simulating LSP response)
29813    cx.update_editor(|editor, _window, cx| {
29814        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
29815
29816        // Create highlights for "variable" occurrences
29817        let highlight_ranges = [
29818            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
29819            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
29820            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
29821        ];
29822
29823        let anchor_ranges: Vec<_> = highlight_ranges
29824            .iter()
29825            .map(|range| range.clone().to_anchors(&buffer_snapshot))
29826            .collect();
29827
29828        editor.highlight_background(
29829            HighlightKey::DocumentHighlightRead,
29830            &anchor_ranges,
29831            |_, theme| theme.colors().editor_document_highlight_read_background,
29832            cx,
29833        );
29834    });
29835
29836    // Go to next highlight - should move to second "variable"
29837    cx.update_editor(|editor, window, cx| {
29838        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
29839    });
29840    cx.assert_editor_state(
29841        "let variable = 42;
29842let another = ˇvariable + 1;
29843let result = variable * 2;",
29844    );
29845
29846    // Go to next highlight - should move to third "variable"
29847    cx.update_editor(|editor, window, cx| {
29848        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
29849    });
29850    cx.assert_editor_state(
29851        "let variable = 42;
29852let another = variable + 1;
29853let result = ˇvariable * 2;",
29854    );
29855
29856    // Go to next highlight - should stay at third "variable" (no wrap-around)
29857    cx.update_editor(|editor, window, cx| {
29858        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
29859    });
29860    cx.assert_editor_state(
29861        "let variable = 42;
29862let another = variable + 1;
29863let result = ˇvariable * 2;",
29864    );
29865
29866    // Now test going backwards from third position
29867    cx.update_editor(|editor, window, cx| {
29868        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
29869    });
29870    cx.assert_editor_state(
29871        "let variable = 42;
29872let another = ˇvariable + 1;
29873let result = variable * 2;",
29874    );
29875
29876    // Go to previous highlight - should move to first "variable"
29877    cx.update_editor(|editor, window, cx| {
29878        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
29879    });
29880    cx.assert_editor_state(
29881        "let ˇvariable = 42;
29882let another = variable + 1;
29883let result = variable * 2;",
29884    );
29885
29886    // Go to previous highlight - should stay on first "variable"
29887    cx.update_editor(|editor, window, cx| {
29888        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
29889    });
29890    cx.assert_editor_state(
29891        "let ˇvariable = 42;
29892let another = variable + 1;
29893let result = variable * 2;",
29894    );
29895}
29896
29897#[gpui::test]
29898async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
29899    cx: &mut gpui::TestAppContext,
29900) {
29901    init_test(cx, |_| {});
29902
29903    let url = "https://zed.dev";
29904
29905    let markdown_language = Arc::new(Language::new(
29906        LanguageConfig {
29907            name: "Markdown".into(),
29908            ..LanguageConfig::default()
29909        },
29910        None,
29911    ));
29912
29913    let mut cx = EditorTestContext::new(cx).await;
29914    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29915    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
29916
29917    cx.update_editor(|editor, window, cx| {
29918        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
29919        editor.paste(&Paste, window, cx);
29920    });
29921
29922    cx.assert_editor_state(&format!(
29923        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
29924    ));
29925}
29926
29927#[gpui::test]
29928async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
29929    init_test(cx, |_| {});
29930
29931    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
29932    let mut cx = EditorTestContext::new(cx).await;
29933
29934    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
29935
29936    // Case 1: Test if adding a character with multi cursors preserves nested list indents
29937    cx.set_state(&indoc! {"
29938        - [ ] Item 1
29939            - [ ] Item 1.a
29940        - [ˇ] Item 2
29941            - [ˇ] Item 2.a
29942            - [ˇ] Item 2.b
29943        "
29944    });
29945    cx.update_editor(|editor, window, cx| {
29946        editor.handle_input("x", window, cx);
29947    });
29948    cx.run_until_parked();
29949    cx.assert_editor_state(indoc! {"
29950        - [ ] Item 1
29951            - [ ] Item 1.a
29952        - [xˇ] Item 2
29953            - [xˇ] Item 2.a
29954            - [xˇ] Item 2.b
29955        "
29956    });
29957
29958    // Case 2: Test adding new line after nested list continues the list with unchecked task
29959    cx.set_state(&indoc! {"
29960        - [ ] Item 1
29961            - [ ] Item 1.a
29962        - [x] Item 2
29963            - [x] Item 2.a
29964            - [x] Item 2.bˇ"
29965    });
29966    cx.update_editor(|editor, window, cx| {
29967        editor.newline(&Newline, window, cx);
29968    });
29969    cx.assert_editor_state(indoc! {"
29970        - [ ] Item 1
29971            - [ ] Item 1.a
29972        - [x] Item 2
29973            - [x] Item 2.a
29974            - [x] Item 2.b
29975            - [ ] ˇ"
29976    });
29977
29978    // Case 3: Test adding content to continued list item
29979    cx.update_editor(|editor, window, cx| {
29980        editor.handle_input("Item 2.c", window, cx);
29981    });
29982    cx.run_until_parked();
29983    cx.assert_editor_state(indoc! {"
29984        - [ ] Item 1
29985            - [ ] Item 1.a
29986        - [x] Item 2
29987            - [x] Item 2.a
29988            - [x] Item 2.b
29989            - [ ] Item 2.cˇ"
29990    });
29991
29992    // Case 4: Test adding new line after nested ordered list continues with next number
29993    cx.set_state(indoc! {"
29994        1. Item 1
29995            1. Item 1.a
29996        2. Item 2
29997            1. Item 2.a
29998            2. Item 2.bˇ"
29999    });
30000    cx.update_editor(|editor, window, cx| {
30001        editor.newline(&Newline, window, cx);
30002    });
30003    cx.assert_editor_state(indoc! {"
30004        1. Item 1
30005            1. Item 1.a
30006        2. Item 2
30007            1. Item 2.a
30008            2. Item 2.b
30009            3. ˇ"
30010    });
30011
30012    // Case 5: Adding content to continued ordered list item
30013    cx.update_editor(|editor, window, cx| {
30014        editor.handle_input("Item 2.c", window, cx);
30015    });
30016    cx.run_until_parked();
30017    cx.assert_editor_state(indoc! {"
30018        1. Item 1
30019            1. Item 1.a
30020        2. Item 2
30021            1. Item 2.a
30022            2. Item 2.b
30023            3. Item 2.cˇ"
30024    });
30025
30026    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
30027    cx.set_state(indoc! {"
30028        - Item 1
30029            - Item 1.a
30030            - Item 1.a
30031        ˇ"});
30032    cx.update_editor(|editor, window, cx| {
30033        editor.handle_input("-", window, cx);
30034    });
30035    cx.run_until_parked();
30036    cx.assert_editor_state(indoc! {"
30037        - Item 1
30038            - Item 1.a
30039            - Item 1.a
30040"});
30041
30042    // Case 7: Test blockquote newline preserves something
30043    cx.set_state(indoc! {"
30044        > Item 1ˇ"
30045    });
30046    cx.update_editor(|editor, window, cx| {
30047        editor.newline(&Newline, window, cx);
30048    });
30049    cx.assert_editor_state(indoc! {"
30050        > Item 1
30051        ˇ"
30052    });
30053}
30054
30055#[gpui::test]
30056async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
30057    cx: &mut gpui::TestAppContext,
30058) {
30059    init_test(cx, |_| {});
30060
30061    let url = "https://zed.dev";
30062
30063    let markdown_language = Arc::new(Language::new(
30064        LanguageConfig {
30065            name: "Markdown".into(),
30066            ..LanguageConfig::default()
30067        },
30068        None,
30069    ));
30070
30071    let mut cx = EditorTestContext::new(cx).await;
30072    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30073    cx.set_state(&format!(
30074        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
30075    ));
30076
30077    cx.update_editor(|editor, window, cx| {
30078        editor.copy(&Copy, window, cx);
30079    });
30080
30081    cx.set_state(&format!(
30082        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
30083    ));
30084
30085    cx.update_editor(|editor, window, cx| {
30086        editor.paste(&Paste, window, cx);
30087    });
30088
30089    cx.assert_editor_state(&format!(
30090        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
30091    ));
30092}
30093
30094#[gpui::test]
30095async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
30096    cx: &mut gpui::TestAppContext,
30097) {
30098    init_test(cx, |_| {});
30099
30100    let url = "https://zed.dev";
30101
30102    let markdown_language = Arc::new(Language::new(
30103        LanguageConfig {
30104            name: "Markdown".into(),
30105            ..LanguageConfig::default()
30106        },
30107        None,
30108    ));
30109
30110    let mut cx = EditorTestContext::new(cx).await;
30111    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30112    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
30113
30114    cx.update_editor(|editor, window, cx| {
30115        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
30116        editor.paste(&Paste, window, cx);
30117    });
30118
30119    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
30120}
30121
30122#[gpui::test]
30123async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
30124    cx: &mut gpui::TestAppContext,
30125) {
30126    init_test(cx, |_| {});
30127
30128    let text = "Awesome";
30129
30130    let markdown_language = Arc::new(Language::new(
30131        LanguageConfig {
30132            name: "Markdown".into(),
30133            ..LanguageConfig::default()
30134        },
30135        None,
30136    ));
30137
30138    let mut cx = EditorTestContext::new(cx).await;
30139    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30140    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
30141
30142    cx.update_editor(|editor, window, cx| {
30143        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
30144        editor.paste(&Paste, window, cx);
30145    });
30146
30147    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
30148}
30149
30150#[gpui::test]
30151async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
30152    cx: &mut gpui::TestAppContext,
30153) {
30154    init_test(cx, |_| {});
30155
30156    let url = "https://zed.dev";
30157
30158    let markdown_language = Arc::new(Language::new(
30159        LanguageConfig {
30160            name: "Rust".into(),
30161            ..LanguageConfig::default()
30162        },
30163        None,
30164    ));
30165
30166    let mut cx = EditorTestContext::new(cx).await;
30167    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
30168    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
30169
30170    cx.update_editor(|editor, window, cx| {
30171        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
30172        editor.paste(&Paste, window, cx);
30173    });
30174
30175    cx.assert_editor_state(&format!(
30176        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
30177    ));
30178}
30179
30180#[gpui::test]
30181async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
30182    cx: &mut TestAppContext,
30183) {
30184    init_test(cx, |_| {});
30185
30186    let url = "https://zed.dev";
30187
30188    let markdown_language = Arc::new(Language::new(
30189        LanguageConfig {
30190            name: "Markdown".into(),
30191            ..LanguageConfig::default()
30192        },
30193        None,
30194    ));
30195
30196    let (editor, cx) = cx.add_window_view(|window, cx| {
30197        let multi_buffer = MultiBuffer::build_multi(
30198            [
30199                ("this will embed -> link", vec![Point::row_range(0..1)]),
30200                ("this will replace -> link", vec![Point::row_range(0..1)]),
30201            ],
30202            cx,
30203        );
30204        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
30205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30206            s.select_ranges(vec![
30207                Point::new(0, 19)..Point::new(0, 23),
30208                Point::new(1, 21)..Point::new(1, 25),
30209            ])
30210        });
30211        let first_buffer_id = multi_buffer
30212            .read(cx)
30213            .excerpt_buffer_ids()
30214            .into_iter()
30215            .next()
30216            .unwrap();
30217        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
30218        first_buffer.update(cx, |buffer, cx| {
30219            buffer.set_language(Some(markdown_language.clone()), cx);
30220        });
30221
30222        editor
30223    });
30224    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
30225
30226    cx.update_editor(|editor, window, cx| {
30227        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
30228        editor.paste(&Paste, window, cx);
30229    });
30230
30231    cx.assert_editor_state(&format!(
30232        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
30233    ));
30234}
30235
30236#[gpui::test]
30237async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
30238    init_test(cx, |_| {});
30239
30240    let fs = FakeFs::new(cx.executor());
30241    fs.insert_tree(
30242        path!("/project"),
30243        json!({
30244            "first.rs": "# First Document\nSome content here.",
30245            "second.rs": "Plain text content for second file.",
30246        }),
30247    )
30248    .await;
30249
30250    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
30251    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
30252    let cx = &mut VisualTestContext::from_window(*window, cx);
30253
30254    let language = rust_lang();
30255    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
30256    language_registry.add(language.clone());
30257    let mut fake_servers = language_registry.register_fake_lsp(
30258        "Rust",
30259        FakeLspAdapter {
30260            ..FakeLspAdapter::default()
30261        },
30262    );
30263
30264    let buffer1 = project
30265        .update(cx, |project, cx| {
30266            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
30267        })
30268        .await
30269        .unwrap();
30270    let buffer2 = project
30271        .update(cx, |project, cx| {
30272            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
30273        })
30274        .await
30275        .unwrap();
30276
30277    let multi_buffer = cx.new(|cx| {
30278        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
30279        multi_buffer.set_excerpts_for_path(
30280            PathKey::for_buffer(&buffer1, cx),
30281            buffer1.clone(),
30282            [Point::zero()..buffer1.read(cx).max_point()],
30283            3,
30284            cx,
30285        );
30286        multi_buffer.set_excerpts_for_path(
30287            PathKey::for_buffer(&buffer2, cx),
30288            buffer2.clone(),
30289            [Point::zero()..buffer1.read(cx).max_point()],
30290            3,
30291            cx,
30292        );
30293        multi_buffer
30294    });
30295
30296    let (editor, cx) = cx.add_window_view(|window, cx| {
30297        Editor::new(
30298            EditorMode::full(),
30299            multi_buffer,
30300            Some(project.clone()),
30301            window,
30302            cx,
30303        )
30304    });
30305
30306    let fake_language_server = fake_servers.next().await.unwrap();
30307
30308    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
30309
30310    let save = editor.update_in(cx, |editor, window, cx| {
30311        assert!(editor.is_dirty(cx));
30312
30313        editor.save(
30314            SaveOptions {
30315                format: true,
30316                autosave: true,
30317            },
30318            project,
30319            window,
30320            cx,
30321        )
30322    });
30323    let (start_edit_tx, start_edit_rx) = oneshot::channel();
30324    let (done_edit_tx, done_edit_rx) = oneshot::channel();
30325    let mut done_edit_rx = Some(done_edit_rx);
30326    let mut start_edit_tx = Some(start_edit_tx);
30327
30328    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
30329        start_edit_tx.take().unwrap().send(()).unwrap();
30330        let done_edit_rx = done_edit_rx.take().unwrap();
30331        async move {
30332            done_edit_rx.await.unwrap();
30333            Ok(None)
30334        }
30335    });
30336
30337    start_edit_rx.await.unwrap();
30338    buffer2
30339        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
30340        .unwrap();
30341
30342    done_edit_tx.send(()).unwrap();
30343
30344    save.await.unwrap();
30345    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
30346}
30347
30348#[gpui::test]
30349fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
30350    init_test(cx, |_| {});
30351
30352    let editor = cx.add_window(|window, cx| {
30353        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
30354        build_editor(buffer, window, cx)
30355    });
30356
30357    editor
30358        .update(cx, |editor, window, cx| {
30359            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30360                s.select_display_ranges([
30361                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
30362                ])
30363            });
30364
30365            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
30366
30367            assert_eq!(
30368                editor.display_text(cx),
30369                "line1\nline2\nline2",
30370                "Duplicating last line upward should create duplicate above, not on same line"
30371            );
30372
30373            assert_eq!(
30374                editor
30375                    .selections
30376                    .display_ranges(&editor.display_snapshot(cx)),
30377                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
30378                "Selection should move to the duplicated line"
30379            );
30380        })
30381        .unwrap();
30382}
30383
30384#[gpui::test]
30385async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
30386    init_test(cx, |_| {});
30387
30388    let mut cx = EditorTestContext::new(cx).await;
30389
30390    cx.set_state("line1\nline2ˇ");
30391
30392    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
30393
30394    let clipboard_text = cx
30395        .read_from_clipboard()
30396        .and_then(|item| item.text().as_deref().map(str::to_string));
30397
30398    assert_eq!(
30399        clipboard_text,
30400        Some("line2\n".to_string()),
30401        "Copying a line without trailing newline should include a newline"
30402    );
30403
30404    cx.set_state("line1\nˇ");
30405
30406    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
30407
30408    cx.assert_editor_state("line1\nline2\nˇ");
30409}
30410
30411#[gpui::test]
30412async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
30413    init_test(cx, |_| {});
30414
30415    let mut cx = EditorTestContext::new(cx).await;
30416
30417    cx.set_state("ˇline1\nˇline2\nˇline3\n");
30418
30419    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
30420
30421    let clipboard_text = cx
30422        .read_from_clipboard()
30423        .and_then(|item| item.text().as_deref().map(str::to_string));
30424
30425    assert_eq!(
30426        clipboard_text,
30427        Some("line1\nline2\nline3\n".to_string()),
30428        "Copying multiple lines should include a single newline between lines"
30429    );
30430
30431    cx.set_state("lineA\nˇ");
30432
30433    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
30434
30435    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
30436}
30437
30438#[gpui::test]
30439async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
30440    init_test(cx, |_| {});
30441
30442    let mut cx = EditorTestContext::new(cx).await;
30443
30444    cx.set_state("ˇline1\nˇline2\nˇline3\n");
30445
30446    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
30447
30448    let clipboard_text = cx
30449        .read_from_clipboard()
30450        .and_then(|item| item.text().as_deref().map(str::to_string));
30451
30452    assert_eq!(
30453        clipboard_text,
30454        Some("line1\nline2\nline3\n".to_string()),
30455        "Copying multiple lines should include a single newline between lines"
30456    );
30457
30458    cx.set_state("lineA\nˇ");
30459
30460    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
30461
30462    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
30463}
30464
30465#[gpui::test]
30466async fn test_end_of_editor_context(cx: &mut TestAppContext) {
30467    init_test(cx, |_| {});
30468
30469    let mut cx = EditorTestContext::new(cx).await;
30470
30471    cx.set_state("line1\nline2ˇ");
30472    cx.update_editor(|e, window, cx| {
30473        e.set_mode(EditorMode::SingleLine);
30474        assert!(e.key_context(window, cx).contains("end_of_input"));
30475    });
30476    cx.set_state("ˇline1\nline2");
30477    cx.update_editor(|e, window, cx| {
30478        assert!(!e.key_context(window, cx).contains("end_of_input"));
30479    });
30480    cx.set_state("line1ˇ\nline2");
30481    cx.update_editor(|e, window, cx| {
30482        assert!(!e.key_context(window, cx).contains("end_of_input"));
30483    });
30484}
30485
30486#[gpui::test]
30487async fn test_sticky_scroll(cx: &mut TestAppContext) {
30488    init_test(cx, |_| {});
30489    let mut cx = EditorTestContext::new(cx).await;
30490
30491    let buffer = indoc! {"
30492            ˇfn foo() {
30493                let abc = 123;
30494            }
30495            struct Bar;
30496            impl Bar {
30497                fn new() -> Self {
30498                    Self
30499                }
30500            }
30501            fn baz() {
30502            }
30503        "};
30504    cx.set_state(&buffer);
30505
30506    cx.update_editor(|e, _, cx| {
30507        e.buffer()
30508            .read(cx)
30509            .as_singleton()
30510            .unwrap()
30511            .update(cx, |buffer, cx| {
30512                buffer.set_language(Some(rust_lang()), cx);
30513            })
30514    });
30515
30516    let mut sticky_headers = |offset: ScrollOffset| {
30517        cx.update_editor(|e, window, cx| {
30518            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
30519        });
30520        cx.run_until_parked();
30521        cx.update_editor(|e, window, cx| {
30522            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
30523                .into_iter()
30524                .map(
30525                    |StickyHeader {
30526                         start_point,
30527                         offset,
30528                         ..
30529                     }| { (start_point, offset) },
30530                )
30531                .collect::<Vec<_>>()
30532        })
30533    };
30534
30535    let fn_foo = Point { row: 0, column: 0 };
30536    let impl_bar = Point { row: 4, column: 0 };
30537    let fn_new = Point { row: 5, column: 4 };
30538
30539    assert_eq!(sticky_headers(0.0), vec![]);
30540    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
30541    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
30542    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
30543    assert_eq!(sticky_headers(2.0), vec![]);
30544    assert_eq!(sticky_headers(2.5), vec![]);
30545    assert_eq!(sticky_headers(3.0), vec![]);
30546    assert_eq!(sticky_headers(3.5), vec![]);
30547    assert_eq!(sticky_headers(4.0), vec![]);
30548    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
30549    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
30550    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
30551    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
30552    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
30553    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
30554    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
30555    assert_eq!(sticky_headers(8.0), vec![]);
30556    assert_eq!(sticky_headers(8.5), vec![]);
30557    assert_eq!(sticky_headers(9.0), vec![]);
30558    assert_eq!(sticky_headers(9.5), vec![]);
30559    assert_eq!(sticky_headers(10.0), vec![]);
30560}
30561
30562#[gpui::test]
30563async fn test_sticky_scroll_with_expanded_deleted_diff_hunks(
30564    executor: BackgroundExecutor,
30565    cx: &mut TestAppContext,
30566) {
30567    init_test(cx, |_| {});
30568    let mut cx = EditorTestContext::new(cx).await;
30569
30570    let diff_base = indoc! {"
30571        fn foo() {
30572            let a = 1;
30573            let b = 2;
30574            let c = 3;
30575            let d = 4;
30576            let e = 5;
30577        }
30578    "};
30579
30580    let buffer = indoc! {"
30581        ˇfn foo() {
30582        }
30583    "};
30584
30585    cx.set_state(&buffer);
30586
30587    cx.update_editor(|e, _, cx| {
30588        e.buffer()
30589            .read(cx)
30590            .as_singleton()
30591            .unwrap()
30592            .update(cx, |buffer, cx| {
30593                buffer.set_language(Some(rust_lang()), cx);
30594            })
30595    });
30596
30597    cx.set_head_text(diff_base);
30598    executor.run_until_parked();
30599
30600    cx.update_editor(|editor, window, cx| {
30601        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
30602    });
30603    executor.run_until_parked();
30604
30605    // After expanding, the display should look like:
30606    //   row 0: fn foo() {
30607    //   row 1: -    let a = 1;   (deleted)
30608    //   row 2: -    let b = 2;   (deleted)
30609    //   row 3: -    let c = 3;   (deleted)
30610    //   row 4: -    let d = 4;   (deleted)
30611    //   row 5: -    let e = 5;   (deleted)
30612    //   row 6: }
30613    //
30614    // fn foo() spans display rows 0-6. Scrolling into the deleted region
30615    // (rows 1-5) should still show fn foo() as a sticky header.
30616
30617    let fn_foo = Point { row: 0, column: 0 };
30618
30619    let mut sticky_headers = |offset: ScrollOffset| {
30620        cx.update_editor(|e, window, cx| {
30621            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
30622        });
30623        cx.run_until_parked();
30624        cx.update_editor(|e, window, cx| {
30625            EditorElement::sticky_headers(&e, &e.snapshot(window, cx))
30626                .into_iter()
30627                .map(
30628                    |StickyHeader {
30629                         start_point,
30630                         offset,
30631                         ..
30632                     }| { (start_point, offset) },
30633                )
30634                .collect::<Vec<_>>()
30635        })
30636    };
30637
30638    assert_eq!(sticky_headers(0.0), vec![]);
30639    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
30640    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
30641    // Scrolling into deleted lines: fn foo() should still be a sticky header.
30642    assert_eq!(sticky_headers(2.0), vec![(fn_foo, 0.0)]);
30643    assert_eq!(sticky_headers(3.0), vec![(fn_foo, 0.0)]);
30644    assert_eq!(sticky_headers(4.0), vec![(fn_foo, 0.0)]);
30645    assert_eq!(sticky_headers(5.0), vec![(fn_foo, 0.0)]);
30646    assert_eq!(sticky_headers(5.5), vec![(fn_foo, -0.5)]);
30647    // Past the closing brace: no more sticky header.
30648    assert_eq!(sticky_headers(6.0), vec![]);
30649}
30650
30651#[gpui::test]
30652fn test_relative_line_numbers(cx: &mut TestAppContext) {
30653    init_test(cx, |_| {});
30654
30655    let buffer_1 = cx.new(|cx| Buffer::local("aaaaaaaaaa\nbbb\n", cx));
30656    let buffer_2 = cx.new(|cx| Buffer::local("cccccccccc\nddd\n", cx));
30657    let buffer_3 = cx.new(|cx| Buffer::local("eee\nffffffffff\n", cx));
30658
30659    let multibuffer = cx.new(|cx| {
30660        let mut multibuffer = MultiBuffer::new(ReadWrite);
30661        multibuffer.set_excerpts_for_path(
30662            PathKey::sorted(0),
30663            buffer_1.clone(),
30664            [Point::new(0, 0)..Point::new(2, 0)],
30665            0,
30666            cx,
30667        );
30668        multibuffer.set_excerpts_for_path(
30669            PathKey::sorted(1),
30670            buffer_2.clone(),
30671            [Point::new(0, 0)..Point::new(2, 0)],
30672            0,
30673            cx,
30674        );
30675        multibuffer.set_excerpts_for_path(
30676            PathKey::sorted(2),
30677            buffer_3.clone(),
30678            [Point::new(0, 0)..Point::new(2, 0)],
30679            0,
30680            cx,
30681        );
30682        multibuffer
30683    });
30684
30685    // wrapped contents of multibuffer:
30686    //    aaa
30687    //    aaa
30688    //    aaa
30689    //    a
30690    //    bbb
30691    //
30692    //    ccc
30693    //    ccc
30694    //    ccc
30695    //    c
30696    //    ddd
30697    //
30698    //    eee
30699    //    fff
30700    //    fff
30701    //    fff
30702    //    f
30703
30704    let editor = cx.add_window(|window, cx| build_editor(multibuffer, window, cx));
30705    _ = editor.update(cx, |editor, window, cx| {
30706        editor.set_wrap_width(Some(30.0.into()), cx); // every 3 characters
30707
30708        // includes trailing newlines.
30709        let expected_line_numbers = [2, 6, 7, 10, 14, 15, 18, 19, 23];
30710        let expected_wrapped_line_numbers = [
30711            2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 18, 19, 20, 21, 22, 23,
30712        ];
30713
30714        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
30715            s.select_ranges([
30716                Point::new(7, 0)..Point::new(7, 1), // second row of `ccc`
30717            ]);
30718        });
30719
30720        let snapshot = editor.snapshot(window, cx);
30721
30722        // these are all 0-indexed
30723        let base_display_row = DisplayRow(11);
30724        let base_row = 3;
30725        let wrapped_base_row = 7;
30726
30727        // test not counting wrapped lines
30728        let expected_relative_numbers = expected_line_numbers
30729            .into_iter()
30730            .enumerate()
30731            .map(|(i, row)| (DisplayRow(row), i.abs_diff(base_row) as u32))
30732            .filter(|(_, relative_line_number)| *relative_line_number != 0)
30733            .collect_vec();
30734        let actual_relative_numbers = snapshot
30735            .calculate_relative_line_numbers(
30736                &(DisplayRow(0)..DisplayRow(24)),
30737                base_display_row,
30738                false,
30739            )
30740            .into_iter()
30741            .sorted()
30742            .collect_vec();
30743        assert_eq!(expected_relative_numbers, actual_relative_numbers);
30744        // check `calculate_relative_line_numbers()` against `relative_line_delta()` for each line
30745        for (display_row, relative_number) in expected_relative_numbers {
30746            assert_eq!(
30747                relative_number,
30748                snapshot
30749                    .relative_line_delta(display_row, base_display_row, false)
30750                    .unsigned_abs() as u32,
30751            );
30752        }
30753
30754        // test counting wrapped lines
30755        let expected_wrapped_relative_numbers = expected_wrapped_line_numbers
30756            .into_iter()
30757            .enumerate()
30758            .map(|(i, row)| (DisplayRow(row), i.abs_diff(wrapped_base_row) as u32))
30759            .filter(|(row, _)| *row != base_display_row)
30760            .collect_vec();
30761        let actual_relative_numbers = snapshot
30762            .calculate_relative_line_numbers(
30763                &(DisplayRow(0)..DisplayRow(24)),
30764                base_display_row,
30765                true,
30766            )
30767            .into_iter()
30768            .sorted()
30769            .collect_vec();
30770        assert_eq!(expected_wrapped_relative_numbers, actual_relative_numbers);
30771        // check `calculate_relative_line_numbers()` against `relative_wrapped_line_delta()` for each line
30772        for (display_row, relative_number) in expected_wrapped_relative_numbers {
30773            assert_eq!(
30774                relative_number,
30775                snapshot
30776                    .relative_line_delta(display_row, base_display_row, true)
30777                    .unsigned_abs() as u32,
30778            );
30779        }
30780    });
30781}
30782
30783#[gpui::test]
30784async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
30785    init_test(cx, |_| {});
30786    cx.update(|cx| {
30787        SettingsStore::update_global(cx, |store, cx| {
30788            store.update_user_settings(cx, |settings| {
30789                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
30790                    enabled: Some(true),
30791                })
30792            });
30793        });
30794    });
30795    let mut cx = EditorTestContext::new(cx).await;
30796
30797    let line_height = cx.update_editor(|editor, window, cx| {
30798        editor
30799            .style(cx)
30800            .text
30801            .line_height_in_pixels(window.rem_size())
30802    });
30803
30804    let buffer = indoc! {"
30805            ˇfn foo() {
30806                let abc = 123;
30807            }
30808            struct Bar;
30809            impl Bar {
30810                fn new() -> Self {
30811                    Self
30812                }
30813            }
30814            fn baz() {
30815            }
30816        "};
30817    cx.set_state(&buffer);
30818
30819    cx.update_editor(|e, _, cx| {
30820        e.buffer()
30821            .read(cx)
30822            .as_singleton()
30823            .unwrap()
30824            .update(cx, |buffer, cx| {
30825                buffer.set_language(Some(rust_lang()), cx);
30826            })
30827    });
30828
30829    let fn_foo = || empty_range(0, 0);
30830    let impl_bar = || empty_range(4, 0);
30831    let fn_new = || empty_range(5, 4);
30832
30833    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
30834        cx.update_editor(|e, window, cx| {
30835            e.scroll(
30836                gpui::Point {
30837                    x: 0.,
30838                    y: scroll_offset,
30839                },
30840                None,
30841                window,
30842                cx,
30843            );
30844        });
30845        cx.run_until_parked();
30846        cx.simulate_click(
30847            gpui::Point {
30848                x: px(0.),
30849                y: click_offset as f32 * line_height,
30850            },
30851            Modifiers::none(),
30852        );
30853        cx.run_until_parked();
30854        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
30855    };
30856    assert_eq!(
30857        scroll_and_click(
30858            4.5, // impl Bar is halfway off the screen
30859            0.0  // click top of screen
30860        ),
30861        // scrolled to impl Bar
30862        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
30863    );
30864
30865    assert_eq!(
30866        scroll_and_click(
30867            4.5,  // impl Bar is halfway off the screen
30868            0.25  // click middle of impl Bar
30869        ),
30870        // scrolled to impl Bar
30871        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
30872    );
30873
30874    assert_eq!(
30875        scroll_and_click(
30876            4.5, // impl Bar is halfway off the screen
30877            1.5  // click below impl Bar (e.g. fn new())
30878        ),
30879        // scrolled to fn new() - this is below the impl Bar header which has persisted
30880        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
30881    );
30882
30883    assert_eq!(
30884        scroll_and_click(
30885            5.5,  // fn new is halfway underneath impl Bar
30886            0.75  // click on the overlap of impl Bar and fn new()
30887        ),
30888        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
30889    );
30890
30891    assert_eq!(
30892        scroll_and_click(
30893            5.5,  // fn new is halfway underneath impl Bar
30894            1.25  // click on the visible part of fn new()
30895        ),
30896        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
30897    );
30898
30899    assert_eq!(
30900        scroll_and_click(
30901            1.5, // fn foo is halfway off the screen
30902            0.0  // click top of screen
30903        ),
30904        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
30905    );
30906
30907    assert_eq!(
30908        scroll_and_click(
30909            1.5,  // fn foo is halfway off the screen
30910            0.75  // click visible part of let abc...
30911        )
30912        .0,
30913        // no change in scroll
30914        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
30915        (gpui::Point { x: 0., y: 1.5 })
30916    );
30917}
30918
30919#[gpui::test]
30920async fn test_next_prev_reference(cx: &mut TestAppContext) {
30921    const CYCLE_POSITIONS: &[&'static str] = &[
30922        indoc! {"
30923            fn foo() {
30924                let ˇabc = 123;
30925                let x = abc + 1;
30926                let y = abc + 2;
30927                let z = abc + 2;
30928            }
30929        "},
30930        indoc! {"
30931            fn foo() {
30932                let abc = 123;
30933                let x = ˇabc + 1;
30934                let y = abc + 2;
30935                let z = abc + 2;
30936            }
30937        "},
30938        indoc! {"
30939            fn foo() {
30940                let abc = 123;
30941                let x = abc + 1;
30942                let y = ˇabc + 2;
30943                let z = abc + 2;
30944            }
30945        "},
30946        indoc! {"
30947            fn foo() {
30948                let abc = 123;
30949                let x = abc + 1;
30950                let y = abc + 2;
30951                let z = ˇabc + 2;
30952            }
30953        "},
30954    ];
30955
30956    init_test(cx, |_| {});
30957
30958    let mut cx = EditorLspTestContext::new_rust(
30959        lsp::ServerCapabilities {
30960            references_provider: Some(lsp::OneOf::Left(true)),
30961            ..Default::default()
30962        },
30963        cx,
30964    )
30965    .await;
30966
30967    // importantly, the cursor is in the middle
30968    cx.set_state(indoc! {"
30969        fn foo() {
30970            let aˇbc = 123;
30971            let x = abc + 1;
30972            let y = abc + 2;
30973            let z = abc + 2;
30974        }
30975    "});
30976
30977    let reference_ranges = [
30978        lsp::Position::new(1, 8),
30979        lsp::Position::new(2, 12),
30980        lsp::Position::new(3, 12),
30981        lsp::Position::new(4, 12),
30982    ]
30983    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
30984
30985    cx.lsp
30986        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
30987            Ok(Some(
30988                reference_ranges
30989                    .map(|range| lsp::Location {
30990                        uri: params.text_document_position.text_document.uri.clone(),
30991                        range,
30992                    })
30993                    .to_vec(),
30994            ))
30995        });
30996
30997    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
30998        cx.update_editor(|editor, window, cx| {
30999            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
31000        })
31001        .unwrap()
31002        .await
31003        .unwrap()
31004    };
31005
31006    _move(Direction::Next, 1, &mut cx).await;
31007    cx.assert_editor_state(CYCLE_POSITIONS[1]);
31008
31009    _move(Direction::Next, 1, &mut cx).await;
31010    cx.assert_editor_state(CYCLE_POSITIONS[2]);
31011
31012    _move(Direction::Next, 1, &mut cx).await;
31013    cx.assert_editor_state(CYCLE_POSITIONS[3]);
31014
31015    // loops back to the start
31016    _move(Direction::Next, 1, &mut cx).await;
31017    cx.assert_editor_state(CYCLE_POSITIONS[0]);
31018
31019    // loops back to the end
31020    _move(Direction::Prev, 1, &mut cx).await;
31021    cx.assert_editor_state(CYCLE_POSITIONS[3]);
31022
31023    _move(Direction::Prev, 1, &mut cx).await;
31024    cx.assert_editor_state(CYCLE_POSITIONS[2]);
31025
31026    _move(Direction::Prev, 1, &mut cx).await;
31027    cx.assert_editor_state(CYCLE_POSITIONS[1]);
31028
31029    _move(Direction::Prev, 1, &mut cx).await;
31030    cx.assert_editor_state(CYCLE_POSITIONS[0]);
31031
31032    _move(Direction::Next, 3, &mut cx).await;
31033    cx.assert_editor_state(CYCLE_POSITIONS[3]);
31034
31035    _move(Direction::Prev, 2, &mut cx).await;
31036    cx.assert_editor_state(CYCLE_POSITIONS[1]);
31037}
31038
31039#[gpui::test]
31040async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
31041    init_test(cx, |_| {});
31042
31043    let (editor, cx) = cx.add_window_view(|window, cx| {
31044        let multi_buffer = MultiBuffer::build_multi(
31045            [
31046                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
31047                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
31048            ],
31049            cx,
31050        );
31051        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31052    });
31053
31054    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
31055    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
31056
31057    cx.assert_excerpts_with_selections(indoc! {"
31058        [EXCERPT]
31059        ˇ1
31060        2
31061        3
31062        [EXCERPT]
31063        1
31064        2
31065        3
31066        "});
31067
31068    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
31069    cx.update_editor(|editor, window, cx| {
31070        editor.change_selections(None.into(), window, cx, |s| {
31071            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
31072        });
31073    });
31074    cx.assert_excerpts_with_selections(indoc! {"
31075        [EXCERPT]
31076        1
3107731078        3
31079        [EXCERPT]
31080        1
31081        2
31082        3
31083        "});
31084
31085    cx.update_editor(|editor, window, cx| {
31086        editor
31087            .select_all_matches(&SelectAllMatches, window, cx)
31088            .unwrap();
31089    });
31090    cx.assert_excerpts_with_selections(indoc! {"
31091        [EXCERPT]
31092        1
3109331094        3
31095        [EXCERPT]
31096        1
3109731098        3
31099        "});
31100
31101    cx.update_editor(|editor, window, cx| {
31102        editor.handle_input("X", window, cx);
31103    });
31104    cx.assert_excerpts_with_selections(indoc! {"
31105        [EXCERPT]
31106        1
3110731108        3
31109        [EXCERPT]
31110        1
3111131112        3
31113        "});
31114
31115    // Scenario 2: Select "2", then fold second buffer before insertion
31116    cx.update_multibuffer(|mb, cx| {
31117        for buffer_id in buffer_ids.iter() {
31118            let buffer = mb.buffer(*buffer_id).unwrap();
31119            buffer.update(cx, |buffer, cx| {
31120                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
31121            });
31122        }
31123    });
31124
31125    // Select "2" and select all matches
31126    cx.update_editor(|editor, window, cx| {
31127        editor.change_selections(None.into(), window, cx, |s| {
31128            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
31129        });
31130        editor
31131            .select_all_matches(&SelectAllMatches, window, cx)
31132            .unwrap();
31133    });
31134
31135    // Fold second buffer - should remove selections from folded buffer
31136    cx.update_editor(|editor, _, cx| {
31137        editor.fold_buffer(buffer_ids[1], cx);
31138    });
31139    cx.assert_excerpts_with_selections(indoc! {"
31140        [EXCERPT]
31141        1
3114231143        3
31144        [EXCERPT]
31145        [FOLDED]
31146        "});
31147
31148    // Insert text - should only affect first buffer
31149    cx.update_editor(|editor, window, cx| {
31150        editor.handle_input("Y", window, cx);
31151    });
31152    cx.update_editor(|editor, _, cx| {
31153        editor.unfold_buffer(buffer_ids[1], cx);
31154    });
31155    cx.assert_excerpts_with_selections(indoc! {"
31156        [EXCERPT]
31157        1
3115831159        3
31160        [EXCERPT]
31161        1
31162        2
31163        3
31164        "});
31165
31166    // Scenario 3: Select "2", then fold first buffer before insertion
31167    cx.update_multibuffer(|mb, cx| {
31168        for buffer_id in buffer_ids.iter() {
31169            let buffer = mb.buffer(*buffer_id).unwrap();
31170            buffer.update(cx, |buffer, cx| {
31171                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
31172            });
31173        }
31174    });
31175
31176    // Select "2" and select all matches
31177    cx.update_editor(|editor, window, cx| {
31178        editor.change_selections(None.into(), window, cx, |s| {
31179            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
31180        });
31181        editor
31182            .select_all_matches(&SelectAllMatches, window, cx)
31183            .unwrap();
31184    });
31185
31186    // Fold first buffer - should remove selections from folded buffer
31187    cx.update_editor(|editor, _, cx| {
31188        editor.fold_buffer(buffer_ids[0], cx);
31189    });
31190    cx.assert_excerpts_with_selections(indoc! {"
31191        [EXCERPT]
31192        [FOLDED]
31193        [EXCERPT]
31194        1
3119531196        3
31197        "});
31198
31199    // Insert text - should only affect second buffer
31200    cx.update_editor(|editor, window, cx| {
31201        editor.handle_input("Z", window, cx);
31202    });
31203    cx.update_editor(|editor, _, cx| {
31204        editor.unfold_buffer(buffer_ids[0], cx);
31205    });
31206    cx.assert_excerpts_with_selections(indoc! {"
31207        [EXCERPT]
31208        1
31209        2
31210        3
31211        [EXCERPT]
31212        1
3121331214        3
31215        "});
31216
31217    // Test correct folded header is selected upon fold
31218    cx.update_editor(|editor, _, cx| {
31219        editor.fold_buffer(buffer_ids[0], cx);
31220        editor.fold_buffer(buffer_ids[1], cx);
31221    });
31222    cx.assert_excerpts_with_selections(indoc! {"
31223        [EXCERPT]
31224        [FOLDED]
31225        [EXCERPT]
31226        ˇ[FOLDED]
31227        "});
31228
31229    // Test selection inside folded buffer unfolds it on type
31230    cx.update_editor(|editor, window, cx| {
31231        editor.handle_input("W", window, cx);
31232    });
31233    cx.update_editor(|editor, _, cx| {
31234        editor.unfold_buffer(buffer_ids[0], cx);
31235    });
31236    cx.assert_excerpts_with_selections(indoc! {"
31237        [EXCERPT]
31238        1
31239        2
31240        3
31241        [EXCERPT]
31242        Wˇ1
31243        Z
31244        3
31245        "});
31246}
31247
31248#[gpui::test]
31249async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
31250    init_test(cx, |_| {});
31251
31252    let (editor, cx) = cx.add_window_view(|window, cx| {
31253        let multi_buffer = MultiBuffer::build_multi(
31254            [
31255                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
31256                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
31257            ],
31258            cx,
31259        );
31260        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
31261    });
31262
31263    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
31264
31265    cx.assert_excerpts_with_selections(indoc! {"
31266        [EXCERPT]
31267        ˇ1
31268        2
31269        3
31270        [EXCERPT]
31271        1
31272        2
31273        3
31274        4
31275        5
31276        6
31277        7
31278        8
31279        9
31280        "});
31281
31282    cx.update_editor(|editor, window, cx| {
31283        editor.change_selections(None.into(), window, cx, |s| {
31284            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
31285        });
31286    });
31287
31288    cx.assert_excerpts_with_selections(indoc! {"
31289        [EXCERPT]
31290        1
31291        2
31292        3
31293        [EXCERPT]
31294        1
31295        2
31296        3
31297        4
31298        5
31299        6
31300        ˇ7
31301        8
31302        9
31303        "});
31304
31305    cx.update_editor(|editor, _window, cx| {
31306        editor.set_vertical_scroll_margin(0, cx);
31307    });
31308
31309    cx.update_editor(|editor, window, cx| {
31310        assert_eq!(editor.vertical_scroll_margin(), 0);
31311        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
31312        assert_eq!(
31313            editor.snapshot(window, cx).scroll_position(),
31314            gpui::Point::new(0., 12.0)
31315        );
31316    });
31317
31318    cx.update_editor(|editor, _window, cx| {
31319        editor.set_vertical_scroll_margin(3, cx);
31320    });
31321
31322    cx.update_editor(|editor, window, cx| {
31323        assert_eq!(editor.vertical_scroll_margin(), 3);
31324        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
31325        assert_eq!(
31326            editor.snapshot(window, cx).scroll_position(),
31327            gpui::Point::new(0., 9.0)
31328        );
31329    });
31330}
31331
31332#[gpui::test]
31333async fn test_find_references_single_case(cx: &mut TestAppContext) {
31334    init_test(cx, |_| {});
31335    let mut cx = EditorLspTestContext::new_rust(
31336        lsp::ServerCapabilities {
31337            references_provider: Some(lsp::OneOf::Left(true)),
31338            ..lsp::ServerCapabilities::default()
31339        },
31340        cx,
31341    )
31342    .await;
31343
31344    let before = indoc!(
31345        r#"
31346        fn main() {
31347            let aˇbc = 123;
31348            let xyz = abc;
31349        }
31350        "#
31351    );
31352    let after = indoc!(
31353        r#"
31354        fn main() {
31355            let abc = 123;
31356            let xyz = ˇabc;
31357        }
31358        "#
31359    );
31360
31361    cx.lsp
31362        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
31363            Ok(Some(vec![
31364                lsp::Location {
31365                    uri: params.text_document_position.text_document.uri.clone(),
31366                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
31367                },
31368                lsp::Location {
31369                    uri: params.text_document_position.text_document.uri,
31370                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
31371                },
31372            ]))
31373        });
31374
31375    cx.set_state(before);
31376
31377    let action = FindAllReferences {
31378        always_open_multibuffer: false,
31379    };
31380
31381    let navigated = cx
31382        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
31383        .expect("should have spawned a task")
31384        .await
31385        .unwrap();
31386
31387    assert_eq!(navigated, Navigated::No);
31388
31389    cx.run_until_parked();
31390
31391    cx.assert_editor_state(after);
31392}
31393
31394#[gpui::test]
31395async fn test_newline_task_list_continuation(cx: &mut TestAppContext) {
31396    init_test(cx, |settings| {
31397        settings.defaults.tab_size = Some(2.try_into().unwrap());
31398    });
31399
31400    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
31401    let mut cx = EditorTestContext::new(cx).await;
31402    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31403
31404    // Case 1: Adding newline after (whitespace + prefix + any non-whitespace) adds marker
31405    cx.set_state(indoc! {"
31406        - [ ] taskˇ
31407    "});
31408    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31409    cx.wait_for_autoindent_applied().await;
31410    cx.assert_editor_state(indoc! {"
31411        - [ ] task
31412        - [ ] ˇ
31413    "});
31414
31415    // Case 2: Works with checked task items too
31416    cx.set_state(indoc! {"
31417        - [x] completed taskˇ
31418    "});
31419    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31420    cx.wait_for_autoindent_applied().await;
31421    cx.assert_editor_state(indoc! {"
31422        - [x] completed task
31423        - [ ] ˇ
31424    "});
31425
31426    // Case 2.1: Works with uppercase checked marker too
31427    cx.set_state(indoc! {"
31428        - [X] completed taskˇ
31429    "});
31430    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31431    cx.wait_for_autoindent_applied().await;
31432    cx.assert_editor_state(indoc! {"
31433        - [X] completed task
31434        - [ ] ˇ
31435    "});
31436
31437    // Case 3: Cursor position doesn't matter - content after marker is what counts
31438    cx.set_state(indoc! {"
31439        - [ ] taˇsk
31440    "});
31441    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31442    cx.wait_for_autoindent_applied().await;
31443    cx.assert_editor_state(indoc! {"
31444        - [ ] ta
31445        - [ ] ˇsk
31446    "});
31447
31448    // Case 4: Adding newline after (whitespace + prefix + some whitespace) does NOT add marker
31449    cx.set_state(indoc! {"
31450        - [ ]  ˇ
31451    "});
31452    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31453    cx.wait_for_autoindent_applied().await;
31454    cx.assert_editor_state(
31455        indoc! {"
31456        - [ ]$$
31457        ˇ
31458    "}
31459        .replace("$", " ")
31460        .as_str(),
31461    );
31462
31463    // Case 5: Adding newline with content adds marker preserving indentation
31464    cx.set_state(indoc! {"
31465        - [ ] task
31466          - [ ] indentedˇ
31467    "});
31468    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31469    cx.wait_for_autoindent_applied().await;
31470    cx.assert_editor_state(indoc! {"
31471        - [ ] task
31472          - [ ] indented
31473          - [ ] ˇ
31474    "});
31475
31476    // Case 6: Adding newline with cursor right after prefix, unindents
31477    cx.set_state(indoc! {"
31478        - [ ] task
31479          - [ ] sub task
31480            - [ ] ˇ
31481    "});
31482    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31483    cx.wait_for_autoindent_applied().await;
31484    cx.assert_editor_state(indoc! {"
31485        - [ ] task
31486          - [ ] sub task
31487          - [ ] ˇ
31488    "});
31489    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31490    cx.wait_for_autoindent_applied().await;
31491
31492    // Case 7: Adding newline with cursor right after prefix, removes marker
31493    cx.assert_editor_state(indoc! {"
31494        - [ ] task
31495          - [ ] sub task
31496        - [ ] ˇ
31497    "});
31498    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31499    cx.wait_for_autoindent_applied().await;
31500    cx.assert_editor_state(indoc! {"
31501        - [ ] task
31502          - [ ] sub task
31503        ˇ
31504    "});
31505
31506    // Case 8: Cursor before or inside prefix does not add marker
31507    cx.set_state(indoc! {"
31508        ˇ- [ ] task
31509    "});
31510    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31511    cx.wait_for_autoindent_applied().await;
31512    cx.assert_editor_state(indoc! {"
31513
31514        ˇ- [ ] task
31515    "});
31516
31517    cx.set_state(indoc! {"
31518        - [ˇ ] task
31519    "});
31520    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31521    cx.wait_for_autoindent_applied().await;
31522    cx.assert_editor_state(indoc! {"
31523        - [
31524        ˇ
31525        ] task
31526    "});
31527}
31528
31529#[gpui::test]
31530async fn test_newline_unordered_list_continuation(cx: &mut TestAppContext) {
31531    init_test(cx, |settings| {
31532        settings.defaults.tab_size = Some(2.try_into().unwrap());
31533    });
31534
31535    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
31536    let mut cx = EditorTestContext::new(cx).await;
31537    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31538
31539    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) adds marker
31540    cx.set_state(indoc! {"
31541        - itemˇ
31542    "});
31543    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31544    cx.wait_for_autoindent_applied().await;
31545    cx.assert_editor_state(indoc! {"
31546        - item
31547        - ˇ
31548    "});
31549
31550    // Case 2: Works with different markers
31551    cx.set_state(indoc! {"
31552        * starred itemˇ
31553    "});
31554    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31555    cx.wait_for_autoindent_applied().await;
31556    cx.assert_editor_state(indoc! {"
31557        * starred item
31558        * ˇ
31559    "});
31560
31561    cx.set_state(indoc! {"
31562        + plus itemˇ
31563    "});
31564    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31565    cx.wait_for_autoindent_applied().await;
31566    cx.assert_editor_state(indoc! {"
31567        + plus item
31568        + ˇ
31569    "});
31570
31571    // Case 3: Cursor position doesn't matter - content after marker is what counts
31572    cx.set_state(indoc! {"
31573        - itˇem
31574    "});
31575    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31576    cx.wait_for_autoindent_applied().await;
31577    cx.assert_editor_state(indoc! {"
31578        - it
31579        - ˇem
31580    "});
31581
31582    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
31583    cx.set_state(indoc! {"
31584        -  ˇ
31585    "});
31586    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31587    cx.wait_for_autoindent_applied().await;
31588    cx.assert_editor_state(
31589        indoc! {"
31590        - $
31591        ˇ
31592    "}
31593        .replace("$", " ")
31594        .as_str(),
31595    );
31596
31597    // Case 5: Adding newline with content adds marker preserving indentation
31598    cx.set_state(indoc! {"
31599        - item
31600          - indentedˇ
31601    "});
31602    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31603    cx.wait_for_autoindent_applied().await;
31604    cx.assert_editor_state(indoc! {"
31605        - item
31606          - indented
31607          - ˇ
31608    "});
31609
31610    // Case 6: Adding newline with cursor right after marker, unindents
31611    cx.set_state(indoc! {"
31612        - item
31613          - sub item
31614            - ˇ
31615    "});
31616    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31617    cx.wait_for_autoindent_applied().await;
31618    cx.assert_editor_state(indoc! {"
31619        - item
31620          - sub item
31621          - ˇ
31622    "});
31623    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31624    cx.wait_for_autoindent_applied().await;
31625
31626    // Case 7: Adding newline with cursor right after marker, removes marker
31627    cx.assert_editor_state(indoc! {"
31628        - item
31629          - sub item
31630        - ˇ
31631    "});
31632    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31633    cx.wait_for_autoindent_applied().await;
31634    cx.assert_editor_state(indoc! {"
31635        - item
31636          - sub item
31637        ˇ
31638    "});
31639
31640    // Case 8: Cursor before or inside prefix does not add marker
31641    cx.set_state(indoc! {"
31642        ˇ- item
31643    "});
31644    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31645    cx.wait_for_autoindent_applied().await;
31646    cx.assert_editor_state(indoc! {"
31647
31648        ˇ- item
31649    "});
31650
31651    cx.set_state(indoc! {"
31652        -ˇ item
31653    "});
31654    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31655    cx.wait_for_autoindent_applied().await;
31656    cx.assert_editor_state(indoc! {"
31657        -
31658        ˇitem
31659    "});
31660}
31661
31662#[gpui::test]
31663async fn test_newline_ordered_list_continuation(cx: &mut TestAppContext) {
31664    init_test(cx, |settings| {
31665        settings.defaults.tab_size = Some(2.try_into().unwrap());
31666    });
31667
31668    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
31669    let mut cx = EditorTestContext::new(cx).await;
31670    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31671
31672    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
31673    cx.set_state(indoc! {"
31674        1. first itemˇ
31675    "});
31676    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31677    cx.wait_for_autoindent_applied().await;
31678    cx.assert_editor_state(indoc! {"
31679        1. first item
31680        2. ˇ
31681    "});
31682
31683    // Case 2: Works with larger numbers
31684    cx.set_state(indoc! {"
31685        10. tenth itemˇ
31686    "});
31687    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31688    cx.wait_for_autoindent_applied().await;
31689    cx.assert_editor_state(indoc! {"
31690        10. tenth item
31691        11. ˇ
31692    "});
31693
31694    // Case 3: Cursor position doesn't matter - content after marker is what counts
31695    cx.set_state(indoc! {"
31696        1. itˇem
31697    "});
31698    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31699    cx.wait_for_autoindent_applied().await;
31700    cx.assert_editor_state(indoc! {"
31701        1. it
31702        2. ˇem
31703    "});
31704
31705    // Case 4: Adding newline after (whitespace + marker + some whitespace) does NOT add marker
31706    cx.set_state(indoc! {"
31707        1.  ˇ
31708    "});
31709    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31710    cx.wait_for_autoindent_applied().await;
31711    cx.assert_editor_state(
31712        indoc! {"
31713        1. $
31714        ˇ
31715    "}
31716        .replace("$", " ")
31717        .as_str(),
31718    );
31719
31720    // Case 5: Adding newline with content adds marker preserving indentation
31721    cx.set_state(indoc! {"
31722        1. item
31723          2. indentedˇ
31724    "});
31725    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31726    cx.wait_for_autoindent_applied().await;
31727    cx.assert_editor_state(indoc! {"
31728        1. item
31729          2. indented
31730          3. ˇ
31731    "});
31732
31733    // Case 6: Adding newline with cursor right after marker, unindents
31734    cx.set_state(indoc! {"
31735        1. item
31736          2. sub item
31737            3. ˇ
31738    "});
31739    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31740    cx.wait_for_autoindent_applied().await;
31741    cx.assert_editor_state(indoc! {"
31742        1. item
31743          2. sub item
31744          1. ˇ
31745    "});
31746    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31747    cx.wait_for_autoindent_applied().await;
31748
31749    // Case 7: Adding newline with cursor right after marker, removes marker
31750    cx.assert_editor_state(indoc! {"
31751        1. item
31752          2. sub item
31753        1. ˇ
31754    "});
31755    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31756    cx.wait_for_autoindent_applied().await;
31757    cx.assert_editor_state(indoc! {"
31758        1. item
31759          2. sub item
31760        ˇ
31761    "});
31762
31763    // Case 8: Cursor before or inside prefix does not add marker
31764    cx.set_state(indoc! {"
31765        ˇ1. item
31766    "});
31767    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31768    cx.wait_for_autoindent_applied().await;
31769    cx.assert_editor_state(indoc! {"
31770
31771        ˇ1. item
31772    "});
31773
31774    cx.set_state(indoc! {"
31775        1ˇ. item
31776    "});
31777    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31778    cx.wait_for_autoindent_applied().await;
31779    cx.assert_editor_state(indoc! {"
31780        1
31781        ˇ. item
31782    "});
31783}
31784
31785#[gpui::test]
31786async fn test_newline_should_not_autoindent_ordered_list(cx: &mut TestAppContext) {
31787    init_test(cx, |settings| {
31788        settings.defaults.tab_size = Some(2.try_into().unwrap());
31789    });
31790
31791    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
31792    let mut cx = EditorTestContext::new(cx).await;
31793    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31794
31795    // Case 1: Adding newline after (whitespace + marker + any non-whitespace) increments number
31796    cx.set_state(indoc! {"
31797        1. first item
31798          1. sub first item
31799          2. sub second item
31800          3. ˇ
31801    "});
31802    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
31803    cx.wait_for_autoindent_applied().await;
31804    cx.assert_editor_state(indoc! {"
31805        1. first item
31806          1. sub first item
31807          2. sub second item
31808        1. ˇ
31809    "});
31810}
31811
31812#[gpui::test]
31813async fn test_tab_list_indent(cx: &mut TestAppContext) {
31814    init_test(cx, |settings| {
31815        settings.defaults.tab_size = Some(2.try_into().unwrap());
31816    });
31817
31818    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
31819    let mut cx = EditorTestContext::new(cx).await;
31820    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
31821
31822    // Case 1: Unordered list - cursor after prefix, adds indent before prefix
31823    cx.set_state(indoc! {"
31824        - ˇitem
31825    "});
31826    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31827    cx.wait_for_autoindent_applied().await;
31828    let expected = indoc! {"
31829        $$- ˇitem
31830    "};
31831    cx.assert_editor_state(expected.replace("$", " ").as_str());
31832
31833    // Case 2: Task list - cursor after prefix
31834    cx.set_state(indoc! {"
31835        - [ ] ˇtask
31836    "});
31837    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31838    cx.wait_for_autoindent_applied().await;
31839    let expected = indoc! {"
31840        $$- [ ] ˇtask
31841    "};
31842    cx.assert_editor_state(expected.replace("$", " ").as_str());
31843
31844    // Case 3: Ordered list - cursor after prefix
31845    cx.set_state(indoc! {"
31846        1. ˇfirst
31847    "});
31848    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31849    cx.wait_for_autoindent_applied().await;
31850    let expected = indoc! {"
31851        $$1. ˇfirst
31852    "};
31853    cx.assert_editor_state(expected.replace("$", " ").as_str());
31854
31855    // Case 4: With existing indentation - adds more indent
31856    let initial = indoc! {"
31857        $$- ˇitem
31858    "};
31859    cx.set_state(initial.replace("$", " ").as_str());
31860    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31861    cx.wait_for_autoindent_applied().await;
31862    let expected = indoc! {"
31863        $$$$- ˇitem
31864    "};
31865    cx.assert_editor_state(expected.replace("$", " ").as_str());
31866
31867    // Case 5: Empty list item
31868    cx.set_state(indoc! {"
31869        - ˇ
31870    "});
31871    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31872    cx.wait_for_autoindent_applied().await;
31873    let expected = indoc! {"
31874        $$- ˇ
31875    "};
31876    cx.assert_editor_state(expected.replace("$", " ").as_str());
31877
31878    // Case 6: Cursor at end of line with content
31879    cx.set_state(indoc! {"
31880        - itemˇ
31881    "});
31882    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31883    cx.wait_for_autoindent_applied().await;
31884    let expected = indoc! {"
31885        $$- itemˇ
31886    "};
31887    cx.assert_editor_state(expected.replace("$", " ").as_str());
31888
31889    // Case 7: Cursor at start of list item, indents it
31890    cx.set_state(indoc! {"
31891        - item
31892        ˇ  - sub item
31893    "});
31894    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31895    cx.wait_for_autoindent_applied().await;
31896    let expected = indoc! {"
31897        - item
31898          ˇ  - sub item
31899    "};
31900    cx.assert_editor_state(expected);
31901
31902    // Case 8: Cursor at start of list item, moves the cursor when "indent_list_on_tab" is false
31903    cx.update_editor(|_, _, cx| {
31904        SettingsStore::update_global(cx, |store, cx| {
31905            store.update_user_settings(cx, |settings| {
31906                settings.project.all_languages.defaults.indent_list_on_tab = Some(false);
31907            });
31908        });
31909    });
31910    cx.set_state(indoc! {"
31911        - item
31912        ˇ  - sub item
31913    "});
31914    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
31915    cx.wait_for_autoindent_applied().await;
31916    let expected = indoc! {"
31917        - item
31918          ˇ- sub item
31919    "};
31920    cx.assert_editor_state(expected);
31921}
31922
31923#[gpui::test]
31924async fn test_local_worktree_trust(cx: &mut TestAppContext) {
31925    init_test(cx, |_| {});
31926    cx.update(|cx| project::trusted_worktrees::init(HashMap::default(), cx));
31927
31928    cx.update(|cx| {
31929        SettingsStore::update_global(cx, |store, cx| {
31930            store.update_user_settings(cx, |settings| {
31931                settings.project.all_languages.defaults.inlay_hints =
31932                    Some(InlayHintSettingsContent {
31933                        enabled: Some(true),
31934                        ..InlayHintSettingsContent::default()
31935                    });
31936            });
31937        });
31938    });
31939
31940    let fs = FakeFs::new(cx.executor());
31941    fs.insert_tree(
31942        path!("/project"),
31943        json!({
31944            ".zed": {
31945                "settings.json": r#"{"languages":{"Rust":{"language_servers":["override-rust-analyzer"]}}}"#
31946            },
31947            "main.rs": "fn main() {}"
31948        }),
31949    )
31950    .await;
31951
31952    let lsp_inlay_hint_request_count = Arc::new(AtomicUsize::new(0));
31953    let server_name = "override-rust-analyzer";
31954    let project = Project::test_with_worktree_trust(fs, [path!("/project").as_ref()], cx).await;
31955
31956    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
31957    language_registry.add(rust_lang());
31958
31959    let capabilities = lsp::ServerCapabilities {
31960        inlay_hint_provider: Some(lsp::OneOf::Left(true)),
31961        ..lsp::ServerCapabilities::default()
31962    };
31963    let mut fake_language_servers = language_registry.register_fake_lsp(
31964        "Rust",
31965        FakeLspAdapter {
31966            name: server_name,
31967            capabilities,
31968            initializer: Some(Box::new({
31969                let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
31970                move |fake_server| {
31971                    let lsp_inlay_hint_request_count = lsp_inlay_hint_request_count.clone();
31972                    fake_server.set_request_handler::<lsp::request::InlayHintRequest, _, _>(
31973                        move |_params, _| {
31974                            lsp_inlay_hint_request_count.fetch_add(1, atomic::Ordering::Release);
31975                            async move {
31976                                Ok(Some(vec![lsp::InlayHint {
31977                                    position: lsp::Position::new(0, 0),
31978                                    label: lsp::InlayHintLabel::String("hint".to_string()),
31979                                    kind: None,
31980                                    text_edits: None,
31981                                    tooltip: None,
31982                                    padding_left: None,
31983                                    padding_right: None,
31984                                    data: None,
31985                                }]))
31986                            }
31987                        },
31988                    );
31989                }
31990            })),
31991            ..FakeLspAdapter::default()
31992        },
31993    );
31994
31995    cx.run_until_parked();
31996
31997    let worktree_id = project.read_with(cx, |project, cx| {
31998        project
31999            .worktrees(cx)
32000            .next()
32001            .map(|wt| wt.read(cx).id())
32002            .expect("should have a worktree")
32003    });
32004    let worktree_store = project.read_with(cx, |project, _| project.worktree_store());
32005
32006    let trusted_worktrees =
32007        cx.update(|cx| TrustedWorktrees::try_get_global(cx).expect("trust global should exist"));
32008
32009    let can_trust = trusted_worktrees.update(cx, |store, cx| {
32010        store.can_trust(&worktree_store, worktree_id, cx)
32011    });
32012    assert!(!can_trust, "worktree should be restricted initially");
32013
32014    let buffer_before_approval = project
32015        .update(cx, |project, cx| {
32016            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
32017        })
32018        .await
32019        .unwrap();
32020
32021    let (editor, cx) = cx.add_window_view(|window, cx| {
32022        Editor::new(
32023            EditorMode::full(),
32024            cx.new(|cx| MultiBuffer::singleton(buffer_before_approval.clone(), cx)),
32025            Some(project.clone()),
32026            window,
32027            cx,
32028        )
32029    });
32030    cx.run_until_parked();
32031    let fake_language_server = fake_language_servers.next();
32032
32033    cx.read(|cx| {
32034        let file = buffer_before_approval.read(cx).file();
32035        assert_eq!(
32036            language::language_settings::language_settings(Some("Rust".into()), file, cx)
32037                .language_servers,
32038            ["...".to_string()],
32039            "local .zed/settings.json must not apply before trust approval"
32040        )
32041    });
32042
32043    editor.update_in(cx, |editor, window, cx| {
32044        editor.handle_input("1", window, cx);
32045    });
32046    cx.run_until_parked();
32047    cx.executor()
32048        .advance_clock(std::time::Duration::from_secs(1));
32049    assert_eq!(
32050        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire),
32051        0,
32052        "inlay hints must not be queried before trust approval"
32053    );
32054
32055    trusted_worktrees.update(cx, |store, cx| {
32056        store.trust(
32057            &worktree_store,
32058            std::collections::HashSet::from_iter([PathTrust::Worktree(worktree_id)]),
32059            cx,
32060        );
32061    });
32062    cx.run_until_parked();
32063
32064    cx.read(|cx| {
32065        let file = buffer_before_approval.read(cx).file();
32066        assert_eq!(
32067            language::language_settings::language_settings(Some("Rust".into()), file, cx)
32068                .language_servers,
32069            ["override-rust-analyzer".to_string()],
32070            "local .zed/settings.json should apply after trust approval"
32071        )
32072    });
32073    let _fake_language_server = fake_language_server.await.unwrap();
32074    editor.update_in(cx, |editor, window, cx| {
32075        editor.handle_input("1", window, cx);
32076    });
32077    cx.run_until_parked();
32078    cx.executor()
32079        .advance_clock(std::time::Duration::from_secs(1));
32080    assert!(
32081        lsp_inlay_hint_request_count.load(atomic::Ordering::Acquire) > 0,
32082        "inlay hints should be queried after trust approval"
32083    );
32084
32085    let can_trust_after = trusted_worktrees.update(cx, |store, cx| {
32086        store.can_trust(&worktree_store, worktree_id, cx)
32087    });
32088    assert!(can_trust_after, "worktree should be trusted after trust()");
32089}
32090
32091#[gpui::test]
32092fn test_editor_rendering_when_positioned_above_viewport(cx: &mut TestAppContext) {
32093    // This test reproduces a bug where drawing an editor at a position above the viewport
32094    // (simulating what happens when an AutoHeight editor inside a List is scrolled past)
32095    // causes an infinite loop in blocks_in_range.
32096    //
32097    // The issue: when the editor's bounds.origin.y is very negative (above the viewport),
32098    // the content mask intersection produces visible_bounds with origin at the viewport top.
32099    // This makes clipped_top_in_lines very large, causing start_row to exceed max_row.
32100    // When blocks_in_range is called with start_row > max_row, the cursor seeks to the end
32101    // but the while loop after seek never terminates because cursor.next() is a no-op at end.
32102    init_test(cx, |_| {});
32103
32104    let window = cx.add_window(|_, _| gpui::Empty);
32105    let mut cx = VisualTestContext::from_window(*window, cx);
32106
32107    let buffer = cx.update(|_, cx| MultiBuffer::build_simple("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\n", cx));
32108    let editor = cx.new_window_entity(|window, cx| build_editor(buffer, window, cx));
32109
32110    // Simulate a small viewport (500x500 pixels at origin 0,0)
32111    cx.simulate_resize(gpui::size(px(500.), px(500.)));
32112
32113    // Draw the editor at a very negative Y position, simulating an editor that's been
32114    // scrolled way above the visible viewport (like in a List that has scrolled past it).
32115    // The editor is 3000px tall but positioned at y=-10000, so it's entirely above the viewport.
32116    // This should NOT hang - it should just render nothing.
32117    cx.draw(
32118        gpui::point(px(0.), px(-10000.)),
32119        gpui::size(px(500.), px(3000.)),
32120        |_, _| editor.clone().into_any_element(),
32121    );
32122
32123    // If we get here without hanging, the test passes
32124}
32125
32126#[gpui::test]
32127async fn test_diff_review_indicator_created_on_gutter_hover(cx: &mut TestAppContext) {
32128    init_test(cx, |_| {});
32129
32130    let fs = FakeFs::new(cx.executor());
32131    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
32132        .await;
32133
32134    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
32135    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
32136    let workspace = window
32137        .read_with(cx, |mw, _| mw.workspace().clone())
32138        .unwrap();
32139    let cx = &mut VisualTestContext::from_window(*window, cx);
32140
32141    let editor = workspace
32142        .update_in(cx, |workspace, window, cx| {
32143            workspace.open_abs_path(
32144                PathBuf::from(path!("/root/file.txt")),
32145                OpenOptions::default(),
32146                window,
32147                cx,
32148            )
32149        })
32150        .await
32151        .unwrap()
32152        .downcast::<Editor>()
32153        .unwrap();
32154
32155    // Enable diff review button mode
32156    editor.update(cx, |editor, cx| {
32157        editor.set_show_diff_review_button(true, cx);
32158    });
32159
32160    // Initially, no indicator should be present
32161    editor.update(cx, |editor, _cx| {
32162        assert!(
32163            editor.gutter_diff_review_indicator.0.is_none(),
32164            "Indicator should be None initially"
32165        );
32166    });
32167}
32168
32169#[gpui::test]
32170async fn test_diff_review_button_hidden_when_ai_disabled(cx: &mut TestAppContext) {
32171    init_test(cx, |_| {});
32172
32173    // Register DisableAiSettings and set disable_ai to true
32174    cx.update(|cx| {
32175        project::DisableAiSettings::register(cx);
32176        project::DisableAiSettings::override_global(
32177            project::DisableAiSettings { disable_ai: true },
32178            cx,
32179        );
32180    });
32181
32182    let fs = FakeFs::new(cx.executor());
32183    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
32184        .await;
32185
32186    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
32187    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
32188    let workspace = window
32189        .read_with(cx, |mw, _| mw.workspace().clone())
32190        .unwrap();
32191    let cx = &mut VisualTestContext::from_window(*window, cx);
32192
32193    let editor = workspace
32194        .update_in(cx, |workspace, window, cx| {
32195            workspace.open_abs_path(
32196                PathBuf::from(path!("/root/file.txt")),
32197                OpenOptions::default(),
32198                window,
32199                cx,
32200            )
32201        })
32202        .await
32203        .unwrap()
32204        .downcast::<Editor>()
32205        .unwrap();
32206
32207    // Enable diff review button mode
32208    editor.update(cx, |editor, cx| {
32209        editor.set_show_diff_review_button(true, cx);
32210    });
32211
32212    // Verify AI is disabled
32213    cx.read(|cx| {
32214        assert!(
32215            project::DisableAiSettings::get_global(cx).disable_ai,
32216            "AI should be disabled"
32217        );
32218    });
32219
32220    // The indicator should not be created when AI is disabled
32221    // (The mouse_moved handler checks DisableAiSettings before creating the indicator)
32222    editor.update(cx, |editor, _cx| {
32223        assert!(
32224            editor.gutter_diff_review_indicator.0.is_none(),
32225            "Indicator should be None when AI is disabled"
32226        );
32227    });
32228}
32229
32230#[gpui::test]
32231async fn test_diff_review_button_shown_when_ai_enabled(cx: &mut TestAppContext) {
32232    init_test(cx, |_| {});
32233
32234    // Register DisableAiSettings and set disable_ai to false
32235    cx.update(|cx| {
32236        project::DisableAiSettings::register(cx);
32237        project::DisableAiSettings::override_global(
32238            project::DisableAiSettings { disable_ai: false },
32239            cx,
32240        );
32241    });
32242
32243    let fs = FakeFs::new(cx.executor());
32244    fs.insert_tree(path!("/root"), json!({ "file.txt": "hello\nworld\n" }))
32245        .await;
32246
32247    let project = Project::test(fs, [path!("/root").as_ref()], cx).await;
32248    let window = cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
32249    let workspace = window
32250        .read_with(cx, |mw, _| mw.workspace().clone())
32251        .unwrap();
32252    let cx = &mut VisualTestContext::from_window(*window, cx);
32253
32254    let editor = workspace
32255        .update_in(cx, |workspace, window, cx| {
32256            workspace.open_abs_path(
32257                PathBuf::from(path!("/root/file.txt")),
32258                OpenOptions::default(),
32259                window,
32260                cx,
32261            )
32262        })
32263        .await
32264        .unwrap()
32265        .downcast::<Editor>()
32266        .unwrap();
32267
32268    // Enable diff review button mode
32269    editor.update(cx, |editor, cx| {
32270        editor.set_show_diff_review_button(true, cx);
32271    });
32272
32273    // Verify AI is enabled
32274    cx.read(|cx| {
32275        assert!(
32276            !project::DisableAiSettings::get_global(cx).disable_ai,
32277            "AI should be enabled"
32278        );
32279    });
32280
32281    // The show_diff_review_button flag should be true
32282    editor.update(cx, |editor, _cx| {
32283        assert!(
32284            editor.show_diff_review_button(),
32285            "show_diff_review_button should be true"
32286        );
32287    });
32288}
32289
32290/// Helper function to create a DiffHunkKey for testing.
32291/// Uses Anchor::min() as a placeholder anchor since these tests don't need
32292/// real buffer positioning.
32293fn test_hunk_key(file_path: &str) -> DiffHunkKey {
32294    DiffHunkKey {
32295        file_path: if file_path.is_empty() {
32296            Arc::from(util::rel_path::RelPath::empty())
32297        } else {
32298            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
32299        },
32300        hunk_start_anchor: Anchor::min(),
32301    }
32302}
32303
32304/// Helper function to create a DiffHunkKey with a specific anchor for testing.
32305fn test_hunk_key_with_anchor(file_path: &str, anchor: Anchor) -> DiffHunkKey {
32306    DiffHunkKey {
32307        file_path: if file_path.is_empty() {
32308            Arc::from(util::rel_path::RelPath::empty())
32309        } else {
32310            Arc::from(util::rel_path::RelPath::unix(file_path).unwrap())
32311        },
32312        hunk_start_anchor: anchor,
32313    }
32314}
32315
32316/// Helper function to add a review comment with default anchors for testing.
32317fn add_test_comment(
32318    editor: &mut Editor,
32319    key: DiffHunkKey,
32320    comment: &str,
32321    cx: &mut Context<Editor>,
32322) -> usize {
32323    editor.add_review_comment(key, comment.to_string(), Anchor::min()..Anchor::max(), cx)
32324}
32325
32326#[gpui::test]
32327fn test_review_comment_add_to_hunk(cx: &mut TestAppContext) {
32328    init_test(cx, |_| {});
32329
32330    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32331
32332    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
32333        let key = test_hunk_key("");
32334
32335        let id = add_test_comment(editor, key.clone(), "Test comment", cx);
32336
32337        let snapshot = editor.buffer().read(cx).snapshot(cx);
32338        assert_eq!(editor.total_review_comment_count(), 1);
32339        assert_eq!(editor.hunk_comment_count(&key, &snapshot), 1);
32340
32341        let comments = editor.comments_for_hunk(&key, &snapshot);
32342        assert_eq!(comments.len(), 1);
32343        assert_eq!(comments[0].comment, "Test comment");
32344        assert_eq!(comments[0].id, id);
32345    });
32346}
32347
32348#[gpui::test]
32349fn test_review_comments_are_per_hunk(cx: &mut TestAppContext) {
32350    init_test(cx, |_| {});
32351
32352    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32353
32354    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
32355        let snapshot = editor.buffer().read(cx).snapshot(cx);
32356        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
32357        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
32358        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
32359        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
32360
32361        add_test_comment(editor, key1.clone(), "Comment for file1", cx);
32362        add_test_comment(editor, key2.clone(), "Comment for file2", cx);
32363
32364        let snapshot = editor.buffer().read(cx).snapshot(cx);
32365        assert_eq!(editor.total_review_comment_count(), 2);
32366        assert_eq!(editor.hunk_comment_count(&key1, &snapshot), 1);
32367        assert_eq!(editor.hunk_comment_count(&key2, &snapshot), 1);
32368
32369        assert_eq!(
32370            editor.comments_for_hunk(&key1, &snapshot)[0].comment,
32371            "Comment for file1"
32372        );
32373        assert_eq!(
32374            editor.comments_for_hunk(&key2, &snapshot)[0].comment,
32375            "Comment for file2"
32376        );
32377    });
32378}
32379
32380#[gpui::test]
32381fn test_review_comment_remove(cx: &mut TestAppContext) {
32382    init_test(cx, |_| {});
32383
32384    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32385
32386    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
32387        let key = test_hunk_key("");
32388
32389        let id = add_test_comment(editor, key, "To be removed", cx);
32390
32391        assert_eq!(editor.total_review_comment_count(), 1);
32392
32393        let removed = editor.remove_review_comment(id, cx);
32394        assert!(removed);
32395        assert_eq!(editor.total_review_comment_count(), 0);
32396
32397        // Try to remove again
32398        let removed_again = editor.remove_review_comment(id, cx);
32399        assert!(!removed_again);
32400    });
32401}
32402
32403#[gpui::test]
32404fn test_review_comment_update(cx: &mut TestAppContext) {
32405    init_test(cx, |_| {});
32406
32407    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32408
32409    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
32410        let key = test_hunk_key("");
32411
32412        let id = add_test_comment(editor, key.clone(), "Original text", cx);
32413
32414        let updated = editor.update_review_comment(id, "Updated text".to_string(), cx);
32415        assert!(updated);
32416
32417        let snapshot = editor.buffer().read(cx).snapshot(cx);
32418        let comments = editor.comments_for_hunk(&key, &snapshot);
32419        assert_eq!(comments[0].comment, "Updated text");
32420        assert!(!comments[0].is_editing); // Should clear editing flag
32421    });
32422}
32423
32424#[gpui::test]
32425fn test_review_comment_take_all(cx: &mut TestAppContext) {
32426    init_test(cx, |_| {});
32427
32428    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32429
32430    _ = editor.update(cx, |editor: &mut Editor, _window, cx| {
32431        let snapshot = editor.buffer().read(cx).snapshot(cx);
32432        let anchor1 = snapshot.anchor_before(Point::new(0, 0));
32433        let anchor2 = snapshot.anchor_before(Point::new(0, 0));
32434        let key1 = test_hunk_key_with_anchor("file1.rs", anchor1);
32435        let key2 = test_hunk_key_with_anchor("file2.rs", anchor2);
32436
32437        let id1 = add_test_comment(editor, key1.clone(), "Comment 1", cx);
32438        let id2 = add_test_comment(editor, key1.clone(), "Comment 2", cx);
32439        let id3 = add_test_comment(editor, key2.clone(), "Comment 3", cx);
32440
32441        // IDs should be sequential starting from 0
32442        assert_eq!(id1, 0);
32443        assert_eq!(id2, 1);
32444        assert_eq!(id3, 2);
32445
32446        assert_eq!(editor.total_review_comment_count(), 3);
32447
32448        let taken = editor.take_all_review_comments(cx);
32449
32450        // Should have 2 entries (one per hunk)
32451        assert_eq!(taken.len(), 2);
32452
32453        // Total comments should be 3
32454        let total: usize = taken
32455            .iter()
32456            .map(|(_, comments): &(DiffHunkKey, Vec<StoredReviewComment>)| comments.len())
32457            .sum();
32458        assert_eq!(total, 3);
32459
32460        // Storage should be empty
32461        assert_eq!(editor.total_review_comment_count(), 0);
32462
32463        // After taking all comments, ID counter should reset
32464        // New comments should get IDs starting from 0 again
32465        let new_id1 = add_test_comment(editor, key1, "New Comment 1", cx);
32466        let new_id2 = add_test_comment(editor, key2, "New Comment 2", cx);
32467
32468        assert_eq!(new_id1, 0, "ID counter should reset after take_all");
32469        assert_eq!(new_id2, 1, "IDs should be sequential after reset");
32470    });
32471}
32472
32473#[gpui::test]
32474fn test_diff_review_overlay_show_and_dismiss(cx: &mut TestAppContext) {
32475    init_test(cx, |_| {});
32476
32477    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32478
32479    // Show overlay
32480    editor
32481        .update(cx, |editor, window, cx| {
32482            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
32483        })
32484        .unwrap();
32485
32486    // Verify overlay is shown
32487    editor
32488        .update(cx, |editor, _window, cx| {
32489            assert!(!editor.diff_review_overlays.is_empty());
32490            assert_eq!(editor.diff_review_line_range(cx), Some((0, 0)));
32491            assert!(editor.diff_review_prompt_editor().is_some());
32492        })
32493        .unwrap();
32494
32495    // Dismiss overlay
32496    editor
32497        .update(cx, |editor, _window, cx| {
32498            editor.dismiss_all_diff_review_overlays(cx);
32499        })
32500        .unwrap();
32501
32502    // Verify overlay is dismissed
32503    editor
32504        .update(cx, |editor, _window, cx| {
32505            assert!(editor.diff_review_overlays.is_empty());
32506            assert_eq!(editor.diff_review_line_range(cx), None);
32507            assert!(editor.diff_review_prompt_editor().is_none());
32508        })
32509        .unwrap();
32510}
32511
32512#[gpui::test]
32513fn test_diff_review_overlay_dismiss_via_cancel(cx: &mut TestAppContext) {
32514    init_test(cx, |_| {});
32515
32516    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32517
32518    // Show overlay
32519    editor
32520        .update(cx, |editor, window, cx| {
32521            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
32522        })
32523        .unwrap();
32524
32525    // Verify overlay is shown
32526    editor
32527        .update(cx, |editor, _window, _cx| {
32528            assert!(!editor.diff_review_overlays.is_empty());
32529        })
32530        .unwrap();
32531
32532    // Dismiss via dismiss_menus_and_popups (which is called by cancel action)
32533    editor
32534        .update(cx, |editor, window, cx| {
32535            editor.dismiss_menus_and_popups(true, window, cx);
32536        })
32537        .unwrap();
32538
32539    // Verify overlay is dismissed
32540    editor
32541        .update(cx, |editor, _window, _cx| {
32542            assert!(editor.diff_review_overlays.is_empty());
32543        })
32544        .unwrap();
32545}
32546
32547#[gpui::test]
32548fn test_diff_review_empty_comment_not_submitted(cx: &mut TestAppContext) {
32549    init_test(cx, |_| {});
32550
32551    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32552
32553    // Show overlay
32554    editor
32555        .update(cx, |editor, window, cx| {
32556            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
32557        })
32558        .unwrap();
32559
32560    // Try to submit without typing anything (empty comment)
32561    editor
32562        .update(cx, |editor, window, cx| {
32563            editor.submit_diff_review_comment(window, cx);
32564        })
32565        .unwrap();
32566
32567    // Verify no comment was added
32568    editor
32569        .update(cx, |editor, _window, _cx| {
32570            assert_eq!(editor.total_review_comment_count(), 0);
32571        })
32572        .unwrap();
32573
32574    // Try to submit with whitespace-only comment
32575    editor
32576        .update(cx, |editor, window, cx| {
32577            if let Some(prompt_editor) = editor.diff_review_prompt_editor().cloned() {
32578                prompt_editor.update(cx, |pe, cx| {
32579                    pe.insert("   \n\t  ", window, cx);
32580                });
32581            }
32582            editor.submit_diff_review_comment(window, cx);
32583        })
32584        .unwrap();
32585
32586    // Verify still no comment was added
32587    editor
32588        .update(cx, |editor, _window, _cx| {
32589            assert_eq!(editor.total_review_comment_count(), 0);
32590        })
32591        .unwrap();
32592}
32593
32594#[gpui::test]
32595fn test_diff_review_inline_edit_flow(cx: &mut TestAppContext) {
32596    init_test(cx, |_| {});
32597
32598    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32599
32600    // Add a comment directly
32601    let comment_id = editor
32602        .update(cx, |editor, _window, cx| {
32603            let key = test_hunk_key("");
32604            add_test_comment(editor, key, "Original comment", cx)
32605        })
32606        .unwrap();
32607
32608    // Set comment to editing mode
32609    editor
32610        .update(cx, |editor, _window, cx| {
32611            editor.set_comment_editing(comment_id, true, cx);
32612        })
32613        .unwrap();
32614
32615    // Verify editing flag is set
32616    editor
32617        .update(cx, |editor, _window, cx| {
32618            let key = test_hunk_key("");
32619            let snapshot = editor.buffer().read(cx).snapshot(cx);
32620            let comments = editor.comments_for_hunk(&key, &snapshot);
32621            assert_eq!(comments.len(), 1);
32622            assert!(comments[0].is_editing);
32623        })
32624        .unwrap();
32625
32626    // Update the comment
32627    editor
32628        .update(cx, |editor, _window, cx| {
32629            let updated =
32630                editor.update_review_comment(comment_id, "Updated comment".to_string(), cx);
32631            assert!(updated);
32632        })
32633        .unwrap();
32634
32635    // Verify comment was updated and editing flag is cleared
32636    editor
32637        .update(cx, |editor, _window, cx| {
32638            let key = test_hunk_key("");
32639            let snapshot = editor.buffer().read(cx).snapshot(cx);
32640            let comments = editor.comments_for_hunk(&key, &snapshot);
32641            assert_eq!(comments[0].comment, "Updated comment");
32642            assert!(!comments[0].is_editing);
32643        })
32644        .unwrap();
32645}
32646
32647#[gpui::test]
32648fn test_orphaned_comments_are_cleaned_up(cx: &mut TestAppContext) {
32649    init_test(cx, |_| {});
32650
32651    // Create an editor with some text
32652    let editor = cx.add_window(|window, cx| {
32653        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
32654        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32655        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
32656    });
32657
32658    // Add a comment with an anchor on line 2
32659    editor
32660        .update(cx, |editor, _window, cx| {
32661            let snapshot = editor.buffer().read(cx).snapshot(cx);
32662            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
32663            let key = DiffHunkKey {
32664                file_path: Arc::from(util::rel_path::RelPath::empty()),
32665                hunk_start_anchor: anchor,
32666            };
32667            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
32668            assert_eq!(editor.total_review_comment_count(), 1);
32669        })
32670        .unwrap();
32671
32672    // Delete all content (this should orphan the comment's anchor)
32673    editor
32674        .update(cx, |editor, window, cx| {
32675            editor.select_all(&SelectAll, window, cx);
32676            editor.insert("completely new content", window, cx);
32677        })
32678        .unwrap();
32679
32680    // Trigger cleanup
32681    editor
32682        .update(cx, |editor, _window, cx| {
32683            editor.cleanup_orphaned_review_comments(cx);
32684            // Comment should be removed because its anchor is invalid
32685            assert_eq!(editor.total_review_comment_count(), 0);
32686        })
32687        .unwrap();
32688}
32689
32690#[gpui::test]
32691fn test_orphaned_comments_cleanup_called_on_buffer_edit(cx: &mut TestAppContext) {
32692    init_test(cx, |_| {});
32693
32694    // Create an editor with some text
32695    let editor = cx.add_window(|window, cx| {
32696        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
32697        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32698        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
32699    });
32700
32701    // Add a comment with an anchor on line 2
32702    editor
32703        .update(cx, |editor, _window, cx| {
32704            let snapshot = editor.buffer().read(cx).snapshot(cx);
32705            let anchor = snapshot.anchor_after(Point::new(1, 0)); // Line 2
32706            let key = DiffHunkKey {
32707                file_path: Arc::from(util::rel_path::RelPath::empty()),
32708                hunk_start_anchor: anchor,
32709            };
32710            editor.add_review_comment(key, "Comment on line 2".to_string(), anchor..anchor, cx);
32711            assert_eq!(editor.total_review_comment_count(), 1);
32712        })
32713        .unwrap();
32714
32715    // Edit the buffer - this should trigger cleanup via on_buffer_event
32716    // Delete all content which orphans the anchor
32717    editor
32718        .update(cx, |editor, window, cx| {
32719            editor.select_all(&SelectAll, window, cx);
32720            editor.insert("completely new content", window, cx);
32721            // The cleanup is called automatically in on_buffer_event when Edited fires
32722        })
32723        .unwrap();
32724
32725    // Verify cleanup happened automatically (not manually triggered)
32726    editor
32727        .update(cx, |editor, _window, _cx| {
32728            // Comment should be removed because its anchor became invalid
32729            // and cleanup was called automatically on buffer edit
32730            assert_eq!(editor.total_review_comment_count(), 0);
32731        })
32732        .unwrap();
32733}
32734
32735#[gpui::test]
32736fn test_comments_stored_for_multiple_hunks(cx: &mut TestAppContext) {
32737    init_test(cx, |_| {});
32738
32739    // This test verifies that comments can be stored for multiple different hunks
32740    // and that hunk_comment_count correctly identifies comments per hunk.
32741    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32742
32743    _ = editor.update(cx, |editor, _window, cx| {
32744        let snapshot = editor.buffer().read(cx).snapshot(cx);
32745
32746        // Create two different hunk keys (simulating two different files)
32747        let anchor = snapshot.anchor_before(Point::new(0, 0));
32748        let key1 = DiffHunkKey {
32749            file_path: Arc::from(util::rel_path::RelPath::unix("file1.rs").unwrap()),
32750            hunk_start_anchor: anchor,
32751        };
32752        let key2 = DiffHunkKey {
32753            file_path: Arc::from(util::rel_path::RelPath::unix("file2.rs").unwrap()),
32754            hunk_start_anchor: anchor,
32755        };
32756
32757        // Add comments to first hunk
32758        editor.add_review_comment(
32759            key1.clone(),
32760            "Comment 1 for file1".to_string(),
32761            anchor..anchor,
32762            cx,
32763        );
32764        editor.add_review_comment(
32765            key1.clone(),
32766            "Comment 2 for file1".to_string(),
32767            anchor..anchor,
32768            cx,
32769        );
32770
32771        // Add comment to second hunk
32772        editor.add_review_comment(
32773            key2.clone(),
32774            "Comment for file2".to_string(),
32775            anchor..anchor,
32776            cx,
32777        );
32778
32779        // Verify total count
32780        assert_eq!(editor.total_review_comment_count(), 3);
32781
32782        // Verify per-hunk counts
32783        let snapshot = editor.buffer().read(cx).snapshot(cx);
32784        assert_eq!(
32785            editor.hunk_comment_count(&key1, &snapshot),
32786            2,
32787            "file1 should have 2 comments"
32788        );
32789        assert_eq!(
32790            editor.hunk_comment_count(&key2, &snapshot),
32791            1,
32792            "file2 should have 1 comment"
32793        );
32794
32795        // Verify comments_for_hunk returns correct comments
32796        let file1_comments = editor.comments_for_hunk(&key1, &snapshot);
32797        assert_eq!(file1_comments.len(), 2);
32798        assert_eq!(file1_comments[0].comment, "Comment 1 for file1");
32799        assert_eq!(file1_comments[1].comment, "Comment 2 for file1");
32800
32801        let file2_comments = editor.comments_for_hunk(&key2, &snapshot);
32802        assert_eq!(file2_comments.len(), 1);
32803        assert_eq!(file2_comments[0].comment, "Comment for file2");
32804    });
32805}
32806
32807#[gpui::test]
32808fn test_same_hunk_detected_by_matching_keys(cx: &mut TestAppContext) {
32809    init_test(cx, |_| {});
32810
32811    // This test verifies that hunk_keys_match correctly identifies when two
32812    // DiffHunkKeys refer to the same hunk (same file path and anchor point).
32813    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32814
32815    _ = editor.update(cx, |editor, _window, cx| {
32816        let snapshot = editor.buffer().read(cx).snapshot(cx);
32817        let anchor = snapshot.anchor_before(Point::new(0, 0));
32818
32819        // Create two keys with the same file path and anchor
32820        let key1 = DiffHunkKey {
32821            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
32822            hunk_start_anchor: anchor,
32823        };
32824        let key2 = DiffHunkKey {
32825            file_path: Arc::from(util::rel_path::RelPath::unix("file.rs").unwrap()),
32826            hunk_start_anchor: anchor,
32827        };
32828
32829        // Add comment to first key
32830        editor.add_review_comment(key1, "Test comment".to_string(), anchor..anchor, cx);
32831
32832        // Verify second key (same hunk) finds the comment
32833        let snapshot = editor.buffer().read(cx).snapshot(cx);
32834        assert_eq!(
32835            editor.hunk_comment_count(&key2, &snapshot),
32836            1,
32837            "Same hunk should find the comment"
32838        );
32839
32840        // Create a key with different file path
32841        let different_file_key = DiffHunkKey {
32842            file_path: Arc::from(util::rel_path::RelPath::unix("other.rs").unwrap()),
32843            hunk_start_anchor: anchor,
32844        };
32845
32846        // Different file should not find the comment
32847        assert_eq!(
32848            editor.hunk_comment_count(&different_file_key, &snapshot),
32849            0,
32850            "Different file should not find the comment"
32851        );
32852    });
32853}
32854
32855#[gpui::test]
32856fn test_overlay_comments_expanded_state(cx: &mut TestAppContext) {
32857    init_test(cx, |_| {});
32858
32859    // This test verifies that set_diff_review_comments_expanded correctly
32860    // updates the expanded state of overlays.
32861    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
32862
32863    // Show overlay
32864    editor
32865        .update(cx, |editor, window, cx| {
32866            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(0), window, cx);
32867        })
32868        .unwrap();
32869
32870    // Verify initially expanded (default)
32871    editor
32872        .update(cx, |editor, _window, _cx| {
32873            assert!(
32874                editor.diff_review_overlays[0].comments_expanded,
32875                "Should be expanded by default"
32876            );
32877        })
32878        .unwrap();
32879
32880    // Set to collapsed using the public method
32881    editor
32882        .update(cx, |editor, _window, cx| {
32883            editor.set_diff_review_comments_expanded(false, cx);
32884        })
32885        .unwrap();
32886
32887    // Verify collapsed
32888    editor
32889        .update(cx, |editor, _window, _cx| {
32890            assert!(
32891                !editor.diff_review_overlays[0].comments_expanded,
32892                "Should be collapsed after setting to false"
32893            );
32894        })
32895        .unwrap();
32896
32897    // Set back to expanded
32898    editor
32899        .update(cx, |editor, _window, cx| {
32900            editor.set_diff_review_comments_expanded(true, cx);
32901        })
32902        .unwrap();
32903
32904    // Verify expanded again
32905    editor
32906        .update(cx, |editor, _window, _cx| {
32907            assert!(
32908                editor.diff_review_overlays[0].comments_expanded,
32909                "Should be expanded after setting to true"
32910            );
32911        })
32912        .unwrap();
32913}
32914
32915#[gpui::test]
32916fn test_diff_review_multiline_selection(cx: &mut TestAppContext) {
32917    init_test(cx, |_| {});
32918
32919    // Create an editor with multiple lines of text
32920    let editor = cx.add_window(|window, cx| {
32921        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\nline 4\nline 5\n", cx));
32922        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32923        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
32924    });
32925
32926    // Test showing overlay with a multi-line selection (lines 1-3, which are rows 0-2)
32927    editor
32928        .update(cx, |editor, window, cx| {
32929            editor.show_diff_review_overlay(DisplayRow(0)..DisplayRow(2), window, cx);
32930        })
32931        .unwrap();
32932
32933    // Verify line range
32934    editor
32935        .update(cx, |editor, _window, cx| {
32936            assert!(!editor.diff_review_overlays.is_empty());
32937            assert_eq!(editor.diff_review_line_range(cx), Some((0, 2)));
32938        })
32939        .unwrap();
32940
32941    // Dismiss and test with reversed range (end < start)
32942    editor
32943        .update(cx, |editor, _window, cx| {
32944            editor.dismiss_all_diff_review_overlays(cx);
32945        })
32946        .unwrap();
32947
32948    // Show overlay with reversed range - should normalize it
32949    editor
32950        .update(cx, |editor, window, cx| {
32951            editor.show_diff_review_overlay(DisplayRow(3)..DisplayRow(1), window, cx);
32952        })
32953        .unwrap();
32954
32955    // Verify range is normalized (start <= end)
32956    editor
32957        .update(cx, |editor, _window, cx| {
32958            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
32959        })
32960        .unwrap();
32961}
32962
32963#[gpui::test]
32964fn test_diff_review_drag_state(cx: &mut TestAppContext) {
32965    init_test(cx, |_| {});
32966
32967    let editor = cx.add_window(|window, cx| {
32968        let buffer = cx.new(|cx| Buffer::local("line 1\nline 2\nline 3\n", cx));
32969        let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
32970        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
32971    });
32972
32973    // Initially no drag state
32974    editor
32975        .update(cx, |editor, _window, _cx| {
32976            assert!(editor.diff_review_drag_state.is_none());
32977        })
32978        .unwrap();
32979
32980    // Start drag at row 1
32981    editor
32982        .update(cx, |editor, window, cx| {
32983            editor.start_diff_review_drag(DisplayRow(1), window, cx);
32984        })
32985        .unwrap();
32986
32987    // Verify drag state is set
32988    editor
32989        .update(cx, |editor, window, cx| {
32990            assert!(editor.diff_review_drag_state.is_some());
32991            let snapshot = editor.snapshot(window, cx);
32992            let range = editor
32993                .diff_review_drag_state
32994                .as_ref()
32995                .unwrap()
32996                .row_range(&snapshot.display_snapshot);
32997            assert_eq!(*range.start(), DisplayRow(1));
32998            assert_eq!(*range.end(), DisplayRow(1));
32999        })
33000        .unwrap();
33001
33002    // Update drag to row 3
33003    editor
33004        .update(cx, |editor, window, cx| {
33005            editor.update_diff_review_drag(DisplayRow(3), window, cx);
33006        })
33007        .unwrap();
33008
33009    // Verify drag state is updated
33010    editor
33011        .update(cx, |editor, window, cx| {
33012            assert!(editor.diff_review_drag_state.is_some());
33013            let snapshot = editor.snapshot(window, cx);
33014            let range = editor
33015                .diff_review_drag_state
33016                .as_ref()
33017                .unwrap()
33018                .row_range(&snapshot.display_snapshot);
33019            assert_eq!(*range.start(), DisplayRow(1));
33020            assert_eq!(*range.end(), DisplayRow(3));
33021        })
33022        .unwrap();
33023
33024    // End drag - should show overlay
33025    editor
33026        .update(cx, |editor, window, cx| {
33027            editor.end_diff_review_drag(window, cx);
33028        })
33029        .unwrap();
33030
33031    // Verify drag state is cleared and overlay is shown
33032    editor
33033        .update(cx, |editor, _window, cx| {
33034            assert!(editor.diff_review_drag_state.is_none());
33035            assert!(!editor.diff_review_overlays.is_empty());
33036            assert_eq!(editor.diff_review_line_range(cx), Some((1, 3)));
33037        })
33038        .unwrap();
33039}
33040
33041#[gpui::test]
33042fn test_diff_review_drag_cancel(cx: &mut TestAppContext) {
33043    init_test(cx, |_| {});
33044
33045    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33046
33047    // Start drag
33048    editor
33049        .update(cx, |editor, window, cx| {
33050            editor.start_diff_review_drag(DisplayRow(0), window, cx);
33051        })
33052        .unwrap();
33053
33054    // Verify drag state is set
33055    editor
33056        .update(cx, |editor, _window, _cx| {
33057            assert!(editor.diff_review_drag_state.is_some());
33058        })
33059        .unwrap();
33060
33061    // Cancel drag
33062    editor
33063        .update(cx, |editor, _window, cx| {
33064            editor.cancel_diff_review_drag(cx);
33065        })
33066        .unwrap();
33067
33068    // Verify drag state is cleared and no overlay was created
33069    editor
33070        .update(cx, |editor, _window, _cx| {
33071            assert!(editor.diff_review_drag_state.is_none());
33072            assert!(editor.diff_review_overlays.is_empty());
33073        })
33074        .unwrap();
33075}
33076
33077#[gpui::test]
33078fn test_calculate_overlay_height(cx: &mut TestAppContext) {
33079    init_test(cx, |_| {});
33080
33081    // This test verifies that calculate_overlay_height returns correct heights
33082    // based on comment count and expanded state.
33083    let editor = cx.add_window(|window, cx| Editor::single_line(window, cx));
33084
33085    _ = editor.update(cx, |editor, _window, cx| {
33086        let snapshot = editor.buffer().read(cx).snapshot(cx);
33087        let anchor = snapshot.anchor_before(Point::new(0, 0));
33088        let key = DiffHunkKey {
33089            file_path: Arc::from(util::rel_path::RelPath::empty()),
33090            hunk_start_anchor: anchor,
33091        };
33092
33093        // No comments: base height of 2
33094        let height_no_comments = editor.calculate_overlay_height(&key, true, &snapshot);
33095        assert_eq!(
33096            height_no_comments, 2,
33097            "Base height should be 2 with no comments"
33098        );
33099
33100        // Add one comment
33101        editor.add_review_comment(key.clone(), "Comment 1".to_string(), anchor..anchor, cx);
33102
33103        let snapshot = editor.buffer().read(cx).snapshot(cx);
33104
33105        // With comments expanded: base (2) + header (1) + 2 per comment
33106        let height_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
33107        assert_eq!(
33108            height_expanded,
33109            2 + 1 + 2, // base + header + 1 comment * 2
33110            "Height with 1 comment expanded"
33111        );
33112
33113        // With comments collapsed: base (2) + header (1)
33114        let height_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
33115        assert_eq!(
33116            height_collapsed,
33117            2 + 1, // base + header only
33118            "Height with comments collapsed"
33119        );
33120
33121        // Add more comments
33122        editor.add_review_comment(key.clone(), "Comment 2".to_string(), anchor..anchor, cx);
33123        editor.add_review_comment(key.clone(), "Comment 3".to_string(), anchor..anchor, cx);
33124
33125        let snapshot = editor.buffer().read(cx).snapshot(cx);
33126
33127        // With 3 comments expanded
33128        let height_3_expanded = editor.calculate_overlay_height(&key, true, &snapshot);
33129        assert_eq!(
33130            height_3_expanded,
33131            2 + 1 + (3 * 2), // base + header + 3 comments * 2
33132            "Height with 3 comments expanded"
33133        );
33134
33135        // Collapsed height stays the same regardless of comment count
33136        let height_3_collapsed = editor.calculate_overlay_height(&key, false, &snapshot);
33137        assert_eq!(
33138            height_3_collapsed,
33139            2 + 1, // base + header only
33140            "Height with 3 comments collapsed should be same as 1 comment collapsed"
33141        );
33142    });
33143}
33144
33145#[gpui::test]
33146async fn test_move_to_start_end_of_larger_syntax_node_single_cursor(cx: &mut TestAppContext) {
33147    init_test(cx, |_| {});
33148
33149    let language = Arc::new(Language::new(
33150        LanguageConfig::default(),
33151        Some(tree_sitter_rust::LANGUAGE.into()),
33152    ));
33153
33154    let text = r#"
33155        fn main() {
33156            let x = foo(1, 2);
33157        }
33158    "#
33159    .unindent();
33160
33161    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
33162    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33163    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33164
33165    editor
33166        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33167        .await;
33168
33169    // Test case 1: Move to end of syntax nodes
33170    editor.update_in(cx, |editor, window, cx| {
33171        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33172            s.select_display_ranges([
33173                DisplayPoint::new(DisplayRow(1), 16)..DisplayPoint::new(DisplayRow(1), 16)
33174            ]);
33175        });
33176    });
33177    editor.update(cx, |editor, cx| {
33178        assert_text_with_selections(
33179            editor,
33180            indoc! {r#"
33181                fn main() {
33182                    let x = foo(ˇ1, 2);
33183                }
33184            "#},
33185            cx,
33186        );
33187    });
33188    editor.update_in(cx, |editor, window, cx| {
33189        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
33190    });
33191    editor.update(cx, |editor, cx| {
33192        assert_text_with_selections(
33193            editor,
33194            indoc! {r#"
33195                fn main() {
33196                    let x = foo(1ˇ, 2);
33197                }
33198            "#},
33199            cx,
33200        );
33201    });
33202    editor.update_in(cx, |editor, window, cx| {
33203        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
33204    });
33205    editor.update(cx, |editor, cx| {
33206        assert_text_with_selections(
33207            editor,
33208            indoc! {r#"
33209                fn main() {
33210                    let x = foo(1, 2)ˇ;
33211                }
33212            "#},
33213            cx,
33214        );
33215    });
33216    editor.update_in(cx, |editor, window, cx| {
33217        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
33218    });
33219    editor.update(cx, |editor, cx| {
33220        assert_text_with_selections(
33221            editor,
33222            indoc! {r#"
33223                fn main() {
33224                    let x = foo(1, 2);ˇ
33225                }
33226            "#},
33227            cx,
33228        );
33229    });
33230    editor.update_in(cx, |editor, window, cx| {
33231        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
33232    });
33233    editor.update(cx, |editor, cx| {
33234        assert_text_with_selections(
33235            editor,
33236            indoc! {r#"
33237                fn main() {
33238                    let x = foo(1, 2);
3323933240            "#},
33241            cx,
33242        );
33243    });
33244
33245    // Test case 2: Move to start of syntax nodes
33246    editor.update_in(cx, |editor, window, cx| {
33247        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33248            s.select_display_ranges([
33249                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20)
33250            ]);
33251        });
33252    });
33253    editor.update(cx, |editor, cx| {
33254        assert_text_with_selections(
33255            editor,
33256            indoc! {r#"
33257                fn main() {
33258                    let x = foo(1, 2ˇ);
33259                }
33260            "#},
33261            cx,
33262        );
33263    });
33264    editor.update_in(cx, |editor, window, cx| {
33265        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
33266    });
33267    editor.update(cx, |editor, cx| {
33268        assert_text_with_selections(
33269            editor,
33270            indoc! {r#"
33271                fn main() {
33272                    let x = fooˇ(1, 2);
33273                }
33274            "#},
33275            cx,
33276        );
33277    });
33278    editor.update_in(cx, |editor, window, cx| {
33279        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
33280    });
33281    editor.update(cx, |editor, cx| {
33282        assert_text_with_selections(
33283            editor,
33284            indoc! {r#"
33285                fn main() {
33286                    let x = ˇfoo(1, 2);
33287                }
33288            "#},
33289            cx,
33290        );
33291    });
33292    editor.update_in(cx, |editor, window, cx| {
33293        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
33294    });
33295    editor.update(cx, |editor, cx| {
33296        assert_text_with_selections(
33297            editor,
33298            indoc! {r#"
33299                fn main() {
33300                    ˇlet x = foo(1, 2);
33301                }
33302            "#},
33303            cx,
33304        );
33305    });
33306    editor.update_in(cx, |editor, window, cx| {
33307        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
33308    });
33309    editor.update(cx, |editor, cx| {
33310        assert_text_with_selections(
33311            editor,
33312            indoc! {r#"
33313                fn main() ˇ{
33314                    let x = foo(1, 2);
33315                }
33316            "#},
33317            cx,
33318        );
33319    });
33320    editor.update_in(cx, |editor, window, cx| {
33321        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
33322    });
33323    editor.update(cx, |editor, cx| {
33324        assert_text_with_selections(
33325            editor,
33326            indoc! {r#"
33327                ˇfn main() {
33328                    let x = foo(1, 2);
33329                }
33330            "#},
33331            cx,
33332        );
33333    });
33334}
33335
33336#[gpui::test]
33337async fn test_move_to_start_end_of_larger_syntax_node_two_cursors(cx: &mut TestAppContext) {
33338    init_test(cx, |_| {});
33339
33340    let language = Arc::new(Language::new(
33341        LanguageConfig::default(),
33342        Some(tree_sitter_rust::LANGUAGE.into()),
33343    ));
33344
33345    let text = r#"
33346        fn main() {
33347            let x = foo(1, 2);
33348            let y = bar(3, 4);
33349        }
33350    "#
33351    .unindent();
33352
33353    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
33354    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33355    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33356
33357    editor
33358        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33359        .await;
33360
33361    // Test case 1: Move to end of syntax nodes with two cursors
33362    editor.update_in(cx, |editor, window, cx| {
33363        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33364            s.select_display_ranges([
33365                DisplayPoint::new(DisplayRow(1), 20)..DisplayPoint::new(DisplayRow(1), 20),
33366                DisplayPoint::new(DisplayRow(2), 20)..DisplayPoint::new(DisplayRow(2), 20),
33367            ]);
33368        });
33369    });
33370    editor.update(cx, |editor, cx| {
33371        assert_text_with_selections(
33372            editor,
33373            indoc! {r#"
33374                fn main() {
33375                    let x = foo(1, 2ˇ);
33376                    let y = bar(3, 4ˇ);
33377                }
33378            "#},
33379            cx,
33380        );
33381    });
33382    editor.update_in(cx, |editor, window, cx| {
33383        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
33384    });
33385    editor.update(cx, |editor, cx| {
33386        assert_text_with_selections(
33387            editor,
33388            indoc! {r#"
33389                fn main() {
33390                    let x = foo(1, 2)ˇ;
33391                    let y = bar(3, 4)ˇ;
33392                }
33393            "#},
33394            cx,
33395        );
33396    });
33397    editor.update_in(cx, |editor, window, cx| {
33398        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
33399    });
33400    editor.update(cx, |editor, cx| {
33401        assert_text_with_selections(
33402            editor,
33403            indoc! {r#"
33404                fn main() {
33405                    let x = foo(1, 2);ˇ
33406                    let y = bar(3, 4);ˇ
33407                }
33408            "#},
33409            cx,
33410        );
33411    });
33412
33413    // Test case 2: Move to start of syntax nodes with two cursors
33414    editor.update_in(cx, |editor, window, cx| {
33415        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33416            s.select_display_ranges([
33417                DisplayPoint::new(DisplayRow(1), 19)..DisplayPoint::new(DisplayRow(1), 19),
33418                DisplayPoint::new(DisplayRow(2), 19)..DisplayPoint::new(DisplayRow(2), 19),
33419            ]);
33420        });
33421    });
33422    editor.update(cx, |editor, cx| {
33423        assert_text_with_selections(
33424            editor,
33425            indoc! {r#"
33426                fn main() {
33427                    let x = foo(1, ˇ2);
33428                    let y = bar(3, ˇ4);
33429                }
33430            "#},
33431            cx,
33432        );
33433    });
33434    editor.update_in(cx, |editor, window, cx| {
33435        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
33436    });
33437    editor.update(cx, |editor, cx| {
33438        assert_text_with_selections(
33439            editor,
33440            indoc! {r#"
33441                fn main() {
33442                    let x = fooˇ(1, 2);
33443                    let y = barˇ(3, 4);
33444                }
33445            "#},
33446            cx,
33447        );
33448    });
33449    editor.update_in(cx, |editor, window, cx| {
33450        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
33451    });
33452    editor.update(cx, |editor, cx| {
33453        assert_text_with_selections(
33454            editor,
33455            indoc! {r#"
33456                fn main() {
33457                    let x = ˇfoo(1, 2);
33458                    let y = ˇbar(3, 4);
33459                }
33460            "#},
33461            cx,
33462        );
33463    });
33464    editor.update_in(cx, |editor, window, cx| {
33465        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
33466    });
33467    editor.update(cx, |editor, cx| {
33468        assert_text_with_selections(
33469            editor,
33470            indoc! {r#"
33471                fn main() {
33472                    ˇlet x = foo(1, 2);
33473                    ˇlet y = bar(3, 4);
33474                }
33475            "#},
33476            cx,
33477        );
33478    });
33479}
33480
33481#[gpui::test]
33482async fn test_move_to_start_end_of_larger_syntax_node_with_selections_and_strings(
33483    cx: &mut TestAppContext,
33484) {
33485    init_test(cx, |_| {});
33486
33487    let language = Arc::new(Language::new(
33488        LanguageConfig::default(),
33489        Some(tree_sitter_rust::LANGUAGE.into()),
33490    ));
33491
33492    let text = r#"
33493        fn main() {
33494            let x = foo(1, 2);
33495            let msg = "hello world";
33496        }
33497    "#
33498    .unindent();
33499
33500    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
33501    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33502    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33503
33504    editor
33505        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33506        .await;
33507
33508    // Test case 1: With existing selection, move_to_end keeps selection
33509    editor.update_in(cx, |editor, window, cx| {
33510        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33511            s.select_display_ranges([
33512                DisplayPoint::new(DisplayRow(1), 12)..DisplayPoint::new(DisplayRow(1), 21)
33513            ]);
33514        });
33515    });
33516    editor.update(cx, |editor, cx| {
33517        assert_text_with_selections(
33518            editor,
33519            indoc! {r#"
33520                fn main() {
33521                    let x = «foo(1, 2)ˇ»;
33522                    let msg = "hello world";
33523                }
33524            "#},
33525            cx,
33526        );
33527    });
33528    editor.update_in(cx, |editor, window, cx| {
33529        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
33530    });
33531    editor.update(cx, |editor, cx| {
33532        assert_text_with_selections(
33533            editor,
33534            indoc! {r#"
33535                fn main() {
33536                    let x = «foo(1, 2)ˇ»;
33537                    let msg = "hello world";
33538                }
33539            "#},
33540            cx,
33541        );
33542    });
33543
33544    // Test case 2: Move to end within a string
33545    editor.update_in(cx, |editor, window, cx| {
33546        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33547            s.select_display_ranges([
33548                DisplayPoint::new(DisplayRow(2), 15)..DisplayPoint::new(DisplayRow(2), 15)
33549            ]);
33550        });
33551    });
33552    editor.update(cx, |editor, cx| {
33553        assert_text_with_selections(
33554            editor,
33555            indoc! {r#"
33556                fn main() {
33557                    let x = foo(1, 2);
33558                    let msg = "ˇhello world";
33559                }
33560            "#},
33561            cx,
33562        );
33563    });
33564    editor.update_in(cx, |editor, window, cx| {
33565        editor.move_to_end_of_larger_syntax_node(&MoveToEndOfLargerSyntaxNode, window, cx);
33566    });
33567    editor.update(cx, |editor, cx| {
33568        assert_text_with_selections(
33569            editor,
33570            indoc! {r#"
33571                fn main() {
33572                    let x = foo(1, 2);
33573                    let msg = "hello worldˇ";
33574                }
33575            "#},
33576            cx,
33577        );
33578    });
33579
33580    // Test case 3: Move to start within a string
33581    editor.update_in(cx, |editor, window, cx| {
33582        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33583            s.select_display_ranges([
33584                DisplayPoint::new(DisplayRow(2), 21)..DisplayPoint::new(DisplayRow(2), 21)
33585            ]);
33586        });
33587    });
33588    editor.update(cx, |editor, cx| {
33589        assert_text_with_selections(
33590            editor,
33591            indoc! {r#"
33592                fn main() {
33593                    let x = foo(1, 2);
33594                    let msg = "hello ˇworld";
33595                }
33596            "#},
33597            cx,
33598        );
33599    });
33600    editor.update_in(cx, |editor, window, cx| {
33601        editor.move_to_start_of_larger_syntax_node(&MoveToStartOfLargerSyntaxNode, window, cx);
33602    });
33603    editor.update(cx, |editor, cx| {
33604        assert_text_with_selections(
33605            editor,
33606            indoc! {r#"
33607                fn main() {
33608                    let x = foo(1, 2);
33609                    let msg = "ˇhello world";
33610                }
33611            "#},
33612            cx,
33613        );
33614    });
33615}
33616
33617#[gpui::test]
33618async fn test_select_to_start_end_of_larger_syntax_node(cx: &mut TestAppContext) {
33619    init_test(cx, |_| {});
33620
33621    let language = Arc::new(Language::new(
33622        LanguageConfig::default(),
33623        Some(tree_sitter_rust::LANGUAGE.into()),
33624    ));
33625
33626    // Test Group 1.1: Cursor in String - First Jump (Select to End)
33627    let text = r#"let msg = "foo bar baz";"#.unindent();
33628
33629    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33630    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33631    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33632
33633    editor
33634        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33635        .await;
33636
33637    editor.update_in(cx, |editor, window, cx| {
33638        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33639            s.select_display_ranges([
33640                DisplayPoint::new(DisplayRow(0), 14)..DisplayPoint::new(DisplayRow(0), 14)
33641            ]);
33642        });
33643    });
33644    editor.update(cx, |editor, cx| {
33645        assert_text_with_selections(editor, indoc! {r#"let msg = "fooˇ bar baz";"#}, cx);
33646    });
33647    editor.update_in(cx, |editor, window, cx| {
33648        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33649    });
33650    editor.update(cx, |editor, cx| {
33651        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar bazˇ»";"#}, cx);
33652    });
33653
33654    // Test Group 1.2: Cursor in String - Second Jump (Select to End)
33655    editor.update_in(cx, |editor, window, cx| {
33656        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33657    });
33658    editor.update(cx, |editor, cx| {
33659        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz"ˇ»;"#}, cx);
33660    });
33661
33662    // Test Group 1.3: Cursor in String - Third Jump (Select to End)
33663    editor.update_in(cx, |editor, window, cx| {
33664        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33665    });
33666    editor.update(cx, |editor, cx| {
33667        assert_text_with_selections(editor, indoc! {r#"let msg = "foo« bar baz";ˇ»"#}, cx);
33668    });
33669
33670    // Test Group 1.4: Cursor in String - First Jump (Select to Start)
33671    editor.update_in(cx, |editor, window, cx| {
33672        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33673            s.select_display_ranges([
33674                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18)
33675            ]);
33676        });
33677    });
33678    editor.update(cx, |editor, cx| {
33679        assert_text_with_selections(editor, indoc! {r#"let msg = "foo barˇ baz";"#}, cx);
33680    });
33681    editor.update_in(cx, |editor, window, cx| {
33682        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
33683    });
33684    editor.update(cx, |editor, cx| {
33685        assert_text_with_selections(editor, indoc! {r#"let msg = "«ˇfoo bar» baz";"#}, cx);
33686    });
33687
33688    // Test Group 1.5: Cursor in String - Second Jump (Select to Start)
33689    editor.update_in(cx, |editor, window, cx| {
33690        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
33691    });
33692    editor.update(cx, |editor, cx| {
33693        assert_text_with_selections(editor, indoc! {r#"let msg = «ˇ"foo bar» baz";"#}, cx);
33694    });
33695
33696    // Test Group 1.6: Cursor in String - Third Jump (Select to Start)
33697    editor.update_in(cx, |editor, window, cx| {
33698        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
33699    });
33700    editor.update(cx, |editor, cx| {
33701        assert_text_with_selections(editor, indoc! {r#"«ˇlet msg = "foo bar» baz";"#}, cx);
33702    });
33703
33704    // Test Group 2.1: Let Statement Progression (Select to End)
33705    let text = r#"
33706fn main() {
33707    let x = "hello";
33708}
33709"#
33710    .unindent();
33711
33712    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33713    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33714    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33715
33716    editor
33717        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33718        .await;
33719
33720    editor.update_in(cx, |editor, window, cx| {
33721        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33722            s.select_display_ranges([
33723                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)
33724            ]);
33725        });
33726    });
33727    editor.update(cx, |editor, cx| {
33728        assert_text_with_selections(
33729            editor,
33730            indoc! {r#"
33731                fn main() {
33732                    let xˇ = "hello";
33733                }
33734            "#},
33735            cx,
33736        );
33737    });
33738    editor.update_in(cx, |editor, window, cx| {
33739        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33740    });
33741    editor.update(cx, |editor, cx| {
33742        assert_text_with_selections(
33743            editor,
33744            indoc! {r##"
33745                fn main() {
33746                    let x« = "hello";ˇ»
33747                }
33748            "##},
33749            cx,
33750        );
33751    });
33752    editor.update_in(cx, |editor, window, cx| {
33753        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33754    });
33755    editor.update(cx, |editor, cx| {
33756        assert_text_with_selections(
33757            editor,
33758            indoc! {r#"
33759                fn main() {
33760                    let x« = "hello";
33761                }ˇ»
33762            "#},
33763            cx,
33764        );
33765    });
33766
33767    // Test Group 2.2a: From Inside String Content Node To String Content Boundary
33768    let text = r#"let x = "hello";"#.unindent();
33769
33770    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33771    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33772    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33773
33774    editor
33775        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33776        .await;
33777
33778    editor.update_in(cx, |editor, window, cx| {
33779        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33780            s.select_display_ranges([
33781                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12)
33782            ]);
33783        });
33784    });
33785    editor.update(cx, |editor, cx| {
33786        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo";"#}, cx);
33787    });
33788    editor.update_in(cx, |editor, window, cx| {
33789        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
33790    });
33791    editor.update(cx, |editor, cx| {
33792        assert_text_with_selections(editor, indoc! {r#"let x = "«ˇhel»lo";"#}, cx);
33793    });
33794
33795    // Test Group 2.2b: From Edge of String Content Node To String Literal Boundary
33796    editor.update_in(cx, |editor, window, cx| {
33797        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33798            s.select_display_ranges([
33799                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
33800            ]);
33801        });
33802    });
33803    editor.update(cx, |editor, cx| {
33804        assert_text_with_selections(editor, indoc! {r#"let x = "ˇhello";"#}, cx);
33805    });
33806    editor.update_in(cx, |editor, window, cx| {
33807        editor.select_to_start_of_larger_syntax_node(&SelectToStartOfLargerSyntaxNode, window, cx);
33808    });
33809    editor.update(cx, |editor, cx| {
33810        assert_text_with_selections(editor, indoc! {r#"let x = «ˇ"»hello";"#}, cx);
33811    });
33812
33813    // Test Group 3.1: Create Selection from Cursor (Select to End)
33814    let text = r#"let x = "hello world";"#.unindent();
33815
33816    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33817    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33818    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33819
33820    editor
33821        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33822        .await;
33823
33824    editor.update_in(cx, |editor, window, cx| {
33825        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33826            s.select_display_ranges([
33827                DisplayPoint::new(DisplayRow(0), 14)..DisplayPoint::new(DisplayRow(0), 14)
33828            ]);
33829        });
33830    });
33831    editor.update(cx, |editor, cx| {
33832        assert_text_with_selections(editor, indoc! {r#"let x = "helloˇ world";"#}, cx);
33833    });
33834    editor.update_in(cx, |editor, window, cx| {
33835        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33836    });
33837    editor.update(cx, |editor, cx| {
33838        assert_text_with_selections(editor, indoc! {r#"let x = "hello« worldˇ»";"#}, cx);
33839    });
33840
33841    // Test Group 3.2: Extend Existing Selection (Select to End)
33842    editor.update_in(cx, |editor, window, cx| {
33843        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33844            s.select_display_ranges([
33845                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 17)
33846            ]);
33847        });
33848    });
33849    editor.update(cx, |editor, cx| {
33850        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo woˇ»rld";"#}, cx);
33851    });
33852    editor.update_in(cx, |editor, window, cx| {
33853        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33854    });
33855    editor.update(cx, |editor, cx| {
33856        assert_text_with_selections(editor, indoc! {r#"let x = "he«llo worldˇ»";"#}, cx);
33857    });
33858
33859    // Test Group 4.1: Multiple Cursors - All Expand to Different Syntax Nodes
33860    let text = r#"let x = "hello"; let y = 42;"#.unindent();
33861
33862    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33863    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33864    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33865
33866    editor
33867        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33868        .await;
33869
33870    editor.update_in(cx, |editor, window, cx| {
33871        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33872            s.select_display_ranges([
33873                // Cursor inside string content
33874                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
33875                // Cursor at let statement semicolon
33876                DisplayPoint::new(DisplayRow(0), 18)..DisplayPoint::new(DisplayRow(0), 18),
33877                // Cursor inside integer literal
33878                DisplayPoint::new(DisplayRow(0), 26)..DisplayPoint::new(DisplayRow(0), 26),
33879            ]);
33880        });
33881    });
33882    editor.update(cx, |editor, cx| {
33883        assert_text_with_selections(editor, indoc! {r#"let x = "helˇlo"; lˇet y = 4ˇ2;"#}, cx);
33884    });
33885    editor.update_in(cx, |editor, window, cx| {
33886        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33887    });
33888    editor.update(cx, |editor, cx| {
33889        assert_text_with_selections(editor, indoc! {r#"let x = "hel«loˇ»"; l«et y = 42;ˇ»"#}, cx);
33890    });
33891
33892    // Test Group 4.2: Multiple Cursors on Separate Lines
33893    let text = r#"
33894let x = "hello";
33895let y = 42;
33896"#
33897    .unindent();
33898
33899    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33900    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33901    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33902
33903    editor
33904        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33905        .await;
33906
33907    editor.update_in(cx, |editor, window, cx| {
33908        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33909            s.select_display_ranges([
33910                DisplayPoint::new(DisplayRow(0), 12)..DisplayPoint::new(DisplayRow(0), 12),
33911                DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9),
33912            ]);
33913        });
33914    });
33915
33916    editor.update(cx, |editor, cx| {
33917        assert_text_with_selections(
33918            editor,
33919            indoc! {r#"
33920                let x = "helˇlo";
33921                let y = 4ˇ2;
33922            "#},
33923            cx,
33924        );
33925    });
33926    editor.update_in(cx, |editor, window, cx| {
33927        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33928    });
33929    editor.update(cx, |editor, cx| {
33930        assert_text_with_selections(
33931            editor,
33932            indoc! {r#"
33933                let x = "hel«loˇ»";
33934                let y = 4«2ˇ»;
33935            "#},
33936            cx,
33937        );
33938    });
33939
33940    // Test Group 5.1: Nested Function Calls
33941    let text = r#"let result = foo(bar("arg"));"#.unindent();
33942
33943    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33944    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33945    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33946
33947    editor
33948        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33949        .await;
33950
33951    editor.update_in(cx, |editor, window, cx| {
33952        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33953            s.select_display_ranges([
33954                DisplayPoint::new(DisplayRow(0), 22)..DisplayPoint::new(DisplayRow(0), 22)
33955            ]);
33956        });
33957    });
33958    editor.update(cx, |editor, cx| {
33959        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("ˇarg"));"#}, cx);
33960    });
33961    editor.update_in(cx, |editor, window, cx| {
33962        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33963    });
33964    editor.update(cx, |editor, cx| {
33965        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«argˇ»"));"#}, cx);
33966    });
33967    editor.update_in(cx, |editor, window, cx| {
33968        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33969    });
33970    editor.update(cx, |editor, cx| {
33971        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg"ˇ»));"#}, cx);
33972    });
33973    editor.update_in(cx, |editor, window, cx| {
33974        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
33975    });
33976    editor.update(cx, |editor, cx| {
33977        assert_text_with_selections(editor, indoc! {r#"let result = foo(bar("«arg")ˇ»);"#}, cx);
33978    });
33979
33980    // Test Group 6.1: Block Comments
33981    let text = r#"let x = /* multi
33982                             line
33983                             comment */;"#
33984        .unindent();
33985
33986    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
33987    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
33988    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
33989
33990    editor
33991        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
33992        .await;
33993
33994    editor.update_in(cx, |editor, window, cx| {
33995        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
33996            s.select_display_ranges([
33997                DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16)
33998            ]);
33999        });
34000    });
34001    editor.update(cx, |editor, cx| {
34002        assert_text_with_selections(
34003            editor,
34004            indoc! {r#"
34005let x = /* multiˇ
34006line
34007comment */;"#},
34008            cx,
34009        );
34010    });
34011    editor.update_in(cx, |editor, window, cx| {
34012        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
34013    });
34014    editor.update(cx, |editor, cx| {
34015        assert_text_with_selections(
34016            editor,
34017            indoc! {r#"
34018let x = /* multi«
34019line
34020comment */ˇ»;"#},
34021            cx,
34022        );
34023    });
34024
34025    // Test Group 6.2: Array/Vector Literals
34026    let text = r#"let arr = [1, 2, 3];"#.unindent();
34027
34028    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language.clone(), cx));
34029    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
34030    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
34031
34032    editor
34033        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
34034        .await;
34035
34036    editor.update_in(cx, |editor, window, cx| {
34037        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
34038            s.select_display_ranges([
34039                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
34040            ]);
34041        });
34042    });
34043    editor.update(cx, |editor, cx| {
34044        assert_text_with_selections(editor, indoc! {r#"let arr = [ˇ1, 2, 3];"#}, cx);
34045    });
34046    editor.update_in(cx, |editor, window, cx| {
34047        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
34048    });
34049    editor.update(cx, |editor, cx| {
34050        assert_text_with_selections(editor, indoc! {r#"let arr = [«1ˇ», 2, 3];"#}, cx);
34051    });
34052    editor.update_in(cx, |editor, window, cx| {
34053        editor.select_to_end_of_larger_syntax_node(&SelectToEndOfLargerSyntaxNode, window, cx);
34054    });
34055    editor.update(cx, |editor, cx| {
34056        assert_text_with_selections(editor, indoc! {r#"let arr = [«1, 2, 3]ˇ»;"#}, cx);
34057    });
34058}
34059
34060#[gpui::test]
34061async fn test_restore_and_next(cx: &mut TestAppContext) {
34062    init_test(cx, |_| {});
34063    let mut cx = EditorTestContext::new(cx).await;
34064
34065    let diff_base = r#"
34066        one
34067        two
34068        three
34069        four
34070        five
34071        "#
34072    .unindent();
34073
34074    cx.set_state(
34075        &r#"
34076        ONE
34077        two
34078        ˇTHREE
34079        four
34080        FIVE
34081        "#
34082        .unindent(),
34083    );
34084    cx.set_head_text(&diff_base);
34085
34086    cx.update_editor(|editor, window, cx| {
34087        editor.set_expand_all_diff_hunks(cx);
34088        editor.restore_and_next(&Default::default(), window, cx);
34089    });
34090    cx.run_until_parked();
34091
34092    cx.assert_state_with_diff(
34093        r#"
34094        - one
34095        + ONE
34096          two
34097          three
34098          four
34099        - ˇfive
34100        + FIVE
34101        "#
34102        .unindent(),
34103    );
34104
34105    cx.update_editor(|editor, window, cx| {
34106        editor.restore_and_next(&Default::default(), window, cx);
34107    });
34108    cx.run_until_parked();
34109
34110    cx.assert_state_with_diff(
34111        r#"
34112        - one
34113        + ONE
34114          two
34115          three
34116          four
34117          ˇfive
34118        "#
34119        .unindent(),
34120    );
34121}
34122
34123#[gpui::test]
34124async fn test_align_selections(cx: &mut TestAppContext) {
34125    init_test(cx, |_| {});
34126    let mut cx = EditorTestContext::new(cx).await;
34127
34128    // 1) one cursor, no action
34129    let before = " abc\n  abc\nabc\n     ˇabc";
34130    cx.set_state(before);
34131    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
34132    cx.assert_editor_state(before);
34133
34134    // 2) multiple cursors at different rows
34135    let before = indoc!(
34136        r#"
34137            let aˇbc = 123;
34138            let  xˇyz = 456;
34139            let   fˇoo = 789;
34140            let    bˇar = 0;
34141        "#
34142    );
34143    let after = indoc!(
34144        r#"
34145            let a   ˇbc = 123;
34146            let  x  ˇyz = 456;
34147            let   f ˇoo = 789;
34148            let    bˇar = 0;
34149        "#
34150    );
34151    cx.set_state(before);
34152    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
34153    cx.assert_editor_state(after);
34154
34155    // 3) multiple selections at different rows
34156    let before = indoc!(
34157        r#"
34158            let «ˇabc» = 123;
34159            let  «ˇxyz» = 456;
34160            let   «ˇfoo» = 789;
34161            let    «ˇbar» = 0;
34162        "#
34163    );
34164    let after = indoc!(
34165        r#"
34166            let    «ˇabc» = 123;
34167            let    «ˇxyz» = 456;
34168            let    «ˇfoo» = 789;
34169            let    «ˇbar» = 0;
34170        "#
34171    );
34172    cx.set_state(before);
34173    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
34174    cx.assert_editor_state(after);
34175
34176    // 4) multiple selections at different rows, inverted head
34177    let before = indoc!(
34178        r#"
34179            let    «abcˇ» = 123;
34180            // comment
34181            let  «xyzˇ» = 456;
34182            let «fooˇ» = 789;
34183            let    «barˇ» = 0;
34184        "#
34185    );
34186    let after = indoc!(
34187        r#"
34188            let    «abcˇ» = 123;
34189            // comment
34190            let    «xyzˇ» = 456;
34191            let    «fooˇ» = 789;
34192            let    «barˇ» = 0;
34193        "#
34194    );
34195    cx.set_state(before);
34196    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
34197    cx.assert_editor_state(after);
34198}
34199
34200#[gpui::test]
34201async fn test_align_selections_multicolumn(cx: &mut TestAppContext) {
34202    init_test(cx, |_| {});
34203    let mut cx = EditorTestContext::new(cx).await;
34204
34205    // 1) Multicolumn, one non affected editor row
34206    let before = indoc!(
34207        r#"
34208            name «|ˇ» age «|ˇ» height «|ˇ» note
34209            Matthew «|ˇ» 7 «|ˇ» 2333 «|ˇ» smart
34210            Mike «|ˇ» 1234 «|ˇ» 567 «|ˇ» lazy
34211            Anything that is not selected
34212            Miles «|ˇ» 88 «|ˇ» 99 «|ˇ» funny
34213        "#
34214    );
34215    let after = indoc!(
34216        r#"
34217            name    «|ˇ» age  «|ˇ» height «|ˇ» note
34218            Matthew «|ˇ» 7    «|ˇ» 2333   «|ˇ» smart
34219            Mike    «|ˇ» 1234 «|ˇ» 567    «|ˇ» lazy
34220            Anything that is not selected
34221            Miles   «|ˇ» 88   «|ˇ» 99     «|ˇ» funny
34222        "#
34223    );
34224    cx.set_state(before);
34225    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
34226    cx.assert_editor_state(after);
34227
34228    // 2) not all alignment rows has the number of alignment columns
34229    let before = indoc!(
34230        r#"
34231            name «|ˇ» age «|ˇ» height
34232            Matthew «|ˇ» 7 «|ˇ» 2333
34233            Mike «|ˇ» 1234
34234            Miles «|ˇ» 88 «|ˇ» 99
34235        "#
34236    );
34237    let after = indoc!(
34238        r#"
34239            name    «|ˇ» age «|ˇ» height
34240            Matthew «|ˇ» 7   «|ˇ» 2333
34241            Mike    «|ˇ» 1234
34242            Miles   «|ˇ» 88  «|ˇ» 99
34243        "#
34244    );
34245    cx.set_state(before);
34246    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
34247    cx.assert_editor_state(after);
34248
34249    // 3) A aligned column shall stay aligned
34250    let before = indoc!(
34251        r#"
34252            $ ˇa    ˇa
34253            $  ˇa   ˇa
34254            $   ˇa  ˇa
34255            $    ˇa ˇa
34256        "#
34257    );
34258    let after = indoc!(
34259        r#"
34260            $    ˇa    ˇa
34261            $    ˇa    ˇa
34262            $    ˇa    ˇa
34263            $    ˇa    ˇa
34264        "#
34265    );
34266    cx.set_state(before);
34267    cx.update_editor(|e, window, cx| e.align_selections(&AlignSelections, window, cx));
34268    cx.assert_editor_state(after);
34269}