editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    invalid_buffer_view::InvalidBufferView,
   61    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   62    register_project_item,
   63};
   64
   65#[gpui::test]
   66fn test_edit_events(cx: &mut TestAppContext) {
   67    init_test(cx, |_| {});
   68
   69    let buffer = cx.new(|cx| {
   70        let mut buffer = language::Buffer::local("123456", cx);
   71        buffer.set_group_interval(Duration::from_secs(1));
   72        buffer
   73    });
   74
   75    let events = Rc::new(RefCell::new(Vec::new()));
   76    let editor1 = cx.add_window({
   77        let events = events.clone();
   78        |window, cx| {
   79            let entity = cx.entity();
   80            cx.subscribe_in(
   81                &entity,
   82                window,
   83                move |_, _, event: &EditorEvent, _, _| match event {
   84                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   85                    EditorEvent::BufferEdited => {
   86                        events.borrow_mut().push(("editor1", "buffer edited"))
   87                    }
   88                    _ => {}
   89                },
   90            )
   91            .detach();
   92            Editor::for_buffer(buffer.clone(), None, window, cx)
   93        }
   94    });
   95
   96    let editor2 = cx.add_window({
   97        let events = events.clone();
   98        |window, cx| {
   99            cx.subscribe_in(
  100                &cx.entity(),
  101                window,
  102                move |_, _, event: &EditorEvent, _, _| match event {
  103                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  104                    EditorEvent::BufferEdited => {
  105                        events.borrow_mut().push(("editor2", "buffer edited"))
  106                    }
  107                    _ => {}
  108                },
  109            )
  110            .detach();
  111            Editor::for_buffer(buffer.clone(), None, window, cx)
  112        }
  113    });
  114
  115    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  116
  117    // Mutating editor 1 will emit an `Edited` event only for that editor.
  118    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  119    assert_eq!(
  120        mem::take(&mut *events.borrow_mut()),
  121        [
  122            ("editor1", "edited"),
  123            ("editor1", "buffer edited"),
  124            ("editor2", "buffer edited"),
  125        ]
  126    );
  127
  128    // Mutating editor 2 will emit an `Edited` event only for that editor.
  129    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  130    assert_eq!(
  131        mem::take(&mut *events.borrow_mut()),
  132        [
  133            ("editor2", "edited"),
  134            ("editor1", "buffer edited"),
  135            ("editor2", "buffer edited"),
  136        ]
  137    );
  138
  139    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  140    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  141    assert_eq!(
  142        mem::take(&mut *events.borrow_mut()),
  143        [
  144            ("editor1", "edited"),
  145            ("editor1", "buffer edited"),
  146            ("editor2", "buffer edited"),
  147        ]
  148    );
  149
  150    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  151    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  152    assert_eq!(
  153        mem::take(&mut *events.borrow_mut()),
  154        [
  155            ("editor1", "edited"),
  156            ("editor1", "buffer edited"),
  157            ("editor2", "buffer edited"),
  158        ]
  159    );
  160
  161    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  162    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  163    assert_eq!(
  164        mem::take(&mut *events.borrow_mut()),
  165        [
  166            ("editor2", "edited"),
  167            ("editor1", "buffer edited"),
  168            ("editor2", "buffer edited"),
  169        ]
  170    );
  171
  172    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  173    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  174    assert_eq!(
  175        mem::take(&mut *events.borrow_mut()),
  176        [
  177            ("editor2", "edited"),
  178            ("editor1", "buffer edited"),
  179            ("editor2", "buffer edited"),
  180        ]
  181    );
  182
  183    // No event is emitted when the mutation is a no-op.
  184    _ = editor2.update(cx, |editor, window, cx| {
  185        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  186            s.select_ranges([0..0])
  187        });
  188
  189        editor.backspace(&Backspace, window, cx);
  190    });
  191    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  192}
  193
  194#[gpui::test]
  195fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  196    init_test(cx, |_| {});
  197
  198    let mut now = Instant::now();
  199    let group_interval = Duration::from_millis(1);
  200    let buffer = cx.new(|cx| {
  201        let mut buf = language::Buffer::local("123456", cx);
  202        buf.set_group_interval(group_interval);
  203        buf
  204    });
  205    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  206    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  207
  208    _ = editor.update(cx, |editor, window, cx| {
  209        editor.start_transaction_at(now, window, cx);
  210        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  211            s.select_ranges([2..4])
  212        });
  213
  214        editor.insert("cd", window, cx);
  215        editor.end_transaction_at(now, cx);
  216        assert_eq!(editor.text(cx), "12cd56");
  217        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  218
  219        editor.start_transaction_at(now, window, cx);
  220        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  221            s.select_ranges([4..5])
  222        });
  223        editor.insert("e", window, cx);
  224        editor.end_transaction_at(now, cx);
  225        assert_eq!(editor.text(cx), "12cde6");
  226        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  227
  228        now += group_interval + Duration::from_millis(1);
  229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  230            s.select_ranges([2..2])
  231        });
  232
  233        // Simulate an edit in another editor
  234        buffer.update(cx, |buffer, cx| {
  235            buffer.start_transaction_at(now, cx);
  236            buffer.edit([(0..1, "a")], None, cx);
  237            buffer.edit([(1..1, "b")], None, cx);
  238            buffer.end_transaction_at(now, cx);
  239        });
  240
  241        assert_eq!(editor.text(cx), "ab2cde6");
  242        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  243
  244        // Last transaction happened past the group interval in a different editor.
  245        // Undo it individually and don't restore selections.
  246        editor.undo(&Undo, window, cx);
  247        assert_eq!(editor.text(cx), "12cde6");
  248        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  249
  250        // First two transactions happened within the group interval in this editor.
  251        // Undo them together and restore selections.
  252        editor.undo(&Undo, window, cx);
  253        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  254        assert_eq!(editor.text(cx), "123456");
  255        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  256
  257        // Redo the first two transactions together.
  258        editor.redo(&Redo, window, cx);
  259        assert_eq!(editor.text(cx), "12cde6");
  260        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  261
  262        // Redo the last transaction on its own.
  263        editor.redo(&Redo, window, cx);
  264        assert_eq!(editor.text(cx), "ab2cde6");
  265        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  266
  267        // Test empty transactions.
  268        editor.start_transaction_at(now, window, cx);
  269        editor.end_transaction_at(now, cx);
  270        editor.undo(&Undo, window, cx);
  271        assert_eq!(editor.text(cx), "12cde6");
  272    });
  273}
  274
  275#[gpui::test]
  276fn test_ime_composition(cx: &mut TestAppContext) {
  277    init_test(cx, |_| {});
  278
  279    let buffer = cx.new(|cx| {
  280        let mut buffer = language::Buffer::local("abcde", cx);
  281        // Ensure automatic grouping doesn't occur.
  282        buffer.set_group_interval(Duration::ZERO);
  283        buffer
  284    });
  285
  286    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  287    cx.add_window(|window, cx| {
  288        let mut editor = build_editor(buffer.clone(), window, cx);
  289
  290        // Start a new IME composition.
  291        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  292        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  293        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  294        assert_eq!(editor.text(cx), "äbcde");
  295        assert_eq!(
  296            editor.marked_text_ranges(cx),
  297            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  298        );
  299
  300        // Finalize IME composition.
  301        editor.replace_text_in_range(None, "ā", window, cx);
  302        assert_eq!(editor.text(cx), "ābcde");
  303        assert_eq!(editor.marked_text_ranges(cx), None);
  304
  305        // IME composition edits are grouped and are undone/redone at once.
  306        editor.undo(&Default::default(), window, cx);
  307        assert_eq!(editor.text(cx), "abcde");
  308        assert_eq!(editor.marked_text_ranges(cx), None);
  309        editor.redo(&Default::default(), window, cx);
  310        assert_eq!(editor.text(cx), "ābcde");
  311        assert_eq!(editor.marked_text_ranges(cx), None);
  312
  313        // Start a new IME composition.
  314        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  315        assert_eq!(
  316            editor.marked_text_ranges(cx),
  317            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  318        );
  319
  320        // Undoing during an IME composition cancels it.
  321        editor.undo(&Default::default(), window, cx);
  322        assert_eq!(editor.text(cx), "ābcde");
  323        assert_eq!(editor.marked_text_ranges(cx), None);
  324
  325        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  326        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  327        assert_eq!(editor.text(cx), "ābcdè");
  328        assert_eq!(
  329            editor.marked_text_ranges(cx),
  330            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  331        );
  332
  333        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  334        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  335        assert_eq!(editor.text(cx), "ābcdę");
  336        assert_eq!(editor.marked_text_ranges(cx), None);
  337
  338        // Start a new IME composition with multiple cursors.
  339        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  340            s.select_ranges([
  341                OffsetUtf16(1)..OffsetUtf16(1),
  342                OffsetUtf16(3)..OffsetUtf16(3),
  343                OffsetUtf16(5)..OffsetUtf16(5),
  344            ])
  345        });
  346        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  347        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  348        assert_eq!(
  349            editor.marked_text_ranges(cx),
  350            Some(vec![
  351                OffsetUtf16(0)..OffsetUtf16(3),
  352                OffsetUtf16(4)..OffsetUtf16(7),
  353                OffsetUtf16(8)..OffsetUtf16(11)
  354            ])
  355        );
  356
  357        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  358        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  359        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  360        assert_eq!(
  361            editor.marked_text_ranges(cx),
  362            Some(vec![
  363                OffsetUtf16(1)..OffsetUtf16(2),
  364                OffsetUtf16(5)..OffsetUtf16(6),
  365                OffsetUtf16(9)..OffsetUtf16(10)
  366            ])
  367        );
  368
  369        // Finalize IME composition with multiple cursors.
  370        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  371        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  372        assert_eq!(editor.marked_text_ranges(cx), None);
  373
  374        editor
  375    });
  376}
  377
  378#[gpui::test]
  379fn test_selection_with_mouse(cx: &mut TestAppContext) {
  380    init_test(cx, |_| {});
  381
  382    let editor = cx.add_window(|window, cx| {
  383        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  384        build_editor(buffer, window, cx)
  385    });
  386
  387    _ = editor.update(cx, |editor, window, cx| {
  388        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  389    });
  390    assert_eq!(
  391        editor
  392            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  393            .unwrap(),
  394        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  395    );
  396
  397    _ = editor.update(cx, |editor, window, cx| {
  398        editor.update_selection(
  399            DisplayPoint::new(DisplayRow(3), 3),
  400            0,
  401            gpui::Point::<f32>::default(),
  402            window,
  403            cx,
  404        );
  405    });
  406
  407    assert_eq!(
  408        editor
  409            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  410            .unwrap(),
  411        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  412    );
  413
  414    _ = editor.update(cx, |editor, window, cx| {
  415        editor.update_selection(
  416            DisplayPoint::new(DisplayRow(1), 1),
  417            0,
  418            gpui::Point::<f32>::default(),
  419            window,
  420            cx,
  421        );
  422    });
  423
  424    assert_eq!(
  425        editor
  426            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  427            .unwrap(),
  428        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  429    );
  430
  431    _ = editor.update(cx, |editor, window, cx| {
  432        editor.end_selection(window, cx);
  433        editor.update_selection(
  434            DisplayPoint::new(DisplayRow(3), 3),
  435            0,
  436            gpui::Point::<f32>::default(),
  437            window,
  438            cx,
  439        );
  440    });
  441
  442    assert_eq!(
  443        editor
  444            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  445            .unwrap(),
  446        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  447    );
  448
  449    _ = editor.update(cx, |editor, window, cx| {
  450        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  451        editor.update_selection(
  452            DisplayPoint::new(DisplayRow(0), 0),
  453            0,
  454            gpui::Point::<f32>::default(),
  455            window,
  456            cx,
  457        );
  458    });
  459
  460    assert_eq!(
  461        editor
  462            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  463            .unwrap(),
  464        [
  465            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  466            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  467        ]
  468    );
  469
  470    _ = editor.update(cx, |editor, window, cx| {
  471        editor.end_selection(window, cx);
  472    });
  473
  474    assert_eq!(
  475        editor
  476            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  477            .unwrap(),
  478        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  479    );
  480}
  481
  482#[gpui::test]
  483fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  484    init_test(cx, |_| {});
  485
  486    let editor = cx.add_window(|window, cx| {
  487        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  488        build_editor(buffer, window, cx)
  489    });
  490
  491    _ = editor.update(cx, |editor, window, cx| {
  492        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  493    });
  494
  495    _ = editor.update(cx, |editor, window, cx| {
  496        editor.end_selection(window, cx);
  497    });
  498
  499    _ = editor.update(cx, |editor, window, cx| {
  500        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  501    });
  502
  503    _ = editor.update(cx, |editor, window, cx| {
  504        editor.end_selection(window, cx);
  505    });
  506
  507    assert_eq!(
  508        editor
  509            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  510            .unwrap(),
  511        [
  512            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  513            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  514        ]
  515    );
  516
  517    _ = editor.update(cx, |editor, window, cx| {
  518        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  519    });
  520
  521    _ = editor.update(cx, |editor, window, cx| {
  522        editor.end_selection(window, cx);
  523    });
  524
  525    assert_eq!(
  526        editor
  527            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  528            .unwrap(),
  529        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  530    );
  531}
  532
  533#[gpui::test]
  534fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  535    init_test(cx, |_| {});
  536
  537    let editor = cx.add_window(|window, cx| {
  538        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  539        build_editor(buffer, window, cx)
  540    });
  541
  542    _ = editor.update(cx, |editor, window, cx| {
  543        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  544        assert_eq!(
  545            editor.selections.display_ranges(cx),
  546            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  547        );
  548    });
  549
  550    _ = editor.update(cx, |editor, window, cx| {
  551        editor.update_selection(
  552            DisplayPoint::new(DisplayRow(3), 3),
  553            0,
  554            gpui::Point::<f32>::default(),
  555            window,
  556            cx,
  557        );
  558        assert_eq!(
  559            editor.selections.display_ranges(cx),
  560            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  561        );
  562    });
  563
  564    _ = editor.update(cx, |editor, window, cx| {
  565        editor.cancel(&Cancel, window, cx);
  566        editor.update_selection(
  567            DisplayPoint::new(DisplayRow(1), 1),
  568            0,
  569            gpui::Point::<f32>::default(),
  570            window,
  571            cx,
  572        );
  573        assert_eq!(
  574            editor.selections.display_ranges(cx),
  575            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  576        );
  577    });
  578}
  579
  580#[gpui::test]
  581fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  582    init_test(cx, |_| {});
  583
  584    let editor = cx.add_window(|window, cx| {
  585        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  586        build_editor(buffer, window, cx)
  587    });
  588
  589    _ = editor.update(cx, |editor, window, cx| {
  590        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  591        assert_eq!(
  592            editor.selections.display_ranges(cx),
  593            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  594        );
  595
  596        editor.move_down(&Default::default(), window, cx);
  597        assert_eq!(
  598            editor.selections.display_ranges(cx),
  599            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  600        );
  601
  602        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  603        assert_eq!(
  604            editor.selections.display_ranges(cx),
  605            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  606        );
  607
  608        editor.move_up(&Default::default(), window, cx);
  609        assert_eq!(
  610            editor.selections.display_ranges(cx),
  611            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  612        );
  613    });
  614}
  615
  616#[gpui::test]
  617fn test_clone(cx: &mut TestAppContext) {
  618    init_test(cx, |_| {});
  619
  620    let (text, selection_ranges) = marked_text_ranges(
  621        indoc! {"
  622            one
  623            two
  624            threeˇ
  625            four
  626            fiveˇ
  627        "},
  628        true,
  629    );
  630
  631    let editor = cx.add_window(|window, cx| {
  632        let buffer = MultiBuffer::build_simple(&text, cx);
  633        build_editor(buffer, window, cx)
  634    });
  635
  636    _ = editor.update(cx, |editor, window, cx| {
  637        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  638            s.select_ranges(selection_ranges.clone())
  639        });
  640        editor.fold_creases(
  641            vec![
  642                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  643                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  644            ],
  645            true,
  646            window,
  647            cx,
  648        );
  649    });
  650
  651    let cloned_editor = editor
  652        .update(cx, |editor, _, cx| {
  653            cx.open_window(Default::default(), |window, cx| {
  654                cx.new(|cx| editor.clone(window, cx))
  655            })
  656        })
  657        .unwrap()
  658        .unwrap();
  659
  660    let snapshot = editor
  661        .update(cx, |e, window, cx| e.snapshot(window, cx))
  662        .unwrap();
  663    let cloned_snapshot = cloned_editor
  664        .update(cx, |e, window, cx| e.snapshot(window, cx))
  665        .unwrap();
  666
  667    assert_eq!(
  668        cloned_editor
  669            .update(cx, |e, _, cx| e.display_text(cx))
  670            .unwrap(),
  671        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  672    );
  673    assert_eq!(
  674        cloned_snapshot
  675            .folds_in_range(0..text.len())
  676            .collect::<Vec<_>>(),
  677        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  678    );
  679    assert_set_eq!(
  680        cloned_editor
  681            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  682            .unwrap(),
  683        editor
  684            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  685            .unwrap()
  686    );
  687    assert_set_eq!(
  688        cloned_editor
  689            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  690            .unwrap(),
  691        editor
  692            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  693            .unwrap()
  694    );
  695}
  696
  697#[gpui::test]
  698async fn test_navigation_history(cx: &mut TestAppContext) {
  699    init_test(cx, |_| {});
  700
  701    use workspace::item::Item;
  702
  703    let fs = FakeFs::new(cx.executor());
  704    let project = Project::test(fs, [], cx).await;
  705    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  706    let pane = workspace
  707        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  708        .unwrap();
  709
  710    _ = workspace.update(cx, |_v, window, cx| {
  711        cx.new(|cx| {
  712            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  713            let mut editor = build_editor(buffer, window, cx);
  714            let handle = cx.entity();
  715            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  716
  717            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  718                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  719            }
  720
  721            // Move the cursor a small distance.
  722            // Nothing is added to the navigation history.
  723            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  724                s.select_display_ranges([
  725                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  726                ])
  727            });
  728            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  729                s.select_display_ranges([
  730                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  731                ])
  732            });
  733            assert!(pop_history(&mut editor, cx).is_none());
  734
  735            // Move the cursor a large distance.
  736            // The history can jump back to the previous position.
  737            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  738                s.select_display_ranges([
  739                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  740                ])
  741            });
  742            let nav_entry = pop_history(&mut editor, cx).unwrap();
  743            editor.navigate(nav_entry.data.unwrap(), window, cx);
  744            assert_eq!(nav_entry.item.id(), cx.entity_id());
  745            assert_eq!(
  746                editor.selections.display_ranges(cx),
  747                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  748            );
  749            assert!(pop_history(&mut editor, cx).is_none());
  750
  751            // Move the cursor a small distance via the mouse.
  752            // Nothing is added to the navigation history.
  753            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  754            editor.end_selection(window, cx);
  755            assert_eq!(
  756                editor.selections.display_ranges(cx),
  757                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  758            );
  759            assert!(pop_history(&mut editor, cx).is_none());
  760
  761            // Move the cursor a large distance via the mouse.
  762            // The history can jump back to the previous position.
  763            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  764            editor.end_selection(window, cx);
  765            assert_eq!(
  766                editor.selections.display_ranges(cx),
  767                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  768            );
  769            let nav_entry = pop_history(&mut editor, cx).unwrap();
  770            editor.navigate(nav_entry.data.unwrap(), window, cx);
  771            assert_eq!(nav_entry.item.id(), cx.entity_id());
  772            assert_eq!(
  773                editor.selections.display_ranges(cx),
  774                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  775            );
  776            assert!(pop_history(&mut editor, cx).is_none());
  777
  778            // Set scroll position to check later
  779            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  780            let original_scroll_position = editor.scroll_manager.anchor();
  781
  782            // Jump to the end of the document and adjust scroll
  783            editor.move_to_end(&MoveToEnd, window, cx);
  784            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  785            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  786
  787            let nav_entry = pop_history(&mut editor, cx).unwrap();
  788            editor.navigate(nav_entry.data.unwrap(), window, cx);
  789            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  790
  791            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  792            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  793            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  794            let invalid_point = Point::new(9999, 0);
  795            editor.navigate(
  796                Box::new(NavigationData {
  797                    cursor_anchor: invalid_anchor,
  798                    cursor_position: invalid_point,
  799                    scroll_anchor: ScrollAnchor {
  800                        anchor: invalid_anchor,
  801                        offset: Default::default(),
  802                    },
  803                    scroll_top_row: invalid_point.row,
  804                }),
  805                window,
  806                cx,
  807            );
  808            assert_eq!(
  809                editor.selections.display_ranges(cx),
  810                &[editor.max_point(cx)..editor.max_point(cx)]
  811            );
  812            assert_eq!(
  813                editor.scroll_position(cx),
  814                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  815            );
  816
  817            editor
  818        })
  819    });
  820}
  821
  822#[gpui::test]
  823fn test_cancel(cx: &mut TestAppContext) {
  824    init_test(cx, |_| {});
  825
  826    let editor = cx.add_window(|window, cx| {
  827        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  828        build_editor(buffer, window, cx)
  829    });
  830
  831    _ = editor.update(cx, |editor, window, cx| {
  832        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  833        editor.update_selection(
  834            DisplayPoint::new(DisplayRow(1), 1),
  835            0,
  836            gpui::Point::<f32>::default(),
  837            window,
  838            cx,
  839        );
  840        editor.end_selection(window, cx);
  841
  842        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  843        editor.update_selection(
  844            DisplayPoint::new(DisplayRow(0), 3),
  845            0,
  846            gpui::Point::<f32>::default(),
  847            window,
  848            cx,
  849        );
  850        editor.end_selection(window, cx);
  851        assert_eq!(
  852            editor.selections.display_ranges(cx),
  853            [
  854                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  855                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  856            ]
  857        );
  858    });
  859
  860    _ = editor.update(cx, |editor, window, cx| {
  861        editor.cancel(&Cancel, window, cx);
  862        assert_eq!(
  863            editor.selections.display_ranges(cx),
  864            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  865        );
  866    });
  867
  868    _ = editor.update(cx, |editor, window, cx| {
  869        editor.cancel(&Cancel, window, cx);
  870        assert_eq!(
  871            editor.selections.display_ranges(cx),
  872            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  873        );
  874    });
  875}
  876
  877#[gpui::test]
  878fn test_fold_action(cx: &mut TestAppContext) {
  879    init_test(cx, |_| {});
  880
  881    let editor = cx.add_window(|window, cx| {
  882        let buffer = MultiBuffer::build_simple(
  883            &"
  884                impl Foo {
  885                    // Hello!
  886
  887                    fn a() {
  888                        1
  889                    }
  890
  891                    fn b() {
  892                        2
  893                    }
  894
  895                    fn c() {
  896                        3
  897                    }
  898                }
  899            "
  900            .unindent(),
  901            cx,
  902        );
  903        build_editor(buffer, window, cx)
  904    });
  905
  906    _ = editor.update(cx, |editor, window, cx| {
  907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  908            s.select_display_ranges([
  909                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  910            ]);
  911        });
  912        editor.fold(&Fold, window, cx);
  913        assert_eq!(
  914            editor.display_text(cx),
  915            "
  916                impl Foo {
  917                    // Hello!
  918
  919                    fn a() {
  920                        1
  921                    }
  922
  923                    fn b() {⋯
  924                    }
  925
  926                    fn c() {⋯
  927                    }
  928                }
  929            "
  930            .unindent(),
  931        );
  932
  933        editor.fold(&Fold, window, cx);
  934        assert_eq!(
  935            editor.display_text(cx),
  936            "
  937                impl Foo {⋯
  938                }
  939            "
  940            .unindent(),
  941        );
  942
  943        editor.unfold_lines(&UnfoldLines, window, cx);
  944        assert_eq!(
  945            editor.display_text(cx),
  946            "
  947                impl Foo {
  948                    // Hello!
  949
  950                    fn a() {
  951                        1
  952                    }
  953
  954                    fn b() {⋯
  955                    }
  956
  957                    fn c() {⋯
  958                    }
  959                }
  960            "
  961            .unindent(),
  962        );
  963
  964        editor.unfold_lines(&UnfoldLines, window, cx);
  965        assert_eq!(
  966            editor.display_text(cx),
  967            editor.buffer.read(cx).read(cx).text()
  968        );
  969    });
  970}
  971
  972#[gpui::test]
  973fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  974    init_test(cx, |_| {});
  975
  976    let editor = cx.add_window(|window, cx| {
  977        let buffer = MultiBuffer::build_simple(
  978            &"
  979                class Foo:
  980                    # Hello!
  981
  982                    def a():
  983                        print(1)
  984
  985                    def b():
  986                        print(2)
  987
  988                    def c():
  989                        print(3)
  990            "
  991            .unindent(),
  992            cx,
  993        );
  994        build_editor(buffer, window, cx)
  995    });
  996
  997    _ = editor.update(cx, |editor, window, cx| {
  998        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  999            s.select_display_ranges([
 1000                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1001            ]);
 1002        });
 1003        editor.fold(&Fold, window, cx);
 1004        assert_eq!(
 1005            editor.display_text(cx),
 1006            "
 1007                class Foo:
 1008                    # Hello!
 1009
 1010                    def a():
 1011                        print(1)
 1012
 1013                    def b():⋯
 1014
 1015                    def c():⋯
 1016            "
 1017            .unindent(),
 1018        );
 1019
 1020        editor.fold(&Fold, window, cx);
 1021        assert_eq!(
 1022            editor.display_text(cx),
 1023            "
 1024                class Foo:⋯
 1025            "
 1026            .unindent(),
 1027        );
 1028
 1029        editor.unfold_lines(&UnfoldLines, window, cx);
 1030        assert_eq!(
 1031            editor.display_text(cx),
 1032            "
 1033                class Foo:
 1034                    # Hello!
 1035
 1036                    def a():
 1037                        print(1)
 1038
 1039                    def b():⋯
 1040
 1041                    def c():⋯
 1042            "
 1043            .unindent(),
 1044        );
 1045
 1046        editor.unfold_lines(&UnfoldLines, window, cx);
 1047        assert_eq!(
 1048            editor.display_text(cx),
 1049            editor.buffer.read(cx).read(cx).text()
 1050        );
 1051    });
 1052}
 1053
 1054#[gpui::test]
 1055fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1056    init_test(cx, |_| {});
 1057
 1058    let editor = cx.add_window(|window, cx| {
 1059        let buffer = MultiBuffer::build_simple(
 1060            &"
 1061                class Foo:
 1062                    # Hello!
 1063
 1064                    def a():
 1065                        print(1)
 1066
 1067                    def b():
 1068                        print(2)
 1069
 1070
 1071                    def c():
 1072                        print(3)
 1073
 1074
 1075            "
 1076            .unindent(),
 1077            cx,
 1078        );
 1079        build_editor(buffer, window, cx)
 1080    });
 1081
 1082    _ = editor.update(cx, |editor, window, cx| {
 1083        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1084            s.select_display_ranges([
 1085                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1086            ]);
 1087        });
 1088        editor.fold(&Fold, window, cx);
 1089        assert_eq!(
 1090            editor.display_text(cx),
 1091            "
 1092                class Foo:
 1093                    # Hello!
 1094
 1095                    def a():
 1096                        print(1)
 1097
 1098                    def b():⋯
 1099
 1100
 1101                    def c():⋯
 1102
 1103
 1104            "
 1105            .unindent(),
 1106        );
 1107
 1108        editor.fold(&Fold, window, cx);
 1109        assert_eq!(
 1110            editor.display_text(cx),
 1111            "
 1112                class Foo:⋯
 1113
 1114
 1115            "
 1116            .unindent(),
 1117        );
 1118
 1119        editor.unfold_lines(&UnfoldLines, window, cx);
 1120        assert_eq!(
 1121            editor.display_text(cx),
 1122            "
 1123                class Foo:
 1124                    # Hello!
 1125
 1126                    def a():
 1127                        print(1)
 1128
 1129                    def b():⋯
 1130
 1131
 1132                    def c():⋯
 1133
 1134
 1135            "
 1136            .unindent(),
 1137        );
 1138
 1139        editor.unfold_lines(&UnfoldLines, window, cx);
 1140        assert_eq!(
 1141            editor.display_text(cx),
 1142            editor.buffer.read(cx).read(cx).text()
 1143        );
 1144    });
 1145}
 1146
 1147#[gpui::test]
 1148fn test_fold_at_level(cx: &mut TestAppContext) {
 1149    init_test(cx, |_| {});
 1150
 1151    let editor = cx.add_window(|window, cx| {
 1152        let buffer = MultiBuffer::build_simple(
 1153            &"
 1154                class Foo:
 1155                    # Hello!
 1156
 1157                    def a():
 1158                        print(1)
 1159
 1160                    def b():
 1161                        print(2)
 1162
 1163
 1164                class Bar:
 1165                    # World!
 1166
 1167                    def a():
 1168                        print(1)
 1169
 1170                    def b():
 1171                        print(2)
 1172
 1173
 1174            "
 1175            .unindent(),
 1176            cx,
 1177        );
 1178        build_editor(buffer, window, cx)
 1179    });
 1180
 1181    _ = editor.update(cx, |editor, window, cx| {
 1182        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1183        assert_eq!(
 1184            editor.display_text(cx),
 1185            "
 1186                class Foo:
 1187                    # Hello!
 1188
 1189                    def a():⋯
 1190
 1191                    def b():⋯
 1192
 1193
 1194                class Bar:
 1195                    # World!
 1196
 1197                    def a():⋯
 1198
 1199                    def b():⋯
 1200
 1201
 1202            "
 1203            .unindent(),
 1204        );
 1205
 1206        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1207        assert_eq!(
 1208            editor.display_text(cx),
 1209            "
 1210                class Foo:⋯
 1211
 1212
 1213                class Bar:⋯
 1214
 1215
 1216            "
 1217            .unindent(),
 1218        );
 1219
 1220        editor.unfold_all(&UnfoldAll, window, cx);
 1221        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1222        assert_eq!(
 1223            editor.display_text(cx),
 1224            "
 1225                class Foo:
 1226                    # Hello!
 1227
 1228                    def a():
 1229                        print(1)
 1230
 1231                    def b():
 1232                        print(2)
 1233
 1234
 1235                class Bar:
 1236                    # World!
 1237
 1238                    def a():
 1239                        print(1)
 1240
 1241                    def b():
 1242                        print(2)
 1243
 1244
 1245            "
 1246            .unindent(),
 1247        );
 1248
 1249        assert_eq!(
 1250            editor.display_text(cx),
 1251            editor.buffer.read(cx).read(cx).text()
 1252        );
 1253    });
 1254}
 1255
 1256#[gpui::test]
 1257fn test_move_cursor(cx: &mut TestAppContext) {
 1258    init_test(cx, |_| {});
 1259
 1260    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1261    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1262
 1263    buffer.update(cx, |buffer, cx| {
 1264        buffer.edit(
 1265            vec![
 1266                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1267                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1268            ],
 1269            None,
 1270            cx,
 1271        );
 1272    });
 1273    _ = editor.update(cx, |editor, window, cx| {
 1274        assert_eq!(
 1275            editor.selections.display_ranges(cx),
 1276            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1277        );
 1278
 1279        editor.move_down(&MoveDown, window, cx);
 1280        assert_eq!(
 1281            editor.selections.display_ranges(cx),
 1282            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1283        );
 1284
 1285        editor.move_right(&MoveRight, window, cx);
 1286        assert_eq!(
 1287            editor.selections.display_ranges(cx),
 1288            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1289        );
 1290
 1291        editor.move_left(&MoveLeft, window, cx);
 1292        assert_eq!(
 1293            editor.selections.display_ranges(cx),
 1294            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1295        );
 1296
 1297        editor.move_up(&MoveUp, window, cx);
 1298        assert_eq!(
 1299            editor.selections.display_ranges(cx),
 1300            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1301        );
 1302
 1303        editor.move_to_end(&MoveToEnd, window, cx);
 1304        assert_eq!(
 1305            editor.selections.display_ranges(cx),
 1306            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1307        );
 1308
 1309        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1310        assert_eq!(
 1311            editor.selections.display_ranges(cx),
 1312            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1313        );
 1314
 1315        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1316            s.select_display_ranges([
 1317                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1318            ]);
 1319        });
 1320        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1321        assert_eq!(
 1322            editor.selections.display_ranges(cx),
 1323            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1324        );
 1325
 1326        editor.select_to_end(&SelectToEnd, window, cx);
 1327        assert_eq!(
 1328            editor.selections.display_ranges(cx),
 1329            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1330        );
 1331    });
 1332}
 1333
 1334#[gpui::test]
 1335fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1336    init_test(cx, |_| {});
 1337
 1338    let editor = cx.add_window(|window, cx| {
 1339        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1340        build_editor(buffer, window, cx)
 1341    });
 1342
 1343    assert_eq!('🟥'.len_utf8(), 4);
 1344    assert_eq!('α'.len_utf8(), 2);
 1345
 1346    _ = editor.update(cx, |editor, window, cx| {
 1347        editor.fold_creases(
 1348            vec![
 1349                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1350                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1351                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1352            ],
 1353            true,
 1354            window,
 1355            cx,
 1356        );
 1357        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1358
 1359        editor.move_right(&MoveRight, window, cx);
 1360        assert_eq!(
 1361            editor.selections.display_ranges(cx),
 1362            &[empty_range(0, "🟥".len())]
 1363        );
 1364        editor.move_right(&MoveRight, window, cx);
 1365        assert_eq!(
 1366            editor.selections.display_ranges(cx),
 1367            &[empty_range(0, "🟥🟧".len())]
 1368        );
 1369        editor.move_right(&MoveRight, window, cx);
 1370        assert_eq!(
 1371            editor.selections.display_ranges(cx),
 1372            &[empty_range(0, "🟥🟧⋯".len())]
 1373        );
 1374
 1375        editor.move_down(&MoveDown, window, cx);
 1376        assert_eq!(
 1377            editor.selections.display_ranges(cx),
 1378            &[empty_range(1, "ab⋯e".len())]
 1379        );
 1380        editor.move_left(&MoveLeft, window, cx);
 1381        assert_eq!(
 1382            editor.selections.display_ranges(cx),
 1383            &[empty_range(1, "ab⋯".len())]
 1384        );
 1385        editor.move_left(&MoveLeft, window, cx);
 1386        assert_eq!(
 1387            editor.selections.display_ranges(cx),
 1388            &[empty_range(1, "ab".len())]
 1389        );
 1390        editor.move_left(&MoveLeft, window, cx);
 1391        assert_eq!(
 1392            editor.selections.display_ranges(cx),
 1393            &[empty_range(1, "a".len())]
 1394        );
 1395
 1396        editor.move_down(&MoveDown, window, cx);
 1397        assert_eq!(
 1398            editor.selections.display_ranges(cx),
 1399            &[empty_range(2, "α".len())]
 1400        );
 1401        editor.move_right(&MoveRight, window, cx);
 1402        assert_eq!(
 1403            editor.selections.display_ranges(cx),
 1404            &[empty_range(2, "αβ".len())]
 1405        );
 1406        editor.move_right(&MoveRight, window, cx);
 1407        assert_eq!(
 1408            editor.selections.display_ranges(cx),
 1409            &[empty_range(2, "αβ⋯".len())]
 1410        );
 1411        editor.move_right(&MoveRight, window, cx);
 1412        assert_eq!(
 1413            editor.selections.display_ranges(cx),
 1414            &[empty_range(2, "αβ⋯ε".len())]
 1415        );
 1416
 1417        editor.move_up(&MoveUp, window, cx);
 1418        assert_eq!(
 1419            editor.selections.display_ranges(cx),
 1420            &[empty_range(1, "ab⋯e".len())]
 1421        );
 1422        editor.move_down(&MoveDown, window, cx);
 1423        assert_eq!(
 1424            editor.selections.display_ranges(cx),
 1425            &[empty_range(2, "αβ⋯ε".len())]
 1426        );
 1427        editor.move_up(&MoveUp, window, cx);
 1428        assert_eq!(
 1429            editor.selections.display_ranges(cx),
 1430            &[empty_range(1, "ab⋯e".len())]
 1431        );
 1432
 1433        editor.move_up(&MoveUp, window, cx);
 1434        assert_eq!(
 1435            editor.selections.display_ranges(cx),
 1436            &[empty_range(0, "🟥🟧".len())]
 1437        );
 1438        editor.move_left(&MoveLeft, window, cx);
 1439        assert_eq!(
 1440            editor.selections.display_ranges(cx),
 1441            &[empty_range(0, "🟥".len())]
 1442        );
 1443        editor.move_left(&MoveLeft, window, cx);
 1444        assert_eq!(
 1445            editor.selections.display_ranges(cx),
 1446            &[empty_range(0, "".len())]
 1447        );
 1448    });
 1449}
 1450
 1451#[gpui::test]
 1452fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1453    init_test(cx, |_| {});
 1454
 1455    let editor = cx.add_window(|window, cx| {
 1456        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1457        build_editor(buffer, window, cx)
 1458    });
 1459    _ = editor.update(cx, |editor, window, cx| {
 1460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1461            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1462        });
 1463
 1464        // moving above start of document should move selection to start of document,
 1465        // but the next move down should still be at the original goal_x
 1466        editor.move_up(&MoveUp, window, cx);
 1467        assert_eq!(
 1468            editor.selections.display_ranges(cx),
 1469            &[empty_range(0, "".len())]
 1470        );
 1471
 1472        editor.move_down(&MoveDown, window, cx);
 1473        assert_eq!(
 1474            editor.selections.display_ranges(cx),
 1475            &[empty_range(1, "abcd".len())]
 1476        );
 1477
 1478        editor.move_down(&MoveDown, window, cx);
 1479        assert_eq!(
 1480            editor.selections.display_ranges(cx),
 1481            &[empty_range(2, "αβγ".len())]
 1482        );
 1483
 1484        editor.move_down(&MoveDown, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[empty_range(3, "abcd".len())]
 1488        );
 1489
 1490        editor.move_down(&MoveDown, window, cx);
 1491        assert_eq!(
 1492            editor.selections.display_ranges(cx),
 1493            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1494        );
 1495
 1496        // moving past end of document should not change goal_x
 1497        editor.move_down(&MoveDown, window, cx);
 1498        assert_eq!(
 1499            editor.selections.display_ranges(cx),
 1500            &[empty_range(5, "".len())]
 1501        );
 1502
 1503        editor.move_down(&MoveDown, window, cx);
 1504        assert_eq!(
 1505            editor.selections.display_ranges(cx),
 1506            &[empty_range(5, "".len())]
 1507        );
 1508
 1509        editor.move_up(&MoveUp, window, cx);
 1510        assert_eq!(
 1511            editor.selections.display_ranges(cx),
 1512            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1513        );
 1514
 1515        editor.move_up(&MoveUp, window, cx);
 1516        assert_eq!(
 1517            editor.selections.display_ranges(cx),
 1518            &[empty_range(3, "abcd".len())]
 1519        );
 1520
 1521        editor.move_up(&MoveUp, window, cx);
 1522        assert_eq!(
 1523            editor.selections.display_ranges(cx),
 1524            &[empty_range(2, "αβγ".len())]
 1525        );
 1526    });
 1527}
 1528
 1529#[gpui::test]
 1530fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1531    init_test(cx, |_| {});
 1532    let move_to_beg = MoveToBeginningOfLine {
 1533        stop_at_soft_wraps: true,
 1534        stop_at_indent: true,
 1535    };
 1536
 1537    let delete_to_beg = DeleteToBeginningOfLine {
 1538        stop_at_indent: false,
 1539    };
 1540
 1541    let move_to_end = MoveToEndOfLine {
 1542        stop_at_soft_wraps: true,
 1543    };
 1544
 1545    let editor = cx.add_window(|window, cx| {
 1546        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1547        build_editor(buffer, window, cx)
 1548    });
 1549    _ = editor.update(cx, |editor, window, cx| {
 1550        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1551            s.select_display_ranges([
 1552                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1553                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1554            ]);
 1555        });
 1556    });
 1557
 1558    _ = editor.update(cx, |editor, window, cx| {
 1559        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1560        assert_eq!(
 1561            editor.selections.display_ranges(cx),
 1562            &[
 1563                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1564                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1565            ]
 1566        );
 1567    });
 1568
 1569    _ = editor.update(cx, |editor, window, cx| {
 1570        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1571        assert_eq!(
 1572            editor.selections.display_ranges(cx),
 1573            &[
 1574                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1575                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1576            ]
 1577        );
 1578    });
 1579
 1580    _ = editor.update(cx, |editor, window, cx| {
 1581        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1582        assert_eq!(
 1583            editor.selections.display_ranges(cx),
 1584            &[
 1585                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1586                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1587            ]
 1588        );
 1589    });
 1590
 1591    _ = editor.update(cx, |editor, window, cx| {
 1592        editor.move_to_end_of_line(&move_to_end, window, cx);
 1593        assert_eq!(
 1594            editor.selections.display_ranges(cx),
 1595            &[
 1596                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1597                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1598            ]
 1599        );
 1600    });
 1601
 1602    // Moving to the end of line again is a no-op.
 1603    _ = editor.update(cx, |editor, window, cx| {
 1604        editor.move_to_end_of_line(&move_to_end, window, cx);
 1605        assert_eq!(
 1606            editor.selections.display_ranges(cx),
 1607            &[
 1608                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1609                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1610            ]
 1611        );
 1612    });
 1613
 1614    _ = editor.update(cx, |editor, window, cx| {
 1615        editor.move_left(&MoveLeft, window, cx);
 1616        editor.select_to_beginning_of_line(
 1617            &SelectToBeginningOfLine {
 1618                stop_at_soft_wraps: true,
 1619                stop_at_indent: true,
 1620            },
 1621            window,
 1622            cx,
 1623        );
 1624        assert_eq!(
 1625            editor.selections.display_ranges(cx),
 1626            &[
 1627                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1628                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1629            ]
 1630        );
 1631    });
 1632
 1633    _ = editor.update(cx, |editor, window, cx| {
 1634        editor.select_to_beginning_of_line(
 1635            &SelectToBeginningOfLine {
 1636                stop_at_soft_wraps: true,
 1637                stop_at_indent: true,
 1638            },
 1639            window,
 1640            cx,
 1641        );
 1642        assert_eq!(
 1643            editor.selections.display_ranges(cx),
 1644            &[
 1645                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1646                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1647            ]
 1648        );
 1649    });
 1650
 1651    _ = editor.update(cx, |editor, window, cx| {
 1652        editor.select_to_beginning_of_line(
 1653            &SelectToBeginningOfLine {
 1654                stop_at_soft_wraps: true,
 1655                stop_at_indent: true,
 1656            },
 1657            window,
 1658            cx,
 1659        );
 1660        assert_eq!(
 1661            editor.selections.display_ranges(cx),
 1662            &[
 1663                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1664                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1665            ]
 1666        );
 1667    });
 1668
 1669    _ = editor.update(cx, |editor, window, cx| {
 1670        editor.select_to_end_of_line(
 1671            &SelectToEndOfLine {
 1672                stop_at_soft_wraps: true,
 1673            },
 1674            window,
 1675            cx,
 1676        );
 1677        assert_eq!(
 1678            editor.selections.display_ranges(cx),
 1679            &[
 1680                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1681                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1682            ]
 1683        );
 1684    });
 1685
 1686    _ = editor.update(cx, |editor, window, cx| {
 1687        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1688        assert_eq!(editor.display_text(cx), "ab\n  de");
 1689        assert_eq!(
 1690            editor.selections.display_ranges(cx),
 1691            &[
 1692                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1693                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1694            ]
 1695        );
 1696    });
 1697
 1698    _ = editor.update(cx, |editor, window, cx| {
 1699        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1700        assert_eq!(editor.display_text(cx), "\n");
 1701        assert_eq!(
 1702            editor.selections.display_ranges(cx),
 1703            &[
 1704                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1705                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1706            ]
 1707        );
 1708    });
 1709}
 1710
 1711#[gpui::test]
 1712fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1713    init_test(cx, |_| {});
 1714    let move_to_beg = MoveToBeginningOfLine {
 1715        stop_at_soft_wraps: false,
 1716        stop_at_indent: false,
 1717    };
 1718
 1719    let move_to_end = MoveToEndOfLine {
 1720        stop_at_soft_wraps: false,
 1721    };
 1722
 1723    let editor = cx.add_window(|window, cx| {
 1724        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1725        build_editor(buffer, window, cx)
 1726    });
 1727
 1728    _ = editor.update(cx, |editor, window, cx| {
 1729        editor.set_wrap_width(Some(140.0.into()), cx);
 1730
 1731        // We expect the following lines after wrapping
 1732        // ```
 1733        // thequickbrownfox
 1734        // jumpedoverthelazydo
 1735        // gs
 1736        // ```
 1737        // The final `gs` was soft-wrapped onto a new line.
 1738        assert_eq!(
 1739            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1740            editor.display_text(cx),
 1741        );
 1742
 1743        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1744        // Start the cursor at the `k` on the first line
 1745        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1746            s.select_display_ranges([
 1747                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1748            ]);
 1749        });
 1750
 1751        // Moving to the beginning of the line should put us at the beginning of the line.
 1752        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1753        assert_eq!(
 1754            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1755            editor.selections.display_ranges(cx)
 1756        );
 1757
 1758        // Moving to the end of the line should put us at the end of the line.
 1759        editor.move_to_end_of_line(&move_to_end, window, cx);
 1760        assert_eq!(
 1761            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1762            editor.selections.display_ranges(cx)
 1763        );
 1764
 1765        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1766        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1767        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1768            s.select_display_ranges([
 1769                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1770            ]);
 1771        });
 1772
 1773        // Moving to the beginning of the line should put us at the start of the second line of
 1774        // display text, i.e., the `j`.
 1775        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1776        assert_eq!(
 1777            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1778            editor.selections.display_ranges(cx)
 1779        );
 1780
 1781        // Moving to the beginning of the line again should be a no-op.
 1782        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1783        assert_eq!(
 1784            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1785            editor.selections.display_ranges(cx)
 1786        );
 1787
 1788        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1789        // next display line.
 1790        editor.move_to_end_of_line(&move_to_end, window, cx);
 1791        assert_eq!(
 1792            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1793            editor.selections.display_ranges(cx)
 1794        );
 1795
 1796        // Moving to the end of the line again should be a no-op.
 1797        editor.move_to_end_of_line(&move_to_end, window, cx);
 1798        assert_eq!(
 1799            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1800            editor.selections.display_ranges(cx)
 1801        );
 1802    });
 1803}
 1804
 1805#[gpui::test]
 1806fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1807    init_test(cx, |_| {});
 1808
 1809    let move_to_beg = MoveToBeginningOfLine {
 1810        stop_at_soft_wraps: true,
 1811        stop_at_indent: true,
 1812    };
 1813
 1814    let select_to_beg = SelectToBeginningOfLine {
 1815        stop_at_soft_wraps: true,
 1816        stop_at_indent: true,
 1817    };
 1818
 1819    let delete_to_beg = DeleteToBeginningOfLine {
 1820        stop_at_indent: true,
 1821    };
 1822
 1823    let move_to_end = MoveToEndOfLine {
 1824        stop_at_soft_wraps: false,
 1825    };
 1826
 1827    let editor = cx.add_window(|window, cx| {
 1828        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1829        build_editor(buffer, window, cx)
 1830    });
 1831
 1832    _ = editor.update(cx, |editor, window, cx| {
 1833        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1834            s.select_display_ranges([
 1835                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1836                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1837            ]);
 1838        });
 1839
 1840        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1841        // and the second cursor at the first non-whitespace character in the line.
 1842        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1843        assert_eq!(
 1844            editor.selections.display_ranges(cx),
 1845            &[
 1846                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1847                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1848            ]
 1849        );
 1850
 1851        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1852        // and should move the second cursor to the beginning of the line.
 1853        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1854        assert_eq!(
 1855            editor.selections.display_ranges(cx),
 1856            &[
 1857                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1858                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1859            ]
 1860        );
 1861
 1862        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1863        // and should move the second cursor back to the first non-whitespace character in the line.
 1864        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1865        assert_eq!(
 1866            editor.selections.display_ranges(cx),
 1867            &[
 1868                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1869                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1870            ]
 1871        );
 1872
 1873        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1874        // and to the first non-whitespace character in the line for the second cursor.
 1875        editor.move_to_end_of_line(&move_to_end, window, cx);
 1876        editor.move_left(&MoveLeft, window, cx);
 1877        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1878        assert_eq!(
 1879            editor.selections.display_ranges(cx),
 1880            &[
 1881                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1882                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1883            ]
 1884        );
 1885
 1886        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1887        // and should select to the beginning of the line for the second cursor.
 1888        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1889        assert_eq!(
 1890            editor.selections.display_ranges(cx),
 1891            &[
 1892                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1893                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1894            ]
 1895        );
 1896
 1897        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1898        // and should delete to the first non-whitespace character in the line for the second cursor.
 1899        editor.move_to_end_of_line(&move_to_end, window, cx);
 1900        editor.move_left(&MoveLeft, window, cx);
 1901        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1902        assert_eq!(editor.text(cx), "c\n  f");
 1903    });
 1904}
 1905
 1906#[gpui::test]
 1907fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1908    init_test(cx, |_| {});
 1909
 1910    let move_to_beg = MoveToBeginningOfLine {
 1911        stop_at_soft_wraps: true,
 1912        stop_at_indent: true,
 1913    };
 1914
 1915    let editor = cx.add_window(|window, cx| {
 1916        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1917        build_editor(buffer, window, cx)
 1918    });
 1919
 1920    _ = editor.update(cx, |editor, window, cx| {
 1921        // test cursor between line_start and indent_start
 1922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1923            s.select_display_ranges([
 1924                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1925            ]);
 1926        });
 1927
 1928        // cursor should move to line_start
 1929        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1930        assert_eq!(
 1931            editor.selections.display_ranges(cx),
 1932            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1933        );
 1934
 1935        // cursor should move to indent_start
 1936        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1937        assert_eq!(
 1938            editor.selections.display_ranges(cx),
 1939            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 1940        );
 1941
 1942        // cursor should move to back to line_start
 1943        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1944        assert_eq!(
 1945            editor.selections.display_ranges(cx),
 1946            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1947        );
 1948    });
 1949}
 1950
 1951#[gpui::test]
 1952fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1953    init_test(cx, |_| {});
 1954
 1955    let editor = cx.add_window(|window, cx| {
 1956        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1957        build_editor(buffer, window, cx)
 1958    });
 1959    _ = editor.update(cx, |editor, window, cx| {
 1960        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1961            s.select_display_ranges([
 1962                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1963                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1964            ])
 1965        });
 1966        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1967        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1968
 1969        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1970        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1971
 1972        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1973        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1974
 1975        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1976        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1977
 1978        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1979        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1980
 1981        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1982        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1983
 1984        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1985        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1986
 1987        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1988        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1989
 1990        editor.move_right(&MoveRight, window, cx);
 1991        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1992        assert_selection_ranges(
 1993            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1994            editor,
 1995            cx,
 1996        );
 1997
 1998        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1999        assert_selection_ranges(
 2000            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2001            editor,
 2002            cx,
 2003        );
 2004
 2005        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2006        assert_selection_ranges(
 2007            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2008            editor,
 2009            cx,
 2010        );
 2011    });
 2012}
 2013
 2014#[gpui::test]
 2015fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2016    init_test(cx, |_| {});
 2017
 2018    let editor = cx.add_window(|window, cx| {
 2019        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2020        build_editor(buffer, window, cx)
 2021    });
 2022
 2023    _ = editor.update(cx, |editor, window, cx| {
 2024        editor.set_wrap_width(Some(140.0.into()), cx);
 2025        assert_eq!(
 2026            editor.display_text(cx),
 2027            "use one::{\n    two::three::\n    four::five\n};"
 2028        );
 2029
 2030        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2031            s.select_display_ranges([
 2032                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2033            ]);
 2034        });
 2035
 2036        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2037        assert_eq!(
 2038            editor.selections.display_ranges(cx),
 2039            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2040        );
 2041
 2042        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2043        assert_eq!(
 2044            editor.selections.display_ranges(cx),
 2045            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2046        );
 2047
 2048        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2049        assert_eq!(
 2050            editor.selections.display_ranges(cx),
 2051            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2052        );
 2053
 2054        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2055        assert_eq!(
 2056            editor.selections.display_ranges(cx),
 2057            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2058        );
 2059
 2060        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2061        assert_eq!(
 2062            editor.selections.display_ranges(cx),
 2063            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2064        );
 2065
 2066        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2067        assert_eq!(
 2068            editor.selections.display_ranges(cx),
 2069            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2070        );
 2071    });
 2072}
 2073
 2074#[gpui::test]
 2075async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2076    init_test(cx, |_| {});
 2077    let mut cx = EditorTestContext::new(cx).await;
 2078
 2079    let line_height = cx.editor(|editor, window, _| {
 2080        editor
 2081            .style()
 2082            .unwrap()
 2083            .text
 2084            .line_height_in_pixels(window.rem_size())
 2085    });
 2086    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2087
 2088    cx.set_state(
 2089        &r#"ˇone
 2090        two
 2091
 2092        three
 2093        fourˇ
 2094        five
 2095
 2096        six"#
 2097            .unindent(),
 2098    );
 2099
 2100    cx.update_editor(|editor, window, cx| {
 2101        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2102    });
 2103    cx.assert_editor_state(
 2104        &r#"one
 2105        two
 2106        ˇ
 2107        three
 2108        four
 2109        five
 2110        ˇ
 2111        six"#
 2112            .unindent(),
 2113    );
 2114
 2115    cx.update_editor(|editor, window, cx| {
 2116        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2117    });
 2118    cx.assert_editor_state(
 2119        &r#"one
 2120        two
 2121
 2122        three
 2123        four
 2124        five
 2125        ˇ
 2126        sixˇ"#
 2127            .unindent(),
 2128    );
 2129
 2130    cx.update_editor(|editor, window, cx| {
 2131        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2132    });
 2133    cx.assert_editor_state(
 2134        &r#"one
 2135        two
 2136
 2137        three
 2138        four
 2139        five
 2140
 2141        sixˇ"#
 2142            .unindent(),
 2143    );
 2144
 2145    cx.update_editor(|editor, window, cx| {
 2146        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2147    });
 2148    cx.assert_editor_state(
 2149        &r#"one
 2150        two
 2151
 2152        three
 2153        four
 2154        five
 2155        ˇ
 2156        six"#
 2157            .unindent(),
 2158    );
 2159
 2160    cx.update_editor(|editor, window, cx| {
 2161        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2162    });
 2163    cx.assert_editor_state(
 2164        &r#"one
 2165        two
 2166        ˇ
 2167        three
 2168        four
 2169        five
 2170
 2171        six"#
 2172            .unindent(),
 2173    );
 2174
 2175    cx.update_editor(|editor, window, cx| {
 2176        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2177    });
 2178    cx.assert_editor_state(
 2179        &r#"ˇone
 2180        two
 2181
 2182        three
 2183        four
 2184        five
 2185
 2186        six"#
 2187            .unindent(),
 2188    );
 2189}
 2190
 2191#[gpui::test]
 2192async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2193    init_test(cx, |_| {});
 2194    let mut cx = EditorTestContext::new(cx).await;
 2195    let line_height = cx.editor(|editor, window, _| {
 2196        editor
 2197            .style()
 2198            .unwrap()
 2199            .text
 2200            .line_height_in_pixels(window.rem_size())
 2201    });
 2202    let window = cx.window;
 2203    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2204
 2205    cx.set_state(
 2206        r#"ˇone
 2207        two
 2208        three
 2209        four
 2210        five
 2211        six
 2212        seven
 2213        eight
 2214        nine
 2215        ten
 2216        "#,
 2217    );
 2218
 2219    cx.update_editor(|editor, window, cx| {
 2220        assert_eq!(
 2221            editor.snapshot(window, cx).scroll_position(),
 2222            gpui::Point::new(0., 0.)
 2223        );
 2224        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2225        assert_eq!(
 2226            editor.snapshot(window, cx).scroll_position(),
 2227            gpui::Point::new(0., 3.)
 2228        );
 2229        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2230        assert_eq!(
 2231            editor.snapshot(window, cx).scroll_position(),
 2232            gpui::Point::new(0., 6.)
 2233        );
 2234        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2235        assert_eq!(
 2236            editor.snapshot(window, cx).scroll_position(),
 2237            gpui::Point::new(0., 3.)
 2238        );
 2239
 2240        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2241        assert_eq!(
 2242            editor.snapshot(window, cx).scroll_position(),
 2243            gpui::Point::new(0., 1.)
 2244        );
 2245        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2246        assert_eq!(
 2247            editor.snapshot(window, cx).scroll_position(),
 2248            gpui::Point::new(0., 3.)
 2249        );
 2250    });
 2251}
 2252
 2253#[gpui::test]
 2254async fn test_autoscroll(cx: &mut TestAppContext) {
 2255    init_test(cx, |_| {});
 2256    let mut cx = EditorTestContext::new(cx).await;
 2257
 2258    let line_height = cx.update_editor(|editor, window, cx| {
 2259        editor.set_vertical_scroll_margin(2, cx);
 2260        editor
 2261            .style()
 2262            .unwrap()
 2263            .text
 2264            .line_height_in_pixels(window.rem_size())
 2265    });
 2266    let window = cx.window;
 2267    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2268
 2269    cx.set_state(
 2270        r#"ˇone
 2271            two
 2272            three
 2273            four
 2274            five
 2275            six
 2276            seven
 2277            eight
 2278            nine
 2279            ten
 2280        "#,
 2281    );
 2282    cx.update_editor(|editor, window, cx| {
 2283        assert_eq!(
 2284            editor.snapshot(window, cx).scroll_position(),
 2285            gpui::Point::new(0., 0.0)
 2286        );
 2287    });
 2288
 2289    // Add a cursor below the visible area. Since both cursors cannot fit
 2290    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2291    // allows the vertical scroll margin below that cursor.
 2292    cx.update_editor(|editor, window, cx| {
 2293        editor.change_selections(Default::default(), window, cx, |selections| {
 2294            selections.select_ranges([
 2295                Point::new(0, 0)..Point::new(0, 0),
 2296                Point::new(6, 0)..Point::new(6, 0),
 2297            ]);
 2298        })
 2299    });
 2300    cx.update_editor(|editor, window, cx| {
 2301        assert_eq!(
 2302            editor.snapshot(window, cx).scroll_position(),
 2303            gpui::Point::new(0., 3.0)
 2304        );
 2305    });
 2306
 2307    // Move down. The editor cursor scrolls down to track the newest cursor.
 2308    cx.update_editor(|editor, window, cx| {
 2309        editor.move_down(&Default::default(), window, cx);
 2310    });
 2311    cx.update_editor(|editor, window, cx| {
 2312        assert_eq!(
 2313            editor.snapshot(window, cx).scroll_position(),
 2314            gpui::Point::new(0., 4.0)
 2315        );
 2316    });
 2317
 2318    // Add a cursor above the visible area. Since both cursors fit on screen,
 2319    // the editor scrolls to show both.
 2320    cx.update_editor(|editor, window, cx| {
 2321        editor.change_selections(Default::default(), window, cx, |selections| {
 2322            selections.select_ranges([
 2323                Point::new(1, 0)..Point::new(1, 0),
 2324                Point::new(6, 0)..Point::new(6, 0),
 2325            ]);
 2326        })
 2327    });
 2328    cx.update_editor(|editor, window, cx| {
 2329        assert_eq!(
 2330            editor.snapshot(window, cx).scroll_position(),
 2331            gpui::Point::new(0., 1.0)
 2332        );
 2333    });
 2334}
 2335
 2336#[gpui::test]
 2337async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2338    init_test(cx, |_| {});
 2339    let mut cx = EditorTestContext::new(cx).await;
 2340
 2341    let line_height = cx.editor(|editor, window, _cx| {
 2342        editor
 2343            .style()
 2344            .unwrap()
 2345            .text
 2346            .line_height_in_pixels(window.rem_size())
 2347    });
 2348    let window = cx.window;
 2349    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2350    cx.set_state(
 2351        &r#"
 2352        ˇone
 2353        two
 2354        threeˇ
 2355        four
 2356        five
 2357        six
 2358        seven
 2359        eight
 2360        nine
 2361        ten
 2362        "#
 2363        .unindent(),
 2364    );
 2365
 2366    cx.update_editor(|editor, window, cx| {
 2367        editor.move_page_down(&MovePageDown::default(), window, cx)
 2368    });
 2369    cx.assert_editor_state(
 2370        &r#"
 2371        one
 2372        two
 2373        three
 2374        ˇfour
 2375        five
 2376        sixˇ
 2377        seven
 2378        eight
 2379        nine
 2380        ten
 2381        "#
 2382        .unindent(),
 2383    );
 2384
 2385    cx.update_editor(|editor, window, cx| {
 2386        editor.move_page_down(&MovePageDown::default(), window, cx)
 2387    });
 2388    cx.assert_editor_state(
 2389        &r#"
 2390        one
 2391        two
 2392        three
 2393        four
 2394        five
 2395        six
 2396        ˇseven
 2397        eight
 2398        nineˇ
 2399        ten
 2400        "#
 2401        .unindent(),
 2402    );
 2403
 2404    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2405    cx.assert_editor_state(
 2406        &r#"
 2407        one
 2408        two
 2409        three
 2410        ˇfour
 2411        five
 2412        sixˇ
 2413        seven
 2414        eight
 2415        nine
 2416        ten
 2417        "#
 2418        .unindent(),
 2419    );
 2420
 2421    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2422    cx.assert_editor_state(
 2423        &r#"
 2424        ˇone
 2425        two
 2426        threeˇ
 2427        four
 2428        five
 2429        six
 2430        seven
 2431        eight
 2432        nine
 2433        ten
 2434        "#
 2435        .unindent(),
 2436    );
 2437
 2438    // Test select collapsing
 2439    cx.update_editor(|editor, window, cx| {
 2440        editor.move_page_down(&MovePageDown::default(), window, cx);
 2441        editor.move_page_down(&MovePageDown::default(), window, cx);
 2442        editor.move_page_down(&MovePageDown::default(), window, cx);
 2443    });
 2444    cx.assert_editor_state(
 2445        &r#"
 2446        one
 2447        two
 2448        three
 2449        four
 2450        five
 2451        six
 2452        seven
 2453        eight
 2454        nine
 2455        ˇten
 2456        ˇ"#
 2457        .unindent(),
 2458    );
 2459}
 2460
 2461#[gpui::test]
 2462async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2463    init_test(cx, |_| {});
 2464    let mut cx = EditorTestContext::new(cx).await;
 2465    cx.set_state("one «two threeˇ» four");
 2466    cx.update_editor(|editor, window, cx| {
 2467        editor.delete_to_beginning_of_line(
 2468            &DeleteToBeginningOfLine {
 2469                stop_at_indent: false,
 2470            },
 2471            window,
 2472            cx,
 2473        );
 2474        assert_eq!(editor.text(cx), " four");
 2475    });
 2476}
 2477
 2478#[gpui::test]
 2479async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2480    init_test(cx, |_| {});
 2481
 2482    let mut cx = EditorTestContext::new(cx).await;
 2483
 2484    // For an empty selection, the preceding word fragment is deleted.
 2485    // For non-empty selections, only selected characters are deleted.
 2486    cx.set_state("onˇe two t«hreˇ»e four");
 2487    cx.update_editor(|editor, window, cx| {
 2488        editor.delete_to_previous_word_start(
 2489            &DeleteToPreviousWordStart {
 2490                ignore_newlines: false,
 2491                ignore_brackets: false,
 2492            },
 2493            window,
 2494            cx,
 2495        );
 2496    });
 2497    cx.assert_editor_state("ˇe two tˇe four");
 2498
 2499    cx.set_state("e tˇwo te «fˇ»our");
 2500    cx.update_editor(|editor, window, cx| {
 2501        editor.delete_to_next_word_end(
 2502            &DeleteToNextWordEnd {
 2503                ignore_newlines: false,
 2504                ignore_brackets: false,
 2505            },
 2506            window,
 2507            cx,
 2508        );
 2509    });
 2510    cx.assert_editor_state("e tˇ te ˇour");
 2511}
 2512
 2513#[gpui::test]
 2514async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2515    init_test(cx, |_| {});
 2516
 2517    let mut cx = EditorTestContext::new(cx).await;
 2518
 2519    cx.set_state("here is some text    ˇwith a space");
 2520    cx.update_editor(|editor, window, cx| {
 2521        editor.delete_to_previous_word_start(
 2522            &DeleteToPreviousWordStart {
 2523                ignore_newlines: false,
 2524                ignore_brackets: true,
 2525            },
 2526            window,
 2527            cx,
 2528        );
 2529    });
 2530    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2531    cx.assert_editor_state("here is some textˇwith a space");
 2532
 2533    cx.set_state("here is some text    ˇwith a space");
 2534    cx.update_editor(|editor, window, cx| {
 2535        editor.delete_to_previous_word_start(
 2536            &DeleteToPreviousWordStart {
 2537                ignore_newlines: false,
 2538                ignore_brackets: false,
 2539            },
 2540            window,
 2541            cx,
 2542        );
 2543    });
 2544    cx.assert_editor_state("here is some textˇwith a space");
 2545
 2546    cx.set_state("here is some textˇ    with a space");
 2547    cx.update_editor(|editor, window, cx| {
 2548        editor.delete_to_next_word_end(
 2549            &DeleteToNextWordEnd {
 2550                ignore_newlines: false,
 2551                ignore_brackets: true,
 2552            },
 2553            window,
 2554            cx,
 2555        );
 2556    });
 2557    // Same happens in the other direction.
 2558    cx.assert_editor_state("here is some textˇwith a space");
 2559
 2560    cx.set_state("here is some textˇ    with a space");
 2561    cx.update_editor(|editor, window, cx| {
 2562        editor.delete_to_next_word_end(
 2563            &DeleteToNextWordEnd {
 2564                ignore_newlines: false,
 2565                ignore_brackets: false,
 2566            },
 2567            window,
 2568            cx,
 2569        );
 2570    });
 2571    cx.assert_editor_state("here is some textˇwith a space");
 2572
 2573    cx.set_state("here is some textˇ    with a space");
 2574    cx.update_editor(|editor, window, cx| {
 2575        editor.delete_to_next_word_end(
 2576            &DeleteToNextWordEnd {
 2577                ignore_newlines: true,
 2578                ignore_brackets: false,
 2579            },
 2580            window,
 2581            cx,
 2582        );
 2583    });
 2584    cx.assert_editor_state("here is some textˇwith a space");
 2585    cx.update_editor(|editor, window, cx| {
 2586        editor.delete_to_previous_word_start(
 2587            &DeleteToPreviousWordStart {
 2588                ignore_newlines: true,
 2589                ignore_brackets: false,
 2590            },
 2591            window,
 2592            cx,
 2593        );
 2594    });
 2595    cx.assert_editor_state("here is some ˇwith a space");
 2596    cx.update_editor(|editor, window, cx| {
 2597        editor.delete_to_previous_word_start(
 2598            &DeleteToPreviousWordStart {
 2599                ignore_newlines: true,
 2600                ignore_brackets: false,
 2601            },
 2602            window,
 2603            cx,
 2604        );
 2605    });
 2606    // Single whitespaces are removed with the word behind them.
 2607    cx.assert_editor_state("here is ˇwith a space");
 2608    cx.update_editor(|editor, window, cx| {
 2609        editor.delete_to_previous_word_start(
 2610            &DeleteToPreviousWordStart {
 2611                ignore_newlines: true,
 2612                ignore_brackets: false,
 2613            },
 2614            window,
 2615            cx,
 2616        );
 2617    });
 2618    cx.assert_editor_state("here ˇwith a space");
 2619    cx.update_editor(|editor, window, cx| {
 2620        editor.delete_to_previous_word_start(
 2621            &DeleteToPreviousWordStart {
 2622                ignore_newlines: true,
 2623                ignore_brackets: false,
 2624            },
 2625            window,
 2626            cx,
 2627        );
 2628    });
 2629    cx.assert_editor_state("ˇwith a space");
 2630    cx.update_editor(|editor, window, cx| {
 2631        editor.delete_to_previous_word_start(
 2632            &DeleteToPreviousWordStart {
 2633                ignore_newlines: true,
 2634                ignore_brackets: false,
 2635            },
 2636            window,
 2637            cx,
 2638        );
 2639    });
 2640    cx.assert_editor_state("ˇwith a space");
 2641    cx.update_editor(|editor, window, cx| {
 2642        editor.delete_to_next_word_end(
 2643            &DeleteToNextWordEnd {
 2644                ignore_newlines: true,
 2645                ignore_brackets: false,
 2646            },
 2647            window,
 2648            cx,
 2649        );
 2650    });
 2651    // Same happens in the other direction.
 2652    cx.assert_editor_state("ˇ a space");
 2653    cx.update_editor(|editor, window, cx| {
 2654        editor.delete_to_next_word_end(
 2655            &DeleteToNextWordEnd {
 2656                ignore_newlines: true,
 2657                ignore_brackets: false,
 2658            },
 2659            window,
 2660            cx,
 2661        );
 2662    });
 2663    cx.assert_editor_state("ˇ space");
 2664    cx.update_editor(|editor, window, cx| {
 2665        editor.delete_to_next_word_end(
 2666            &DeleteToNextWordEnd {
 2667                ignore_newlines: true,
 2668                ignore_brackets: false,
 2669            },
 2670            window,
 2671            cx,
 2672        );
 2673    });
 2674    cx.assert_editor_state("ˇ");
 2675    cx.update_editor(|editor, window, cx| {
 2676        editor.delete_to_next_word_end(
 2677            &DeleteToNextWordEnd {
 2678                ignore_newlines: true,
 2679                ignore_brackets: false,
 2680            },
 2681            window,
 2682            cx,
 2683        );
 2684    });
 2685    cx.assert_editor_state("ˇ");
 2686    cx.update_editor(|editor, window, cx| {
 2687        editor.delete_to_previous_word_start(
 2688            &DeleteToPreviousWordStart {
 2689                ignore_newlines: true,
 2690                ignore_brackets: false,
 2691            },
 2692            window,
 2693            cx,
 2694        );
 2695    });
 2696    cx.assert_editor_state("ˇ");
 2697}
 2698
 2699#[gpui::test]
 2700async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2701    init_test(cx, |_| {});
 2702
 2703    let language = Arc::new(
 2704        Language::new(
 2705            LanguageConfig {
 2706                brackets: BracketPairConfig {
 2707                    pairs: vec![
 2708                        BracketPair {
 2709                            start: "\"".to_string(),
 2710                            end: "\"".to_string(),
 2711                            close: true,
 2712                            surround: true,
 2713                            newline: false,
 2714                        },
 2715                        BracketPair {
 2716                            start: "(".to_string(),
 2717                            end: ")".to_string(),
 2718                            close: true,
 2719                            surround: true,
 2720                            newline: true,
 2721                        },
 2722                    ],
 2723                    ..BracketPairConfig::default()
 2724                },
 2725                ..LanguageConfig::default()
 2726            },
 2727            Some(tree_sitter_rust::LANGUAGE.into()),
 2728        )
 2729        .with_brackets_query(
 2730            r#"
 2731                ("(" @open ")" @close)
 2732                ("\"" @open "\"" @close)
 2733            "#,
 2734        )
 2735        .unwrap(),
 2736    );
 2737
 2738    let mut cx = EditorTestContext::new(cx).await;
 2739    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2740
 2741    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2742    cx.update_editor(|editor, window, cx| {
 2743        editor.delete_to_previous_word_start(
 2744            &DeleteToPreviousWordStart {
 2745                ignore_newlines: true,
 2746                ignore_brackets: false,
 2747            },
 2748            window,
 2749            cx,
 2750        );
 2751    });
 2752    // Deletion stops before brackets if asked to not ignore them.
 2753    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2754    cx.update_editor(|editor, window, cx| {
 2755        editor.delete_to_previous_word_start(
 2756            &DeleteToPreviousWordStart {
 2757                ignore_newlines: true,
 2758                ignore_brackets: false,
 2759            },
 2760            window,
 2761            cx,
 2762        );
 2763    });
 2764    // Deletion has to remove a single bracket and then stop again.
 2765    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2766
 2767    cx.update_editor(|editor, window, cx| {
 2768        editor.delete_to_previous_word_start(
 2769            &DeleteToPreviousWordStart {
 2770                ignore_newlines: true,
 2771                ignore_brackets: false,
 2772            },
 2773            window,
 2774            cx,
 2775        );
 2776    });
 2777    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2778
 2779    cx.update_editor(|editor, window, cx| {
 2780        editor.delete_to_previous_word_start(
 2781            &DeleteToPreviousWordStart {
 2782                ignore_newlines: true,
 2783                ignore_brackets: false,
 2784            },
 2785            window,
 2786            cx,
 2787        );
 2788    });
 2789    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2790
 2791    cx.update_editor(|editor, window, cx| {
 2792        editor.delete_to_previous_word_start(
 2793            &DeleteToPreviousWordStart {
 2794                ignore_newlines: true,
 2795                ignore_brackets: false,
 2796            },
 2797            window,
 2798            cx,
 2799        );
 2800    });
 2801    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2802
 2803    cx.update_editor(|editor, window, cx| {
 2804        editor.delete_to_next_word_end(
 2805            &DeleteToNextWordEnd {
 2806                ignore_newlines: true,
 2807                ignore_brackets: false,
 2808            },
 2809            window,
 2810            cx,
 2811        );
 2812    });
 2813    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2814    cx.assert_editor_state(r#"ˇ");"#);
 2815
 2816    cx.update_editor(|editor, window, cx| {
 2817        editor.delete_to_next_word_end(
 2818            &DeleteToNextWordEnd {
 2819                ignore_newlines: true,
 2820                ignore_brackets: false,
 2821            },
 2822            window,
 2823            cx,
 2824        );
 2825    });
 2826    cx.assert_editor_state(r#"ˇ"#);
 2827
 2828    cx.update_editor(|editor, window, cx| {
 2829        editor.delete_to_next_word_end(
 2830            &DeleteToNextWordEnd {
 2831                ignore_newlines: true,
 2832                ignore_brackets: false,
 2833            },
 2834            window,
 2835            cx,
 2836        );
 2837    });
 2838    cx.assert_editor_state(r#"ˇ"#);
 2839
 2840    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2841    cx.update_editor(|editor, window, cx| {
 2842        editor.delete_to_previous_word_start(
 2843            &DeleteToPreviousWordStart {
 2844                ignore_newlines: true,
 2845                ignore_brackets: true,
 2846            },
 2847            window,
 2848            cx,
 2849        );
 2850    });
 2851    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2852}
 2853
 2854#[gpui::test]
 2855fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2856    init_test(cx, |_| {});
 2857
 2858    let editor = cx.add_window(|window, cx| {
 2859        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2860        build_editor(buffer, window, cx)
 2861    });
 2862    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2863        ignore_newlines: false,
 2864        ignore_brackets: false,
 2865    };
 2866    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2867        ignore_newlines: true,
 2868        ignore_brackets: false,
 2869    };
 2870
 2871    _ = editor.update(cx, |editor, window, cx| {
 2872        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2873            s.select_display_ranges([
 2874                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2875            ])
 2876        });
 2877        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2878        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2879        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2880        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2881        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2882        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2883        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2884        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2885        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2886        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2887        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2888        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2889    });
 2890}
 2891
 2892#[gpui::test]
 2893fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2894    init_test(cx, |_| {});
 2895
 2896    let editor = cx.add_window(|window, cx| {
 2897        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2898        build_editor(buffer, window, cx)
 2899    });
 2900    let del_to_next_word_end = DeleteToNextWordEnd {
 2901        ignore_newlines: false,
 2902        ignore_brackets: false,
 2903    };
 2904    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2905        ignore_newlines: true,
 2906        ignore_brackets: false,
 2907    };
 2908
 2909    _ = editor.update(cx, |editor, window, cx| {
 2910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2911            s.select_display_ranges([
 2912                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2913            ])
 2914        });
 2915        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2916        assert_eq!(
 2917            editor.buffer.read(cx).read(cx).text(),
 2918            "one\n   two\nthree\n   four"
 2919        );
 2920        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2921        assert_eq!(
 2922            editor.buffer.read(cx).read(cx).text(),
 2923            "\n   two\nthree\n   four"
 2924        );
 2925        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2926        assert_eq!(
 2927            editor.buffer.read(cx).read(cx).text(),
 2928            "two\nthree\n   four"
 2929        );
 2930        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2931        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2932        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2933        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2934        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2935        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 2936        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2937        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2938    });
 2939}
 2940
 2941#[gpui::test]
 2942fn test_newline(cx: &mut TestAppContext) {
 2943    init_test(cx, |_| {});
 2944
 2945    let editor = cx.add_window(|window, cx| {
 2946        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2947        build_editor(buffer, window, cx)
 2948    });
 2949
 2950    _ = editor.update(cx, |editor, window, cx| {
 2951        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2952            s.select_display_ranges([
 2953                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2954                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2955                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2956            ])
 2957        });
 2958
 2959        editor.newline(&Newline, window, cx);
 2960        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2961    });
 2962}
 2963
 2964#[gpui::test]
 2965fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2966    init_test(cx, |_| {});
 2967
 2968    let editor = cx.add_window(|window, cx| {
 2969        let buffer = MultiBuffer::build_simple(
 2970            "
 2971                a
 2972                b(
 2973                    X
 2974                )
 2975                c(
 2976                    X
 2977                )
 2978            "
 2979            .unindent()
 2980            .as_str(),
 2981            cx,
 2982        );
 2983        let mut editor = build_editor(buffer, window, cx);
 2984        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2985            s.select_ranges([
 2986                Point::new(2, 4)..Point::new(2, 5),
 2987                Point::new(5, 4)..Point::new(5, 5),
 2988            ])
 2989        });
 2990        editor
 2991    });
 2992
 2993    _ = editor.update(cx, |editor, window, cx| {
 2994        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2995        editor.buffer.update(cx, |buffer, cx| {
 2996            buffer.edit(
 2997                [
 2998                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2999                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3000                ],
 3001                None,
 3002                cx,
 3003            );
 3004            assert_eq!(
 3005                buffer.read(cx).text(),
 3006                "
 3007                    a
 3008                    b()
 3009                    c()
 3010                "
 3011                .unindent()
 3012            );
 3013        });
 3014        assert_eq!(
 3015            editor.selections.ranges(cx),
 3016            &[
 3017                Point::new(1, 2)..Point::new(1, 2),
 3018                Point::new(2, 2)..Point::new(2, 2),
 3019            ],
 3020        );
 3021
 3022        editor.newline(&Newline, window, cx);
 3023        assert_eq!(
 3024            editor.text(cx),
 3025            "
 3026                a
 3027                b(
 3028                )
 3029                c(
 3030                )
 3031            "
 3032            .unindent()
 3033        );
 3034
 3035        // The selections are moved after the inserted newlines
 3036        assert_eq!(
 3037            editor.selections.ranges(cx),
 3038            &[
 3039                Point::new(2, 0)..Point::new(2, 0),
 3040                Point::new(4, 0)..Point::new(4, 0),
 3041            ],
 3042        );
 3043    });
 3044}
 3045
 3046#[gpui::test]
 3047async fn test_newline_above(cx: &mut TestAppContext) {
 3048    init_test(cx, |settings| {
 3049        settings.defaults.tab_size = NonZeroU32::new(4)
 3050    });
 3051
 3052    let language = Arc::new(
 3053        Language::new(
 3054            LanguageConfig::default(),
 3055            Some(tree_sitter_rust::LANGUAGE.into()),
 3056        )
 3057        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3058        .unwrap(),
 3059    );
 3060
 3061    let mut cx = EditorTestContext::new(cx).await;
 3062    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3063    cx.set_state(indoc! {"
 3064        const a: ˇA = (
 3065 3066                «const_functionˇ»(ˇ),
 3067                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3068 3069        ˇ);ˇ
 3070    "});
 3071
 3072    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3073    cx.assert_editor_state(indoc! {"
 3074        ˇ
 3075        const a: A = (
 3076            ˇ
 3077            (
 3078                ˇ
 3079                ˇ
 3080                const_function(),
 3081                ˇ
 3082                ˇ
 3083                ˇ
 3084                ˇ
 3085                something_else,
 3086                ˇ
 3087            )
 3088            ˇ
 3089            ˇ
 3090        );
 3091    "});
 3092}
 3093
 3094#[gpui::test]
 3095async fn test_newline_below(cx: &mut TestAppContext) {
 3096    init_test(cx, |settings| {
 3097        settings.defaults.tab_size = NonZeroU32::new(4)
 3098    });
 3099
 3100    let language = Arc::new(
 3101        Language::new(
 3102            LanguageConfig::default(),
 3103            Some(tree_sitter_rust::LANGUAGE.into()),
 3104        )
 3105        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3106        .unwrap(),
 3107    );
 3108
 3109    let mut cx = EditorTestContext::new(cx).await;
 3110    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3111    cx.set_state(indoc! {"
 3112        const a: ˇA = (
 3113 3114                «const_functionˇ»(ˇ),
 3115                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3116 3117        ˇ);ˇ
 3118    "});
 3119
 3120    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3121    cx.assert_editor_state(indoc! {"
 3122        const a: A = (
 3123            ˇ
 3124            (
 3125                ˇ
 3126                const_function(),
 3127                ˇ
 3128                ˇ
 3129                something_else,
 3130                ˇ
 3131                ˇ
 3132                ˇ
 3133                ˇ
 3134            )
 3135            ˇ
 3136        );
 3137        ˇ
 3138        ˇ
 3139    "});
 3140}
 3141
 3142#[gpui::test]
 3143async fn test_newline_comments(cx: &mut TestAppContext) {
 3144    init_test(cx, |settings| {
 3145        settings.defaults.tab_size = NonZeroU32::new(4)
 3146    });
 3147
 3148    let language = Arc::new(Language::new(
 3149        LanguageConfig {
 3150            line_comments: vec!["// ".into()],
 3151            ..LanguageConfig::default()
 3152        },
 3153        None,
 3154    ));
 3155    {
 3156        let mut cx = EditorTestContext::new(cx).await;
 3157        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3158        cx.set_state(indoc! {"
 3159        // Fooˇ
 3160    "});
 3161
 3162        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3163        cx.assert_editor_state(indoc! {"
 3164        // Foo
 3165        // ˇ
 3166    "});
 3167        // Ensure that we add comment prefix when existing line contains space
 3168        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3169        cx.assert_editor_state(
 3170            indoc! {"
 3171        // Foo
 3172        //s
 3173        // ˇ
 3174    "}
 3175            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3176            .as_str(),
 3177        );
 3178        // Ensure that we add comment prefix when existing line does not contain space
 3179        cx.set_state(indoc! {"
 3180        // Foo
 3181        //ˇ
 3182    "});
 3183        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3184        cx.assert_editor_state(indoc! {"
 3185        // Foo
 3186        //
 3187        // ˇ
 3188    "});
 3189        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3190        cx.set_state(indoc! {"
 3191        ˇ// Foo
 3192    "});
 3193        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3194        cx.assert_editor_state(indoc! {"
 3195
 3196        ˇ// Foo
 3197    "});
 3198    }
 3199    // Ensure that comment continuations can be disabled.
 3200    update_test_language_settings(cx, |settings| {
 3201        settings.defaults.extend_comment_on_newline = Some(false);
 3202    });
 3203    let mut cx = EditorTestContext::new(cx).await;
 3204    cx.set_state(indoc! {"
 3205        // Fooˇ
 3206    "});
 3207    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3208    cx.assert_editor_state(indoc! {"
 3209        // Foo
 3210        ˇ
 3211    "});
 3212}
 3213
 3214#[gpui::test]
 3215async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3216    init_test(cx, |settings| {
 3217        settings.defaults.tab_size = NonZeroU32::new(4)
 3218    });
 3219
 3220    let language = Arc::new(Language::new(
 3221        LanguageConfig {
 3222            line_comments: vec!["// ".into(), "/// ".into()],
 3223            ..LanguageConfig::default()
 3224        },
 3225        None,
 3226    ));
 3227    {
 3228        let mut cx = EditorTestContext::new(cx).await;
 3229        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3230        cx.set_state(indoc! {"
 3231        //ˇ
 3232    "});
 3233        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3234        cx.assert_editor_state(indoc! {"
 3235        //
 3236        // ˇ
 3237    "});
 3238
 3239        cx.set_state(indoc! {"
 3240        ///ˇ
 3241    "});
 3242        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3243        cx.assert_editor_state(indoc! {"
 3244        ///
 3245        /// ˇ
 3246    "});
 3247    }
 3248}
 3249
 3250#[gpui::test]
 3251async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3252    init_test(cx, |settings| {
 3253        settings.defaults.tab_size = NonZeroU32::new(4)
 3254    });
 3255
 3256    let language = Arc::new(
 3257        Language::new(
 3258            LanguageConfig {
 3259                documentation_comment: Some(language::BlockCommentConfig {
 3260                    start: "/**".into(),
 3261                    end: "*/".into(),
 3262                    prefix: "* ".into(),
 3263                    tab_size: 1,
 3264                }),
 3265
 3266                ..LanguageConfig::default()
 3267            },
 3268            Some(tree_sitter_rust::LANGUAGE.into()),
 3269        )
 3270        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3271        .unwrap(),
 3272    );
 3273
 3274    {
 3275        let mut cx = EditorTestContext::new(cx).await;
 3276        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3277        cx.set_state(indoc! {"
 3278        /**ˇ
 3279    "});
 3280
 3281        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3282        cx.assert_editor_state(indoc! {"
 3283        /**
 3284         * ˇ
 3285    "});
 3286        // Ensure that if cursor is before the comment start,
 3287        // we do not actually insert a comment prefix.
 3288        cx.set_state(indoc! {"
 3289        ˇ/**
 3290    "});
 3291        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3292        cx.assert_editor_state(indoc! {"
 3293
 3294        ˇ/**
 3295    "});
 3296        // Ensure that if cursor is between it doesn't add comment prefix.
 3297        cx.set_state(indoc! {"
 3298        /*ˇ*
 3299    "});
 3300        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3301        cx.assert_editor_state(indoc! {"
 3302        /*
 3303        ˇ*
 3304    "});
 3305        // Ensure that if suffix exists on same line after cursor it adds new line.
 3306        cx.set_state(indoc! {"
 3307        /**ˇ*/
 3308    "});
 3309        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3310        cx.assert_editor_state(indoc! {"
 3311        /**
 3312         * ˇ
 3313         */
 3314    "});
 3315        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3316        cx.set_state(indoc! {"
 3317        /**ˇ */
 3318    "});
 3319        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3320        cx.assert_editor_state(indoc! {"
 3321        /**
 3322         * ˇ
 3323         */
 3324    "});
 3325        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3326        cx.set_state(indoc! {"
 3327        /** ˇ*/
 3328    "});
 3329        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3330        cx.assert_editor_state(
 3331            indoc! {"
 3332        /**s
 3333         * ˇ
 3334         */
 3335    "}
 3336            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3337            .as_str(),
 3338        );
 3339        // Ensure that delimiter space is preserved when newline on already
 3340        // spaced delimiter.
 3341        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3342        cx.assert_editor_state(
 3343            indoc! {"
 3344        /**s
 3345         *s
 3346         * ˇ
 3347         */
 3348    "}
 3349            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3350            .as_str(),
 3351        );
 3352        // Ensure that delimiter space is preserved when space is not
 3353        // on existing delimiter.
 3354        cx.set_state(indoc! {"
 3355        /**
 3356 3357         */
 3358    "});
 3359        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3360        cx.assert_editor_state(indoc! {"
 3361        /**
 3362         *
 3363         * ˇ
 3364         */
 3365    "});
 3366        // Ensure that if suffix exists on same line after cursor it
 3367        // doesn't add extra new line if prefix is not on same line.
 3368        cx.set_state(indoc! {"
 3369        /**
 3370        ˇ*/
 3371    "});
 3372        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3373        cx.assert_editor_state(indoc! {"
 3374        /**
 3375
 3376        ˇ*/
 3377    "});
 3378        // Ensure that it detects suffix after existing prefix.
 3379        cx.set_state(indoc! {"
 3380        /**ˇ/
 3381    "});
 3382        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3383        cx.assert_editor_state(indoc! {"
 3384        /**
 3385        ˇ/
 3386    "});
 3387        // Ensure that if suffix exists on same line before
 3388        // cursor it does not add comment prefix.
 3389        cx.set_state(indoc! {"
 3390        /** */ˇ
 3391    "});
 3392        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3393        cx.assert_editor_state(indoc! {"
 3394        /** */
 3395        ˇ
 3396    "});
 3397        // Ensure that if suffix exists on same line before
 3398        // cursor it does not add comment prefix.
 3399        cx.set_state(indoc! {"
 3400        /**
 3401         *
 3402         */ˇ
 3403    "});
 3404        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3405        cx.assert_editor_state(indoc! {"
 3406        /**
 3407         *
 3408         */
 3409         ˇ
 3410    "});
 3411
 3412        // Ensure that inline comment followed by code
 3413        // doesn't add comment prefix on newline
 3414        cx.set_state(indoc! {"
 3415        /** */ textˇ
 3416    "});
 3417        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3418        cx.assert_editor_state(indoc! {"
 3419        /** */ text
 3420        ˇ
 3421    "});
 3422
 3423        // Ensure that text after comment end tag
 3424        // doesn't add comment prefix on newline
 3425        cx.set_state(indoc! {"
 3426        /**
 3427         *
 3428         */ˇtext
 3429    "});
 3430        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3431        cx.assert_editor_state(indoc! {"
 3432        /**
 3433         *
 3434         */
 3435         ˇtext
 3436    "});
 3437
 3438        // Ensure if not comment block it doesn't
 3439        // add comment prefix on newline
 3440        cx.set_state(indoc! {"
 3441        * textˇ
 3442    "});
 3443        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3444        cx.assert_editor_state(indoc! {"
 3445        * text
 3446        ˇ
 3447    "});
 3448    }
 3449    // Ensure that comment continuations can be disabled.
 3450    update_test_language_settings(cx, |settings| {
 3451        settings.defaults.extend_comment_on_newline = Some(false);
 3452    });
 3453    let mut cx = EditorTestContext::new(cx).await;
 3454    cx.set_state(indoc! {"
 3455        /**ˇ
 3456    "});
 3457    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3458    cx.assert_editor_state(indoc! {"
 3459        /**
 3460        ˇ
 3461    "});
 3462}
 3463
 3464#[gpui::test]
 3465async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3466    init_test(cx, |settings| {
 3467        settings.defaults.tab_size = NonZeroU32::new(4)
 3468    });
 3469
 3470    let lua_language = Arc::new(Language::new(
 3471        LanguageConfig {
 3472            line_comments: vec!["--".into()],
 3473            block_comment: Some(language::BlockCommentConfig {
 3474                start: "--[[".into(),
 3475                prefix: "".into(),
 3476                end: "]]".into(),
 3477                tab_size: 0,
 3478            }),
 3479            ..LanguageConfig::default()
 3480        },
 3481        None,
 3482    ));
 3483
 3484    let mut cx = EditorTestContext::new(cx).await;
 3485    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3486
 3487    // Line with line comment should extend
 3488    cx.set_state(indoc! {"
 3489        --ˇ
 3490    "});
 3491    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3492    cx.assert_editor_state(indoc! {"
 3493        --
 3494        --ˇ
 3495    "});
 3496
 3497    // Line with block comment that matches line comment should not extend
 3498    cx.set_state(indoc! {"
 3499        --[[ˇ
 3500    "});
 3501    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3502    cx.assert_editor_state(indoc! {"
 3503        --[[
 3504        ˇ
 3505    "});
 3506}
 3507
 3508#[gpui::test]
 3509fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3510    init_test(cx, |_| {});
 3511
 3512    let editor = cx.add_window(|window, cx| {
 3513        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3514        let mut editor = build_editor(buffer, window, cx);
 3515        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3516            s.select_ranges([3..4, 11..12, 19..20])
 3517        });
 3518        editor
 3519    });
 3520
 3521    _ = editor.update(cx, |editor, window, cx| {
 3522        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3523        editor.buffer.update(cx, |buffer, cx| {
 3524            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3525            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3526        });
 3527        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3528
 3529        editor.insert("Z", window, cx);
 3530        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3531
 3532        // The selections are moved after the inserted characters
 3533        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3534    });
 3535}
 3536
 3537#[gpui::test]
 3538async fn test_tab(cx: &mut TestAppContext) {
 3539    init_test(cx, |settings| {
 3540        settings.defaults.tab_size = NonZeroU32::new(3)
 3541    });
 3542
 3543    let mut cx = EditorTestContext::new(cx).await;
 3544    cx.set_state(indoc! {"
 3545        ˇabˇc
 3546        ˇ🏀ˇ🏀ˇefg
 3547 3548    "});
 3549    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3550    cx.assert_editor_state(indoc! {"
 3551           ˇab ˇc
 3552           ˇ🏀  ˇ🏀  ˇefg
 3553        d  ˇ
 3554    "});
 3555
 3556    cx.set_state(indoc! {"
 3557        a
 3558        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3559    "});
 3560    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3561    cx.assert_editor_state(indoc! {"
 3562        a
 3563           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3564    "});
 3565}
 3566
 3567#[gpui::test]
 3568async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3569    init_test(cx, |_| {});
 3570
 3571    let mut cx = EditorTestContext::new(cx).await;
 3572    let language = Arc::new(
 3573        Language::new(
 3574            LanguageConfig::default(),
 3575            Some(tree_sitter_rust::LANGUAGE.into()),
 3576        )
 3577        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3578        .unwrap(),
 3579    );
 3580    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3581
 3582    // test when all cursors are not at suggested indent
 3583    // then simply move to their suggested indent location
 3584    cx.set_state(indoc! {"
 3585        const a: B = (
 3586            c(
 3587        ˇ
 3588        ˇ    )
 3589        );
 3590    "});
 3591    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3592    cx.assert_editor_state(indoc! {"
 3593        const a: B = (
 3594            c(
 3595                ˇ
 3596            ˇ)
 3597        );
 3598    "});
 3599
 3600    // test cursor already at suggested indent not moving when
 3601    // other cursors are yet to reach their suggested indents
 3602    cx.set_state(indoc! {"
 3603        ˇ
 3604        const a: B = (
 3605            c(
 3606                d(
 3607        ˇ
 3608                )
 3609        ˇ
 3610        ˇ    )
 3611        );
 3612    "});
 3613    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3614    cx.assert_editor_state(indoc! {"
 3615        ˇ
 3616        const a: B = (
 3617            c(
 3618                d(
 3619                    ˇ
 3620                )
 3621                ˇ
 3622            ˇ)
 3623        );
 3624    "});
 3625    // test when all cursors are at suggested indent then tab is inserted
 3626    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3627    cx.assert_editor_state(indoc! {"
 3628            ˇ
 3629        const a: B = (
 3630            c(
 3631                d(
 3632                        ˇ
 3633                )
 3634                    ˇ
 3635                ˇ)
 3636        );
 3637    "});
 3638
 3639    // test when current indent is less than suggested indent,
 3640    // we adjust line to match suggested indent and move cursor to it
 3641    //
 3642    // when no other cursor is at word boundary, all of them should move
 3643    cx.set_state(indoc! {"
 3644        const a: B = (
 3645            c(
 3646                d(
 3647        ˇ
 3648        ˇ   )
 3649        ˇ   )
 3650        );
 3651    "});
 3652    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3653    cx.assert_editor_state(indoc! {"
 3654        const a: B = (
 3655            c(
 3656                d(
 3657                    ˇ
 3658                ˇ)
 3659            ˇ)
 3660        );
 3661    "});
 3662
 3663    // test when current indent is less than suggested indent,
 3664    // we adjust line to match suggested indent and move cursor to it
 3665    //
 3666    // when some other cursor is at word boundary, it should not move
 3667    cx.set_state(indoc! {"
 3668        const a: B = (
 3669            c(
 3670                d(
 3671        ˇ
 3672        ˇ   )
 3673           ˇ)
 3674        );
 3675    "});
 3676    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3677    cx.assert_editor_state(indoc! {"
 3678        const a: B = (
 3679            c(
 3680                d(
 3681                    ˇ
 3682                ˇ)
 3683            ˇ)
 3684        );
 3685    "});
 3686
 3687    // test when current indent is more than suggested indent,
 3688    // we just move cursor to current indent instead of suggested indent
 3689    //
 3690    // when no other cursor is at word boundary, all of them should move
 3691    cx.set_state(indoc! {"
 3692        const a: B = (
 3693            c(
 3694                d(
 3695        ˇ
 3696        ˇ                )
 3697        ˇ   )
 3698        );
 3699    "});
 3700    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3701    cx.assert_editor_state(indoc! {"
 3702        const a: B = (
 3703            c(
 3704                d(
 3705                    ˇ
 3706                        ˇ)
 3707            ˇ)
 3708        );
 3709    "});
 3710    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3711    cx.assert_editor_state(indoc! {"
 3712        const a: B = (
 3713            c(
 3714                d(
 3715                        ˇ
 3716                            ˇ)
 3717                ˇ)
 3718        );
 3719    "});
 3720
 3721    // test when current indent is more than suggested indent,
 3722    // we just move cursor to current indent instead of suggested indent
 3723    //
 3724    // when some other cursor is at word boundary, it doesn't move
 3725    cx.set_state(indoc! {"
 3726        const a: B = (
 3727            c(
 3728                d(
 3729        ˇ
 3730        ˇ                )
 3731            ˇ)
 3732        );
 3733    "});
 3734    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3735    cx.assert_editor_state(indoc! {"
 3736        const a: B = (
 3737            c(
 3738                d(
 3739                    ˇ
 3740                        ˇ)
 3741            ˇ)
 3742        );
 3743    "});
 3744
 3745    // handle auto-indent when there are multiple cursors on the same line
 3746    cx.set_state(indoc! {"
 3747        const a: B = (
 3748            c(
 3749        ˇ    ˇ
 3750        ˇ    )
 3751        );
 3752    "});
 3753    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3754    cx.assert_editor_state(indoc! {"
 3755        const a: B = (
 3756            c(
 3757                ˇ
 3758            ˇ)
 3759        );
 3760    "});
 3761}
 3762
 3763#[gpui::test]
 3764async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3765    init_test(cx, |settings| {
 3766        settings.defaults.tab_size = NonZeroU32::new(3)
 3767    });
 3768
 3769    let mut cx = EditorTestContext::new(cx).await;
 3770    cx.set_state(indoc! {"
 3771         ˇ
 3772        \t ˇ
 3773        \t  ˇ
 3774        \t   ˇ
 3775         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3776    "});
 3777
 3778    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3779    cx.assert_editor_state(indoc! {"
 3780           ˇ
 3781        \t   ˇ
 3782        \t   ˇ
 3783        \t      ˇ
 3784         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3785    "});
 3786}
 3787
 3788#[gpui::test]
 3789async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3790    init_test(cx, |settings| {
 3791        settings.defaults.tab_size = NonZeroU32::new(4)
 3792    });
 3793
 3794    let language = Arc::new(
 3795        Language::new(
 3796            LanguageConfig::default(),
 3797            Some(tree_sitter_rust::LANGUAGE.into()),
 3798        )
 3799        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3800        .unwrap(),
 3801    );
 3802
 3803    let mut cx = EditorTestContext::new(cx).await;
 3804    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3805    cx.set_state(indoc! {"
 3806        fn a() {
 3807            if b {
 3808        \t ˇc
 3809            }
 3810        }
 3811    "});
 3812
 3813    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3814    cx.assert_editor_state(indoc! {"
 3815        fn a() {
 3816            if b {
 3817                ˇc
 3818            }
 3819        }
 3820    "});
 3821}
 3822
 3823#[gpui::test]
 3824async fn test_indent_outdent(cx: &mut TestAppContext) {
 3825    init_test(cx, |settings| {
 3826        settings.defaults.tab_size = NonZeroU32::new(4);
 3827    });
 3828
 3829    let mut cx = EditorTestContext::new(cx).await;
 3830
 3831    cx.set_state(indoc! {"
 3832          «oneˇ» «twoˇ»
 3833        three
 3834         four
 3835    "});
 3836    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3837    cx.assert_editor_state(indoc! {"
 3838            «oneˇ» «twoˇ»
 3839        three
 3840         four
 3841    "});
 3842
 3843    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3844    cx.assert_editor_state(indoc! {"
 3845        «oneˇ» «twoˇ»
 3846        three
 3847         four
 3848    "});
 3849
 3850    // select across line ending
 3851    cx.set_state(indoc! {"
 3852        one two
 3853        t«hree
 3854        ˇ» four
 3855    "});
 3856    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3857    cx.assert_editor_state(indoc! {"
 3858        one two
 3859            t«hree
 3860        ˇ» four
 3861    "});
 3862
 3863    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3864    cx.assert_editor_state(indoc! {"
 3865        one two
 3866        t«hree
 3867        ˇ» four
 3868    "});
 3869
 3870    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3871    cx.set_state(indoc! {"
 3872        one two
 3873        ˇthree
 3874            four
 3875    "});
 3876    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3877    cx.assert_editor_state(indoc! {"
 3878        one two
 3879            ˇthree
 3880            four
 3881    "});
 3882
 3883    cx.set_state(indoc! {"
 3884        one two
 3885        ˇ    three
 3886            four
 3887    "});
 3888    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3889    cx.assert_editor_state(indoc! {"
 3890        one two
 3891        ˇthree
 3892            four
 3893    "});
 3894}
 3895
 3896#[gpui::test]
 3897async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3898    // This is a regression test for issue #33761
 3899    init_test(cx, |_| {});
 3900
 3901    let mut cx = EditorTestContext::new(cx).await;
 3902    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3903    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3904
 3905    cx.set_state(
 3906        r#"ˇ#     ingress:
 3907ˇ#         api:
 3908ˇ#             enabled: false
 3909ˇ#             pathType: Prefix
 3910ˇ#           console:
 3911ˇ#               enabled: false
 3912ˇ#               pathType: Prefix
 3913"#,
 3914    );
 3915
 3916    // Press tab to indent all lines
 3917    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3918
 3919    cx.assert_editor_state(
 3920        r#"    ˇ#     ingress:
 3921    ˇ#         api:
 3922    ˇ#             enabled: false
 3923    ˇ#             pathType: Prefix
 3924    ˇ#           console:
 3925    ˇ#               enabled: false
 3926    ˇ#               pathType: Prefix
 3927"#,
 3928    );
 3929}
 3930
 3931#[gpui::test]
 3932async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3933    // This is a test to make sure our fix for issue #33761 didn't break anything
 3934    init_test(cx, |_| {});
 3935
 3936    let mut cx = EditorTestContext::new(cx).await;
 3937    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3938    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3939
 3940    cx.set_state(
 3941        r#"ˇingress:
 3942ˇ  api:
 3943ˇ    enabled: false
 3944ˇ    pathType: Prefix
 3945"#,
 3946    );
 3947
 3948    // Press tab to indent all lines
 3949    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3950
 3951    cx.assert_editor_state(
 3952        r#"ˇingress:
 3953    ˇapi:
 3954        ˇenabled: false
 3955        ˇpathType: Prefix
 3956"#,
 3957    );
 3958}
 3959
 3960#[gpui::test]
 3961async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3962    init_test(cx, |settings| {
 3963        settings.defaults.hard_tabs = Some(true);
 3964    });
 3965
 3966    let mut cx = EditorTestContext::new(cx).await;
 3967
 3968    // select two ranges on one line
 3969    cx.set_state(indoc! {"
 3970        «oneˇ» «twoˇ»
 3971        three
 3972        four
 3973    "});
 3974    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3975    cx.assert_editor_state(indoc! {"
 3976        \t«oneˇ» «twoˇ»
 3977        three
 3978        four
 3979    "});
 3980    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3981    cx.assert_editor_state(indoc! {"
 3982        \t\t«oneˇ» «twoˇ»
 3983        three
 3984        four
 3985    "});
 3986    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3987    cx.assert_editor_state(indoc! {"
 3988        \t«oneˇ» «twoˇ»
 3989        three
 3990        four
 3991    "});
 3992    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3993    cx.assert_editor_state(indoc! {"
 3994        «oneˇ» «twoˇ»
 3995        three
 3996        four
 3997    "});
 3998
 3999    // select across a line ending
 4000    cx.set_state(indoc! {"
 4001        one two
 4002        t«hree
 4003        ˇ»four
 4004    "});
 4005    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4006    cx.assert_editor_state(indoc! {"
 4007        one two
 4008        \tt«hree
 4009        ˇ»four
 4010    "});
 4011    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4012    cx.assert_editor_state(indoc! {"
 4013        one two
 4014        \t\tt«hree
 4015        ˇ»four
 4016    "});
 4017    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4018    cx.assert_editor_state(indoc! {"
 4019        one two
 4020        \tt«hree
 4021        ˇ»four
 4022    "});
 4023    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4024    cx.assert_editor_state(indoc! {"
 4025        one two
 4026        t«hree
 4027        ˇ»four
 4028    "});
 4029
 4030    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4031    cx.set_state(indoc! {"
 4032        one two
 4033        ˇthree
 4034        four
 4035    "});
 4036    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4037    cx.assert_editor_state(indoc! {"
 4038        one two
 4039        ˇthree
 4040        four
 4041    "});
 4042    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4043    cx.assert_editor_state(indoc! {"
 4044        one two
 4045        \tˇthree
 4046        four
 4047    "});
 4048    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4049    cx.assert_editor_state(indoc! {"
 4050        one two
 4051        ˇthree
 4052        four
 4053    "});
 4054}
 4055
 4056#[gpui::test]
 4057fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4058    init_test(cx, |settings| {
 4059        settings.languages.0.extend([
 4060            (
 4061                "TOML".into(),
 4062                LanguageSettingsContent {
 4063                    tab_size: NonZeroU32::new(2),
 4064                    ..Default::default()
 4065                },
 4066            ),
 4067            (
 4068                "Rust".into(),
 4069                LanguageSettingsContent {
 4070                    tab_size: NonZeroU32::new(4),
 4071                    ..Default::default()
 4072                },
 4073            ),
 4074        ]);
 4075    });
 4076
 4077    let toml_language = Arc::new(Language::new(
 4078        LanguageConfig {
 4079            name: "TOML".into(),
 4080            ..Default::default()
 4081        },
 4082        None,
 4083    ));
 4084    let rust_language = Arc::new(Language::new(
 4085        LanguageConfig {
 4086            name: "Rust".into(),
 4087            ..Default::default()
 4088        },
 4089        None,
 4090    ));
 4091
 4092    let toml_buffer =
 4093        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4094    let rust_buffer =
 4095        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4096    let multibuffer = cx.new(|cx| {
 4097        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4098        multibuffer.push_excerpts(
 4099            toml_buffer.clone(),
 4100            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4101            cx,
 4102        );
 4103        multibuffer.push_excerpts(
 4104            rust_buffer.clone(),
 4105            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4106            cx,
 4107        );
 4108        multibuffer
 4109    });
 4110
 4111    cx.add_window(|window, cx| {
 4112        let mut editor = build_editor(multibuffer, window, cx);
 4113
 4114        assert_eq!(
 4115            editor.text(cx),
 4116            indoc! {"
 4117                a = 1
 4118                b = 2
 4119
 4120                const c: usize = 3;
 4121            "}
 4122        );
 4123
 4124        select_ranges(
 4125            &mut editor,
 4126            indoc! {"
 4127                «aˇ» = 1
 4128                b = 2
 4129
 4130                «const c:ˇ» usize = 3;
 4131            "},
 4132            window,
 4133            cx,
 4134        );
 4135
 4136        editor.tab(&Tab, window, cx);
 4137        assert_text_with_selections(
 4138            &mut editor,
 4139            indoc! {"
 4140                  «aˇ» = 1
 4141                b = 2
 4142
 4143                    «const c:ˇ» usize = 3;
 4144            "},
 4145            cx,
 4146        );
 4147        editor.backtab(&Backtab, window, cx);
 4148        assert_text_with_selections(
 4149            &mut editor,
 4150            indoc! {"
 4151                «aˇ» = 1
 4152                b = 2
 4153
 4154                «const c:ˇ» usize = 3;
 4155            "},
 4156            cx,
 4157        );
 4158
 4159        editor
 4160    });
 4161}
 4162
 4163#[gpui::test]
 4164async fn test_backspace(cx: &mut TestAppContext) {
 4165    init_test(cx, |_| {});
 4166
 4167    let mut cx = EditorTestContext::new(cx).await;
 4168
 4169    // Basic backspace
 4170    cx.set_state(indoc! {"
 4171        onˇe two three
 4172        fou«rˇ» five six
 4173        seven «ˇeight nine
 4174        »ten
 4175    "});
 4176    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4177    cx.assert_editor_state(indoc! {"
 4178        oˇe two three
 4179        fouˇ five six
 4180        seven ˇten
 4181    "});
 4182
 4183    // Test backspace inside and around indents
 4184    cx.set_state(indoc! {"
 4185        zero
 4186            ˇone
 4187                ˇtwo
 4188            ˇ ˇ ˇ  three
 4189        ˇ  ˇ  four
 4190    "});
 4191    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4192    cx.assert_editor_state(indoc! {"
 4193        zero
 4194        ˇone
 4195            ˇtwo
 4196        ˇ  threeˇ  four
 4197    "});
 4198}
 4199
 4200#[gpui::test]
 4201async fn test_delete(cx: &mut TestAppContext) {
 4202    init_test(cx, |_| {});
 4203
 4204    let mut cx = EditorTestContext::new(cx).await;
 4205    cx.set_state(indoc! {"
 4206        onˇe two three
 4207        fou«rˇ» five six
 4208        seven «ˇeight nine
 4209        »ten
 4210    "});
 4211    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4212    cx.assert_editor_state(indoc! {"
 4213        onˇ two three
 4214        fouˇ five six
 4215        seven ˇten
 4216    "});
 4217}
 4218
 4219#[gpui::test]
 4220fn test_delete_line(cx: &mut TestAppContext) {
 4221    init_test(cx, |_| {});
 4222
 4223    let editor = cx.add_window(|window, cx| {
 4224        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4225        build_editor(buffer, window, cx)
 4226    });
 4227    _ = editor.update(cx, |editor, window, cx| {
 4228        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4229            s.select_display_ranges([
 4230                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4231                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4232                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4233            ])
 4234        });
 4235        editor.delete_line(&DeleteLine, window, cx);
 4236        assert_eq!(editor.display_text(cx), "ghi");
 4237        assert_eq!(
 4238            editor.selections.display_ranges(cx),
 4239            vec![
 4240                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4241                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 4242            ]
 4243        );
 4244    });
 4245
 4246    let editor = cx.add_window(|window, cx| {
 4247        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4248        build_editor(buffer, window, cx)
 4249    });
 4250    _ = editor.update(cx, |editor, window, cx| {
 4251        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4252            s.select_display_ranges([
 4253                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4254            ])
 4255        });
 4256        editor.delete_line(&DeleteLine, window, cx);
 4257        assert_eq!(editor.display_text(cx), "ghi\n");
 4258        assert_eq!(
 4259            editor.selections.display_ranges(cx),
 4260            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4261        );
 4262    });
 4263}
 4264
 4265#[gpui::test]
 4266fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4267    init_test(cx, |_| {});
 4268
 4269    cx.add_window(|window, cx| {
 4270        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4271        let mut editor = build_editor(buffer.clone(), window, cx);
 4272        let buffer = buffer.read(cx).as_singleton().unwrap();
 4273
 4274        assert_eq!(
 4275            editor.selections.ranges::<Point>(cx),
 4276            &[Point::new(0, 0)..Point::new(0, 0)]
 4277        );
 4278
 4279        // When on single line, replace newline at end by space
 4280        editor.join_lines(&JoinLines, window, cx);
 4281        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4282        assert_eq!(
 4283            editor.selections.ranges::<Point>(cx),
 4284            &[Point::new(0, 3)..Point::new(0, 3)]
 4285        );
 4286
 4287        // When multiple lines are selected, remove newlines that are spanned by the selection
 4288        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4289            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4290        });
 4291        editor.join_lines(&JoinLines, window, cx);
 4292        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4293        assert_eq!(
 4294            editor.selections.ranges::<Point>(cx),
 4295            &[Point::new(0, 11)..Point::new(0, 11)]
 4296        );
 4297
 4298        // Undo should be transactional
 4299        editor.undo(&Undo, window, cx);
 4300        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4301        assert_eq!(
 4302            editor.selections.ranges::<Point>(cx),
 4303            &[Point::new(0, 5)..Point::new(2, 2)]
 4304        );
 4305
 4306        // When joining an empty line don't insert a space
 4307        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4308            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4309        });
 4310        editor.join_lines(&JoinLines, window, cx);
 4311        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4312        assert_eq!(
 4313            editor.selections.ranges::<Point>(cx),
 4314            [Point::new(2, 3)..Point::new(2, 3)]
 4315        );
 4316
 4317        // We can remove trailing newlines
 4318        editor.join_lines(&JoinLines, window, cx);
 4319        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4320        assert_eq!(
 4321            editor.selections.ranges::<Point>(cx),
 4322            [Point::new(2, 3)..Point::new(2, 3)]
 4323        );
 4324
 4325        // We don't blow up on the last line
 4326        editor.join_lines(&JoinLines, window, cx);
 4327        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4328        assert_eq!(
 4329            editor.selections.ranges::<Point>(cx),
 4330            [Point::new(2, 3)..Point::new(2, 3)]
 4331        );
 4332
 4333        // reset to test indentation
 4334        editor.buffer.update(cx, |buffer, cx| {
 4335            buffer.edit(
 4336                [
 4337                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4338                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4339                ],
 4340                None,
 4341                cx,
 4342            )
 4343        });
 4344
 4345        // We remove any leading spaces
 4346        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4347        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4348            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4349        });
 4350        editor.join_lines(&JoinLines, window, cx);
 4351        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4352
 4353        // We don't insert a space for a line containing only spaces
 4354        editor.join_lines(&JoinLines, window, cx);
 4355        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4356
 4357        // We ignore any leading tabs
 4358        editor.join_lines(&JoinLines, window, cx);
 4359        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4360
 4361        editor
 4362    });
 4363}
 4364
 4365#[gpui::test]
 4366fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4367    init_test(cx, |_| {});
 4368
 4369    cx.add_window(|window, cx| {
 4370        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4371        let mut editor = build_editor(buffer.clone(), window, cx);
 4372        let buffer = buffer.read(cx).as_singleton().unwrap();
 4373
 4374        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4375            s.select_ranges([
 4376                Point::new(0, 2)..Point::new(1, 1),
 4377                Point::new(1, 2)..Point::new(1, 2),
 4378                Point::new(3, 1)..Point::new(3, 2),
 4379            ])
 4380        });
 4381
 4382        editor.join_lines(&JoinLines, window, cx);
 4383        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4384
 4385        assert_eq!(
 4386            editor.selections.ranges::<Point>(cx),
 4387            [
 4388                Point::new(0, 7)..Point::new(0, 7),
 4389                Point::new(1, 3)..Point::new(1, 3)
 4390            ]
 4391        );
 4392        editor
 4393    });
 4394}
 4395
 4396#[gpui::test]
 4397async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4398    init_test(cx, |_| {});
 4399
 4400    let mut cx = EditorTestContext::new(cx).await;
 4401
 4402    let diff_base = r#"
 4403        Line 0
 4404        Line 1
 4405        Line 2
 4406        Line 3
 4407        "#
 4408    .unindent();
 4409
 4410    cx.set_state(
 4411        &r#"
 4412        ˇLine 0
 4413        Line 1
 4414        Line 2
 4415        Line 3
 4416        "#
 4417        .unindent(),
 4418    );
 4419
 4420    cx.set_head_text(&diff_base);
 4421    executor.run_until_parked();
 4422
 4423    // Join lines
 4424    cx.update_editor(|editor, window, cx| {
 4425        editor.join_lines(&JoinLines, window, cx);
 4426    });
 4427    executor.run_until_parked();
 4428
 4429    cx.assert_editor_state(
 4430        &r#"
 4431        Line 0ˇ Line 1
 4432        Line 2
 4433        Line 3
 4434        "#
 4435        .unindent(),
 4436    );
 4437    // Join again
 4438    cx.update_editor(|editor, window, cx| {
 4439        editor.join_lines(&JoinLines, window, cx);
 4440    });
 4441    executor.run_until_parked();
 4442
 4443    cx.assert_editor_state(
 4444        &r#"
 4445        Line 0 Line 1ˇ Line 2
 4446        Line 3
 4447        "#
 4448        .unindent(),
 4449    );
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_custom_newlines_cause_no_false_positive_diffs(
 4454    executor: BackgroundExecutor,
 4455    cx: &mut TestAppContext,
 4456) {
 4457    init_test(cx, |_| {});
 4458    let mut cx = EditorTestContext::new(cx).await;
 4459    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4460    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4461    executor.run_until_parked();
 4462
 4463    cx.update_editor(|editor, window, cx| {
 4464        let snapshot = editor.snapshot(window, cx);
 4465        assert_eq!(
 4466            snapshot
 4467                .buffer_snapshot
 4468                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4469                .collect::<Vec<_>>(),
 4470            Vec::new(),
 4471            "Should not have any diffs for files with custom newlines"
 4472        );
 4473    });
 4474}
 4475
 4476#[gpui::test]
 4477async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4478    init_test(cx, |_| {});
 4479
 4480    let mut cx = EditorTestContext::new(cx).await;
 4481
 4482    // Test sort_lines_case_insensitive()
 4483    cx.set_state(indoc! {"
 4484        «z
 4485        y
 4486        x
 4487        Z
 4488        Y
 4489        Xˇ»
 4490    "});
 4491    cx.update_editor(|e, window, cx| {
 4492        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4493    });
 4494    cx.assert_editor_state(indoc! {"
 4495        «x
 4496        X
 4497        y
 4498        Y
 4499        z
 4500        Zˇ»
 4501    "});
 4502
 4503    // Test sort_lines_by_length()
 4504    //
 4505    // Demonstrates:
 4506    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4507    // - sort is stable
 4508    cx.set_state(indoc! {"
 4509        «123
 4510        æ
 4511        12
 4512 4513        1
 4514        æˇ»
 4515    "});
 4516    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4517    cx.assert_editor_state(indoc! {"
 4518        «æ
 4519 4520        1
 4521        æ
 4522        12
 4523        123ˇ»
 4524    "});
 4525
 4526    // Test reverse_lines()
 4527    cx.set_state(indoc! {"
 4528        «5
 4529        4
 4530        3
 4531        2
 4532        1ˇ»
 4533    "});
 4534    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4535    cx.assert_editor_state(indoc! {"
 4536        «1
 4537        2
 4538        3
 4539        4
 4540        5ˇ»
 4541    "});
 4542
 4543    // Skip testing shuffle_line()
 4544
 4545    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4546    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4547
 4548    // Don't manipulate when cursor is on single line, but expand the selection
 4549    cx.set_state(indoc! {"
 4550        ddˇdd
 4551        ccc
 4552        bb
 4553        a
 4554    "});
 4555    cx.update_editor(|e, window, cx| {
 4556        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4557    });
 4558    cx.assert_editor_state(indoc! {"
 4559        «ddddˇ»
 4560        ccc
 4561        bb
 4562        a
 4563    "});
 4564
 4565    // Basic manipulate case
 4566    // Start selection moves to column 0
 4567    // End of selection shrinks to fit shorter line
 4568    cx.set_state(indoc! {"
 4569        dd«d
 4570        ccc
 4571        bb
 4572        aaaaaˇ»
 4573    "});
 4574    cx.update_editor(|e, window, cx| {
 4575        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4576    });
 4577    cx.assert_editor_state(indoc! {"
 4578        «aaaaa
 4579        bb
 4580        ccc
 4581        dddˇ»
 4582    "});
 4583
 4584    // Manipulate case with newlines
 4585    cx.set_state(indoc! {"
 4586        dd«d
 4587        ccc
 4588
 4589        bb
 4590        aaaaa
 4591
 4592        ˇ»
 4593    "});
 4594    cx.update_editor(|e, window, cx| {
 4595        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4596    });
 4597    cx.assert_editor_state(indoc! {"
 4598        «
 4599
 4600        aaaaa
 4601        bb
 4602        ccc
 4603        dddˇ»
 4604
 4605    "});
 4606
 4607    // Adding new line
 4608    cx.set_state(indoc! {"
 4609        aa«a
 4610        bbˇ»b
 4611    "});
 4612    cx.update_editor(|e, window, cx| {
 4613        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4614    });
 4615    cx.assert_editor_state(indoc! {"
 4616        «aaa
 4617        bbb
 4618        added_lineˇ»
 4619    "});
 4620
 4621    // Removing line
 4622    cx.set_state(indoc! {"
 4623        aa«a
 4624        bbbˇ»
 4625    "});
 4626    cx.update_editor(|e, window, cx| {
 4627        e.manipulate_immutable_lines(window, cx, |lines| {
 4628            lines.pop();
 4629        })
 4630    });
 4631    cx.assert_editor_state(indoc! {"
 4632        «aaaˇ»
 4633    "});
 4634
 4635    // Removing all lines
 4636    cx.set_state(indoc! {"
 4637        aa«a
 4638        bbbˇ»
 4639    "});
 4640    cx.update_editor(|e, window, cx| {
 4641        e.manipulate_immutable_lines(window, cx, |lines| {
 4642            lines.drain(..);
 4643        })
 4644    });
 4645    cx.assert_editor_state(indoc! {"
 4646        ˇ
 4647    "});
 4648}
 4649
 4650#[gpui::test]
 4651async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4652    init_test(cx, |_| {});
 4653
 4654    let mut cx = EditorTestContext::new(cx).await;
 4655
 4656    // Consider continuous selection as single selection
 4657    cx.set_state(indoc! {"
 4658        Aaa«aa
 4659        cˇ»c«c
 4660        bb
 4661        aaaˇ»aa
 4662    "});
 4663    cx.update_editor(|e, window, cx| {
 4664        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4665    });
 4666    cx.assert_editor_state(indoc! {"
 4667        «Aaaaa
 4668        ccc
 4669        bb
 4670        aaaaaˇ»
 4671    "});
 4672
 4673    cx.set_state(indoc! {"
 4674        Aaa«aa
 4675        cˇ»c«c
 4676        bb
 4677        aaaˇ»aa
 4678    "});
 4679    cx.update_editor(|e, window, cx| {
 4680        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4681    });
 4682    cx.assert_editor_state(indoc! {"
 4683        «Aaaaa
 4684        ccc
 4685        bbˇ»
 4686    "});
 4687
 4688    // Consider non continuous selection as distinct dedup operations
 4689    cx.set_state(indoc! {"
 4690        «aaaaa
 4691        bb
 4692        aaaaa
 4693        aaaaaˇ»
 4694
 4695        aaa«aaˇ»
 4696    "});
 4697    cx.update_editor(|e, window, cx| {
 4698        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4699    });
 4700    cx.assert_editor_state(indoc! {"
 4701        «aaaaa
 4702        bbˇ»
 4703
 4704        «aaaaaˇ»
 4705    "});
 4706}
 4707
 4708#[gpui::test]
 4709async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4710    init_test(cx, |_| {});
 4711
 4712    let mut cx = EditorTestContext::new(cx).await;
 4713
 4714    cx.set_state(indoc! {"
 4715        «Aaa
 4716        aAa
 4717        Aaaˇ»
 4718    "});
 4719    cx.update_editor(|e, window, cx| {
 4720        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4721    });
 4722    cx.assert_editor_state(indoc! {"
 4723        «Aaa
 4724        aAaˇ»
 4725    "});
 4726
 4727    cx.set_state(indoc! {"
 4728        «Aaa
 4729        aAa
 4730        aaAˇ»
 4731    "});
 4732    cx.update_editor(|e, window, cx| {
 4733        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4734    });
 4735    cx.assert_editor_state(indoc! {"
 4736        «Aaaˇ»
 4737    "});
 4738}
 4739
 4740#[gpui::test]
 4741async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4742    init_test(cx, |_| {});
 4743
 4744    let mut cx = EditorTestContext::new(cx).await;
 4745
 4746    let js_language = Arc::new(Language::new(
 4747        LanguageConfig {
 4748            name: "JavaScript".into(),
 4749            wrap_characters: Some(language::WrapCharactersConfig {
 4750                start_prefix: "<".into(),
 4751                start_suffix: ">".into(),
 4752                end_prefix: "</".into(),
 4753                end_suffix: ">".into(),
 4754            }),
 4755            ..LanguageConfig::default()
 4756        },
 4757        None,
 4758    ));
 4759
 4760    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4761
 4762    cx.set_state(indoc! {"
 4763        «testˇ»
 4764    "});
 4765    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4766    cx.assert_editor_state(indoc! {"
 4767        <«ˇ»>test</«ˇ»>
 4768    "});
 4769
 4770    cx.set_state(indoc! {"
 4771        «test
 4772         testˇ»
 4773    "});
 4774    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4775    cx.assert_editor_state(indoc! {"
 4776        <«ˇ»>test
 4777         test</«ˇ»>
 4778    "});
 4779
 4780    cx.set_state(indoc! {"
 4781        teˇst
 4782    "});
 4783    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4784    cx.assert_editor_state(indoc! {"
 4785        te<«ˇ»></«ˇ»>st
 4786    "});
 4787}
 4788
 4789#[gpui::test]
 4790async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4791    init_test(cx, |_| {});
 4792
 4793    let mut cx = EditorTestContext::new(cx).await;
 4794
 4795    let js_language = Arc::new(Language::new(
 4796        LanguageConfig {
 4797            name: "JavaScript".into(),
 4798            wrap_characters: Some(language::WrapCharactersConfig {
 4799                start_prefix: "<".into(),
 4800                start_suffix: ">".into(),
 4801                end_prefix: "</".into(),
 4802                end_suffix: ">".into(),
 4803            }),
 4804            ..LanguageConfig::default()
 4805        },
 4806        None,
 4807    ));
 4808
 4809    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4810
 4811    cx.set_state(indoc! {"
 4812        «testˇ»
 4813        «testˇ» «testˇ»
 4814        «testˇ»
 4815    "});
 4816    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4817    cx.assert_editor_state(indoc! {"
 4818        <«ˇ»>test</«ˇ»>
 4819        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4820        <«ˇ»>test</«ˇ»>
 4821    "});
 4822
 4823    cx.set_state(indoc! {"
 4824        «test
 4825         testˇ»
 4826        «test
 4827         testˇ»
 4828    "});
 4829    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4830    cx.assert_editor_state(indoc! {"
 4831        <«ˇ»>test
 4832         test</«ˇ»>
 4833        <«ˇ»>test
 4834         test</«ˇ»>
 4835    "});
 4836}
 4837
 4838#[gpui::test]
 4839async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4840    init_test(cx, |_| {});
 4841
 4842    let mut cx = EditorTestContext::new(cx).await;
 4843
 4844    let plaintext_language = Arc::new(Language::new(
 4845        LanguageConfig {
 4846            name: "Plain Text".into(),
 4847            ..LanguageConfig::default()
 4848        },
 4849        None,
 4850    ));
 4851
 4852    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4853
 4854    cx.set_state(indoc! {"
 4855        «testˇ»
 4856    "});
 4857    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4858    cx.assert_editor_state(indoc! {"
 4859      «testˇ»
 4860    "});
 4861}
 4862
 4863#[gpui::test]
 4864async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4865    init_test(cx, |_| {});
 4866
 4867    let mut cx = EditorTestContext::new(cx).await;
 4868
 4869    // Manipulate with multiple selections on a single line
 4870    cx.set_state(indoc! {"
 4871        dd«dd
 4872        cˇ»c«c
 4873        bb
 4874        aaaˇ»aa
 4875    "});
 4876    cx.update_editor(|e, window, cx| {
 4877        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4878    });
 4879    cx.assert_editor_state(indoc! {"
 4880        «aaaaa
 4881        bb
 4882        ccc
 4883        ddddˇ»
 4884    "});
 4885
 4886    // Manipulate with multiple disjoin selections
 4887    cx.set_state(indoc! {"
 4888 4889        4
 4890        3
 4891        2
 4892        1ˇ»
 4893
 4894        dd«dd
 4895        ccc
 4896        bb
 4897        aaaˇ»aa
 4898    "});
 4899    cx.update_editor(|e, window, cx| {
 4900        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4901    });
 4902    cx.assert_editor_state(indoc! {"
 4903        «1
 4904        2
 4905        3
 4906        4
 4907        5ˇ»
 4908
 4909        «aaaaa
 4910        bb
 4911        ccc
 4912        ddddˇ»
 4913    "});
 4914
 4915    // Adding lines on each selection
 4916    cx.set_state(indoc! {"
 4917 4918        1ˇ»
 4919
 4920        bb«bb
 4921        aaaˇ»aa
 4922    "});
 4923    cx.update_editor(|e, window, cx| {
 4924        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4925    });
 4926    cx.assert_editor_state(indoc! {"
 4927        «2
 4928        1
 4929        added lineˇ»
 4930
 4931        «bbbb
 4932        aaaaa
 4933        added lineˇ»
 4934    "});
 4935
 4936    // Removing lines on each selection
 4937    cx.set_state(indoc! {"
 4938 4939        1ˇ»
 4940
 4941        bb«bb
 4942        aaaˇ»aa
 4943    "});
 4944    cx.update_editor(|e, window, cx| {
 4945        e.manipulate_immutable_lines(window, cx, |lines| {
 4946            lines.pop();
 4947        })
 4948    });
 4949    cx.assert_editor_state(indoc! {"
 4950        «2ˇ»
 4951
 4952        «bbbbˇ»
 4953    "});
 4954}
 4955
 4956#[gpui::test]
 4957async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4958    init_test(cx, |settings| {
 4959        settings.defaults.tab_size = NonZeroU32::new(3)
 4960    });
 4961
 4962    let mut cx = EditorTestContext::new(cx).await;
 4963
 4964    // MULTI SELECTION
 4965    // Ln.1 "«" tests empty lines
 4966    // Ln.9 tests just leading whitespace
 4967    cx.set_state(indoc! {"
 4968        «
 4969        abc                 // No indentationˇ»
 4970        «\tabc              // 1 tabˇ»
 4971        \t\tabc «      ˇ»   // 2 tabs
 4972        \t ab«c             // Tab followed by space
 4973         \tabc              // Space followed by tab (3 spaces should be the result)
 4974        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4975           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4976        \t
 4977        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4978    "});
 4979    cx.update_editor(|e, window, cx| {
 4980        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4981    });
 4982    cx.assert_editor_state(
 4983        indoc! {"
 4984            «
 4985            abc                 // No indentation
 4986               abc              // 1 tab
 4987                  abc          // 2 tabs
 4988                abc             // Tab followed by space
 4989               abc              // Space followed by tab (3 spaces should be the result)
 4990                           abc   // Mixed indentation (tab conversion depends on the column)
 4991               abc         // Already space indented
 4992               ·
 4993               abc\tdef          // Only the leading tab is manipulatedˇ»
 4994        "}
 4995        .replace("·", "")
 4996        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4997    );
 4998
 4999    // Test on just a few lines, the others should remain unchanged
 5000    // Only lines (3, 5, 10, 11) should change
 5001    cx.set_state(
 5002        indoc! {"
 5003            ·
 5004            abc                 // No indentation
 5005            \tabcˇ               // 1 tab
 5006            \t\tabc             // 2 tabs
 5007            \t abcˇ              // Tab followed by space
 5008             \tabc              // Space followed by tab (3 spaces should be the result)
 5009            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5010               abc              // Already space indented
 5011            «\t
 5012            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5013        "}
 5014        .replace("·", "")
 5015        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5016    );
 5017    cx.update_editor(|e, window, cx| {
 5018        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5019    });
 5020    cx.assert_editor_state(
 5021        indoc! {"
 5022            ·
 5023            abc                 // No indentation
 5024            «   abc               // 1 tabˇ»
 5025            \t\tabc             // 2 tabs
 5026            «    abc              // Tab followed by spaceˇ»
 5027             \tabc              // Space followed by tab (3 spaces should be the result)
 5028            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5029               abc              // Already space indented
 5030            «   ·
 5031               abc\tdef          // Only the leading tab is manipulatedˇ»
 5032        "}
 5033        .replace("·", "")
 5034        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5035    );
 5036
 5037    // SINGLE SELECTION
 5038    // Ln.1 "«" tests empty lines
 5039    // Ln.9 tests just leading whitespace
 5040    cx.set_state(indoc! {"
 5041        «
 5042        abc                 // No indentation
 5043        \tabc               // 1 tab
 5044        \t\tabc             // 2 tabs
 5045        \t abc              // Tab followed by space
 5046         \tabc              // Space followed by tab (3 spaces should be the result)
 5047        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5048           abc              // Already space indented
 5049        \t
 5050        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5051    "});
 5052    cx.update_editor(|e, window, cx| {
 5053        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5054    });
 5055    cx.assert_editor_state(
 5056        indoc! {"
 5057            «
 5058            abc                 // No indentation
 5059               abc               // 1 tab
 5060                  abc             // 2 tabs
 5061                abc              // Tab followed by space
 5062               abc              // Space followed by tab (3 spaces should be the result)
 5063                           abc   // Mixed indentation (tab conversion depends on the column)
 5064               abc              // Already space indented
 5065               ·
 5066               abc\tdef          // Only the leading tab is manipulatedˇ»
 5067        "}
 5068        .replace("·", "")
 5069        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5070    );
 5071}
 5072
 5073#[gpui::test]
 5074async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5075    init_test(cx, |settings| {
 5076        settings.defaults.tab_size = NonZeroU32::new(3)
 5077    });
 5078
 5079    let mut cx = EditorTestContext::new(cx).await;
 5080
 5081    // MULTI SELECTION
 5082    // Ln.1 "«" tests empty lines
 5083    // Ln.11 tests just leading whitespace
 5084    cx.set_state(indoc! {"
 5085        «
 5086        abˇ»ˇc                 // No indentation
 5087         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5088          abc  «             // 2 spaces (< 3 so dont convert)
 5089           abc              // 3 spaces (convert)
 5090             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5091        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5092        «\t abc              // Tab followed by space
 5093         \tabc              // Space followed by tab (should be consumed due to tab)
 5094        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5095           \tˇ»  «\t
 5096           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5097    "});
 5098    cx.update_editor(|e, window, cx| {
 5099        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5100    });
 5101    cx.assert_editor_state(indoc! {"
 5102        «
 5103        abc                 // No indentation
 5104         abc                // 1 space (< 3 so dont convert)
 5105          abc               // 2 spaces (< 3 so dont convert)
 5106        \tabc              // 3 spaces (convert)
 5107        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5108        \t\t\tabc           // Already tab indented
 5109        \t abc              // Tab followed by space
 5110        \tabc              // Space followed by tab (should be consumed due to tab)
 5111        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5112        \t\t\t
 5113        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5114    "});
 5115
 5116    // Test on just a few lines, the other should remain unchanged
 5117    // Only lines (4, 8, 11, 12) should change
 5118    cx.set_state(
 5119        indoc! {"
 5120            ·
 5121            abc                 // No indentation
 5122             abc                // 1 space (< 3 so dont convert)
 5123              abc               // 2 spaces (< 3 so dont convert)
 5124            «   abc              // 3 spaces (convert)ˇ»
 5125                 abc            // 5 spaces (1 tab + 2 spaces)
 5126            \t\t\tabc           // Already tab indented
 5127            \t abc              // Tab followed by space
 5128             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5129               \t\t  \tabc      // Mixed indentation
 5130            \t \t  \t   \tabc   // Mixed indentation
 5131               \t  \tˇ
 5132            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5133        "}
 5134        .replace("·", "")
 5135        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5136    );
 5137    cx.update_editor(|e, window, cx| {
 5138        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5139    });
 5140    cx.assert_editor_state(
 5141        indoc! {"
 5142            ·
 5143            abc                 // No indentation
 5144             abc                // 1 space (< 3 so dont convert)
 5145              abc               // 2 spaces (< 3 so dont convert)
 5146            «\tabc              // 3 spaces (convert)ˇ»
 5147                 abc            // 5 spaces (1 tab + 2 spaces)
 5148            \t\t\tabc           // Already tab indented
 5149            \t abc              // Tab followed by space
 5150            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5151               \t\t  \tabc      // Mixed indentation
 5152            \t \t  \t   \tabc   // Mixed indentation
 5153            «\t\t\t
 5154            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5155        "}
 5156        .replace("·", "")
 5157        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5158    );
 5159
 5160    // SINGLE SELECTION
 5161    // Ln.1 "«" tests empty lines
 5162    // Ln.11 tests just leading whitespace
 5163    cx.set_state(indoc! {"
 5164        «
 5165        abc                 // No indentation
 5166         abc                // 1 space (< 3 so dont convert)
 5167          abc               // 2 spaces (< 3 so dont convert)
 5168           abc              // 3 spaces (convert)
 5169             abc            // 5 spaces (1 tab + 2 spaces)
 5170        \t\t\tabc           // Already tab indented
 5171        \t abc              // Tab followed by space
 5172         \tabc              // Space followed by tab (should be consumed due to tab)
 5173        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5174           \t  \t
 5175           abc   \t         // Only the leading spaces should be convertedˇ»
 5176    "});
 5177    cx.update_editor(|e, window, cx| {
 5178        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5179    });
 5180    cx.assert_editor_state(indoc! {"
 5181        «
 5182        abc                 // No indentation
 5183         abc                // 1 space (< 3 so dont convert)
 5184          abc               // 2 spaces (< 3 so dont convert)
 5185        \tabc              // 3 spaces (convert)
 5186        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5187        \t\t\tabc           // Already tab indented
 5188        \t abc              // Tab followed by space
 5189        \tabc              // Space followed by tab (should be consumed due to tab)
 5190        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5191        \t\t\t
 5192        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5193    "});
 5194}
 5195
 5196#[gpui::test]
 5197async fn test_toggle_case(cx: &mut TestAppContext) {
 5198    init_test(cx, |_| {});
 5199
 5200    let mut cx = EditorTestContext::new(cx).await;
 5201
 5202    // If all lower case -> upper case
 5203    cx.set_state(indoc! {"
 5204        «hello worldˇ»
 5205    "});
 5206    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5207    cx.assert_editor_state(indoc! {"
 5208        «HELLO WORLDˇ»
 5209    "});
 5210
 5211    // If all upper case -> lower case
 5212    cx.set_state(indoc! {"
 5213        «HELLO WORLDˇ»
 5214    "});
 5215    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5216    cx.assert_editor_state(indoc! {"
 5217        «hello worldˇ»
 5218    "});
 5219
 5220    // If any upper case characters are identified -> lower case
 5221    // This matches JetBrains IDEs
 5222    cx.set_state(indoc! {"
 5223        «hEllo worldˇ»
 5224    "});
 5225    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5226    cx.assert_editor_state(indoc! {"
 5227        «hello worldˇ»
 5228    "});
 5229}
 5230
 5231#[gpui::test]
 5232async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5233    init_test(cx, |_| {});
 5234
 5235    let mut cx = EditorTestContext::new(cx).await;
 5236
 5237    cx.set_state(indoc! {"
 5238        «implement-windows-supportˇ»
 5239    "});
 5240    cx.update_editor(|e, window, cx| {
 5241        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5242    });
 5243    cx.assert_editor_state(indoc! {"
 5244        «Implement windows supportˇ»
 5245    "});
 5246}
 5247
 5248#[gpui::test]
 5249async fn test_manipulate_text(cx: &mut TestAppContext) {
 5250    init_test(cx, |_| {});
 5251
 5252    let mut cx = EditorTestContext::new(cx).await;
 5253
 5254    // Test convert_to_upper_case()
 5255    cx.set_state(indoc! {"
 5256        «hello worldˇ»
 5257    "});
 5258    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5259    cx.assert_editor_state(indoc! {"
 5260        «HELLO WORLDˇ»
 5261    "});
 5262
 5263    // Test convert_to_lower_case()
 5264    cx.set_state(indoc! {"
 5265        «HELLO WORLDˇ»
 5266    "});
 5267    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5268    cx.assert_editor_state(indoc! {"
 5269        «hello worldˇ»
 5270    "});
 5271
 5272    // Test multiple line, single selection case
 5273    cx.set_state(indoc! {"
 5274        «The quick brown
 5275        fox jumps over
 5276        the lazy dogˇ»
 5277    "});
 5278    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5279    cx.assert_editor_state(indoc! {"
 5280        «The Quick Brown
 5281        Fox Jumps Over
 5282        The Lazy Dogˇ»
 5283    "});
 5284
 5285    // Test multiple line, single selection case
 5286    cx.set_state(indoc! {"
 5287        «The quick brown
 5288        fox jumps over
 5289        the lazy dogˇ»
 5290    "});
 5291    cx.update_editor(|e, window, cx| {
 5292        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5293    });
 5294    cx.assert_editor_state(indoc! {"
 5295        «TheQuickBrown
 5296        FoxJumpsOver
 5297        TheLazyDogˇ»
 5298    "});
 5299
 5300    // From here on out, test more complex cases of manipulate_text()
 5301
 5302    // Test no selection case - should affect words cursors are in
 5303    // Cursor at beginning, middle, and end of word
 5304    cx.set_state(indoc! {"
 5305        ˇhello big beauˇtiful worldˇ
 5306    "});
 5307    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5308    cx.assert_editor_state(indoc! {"
 5309        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5310    "});
 5311
 5312    // Test multiple selections on a single line and across multiple lines
 5313    cx.set_state(indoc! {"
 5314        «Theˇ» quick «brown
 5315        foxˇ» jumps «overˇ»
 5316        the «lazyˇ» dog
 5317    "});
 5318    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5319    cx.assert_editor_state(indoc! {"
 5320        «THEˇ» quick «BROWN
 5321        FOXˇ» jumps «OVERˇ»
 5322        the «LAZYˇ» dog
 5323    "});
 5324
 5325    // Test case where text length grows
 5326    cx.set_state(indoc! {"
 5327        «tschüߡ»
 5328    "});
 5329    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5330    cx.assert_editor_state(indoc! {"
 5331        «TSCHÜSSˇ»
 5332    "});
 5333
 5334    // Test to make sure we don't crash when text shrinks
 5335    cx.set_state(indoc! {"
 5336        aaa_bbbˇ
 5337    "});
 5338    cx.update_editor(|e, window, cx| {
 5339        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5340    });
 5341    cx.assert_editor_state(indoc! {"
 5342        «aaaBbbˇ»
 5343    "});
 5344
 5345    // Test to make sure we all aware of the fact that each word can grow and shrink
 5346    // Final selections should be aware of this fact
 5347    cx.set_state(indoc! {"
 5348        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5349    "});
 5350    cx.update_editor(|e, window, cx| {
 5351        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5352    });
 5353    cx.assert_editor_state(indoc! {"
 5354        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5355    "});
 5356
 5357    cx.set_state(indoc! {"
 5358        «hElLo, WoRld!ˇ»
 5359    "});
 5360    cx.update_editor(|e, window, cx| {
 5361        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5362    });
 5363    cx.assert_editor_state(indoc! {"
 5364        «HeLlO, wOrLD!ˇ»
 5365    "});
 5366
 5367    // Test selections with `line_mode = true`.
 5368    cx.update_editor(|editor, _window, _cx| editor.selections.line_mode = true);
 5369    cx.set_state(indoc! {"
 5370        «The quick brown
 5371        fox jumps over
 5372        tˇ»he lazy dog
 5373    "});
 5374    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5375    cx.assert_editor_state(indoc! {"
 5376        «THE QUICK BROWN
 5377        FOX JUMPS OVER
 5378        THE LAZY DOGˇ»
 5379    "});
 5380}
 5381
 5382#[gpui::test]
 5383fn test_duplicate_line(cx: &mut TestAppContext) {
 5384    init_test(cx, |_| {});
 5385
 5386    let editor = cx.add_window(|window, cx| {
 5387        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5388        build_editor(buffer, window, cx)
 5389    });
 5390    _ = editor.update(cx, |editor, window, cx| {
 5391        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5392            s.select_display_ranges([
 5393                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5394                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5395                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5396                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5397            ])
 5398        });
 5399        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5400        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5401        assert_eq!(
 5402            editor.selections.display_ranges(cx),
 5403            vec![
 5404                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5405                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5406                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5407                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5408            ]
 5409        );
 5410    });
 5411
 5412    let editor = cx.add_window(|window, cx| {
 5413        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5414        build_editor(buffer, window, cx)
 5415    });
 5416    _ = editor.update(cx, |editor, window, cx| {
 5417        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5418            s.select_display_ranges([
 5419                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5420                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5421            ])
 5422        });
 5423        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5424        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5425        assert_eq!(
 5426            editor.selections.display_ranges(cx),
 5427            vec![
 5428                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5429                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5430            ]
 5431        );
 5432    });
 5433
 5434    // With `move_upwards` the selections stay in place, except for
 5435    // the lines inserted above them
 5436    let editor = cx.add_window(|window, cx| {
 5437        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5438        build_editor(buffer, window, cx)
 5439    });
 5440    _ = editor.update(cx, |editor, window, cx| {
 5441        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5442            s.select_display_ranges([
 5443                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5444                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5445                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5446                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5447            ])
 5448        });
 5449        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5450        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5451        assert_eq!(
 5452            editor.selections.display_ranges(cx),
 5453            vec![
 5454                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5455                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5456                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5457                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5458            ]
 5459        );
 5460    });
 5461
 5462    let editor = cx.add_window(|window, cx| {
 5463        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5464        build_editor(buffer, window, cx)
 5465    });
 5466    _ = editor.update(cx, |editor, window, cx| {
 5467        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5468            s.select_display_ranges([
 5469                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5470                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5471            ])
 5472        });
 5473        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5474        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5475        assert_eq!(
 5476            editor.selections.display_ranges(cx),
 5477            vec![
 5478                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5479                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5480            ]
 5481        );
 5482    });
 5483
 5484    let editor = cx.add_window(|window, cx| {
 5485        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5486        build_editor(buffer, window, cx)
 5487    });
 5488    _ = editor.update(cx, |editor, window, cx| {
 5489        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5490            s.select_display_ranges([
 5491                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5492                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5493            ])
 5494        });
 5495        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5496        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5497        assert_eq!(
 5498            editor.selections.display_ranges(cx),
 5499            vec![
 5500                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5501                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5502            ]
 5503        );
 5504    });
 5505}
 5506
 5507#[gpui::test]
 5508fn test_move_line_up_down(cx: &mut TestAppContext) {
 5509    init_test(cx, |_| {});
 5510
 5511    let editor = cx.add_window(|window, cx| {
 5512        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5513        build_editor(buffer, window, cx)
 5514    });
 5515    _ = editor.update(cx, |editor, window, cx| {
 5516        editor.fold_creases(
 5517            vec![
 5518                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5519                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5520                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5521            ],
 5522            true,
 5523            window,
 5524            cx,
 5525        );
 5526        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5527            s.select_display_ranges([
 5528                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5529                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5530                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5531                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5532            ])
 5533        });
 5534        assert_eq!(
 5535            editor.display_text(cx),
 5536            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5537        );
 5538
 5539        editor.move_line_up(&MoveLineUp, window, cx);
 5540        assert_eq!(
 5541            editor.display_text(cx),
 5542            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5543        );
 5544        assert_eq!(
 5545            editor.selections.display_ranges(cx),
 5546            vec![
 5547                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5548                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5549                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5550                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5551            ]
 5552        );
 5553    });
 5554
 5555    _ = editor.update(cx, |editor, window, cx| {
 5556        editor.move_line_down(&MoveLineDown, window, cx);
 5557        assert_eq!(
 5558            editor.display_text(cx),
 5559            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5560        );
 5561        assert_eq!(
 5562            editor.selections.display_ranges(cx),
 5563            vec![
 5564                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5565                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5566                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5567                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5568            ]
 5569        );
 5570    });
 5571
 5572    _ = editor.update(cx, |editor, window, cx| {
 5573        editor.move_line_down(&MoveLineDown, window, cx);
 5574        assert_eq!(
 5575            editor.display_text(cx),
 5576            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5577        );
 5578        assert_eq!(
 5579            editor.selections.display_ranges(cx),
 5580            vec![
 5581                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5582                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5583                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5584                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5585            ]
 5586        );
 5587    });
 5588
 5589    _ = editor.update(cx, |editor, window, cx| {
 5590        editor.move_line_up(&MoveLineUp, window, cx);
 5591        assert_eq!(
 5592            editor.display_text(cx),
 5593            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5594        );
 5595        assert_eq!(
 5596            editor.selections.display_ranges(cx),
 5597            vec![
 5598                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5599                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5600                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5601                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5602            ]
 5603        );
 5604    });
 5605}
 5606
 5607#[gpui::test]
 5608fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5609    init_test(cx, |_| {});
 5610    let editor = cx.add_window(|window, cx| {
 5611        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5612        build_editor(buffer, window, cx)
 5613    });
 5614    _ = editor.update(cx, |editor, window, cx| {
 5615        editor.fold_creases(
 5616            vec![Crease::simple(
 5617                Point::new(6, 4)..Point::new(7, 4),
 5618                FoldPlaceholder::test(),
 5619            )],
 5620            true,
 5621            window,
 5622            cx,
 5623        );
 5624        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5625            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5626        });
 5627        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5628        editor.move_line_up(&MoveLineUp, window, cx);
 5629        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5630        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5631    });
 5632}
 5633
 5634#[gpui::test]
 5635fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5636    init_test(cx, |_| {});
 5637
 5638    let editor = cx.add_window(|window, cx| {
 5639        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5640        build_editor(buffer, window, cx)
 5641    });
 5642    _ = editor.update(cx, |editor, window, cx| {
 5643        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5644        editor.insert_blocks(
 5645            [BlockProperties {
 5646                style: BlockStyle::Fixed,
 5647                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5648                height: Some(1),
 5649                render: Arc::new(|_| div().into_any()),
 5650                priority: 0,
 5651            }],
 5652            Some(Autoscroll::fit()),
 5653            cx,
 5654        );
 5655        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5656            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5657        });
 5658        editor.move_line_down(&MoveLineDown, window, cx);
 5659    });
 5660}
 5661
 5662#[gpui::test]
 5663async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5664    init_test(cx, |_| {});
 5665
 5666    let mut cx = EditorTestContext::new(cx).await;
 5667    cx.set_state(
 5668        &"
 5669            ˇzero
 5670            one
 5671            two
 5672            three
 5673            four
 5674            five
 5675        "
 5676        .unindent(),
 5677    );
 5678
 5679    // Create a four-line block that replaces three lines of text.
 5680    cx.update_editor(|editor, window, cx| {
 5681        let snapshot = editor.snapshot(window, cx);
 5682        let snapshot = &snapshot.buffer_snapshot;
 5683        let placement = BlockPlacement::Replace(
 5684            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5685        );
 5686        editor.insert_blocks(
 5687            [BlockProperties {
 5688                placement,
 5689                height: Some(4),
 5690                style: BlockStyle::Sticky,
 5691                render: Arc::new(|_| gpui::div().into_any_element()),
 5692                priority: 0,
 5693            }],
 5694            None,
 5695            cx,
 5696        );
 5697    });
 5698
 5699    // Move down so that the cursor touches the block.
 5700    cx.update_editor(|editor, window, cx| {
 5701        editor.move_down(&Default::default(), window, cx);
 5702    });
 5703    cx.assert_editor_state(
 5704        &"
 5705            zero
 5706            «one
 5707            two
 5708            threeˇ»
 5709            four
 5710            five
 5711        "
 5712        .unindent(),
 5713    );
 5714
 5715    // Move down past the block.
 5716    cx.update_editor(|editor, window, cx| {
 5717        editor.move_down(&Default::default(), window, cx);
 5718    });
 5719    cx.assert_editor_state(
 5720        &"
 5721            zero
 5722            one
 5723            two
 5724            three
 5725            ˇfour
 5726            five
 5727        "
 5728        .unindent(),
 5729    );
 5730}
 5731
 5732#[gpui::test]
 5733fn test_transpose(cx: &mut TestAppContext) {
 5734    init_test(cx, |_| {});
 5735
 5736    _ = cx.add_window(|window, cx| {
 5737        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5738        editor.set_style(EditorStyle::default(), window, cx);
 5739        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5740            s.select_ranges([1..1])
 5741        });
 5742        editor.transpose(&Default::default(), window, cx);
 5743        assert_eq!(editor.text(cx), "bac");
 5744        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5745
 5746        editor.transpose(&Default::default(), window, cx);
 5747        assert_eq!(editor.text(cx), "bca");
 5748        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5749
 5750        editor.transpose(&Default::default(), window, cx);
 5751        assert_eq!(editor.text(cx), "bac");
 5752        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5753
 5754        editor
 5755    });
 5756
 5757    _ = cx.add_window(|window, cx| {
 5758        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5759        editor.set_style(EditorStyle::default(), window, cx);
 5760        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5761            s.select_ranges([3..3])
 5762        });
 5763        editor.transpose(&Default::default(), window, cx);
 5764        assert_eq!(editor.text(cx), "acb\nde");
 5765        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5766
 5767        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5768            s.select_ranges([4..4])
 5769        });
 5770        editor.transpose(&Default::default(), window, cx);
 5771        assert_eq!(editor.text(cx), "acbd\ne");
 5772        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5773
 5774        editor.transpose(&Default::default(), window, cx);
 5775        assert_eq!(editor.text(cx), "acbde\n");
 5776        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5777
 5778        editor.transpose(&Default::default(), window, cx);
 5779        assert_eq!(editor.text(cx), "acbd\ne");
 5780        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5781
 5782        editor
 5783    });
 5784
 5785    _ = cx.add_window(|window, cx| {
 5786        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5787        editor.set_style(EditorStyle::default(), window, cx);
 5788        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5789            s.select_ranges([1..1, 2..2, 4..4])
 5790        });
 5791        editor.transpose(&Default::default(), window, cx);
 5792        assert_eq!(editor.text(cx), "bacd\ne");
 5793        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5794
 5795        editor.transpose(&Default::default(), window, cx);
 5796        assert_eq!(editor.text(cx), "bcade\n");
 5797        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5798
 5799        editor.transpose(&Default::default(), window, cx);
 5800        assert_eq!(editor.text(cx), "bcda\ne");
 5801        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5802
 5803        editor.transpose(&Default::default(), window, cx);
 5804        assert_eq!(editor.text(cx), "bcade\n");
 5805        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5806
 5807        editor.transpose(&Default::default(), window, cx);
 5808        assert_eq!(editor.text(cx), "bcaed\n");
 5809        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5810
 5811        editor
 5812    });
 5813
 5814    _ = cx.add_window(|window, cx| {
 5815        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5816        editor.set_style(EditorStyle::default(), window, cx);
 5817        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5818            s.select_ranges([4..4])
 5819        });
 5820        editor.transpose(&Default::default(), window, cx);
 5821        assert_eq!(editor.text(cx), "🏀🍐✋");
 5822        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5823
 5824        editor.transpose(&Default::default(), window, cx);
 5825        assert_eq!(editor.text(cx), "🏀✋🍐");
 5826        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5827
 5828        editor.transpose(&Default::default(), window, cx);
 5829        assert_eq!(editor.text(cx), "🏀🍐✋");
 5830        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5831
 5832        editor
 5833    });
 5834}
 5835
 5836#[gpui::test]
 5837async fn test_rewrap(cx: &mut TestAppContext) {
 5838    init_test(cx, |settings| {
 5839        settings.languages.0.extend([
 5840            (
 5841                "Markdown".into(),
 5842                LanguageSettingsContent {
 5843                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5844                    preferred_line_length: Some(40),
 5845                    ..Default::default()
 5846                },
 5847            ),
 5848            (
 5849                "Plain Text".into(),
 5850                LanguageSettingsContent {
 5851                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5852                    preferred_line_length: Some(40),
 5853                    ..Default::default()
 5854                },
 5855            ),
 5856            (
 5857                "C++".into(),
 5858                LanguageSettingsContent {
 5859                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5860                    preferred_line_length: Some(40),
 5861                    ..Default::default()
 5862                },
 5863            ),
 5864            (
 5865                "Python".into(),
 5866                LanguageSettingsContent {
 5867                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5868                    preferred_line_length: Some(40),
 5869                    ..Default::default()
 5870                },
 5871            ),
 5872            (
 5873                "Rust".into(),
 5874                LanguageSettingsContent {
 5875                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5876                    preferred_line_length: Some(40),
 5877                    ..Default::default()
 5878                },
 5879            ),
 5880        ])
 5881    });
 5882
 5883    let mut cx = EditorTestContext::new(cx).await;
 5884
 5885    let cpp_language = Arc::new(Language::new(
 5886        LanguageConfig {
 5887            name: "C++".into(),
 5888            line_comments: vec!["// ".into()],
 5889            ..LanguageConfig::default()
 5890        },
 5891        None,
 5892    ));
 5893    let python_language = Arc::new(Language::new(
 5894        LanguageConfig {
 5895            name: "Python".into(),
 5896            line_comments: vec!["# ".into()],
 5897            ..LanguageConfig::default()
 5898        },
 5899        None,
 5900    ));
 5901    let markdown_language = Arc::new(Language::new(
 5902        LanguageConfig {
 5903            name: "Markdown".into(),
 5904            rewrap_prefixes: vec![
 5905                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5906                regex::Regex::new("[-*+]\\s+").unwrap(),
 5907            ],
 5908            ..LanguageConfig::default()
 5909        },
 5910        None,
 5911    ));
 5912    let rust_language = Arc::new(
 5913        Language::new(
 5914            LanguageConfig {
 5915                name: "Rust".into(),
 5916                line_comments: vec!["// ".into(), "/// ".into()],
 5917                ..LanguageConfig::default()
 5918            },
 5919            Some(tree_sitter_rust::LANGUAGE.into()),
 5920        )
 5921        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 5922        .unwrap(),
 5923    );
 5924
 5925    let plaintext_language = Arc::new(Language::new(
 5926        LanguageConfig {
 5927            name: "Plain Text".into(),
 5928            ..LanguageConfig::default()
 5929        },
 5930        None,
 5931    ));
 5932
 5933    // Test basic rewrapping of a long line with a cursor
 5934    assert_rewrap(
 5935        indoc! {"
 5936            // ˇThis is a long comment that needs to be wrapped.
 5937        "},
 5938        indoc! {"
 5939            // ˇThis is a long comment that needs to
 5940            // be wrapped.
 5941        "},
 5942        cpp_language.clone(),
 5943        &mut cx,
 5944    );
 5945
 5946    // Test rewrapping a full selection
 5947    assert_rewrap(
 5948        indoc! {"
 5949            «// This selected long comment needs to be wrapped.ˇ»"
 5950        },
 5951        indoc! {"
 5952            «// This selected long comment needs to
 5953            // be wrapped.ˇ»"
 5954        },
 5955        cpp_language.clone(),
 5956        &mut cx,
 5957    );
 5958
 5959    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5960    assert_rewrap(
 5961        indoc! {"
 5962            // ˇThis is the first line.
 5963            // Thisˇ is the second line.
 5964            // This is the thirdˇ line, all part of one paragraph.
 5965         "},
 5966        indoc! {"
 5967            // ˇThis is the first line. Thisˇ is the
 5968            // second line. This is the thirdˇ line,
 5969            // all part of one paragraph.
 5970         "},
 5971        cpp_language.clone(),
 5972        &mut cx,
 5973    );
 5974
 5975    // Test multiple cursors in different paragraphs trigger separate rewraps
 5976    assert_rewrap(
 5977        indoc! {"
 5978            // ˇThis is the first paragraph, first line.
 5979            // ˇThis is the first paragraph, second line.
 5980
 5981            // ˇThis is the second paragraph, first line.
 5982            // ˇThis is the second paragraph, second line.
 5983        "},
 5984        indoc! {"
 5985            // ˇThis is the first paragraph, first
 5986            // line. ˇThis is the first paragraph,
 5987            // second line.
 5988
 5989            // ˇThis is the second paragraph, first
 5990            // line. ˇThis is the second paragraph,
 5991            // second line.
 5992        "},
 5993        cpp_language.clone(),
 5994        &mut cx,
 5995    );
 5996
 5997    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5998    assert_rewrap(
 5999        indoc! {"
 6000            «// A regular long long comment to be wrapped.
 6001            /// A documentation long comment to be wrapped.ˇ»
 6002          "},
 6003        indoc! {"
 6004            «// A regular long long comment to be
 6005            // wrapped.
 6006            /// A documentation long comment to be
 6007            /// wrapped.ˇ»
 6008          "},
 6009        rust_language.clone(),
 6010        &mut cx,
 6011    );
 6012
 6013    // Test that change in indentation level trigger seperate rewraps
 6014    assert_rewrap(
 6015        indoc! {"
 6016            fn foo() {
 6017                «// This is a long comment at the base indent.
 6018                    // This is a long comment at the next indent.ˇ»
 6019            }
 6020        "},
 6021        indoc! {"
 6022            fn foo() {
 6023                «// This is a long comment at the
 6024                // base indent.
 6025                    // This is a long comment at the
 6026                    // next indent.ˇ»
 6027            }
 6028        "},
 6029        rust_language.clone(),
 6030        &mut cx,
 6031    );
 6032
 6033    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6034    assert_rewrap(
 6035        indoc! {"
 6036            # ˇThis is a long comment using a pound sign.
 6037        "},
 6038        indoc! {"
 6039            # ˇThis is a long comment using a pound
 6040            # sign.
 6041        "},
 6042        python_language,
 6043        &mut cx,
 6044    );
 6045
 6046    // Test rewrapping only affects comments, not code even when selected
 6047    assert_rewrap(
 6048        indoc! {"
 6049            «/// This doc comment is long and should be wrapped.
 6050            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6051        "},
 6052        indoc! {"
 6053            «/// This doc comment is long and should
 6054            /// be wrapped.
 6055            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6056        "},
 6057        rust_language.clone(),
 6058        &mut cx,
 6059    );
 6060
 6061    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6062    assert_rewrap(
 6063        indoc! {"
 6064            # Header
 6065
 6066            A long long long line of markdown text to wrap.ˇ
 6067         "},
 6068        indoc! {"
 6069            # Header
 6070
 6071            A long long long line of markdown text
 6072            to wrap.ˇ
 6073         "},
 6074        markdown_language.clone(),
 6075        &mut cx,
 6076    );
 6077
 6078    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6079    assert_rewrap(
 6080        indoc! {"
 6081            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6082            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6083            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6084        "},
 6085        indoc! {"
 6086            «1. This is a numbered list item that is
 6087               very long and needs to be wrapped
 6088               properly.
 6089            2. This is a numbered list item that is
 6090               very long and needs to be wrapped
 6091               properly.
 6092            - This is an unordered list item that is
 6093              also very long and should not merge
 6094              with the numbered item.ˇ»
 6095        "},
 6096        markdown_language.clone(),
 6097        &mut cx,
 6098    );
 6099
 6100    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6101    assert_rewrap(
 6102        indoc! {"
 6103            «1. This is a numbered list item that is
 6104            very long and needs to be wrapped
 6105            properly.
 6106            2. This is a numbered list item that is
 6107            very long and needs to be wrapped
 6108            properly.
 6109            - This is an unordered list item that is
 6110            also very long and should not merge with
 6111            the numbered item.ˇ»
 6112        "},
 6113        indoc! {"
 6114            «1. This is a numbered list item that is
 6115               very long and needs to be wrapped
 6116               properly.
 6117            2. This is a numbered list item that is
 6118               very long and needs to be wrapped
 6119               properly.
 6120            - This is an unordered list item that is
 6121              also very long and should not merge
 6122              with the numbered item.ˇ»
 6123        "},
 6124        markdown_language.clone(),
 6125        &mut cx,
 6126    );
 6127
 6128    // Test that rewrapping maintain indents even when they already exists.
 6129    assert_rewrap(
 6130        indoc! {"
 6131            «1. This is a numbered list
 6132               item that is very long and needs to be wrapped properly.
 6133            2. This is a numbered list
 6134               item that is very long and needs to be wrapped properly.
 6135            - This is an unordered list item that is also very long and
 6136              should not merge with the numbered item.ˇ»
 6137        "},
 6138        indoc! {"
 6139            «1. This is a numbered list item that is
 6140               very long and needs to be wrapped
 6141               properly.
 6142            2. This is a numbered list item that is
 6143               very long and needs to be wrapped
 6144               properly.
 6145            - This is an unordered list item that is
 6146              also very long and should not merge
 6147              with the numbered item.ˇ»
 6148        "},
 6149        markdown_language,
 6150        &mut cx,
 6151    );
 6152
 6153    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6154    assert_rewrap(
 6155        indoc! {"
 6156            ˇThis is a very long line of plain text that will be wrapped.
 6157        "},
 6158        indoc! {"
 6159            ˇThis is a very long line of plain text
 6160            that will be wrapped.
 6161        "},
 6162        plaintext_language.clone(),
 6163        &mut cx,
 6164    );
 6165
 6166    // Test that non-commented code acts as a paragraph boundary within a selection
 6167    assert_rewrap(
 6168        indoc! {"
 6169               «// This is the first long comment block to be wrapped.
 6170               fn my_func(a: u32);
 6171               // This is the second long comment block to be wrapped.ˇ»
 6172           "},
 6173        indoc! {"
 6174               «// This is the first long comment block
 6175               // to be wrapped.
 6176               fn my_func(a: u32);
 6177               // This is the second long comment block
 6178               // to be wrapped.ˇ»
 6179           "},
 6180        rust_language,
 6181        &mut cx,
 6182    );
 6183
 6184    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6185    assert_rewrap(
 6186        indoc! {"
 6187            «ˇThis is a very long line that will be wrapped.
 6188
 6189            This is another paragraph in the same selection.»
 6190
 6191            «\tThis is a very long indented line that will be wrapped.ˇ»
 6192         "},
 6193        indoc! {"
 6194            «ˇThis is a very long line that will be
 6195            wrapped.
 6196
 6197            This is another paragraph in the same
 6198            selection.»
 6199
 6200            «\tThis is a very long indented line
 6201            \tthat will be wrapped.ˇ»
 6202         "},
 6203        plaintext_language,
 6204        &mut cx,
 6205    );
 6206
 6207    // Test that an empty comment line acts as a paragraph boundary
 6208    assert_rewrap(
 6209        indoc! {"
 6210            // ˇThis is a long comment that will be wrapped.
 6211            //
 6212            // And this is another long comment that will also be wrapped.ˇ
 6213         "},
 6214        indoc! {"
 6215            // ˇThis is a long comment that will be
 6216            // wrapped.
 6217            //
 6218            // And this is another long comment that
 6219            // will also be wrapped.ˇ
 6220         "},
 6221        cpp_language,
 6222        &mut cx,
 6223    );
 6224
 6225    #[track_caller]
 6226    fn assert_rewrap(
 6227        unwrapped_text: &str,
 6228        wrapped_text: &str,
 6229        language: Arc<Language>,
 6230        cx: &mut EditorTestContext,
 6231    ) {
 6232        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6233        cx.set_state(unwrapped_text);
 6234        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6235        cx.assert_editor_state(wrapped_text);
 6236    }
 6237}
 6238
 6239#[gpui::test]
 6240async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6241    init_test(cx, |settings| {
 6242        settings.languages.0.extend([(
 6243            "Rust".into(),
 6244            LanguageSettingsContent {
 6245                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6246                preferred_line_length: Some(40),
 6247                ..Default::default()
 6248            },
 6249        )])
 6250    });
 6251
 6252    let mut cx = EditorTestContext::new(cx).await;
 6253
 6254    let rust_lang = Arc::new(
 6255        Language::new(
 6256            LanguageConfig {
 6257                name: "Rust".into(),
 6258                line_comments: vec!["// ".into()],
 6259                block_comment: Some(BlockCommentConfig {
 6260                    start: "/*".into(),
 6261                    end: "*/".into(),
 6262                    prefix: "* ".into(),
 6263                    tab_size: 1,
 6264                }),
 6265                documentation_comment: Some(BlockCommentConfig {
 6266                    start: "/**".into(),
 6267                    end: "*/".into(),
 6268                    prefix: "* ".into(),
 6269                    tab_size: 1,
 6270                }),
 6271
 6272                ..LanguageConfig::default()
 6273            },
 6274            Some(tree_sitter_rust::LANGUAGE.into()),
 6275        )
 6276        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6277        .unwrap(),
 6278    );
 6279
 6280    // regular block comment
 6281    assert_rewrap(
 6282        indoc! {"
 6283            /*
 6284             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6285             */
 6286            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6287        "},
 6288        indoc! {"
 6289            /*
 6290             *ˇ Lorem ipsum dolor sit amet,
 6291             * consectetur adipiscing elit.
 6292             */
 6293            /*
 6294             *ˇ Lorem ipsum dolor sit amet,
 6295             * consectetur adipiscing elit.
 6296             */
 6297        "},
 6298        rust_lang.clone(),
 6299        &mut cx,
 6300    );
 6301
 6302    // indent is respected
 6303    assert_rewrap(
 6304        indoc! {"
 6305            {}
 6306                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6307        "},
 6308        indoc! {"
 6309            {}
 6310                /*
 6311                 *ˇ Lorem ipsum dolor sit amet,
 6312                 * consectetur adipiscing elit.
 6313                 */
 6314        "},
 6315        rust_lang.clone(),
 6316        &mut cx,
 6317    );
 6318
 6319    // short block comments with inline delimiters
 6320    assert_rewrap(
 6321        indoc! {"
 6322            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6323            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6324             */
 6325            /*
 6326             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6327        "},
 6328        indoc! {"
 6329            /*
 6330             *ˇ Lorem ipsum dolor sit amet,
 6331             * consectetur adipiscing elit.
 6332             */
 6333            /*
 6334             *ˇ Lorem ipsum dolor sit amet,
 6335             * consectetur adipiscing elit.
 6336             */
 6337            /*
 6338             *ˇ Lorem ipsum dolor sit amet,
 6339             * consectetur adipiscing elit.
 6340             */
 6341        "},
 6342        rust_lang.clone(),
 6343        &mut cx,
 6344    );
 6345
 6346    // multiline block comment with inline start/end delimiters
 6347    assert_rewrap(
 6348        indoc! {"
 6349            /*ˇ Lorem ipsum dolor sit amet,
 6350             * consectetur adipiscing elit. */
 6351        "},
 6352        indoc! {"
 6353            /*
 6354             *ˇ Lorem ipsum dolor sit amet,
 6355             * consectetur adipiscing elit.
 6356             */
 6357        "},
 6358        rust_lang.clone(),
 6359        &mut cx,
 6360    );
 6361
 6362    // block comment rewrap still respects paragraph bounds
 6363    assert_rewrap(
 6364        indoc! {"
 6365            /*
 6366             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6367             *
 6368             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6369             */
 6370        "},
 6371        indoc! {"
 6372            /*
 6373             *ˇ Lorem ipsum dolor sit amet,
 6374             * consectetur adipiscing elit.
 6375             *
 6376             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6377             */
 6378        "},
 6379        rust_lang.clone(),
 6380        &mut cx,
 6381    );
 6382
 6383    // documentation comments
 6384    assert_rewrap(
 6385        indoc! {"
 6386            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6387            /**
 6388             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6389             */
 6390        "},
 6391        indoc! {"
 6392            /**
 6393             *ˇ Lorem ipsum dolor sit amet,
 6394             * consectetur adipiscing elit.
 6395             */
 6396            /**
 6397             *ˇ Lorem ipsum dolor sit amet,
 6398             * consectetur adipiscing elit.
 6399             */
 6400        "},
 6401        rust_lang.clone(),
 6402        &mut cx,
 6403    );
 6404
 6405    // different, adjacent comments
 6406    assert_rewrap(
 6407        indoc! {"
 6408            /**
 6409             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6410             */
 6411            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6412            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6413        "},
 6414        indoc! {"
 6415            /**
 6416             *ˇ Lorem ipsum dolor sit amet,
 6417             * consectetur adipiscing elit.
 6418             */
 6419            /*
 6420             *ˇ Lorem ipsum dolor sit amet,
 6421             * consectetur adipiscing elit.
 6422             */
 6423            //ˇ Lorem ipsum dolor sit amet,
 6424            // consectetur adipiscing elit.
 6425        "},
 6426        rust_lang.clone(),
 6427        &mut cx,
 6428    );
 6429
 6430    // selection w/ single short block comment
 6431    assert_rewrap(
 6432        indoc! {"
 6433            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6434        "},
 6435        indoc! {"
 6436            «/*
 6437             * Lorem ipsum dolor sit amet,
 6438             * consectetur adipiscing elit.
 6439             */ˇ»
 6440        "},
 6441        rust_lang.clone(),
 6442        &mut cx,
 6443    );
 6444
 6445    // rewrapping a single comment w/ abutting comments
 6446    assert_rewrap(
 6447        indoc! {"
 6448            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6449            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6450        "},
 6451        indoc! {"
 6452            /*
 6453             * ˇLorem ipsum dolor sit amet,
 6454             * consectetur adipiscing elit.
 6455             */
 6456            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6457        "},
 6458        rust_lang.clone(),
 6459        &mut cx,
 6460    );
 6461
 6462    // selection w/ non-abutting short block comments
 6463    assert_rewrap(
 6464        indoc! {"
 6465            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6466
 6467            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6468        "},
 6469        indoc! {"
 6470            «/*
 6471             * Lorem ipsum dolor sit amet,
 6472             * consectetur adipiscing elit.
 6473             */
 6474
 6475            /*
 6476             * Lorem ipsum dolor sit amet,
 6477             * consectetur adipiscing elit.
 6478             */ˇ»
 6479        "},
 6480        rust_lang.clone(),
 6481        &mut cx,
 6482    );
 6483
 6484    // selection of multiline block comments
 6485    assert_rewrap(
 6486        indoc! {"
 6487            «/* Lorem ipsum dolor sit amet,
 6488             * consectetur adipiscing elit. */ˇ»
 6489        "},
 6490        indoc! {"
 6491            «/*
 6492             * Lorem ipsum dolor sit amet,
 6493             * consectetur adipiscing elit.
 6494             */ˇ»
 6495        "},
 6496        rust_lang.clone(),
 6497        &mut cx,
 6498    );
 6499
 6500    // partial selection of multiline block comments
 6501    assert_rewrap(
 6502        indoc! {"
 6503            «/* Lorem ipsum dolor sit amet,ˇ»
 6504             * consectetur adipiscing elit. */
 6505            /* Lorem ipsum dolor sit amet,
 6506             «* consectetur adipiscing elit. */ˇ»
 6507        "},
 6508        indoc! {"
 6509            «/*
 6510             * Lorem ipsum dolor sit amet,ˇ»
 6511             * consectetur adipiscing elit. */
 6512            /* Lorem ipsum dolor sit amet,
 6513             «* consectetur adipiscing elit.
 6514             */ˇ»
 6515        "},
 6516        rust_lang.clone(),
 6517        &mut cx,
 6518    );
 6519
 6520    // selection w/ abutting short block comments
 6521    // TODO: should not be combined; should rewrap as 2 comments
 6522    assert_rewrap(
 6523        indoc! {"
 6524            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6525            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6526        "},
 6527        // desired behavior:
 6528        // indoc! {"
 6529        //     «/*
 6530        //      * Lorem ipsum dolor sit amet,
 6531        //      * consectetur adipiscing elit.
 6532        //      */
 6533        //     /*
 6534        //      * Lorem ipsum dolor sit amet,
 6535        //      * consectetur adipiscing elit.
 6536        //      */ˇ»
 6537        // "},
 6538        // actual behaviour:
 6539        indoc! {"
 6540            «/*
 6541             * Lorem ipsum dolor sit amet,
 6542             * consectetur adipiscing elit. Lorem
 6543             * ipsum dolor sit amet, consectetur
 6544             * adipiscing elit.
 6545             */ˇ»
 6546        "},
 6547        rust_lang.clone(),
 6548        &mut cx,
 6549    );
 6550
 6551    // TODO: same as above, but with delimiters on separate line
 6552    // assert_rewrap(
 6553    //     indoc! {"
 6554    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6555    //          */
 6556    //         /*
 6557    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6558    //     "},
 6559    //     // desired:
 6560    //     // indoc! {"
 6561    //     //     «/*
 6562    //     //      * Lorem ipsum dolor sit amet,
 6563    //     //      * consectetur adipiscing elit.
 6564    //     //      */
 6565    //     //     /*
 6566    //     //      * Lorem ipsum dolor sit amet,
 6567    //     //      * consectetur adipiscing elit.
 6568    //     //      */ˇ»
 6569    //     // "},
 6570    //     // actual: (but with trailing w/s on the empty lines)
 6571    //     indoc! {"
 6572    //         «/*
 6573    //          * Lorem ipsum dolor sit amet,
 6574    //          * consectetur adipiscing elit.
 6575    //          *
 6576    //          */
 6577    //         /*
 6578    //          *
 6579    //          * Lorem ipsum dolor sit amet,
 6580    //          * consectetur adipiscing elit.
 6581    //          */ˇ»
 6582    //     "},
 6583    //     rust_lang.clone(),
 6584    //     &mut cx,
 6585    // );
 6586
 6587    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6588    assert_rewrap(
 6589        indoc! {"
 6590            /*
 6591             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6592             */
 6593            /*
 6594             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6595            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6596        "},
 6597        // desired:
 6598        // indoc! {"
 6599        //     /*
 6600        //      *ˇ Lorem ipsum dolor sit amet,
 6601        //      * consectetur adipiscing elit.
 6602        //      */
 6603        //     /*
 6604        //      *ˇ Lorem ipsum dolor sit amet,
 6605        //      * consectetur adipiscing elit.
 6606        //      */
 6607        //     /*
 6608        //      *ˇ Lorem ipsum dolor sit amet
 6609        //      */ /* consectetur adipiscing elit. */
 6610        // "},
 6611        // actual:
 6612        indoc! {"
 6613            /*
 6614             //ˇ Lorem ipsum dolor sit amet,
 6615             // consectetur adipiscing elit.
 6616             */
 6617            /*
 6618             * //ˇ Lorem ipsum dolor sit amet,
 6619             * consectetur adipiscing elit.
 6620             */
 6621            /*
 6622             *ˇ Lorem ipsum dolor sit amet */ /*
 6623             * consectetur adipiscing elit.
 6624             */
 6625        "},
 6626        rust_lang,
 6627        &mut cx,
 6628    );
 6629
 6630    #[track_caller]
 6631    fn assert_rewrap(
 6632        unwrapped_text: &str,
 6633        wrapped_text: &str,
 6634        language: Arc<Language>,
 6635        cx: &mut EditorTestContext,
 6636    ) {
 6637        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6638        cx.set_state(unwrapped_text);
 6639        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6640        cx.assert_editor_state(wrapped_text);
 6641    }
 6642}
 6643
 6644#[gpui::test]
 6645async fn test_hard_wrap(cx: &mut TestAppContext) {
 6646    init_test(cx, |_| {});
 6647    let mut cx = EditorTestContext::new(cx).await;
 6648
 6649    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6650    cx.update_editor(|editor, _, cx| {
 6651        editor.set_hard_wrap(Some(14), cx);
 6652    });
 6653
 6654    cx.set_state(indoc!(
 6655        "
 6656        one two three ˇ
 6657        "
 6658    ));
 6659    cx.simulate_input("four");
 6660    cx.run_until_parked();
 6661
 6662    cx.assert_editor_state(indoc!(
 6663        "
 6664        one two three
 6665        fourˇ
 6666        "
 6667    ));
 6668
 6669    cx.update_editor(|editor, window, cx| {
 6670        editor.newline(&Default::default(), window, cx);
 6671    });
 6672    cx.run_until_parked();
 6673    cx.assert_editor_state(indoc!(
 6674        "
 6675        one two three
 6676        four
 6677        ˇ
 6678        "
 6679    ));
 6680
 6681    cx.simulate_input("five");
 6682    cx.run_until_parked();
 6683    cx.assert_editor_state(indoc!(
 6684        "
 6685        one two three
 6686        four
 6687        fiveˇ
 6688        "
 6689    ));
 6690
 6691    cx.update_editor(|editor, window, cx| {
 6692        editor.newline(&Default::default(), window, cx);
 6693    });
 6694    cx.run_until_parked();
 6695    cx.simulate_input("# ");
 6696    cx.run_until_parked();
 6697    cx.assert_editor_state(indoc!(
 6698        "
 6699        one two three
 6700        four
 6701        five
 6702        # ˇ
 6703        "
 6704    ));
 6705
 6706    cx.update_editor(|editor, window, cx| {
 6707        editor.newline(&Default::default(), window, cx);
 6708    });
 6709    cx.run_until_parked();
 6710    cx.assert_editor_state(indoc!(
 6711        "
 6712        one two three
 6713        four
 6714        five
 6715        #\x20
 6716 6717        "
 6718    ));
 6719
 6720    cx.simulate_input(" 6");
 6721    cx.run_until_parked();
 6722    cx.assert_editor_state(indoc!(
 6723        "
 6724        one two three
 6725        four
 6726        five
 6727        #
 6728        # 6ˇ
 6729        "
 6730    ));
 6731}
 6732
 6733#[gpui::test]
 6734async fn test_clipboard(cx: &mut TestAppContext) {
 6735    init_test(cx, |_| {});
 6736
 6737    let mut cx = EditorTestContext::new(cx).await;
 6738
 6739    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6740    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6741    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6742
 6743    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6744    cx.set_state("two ˇfour ˇsix ˇ");
 6745    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6746    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6747
 6748    // Paste again but with only two cursors. Since the number of cursors doesn't
 6749    // match the number of slices in the clipboard, the entire clipboard text
 6750    // is pasted at each cursor.
 6751    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6752    cx.update_editor(|e, window, cx| {
 6753        e.handle_input("( ", window, cx);
 6754        e.paste(&Paste, window, cx);
 6755        e.handle_input(") ", window, cx);
 6756    });
 6757    cx.assert_editor_state(
 6758        &([
 6759            "( one✅ ",
 6760            "three ",
 6761            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6762            "three ",
 6763            "five ) ˇ",
 6764        ]
 6765        .join("\n")),
 6766    );
 6767
 6768    // Cut with three selections, one of which is full-line.
 6769    cx.set_state(indoc! {"
 6770        1«2ˇ»3
 6771        4ˇ567
 6772        «8ˇ»9"});
 6773    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6774    cx.assert_editor_state(indoc! {"
 6775        1ˇ3
 6776        ˇ9"});
 6777
 6778    // Paste with three selections, noticing how the copied selection that was full-line
 6779    // gets inserted before the second cursor.
 6780    cx.set_state(indoc! {"
 6781        1ˇ3
 6782 6783        «oˇ»ne"});
 6784    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6785    cx.assert_editor_state(indoc! {"
 6786        12ˇ3
 6787        4567
 6788 6789        8ˇne"});
 6790
 6791    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6792    cx.set_state(indoc! {"
 6793        The quick brown
 6794        fox juˇmps over
 6795        the lazy dog"});
 6796    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6797    assert_eq!(
 6798        cx.read_from_clipboard()
 6799            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6800        Some("fox jumps over\n".to_string())
 6801    );
 6802
 6803    // Paste with three selections, noticing how the copied full-line selection is inserted
 6804    // before the empty selections but replaces the selection that is non-empty.
 6805    cx.set_state(indoc! {"
 6806        Tˇhe quick brown
 6807        «foˇ»x jumps over
 6808        tˇhe lazy dog"});
 6809    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6810    cx.assert_editor_state(indoc! {"
 6811        fox jumps over
 6812        Tˇhe quick brown
 6813        fox jumps over
 6814        ˇx jumps over
 6815        fox jumps over
 6816        tˇhe lazy dog"});
 6817}
 6818
 6819#[gpui::test]
 6820async fn test_copy_trim(cx: &mut TestAppContext) {
 6821    init_test(cx, |_| {});
 6822
 6823    let mut cx = EditorTestContext::new(cx).await;
 6824    cx.set_state(
 6825        r#"            «for selection in selections.iter() {
 6826            let mut start = selection.start;
 6827            let mut end = selection.end;
 6828            let is_entire_line = selection.is_empty();
 6829            if is_entire_line {
 6830                start = Point::new(start.row, 0);ˇ»
 6831                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6832            }
 6833        "#,
 6834    );
 6835    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6836    assert_eq!(
 6837        cx.read_from_clipboard()
 6838            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6839        Some(
 6840            "for selection in selections.iter() {
 6841            let mut start = selection.start;
 6842            let mut end = selection.end;
 6843            let is_entire_line = selection.is_empty();
 6844            if is_entire_line {
 6845                start = Point::new(start.row, 0);"
 6846                .to_string()
 6847        ),
 6848        "Regular copying preserves all indentation selected",
 6849    );
 6850    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6851    assert_eq!(
 6852        cx.read_from_clipboard()
 6853            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6854        Some(
 6855            "for selection in selections.iter() {
 6856let mut start = selection.start;
 6857let mut end = selection.end;
 6858let is_entire_line = selection.is_empty();
 6859if is_entire_line {
 6860    start = Point::new(start.row, 0);"
 6861                .to_string()
 6862        ),
 6863        "Copying with stripping should strip all leading whitespaces"
 6864    );
 6865
 6866    cx.set_state(
 6867        r#"       «     for selection in selections.iter() {
 6868            let mut start = selection.start;
 6869            let mut end = selection.end;
 6870            let is_entire_line = selection.is_empty();
 6871            if is_entire_line {
 6872                start = Point::new(start.row, 0);ˇ»
 6873                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6874            }
 6875        "#,
 6876    );
 6877    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6878    assert_eq!(
 6879        cx.read_from_clipboard()
 6880            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6881        Some(
 6882            "     for selection in selections.iter() {
 6883            let mut start = selection.start;
 6884            let mut end = selection.end;
 6885            let is_entire_line = selection.is_empty();
 6886            if is_entire_line {
 6887                start = Point::new(start.row, 0);"
 6888                .to_string()
 6889        ),
 6890        "Regular copying preserves all indentation selected",
 6891    );
 6892    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6893    assert_eq!(
 6894        cx.read_from_clipboard()
 6895            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6896        Some(
 6897            "for selection in selections.iter() {
 6898let mut start = selection.start;
 6899let mut end = selection.end;
 6900let is_entire_line = selection.is_empty();
 6901if is_entire_line {
 6902    start = Point::new(start.row, 0);"
 6903                .to_string()
 6904        ),
 6905        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6906    );
 6907
 6908    cx.set_state(
 6909        r#"       «ˇ     for selection in selections.iter() {
 6910            let mut start = selection.start;
 6911            let mut end = selection.end;
 6912            let is_entire_line = selection.is_empty();
 6913            if is_entire_line {
 6914                start = Point::new(start.row, 0);»
 6915                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6916            }
 6917        "#,
 6918    );
 6919    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6920    assert_eq!(
 6921        cx.read_from_clipboard()
 6922            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6923        Some(
 6924            "     for selection in selections.iter() {
 6925            let mut start = selection.start;
 6926            let mut end = selection.end;
 6927            let is_entire_line = selection.is_empty();
 6928            if is_entire_line {
 6929                start = Point::new(start.row, 0);"
 6930                .to_string()
 6931        ),
 6932        "Regular copying for reverse selection works the same",
 6933    );
 6934    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6935    assert_eq!(
 6936        cx.read_from_clipboard()
 6937            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6938        Some(
 6939            "for selection in selections.iter() {
 6940let mut start = selection.start;
 6941let mut end = selection.end;
 6942let is_entire_line = selection.is_empty();
 6943if is_entire_line {
 6944    start = Point::new(start.row, 0);"
 6945                .to_string()
 6946        ),
 6947        "Copying with stripping for reverse selection works the same"
 6948    );
 6949
 6950    cx.set_state(
 6951        r#"            for selection «in selections.iter() {
 6952            let mut start = selection.start;
 6953            let mut end = selection.end;
 6954            let is_entire_line = selection.is_empty();
 6955            if is_entire_line {
 6956                start = Point::new(start.row, 0);ˇ»
 6957                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6958            }
 6959        "#,
 6960    );
 6961    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6962    assert_eq!(
 6963        cx.read_from_clipboard()
 6964            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6965        Some(
 6966            "in selections.iter() {
 6967            let mut start = selection.start;
 6968            let mut end = selection.end;
 6969            let is_entire_line = selection.is_empty();
 6970            if is_entire_line {
 6971                start = Point::new(start.row, 0);"
 6972                .to_string()
 6973        ),
 6974        "When selecting past the indent, the copying works as usual",
 6975    );
 6976    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6977    assert_eq!(
 6978        cx.read_from_clipboard()
 6979            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6980        Some(
 6981            "in selections.iter() {
 6982            let mut start = selection.start;
 6983            let mut end = selection.end;
 6984            let is_entire_line = selection.is_empty();
 6985            if is_entire_line {
 6986                start = Point::new(start.row, 0);"
 6987                .to_string()
 6988        ),
 6989        "When selecting past the indent, nothing is trimmed"
 6990    );
 6991
 6992    cx.set_state(
 6993        r#"            «for selection in selections.iter() {
 6994            let mut start = selection.start;
 6995
 6996            let mut end = selection.end;
 6997            let is_entire_line = selection.is_empty();
 6998            if is_entire_line {
 6999                start = Point::new(start.row, 0);
 7000ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7001            }
 7002        "#,
 7003    );
 7004    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7005    assert_eq!(
 7006        cx.read_from_clipboard()
 7007            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7008        Some(
 7009            "for selection in selections.iter() {
 7010let mut start = selection.start;
 7011
 7012let mut end = selection.end;
 7013let is_entire_line = selection.is_empty();
 7014if is_entire_line {
 7015    start = Point::new(start.row, 0);
 7016"
 7017            .to_string()
 7018        ),
 7019        "Copying with stripping should ignore empty lines"
 7020    );
 7021}
 7022
 7023#[gpui::test]
 7024async fn test_paste_multiline(cx: &mut TestAppContext) {
 7025    init_test(cx, |_| {});
 7026
 7027    let mut cx = EditorTestContext::new(cx).await;
 7028    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7029
 7030    // Cut an indented block, without the leading whitespace.
 7031    cx.set_state(indoc! {"
 7032        const a: B = (
 7033            c(),
 7034            «d(
 7035                e,
 7036                f
 7037            )ˇ»
 7038        );
 7039    "});
 7040    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7041    cx.assert_editor_state(indoc! {"
 7042        const a: B = (
 7043            c(),
 7044            ˇ
 7045        );
 7046    "});
 7047
 7048    // Paste it at the same position.
 7049    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7050    cx.assert_editor_state(indoc! {"
 7051        const a: B = (
 7052            c(),
 7053            d(
 7054                e,
 7055                f
 7056 7057        );
 7058    "});
 7059
 7060    // Paste it at a line with a lower indent level.
 7061    cx.set_state(indoc! {"
 7062        ˇ
 7063        const a: B = (
 7064            c(),
 7065        );
 7066    "});
 7067    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7068    cx.assert_editor_state(indoc! {"
 7069        d(
 7070            e,
 7071            f
 7072 7073        const a: B = (
 7074            c(),
 7075        );
 7076    "});
 7077
 7078    // Cut an indented block, with the leading whitespace.
 7079    cx.set_state(indoc! {"
 7080        const a: B = (
 7081            c(),
 7082        «    d(
 7083                e,
 7084                f
 7085            )
 7086        ˇ»);
 7087    "});
 7088    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7089    cx.assert_editor_state(indoc! {"
 7090        const a: B = (
 7091            c(),
 7092        ˇ);
 7093    "});
 7094
 7095    // Paste it at the same position.
 7096    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7097    cx.assert_editor_state(indoc! {"
 7098        const a: B = (
 7099            c(),
 7100            d(
 7101                e,
 7102                f
 7103            )
 7104        ˇ);
 7105    "});
 7106
 7107    // Paste it at a line with a higher indent level.
 7108    cx.set_state(indoc! {"
 7109        const a: B = (
 7110            c(),
 7111            d(
 7112                e,
 7113 7114            )
 7115        );
 7116    "});
 7117    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7118    cx.assert_editor_state(indoc! {"
 7119        const a: B = (
 7120            c(),
 7121            d(
 7122                e,
 7123                f    d(
 7124                    e,
 7125                    f
 7126                )
 7127        ˇ
 7128            )
 7129        );
 7130    "});
 7131
 7132    // Copy an indented block, starting mid-line
 7133    cx.set_state(indoc! {"
 7134        const a: B = (
 7135            c(),
 7136            somethin«g(
 7137                e,
 7138                f
 7139            )ˇ»
 7140        );
 7141    "});
 7142    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7143
 7144    // Paste it on a line with a lower indent level
 7145    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7146    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7147    cx.assert_editor_state(indoc! {"
 7148        const a: B = (
 7149            c(),
 7150            something(
 7151                e,
 7152                f
 7153            )
 7154        );
 7155        g(
 7156            e,
 7157            f
 7158"});
 7159}
 7160
 7161#[gpui::test]
 7162async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7163    init_test(cx, |_| {});
 7164
 7165    cx.write_to_clipboard(ClipboardItem::new_string(
 7166        "    d(\n        e\n    );\n".into(),
 7167    ));
 7168
 7169    let mut cx = EditorTestContext::new(cx).await;
 7170    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7171
 7172    cx.set_state(indoc! {"
 7173        fn a() {
 7174            b();
 7175            if c() {
 7176                ˇ
 7177            }
 7178        }
 7179    "});
 7180
 7181    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7182    cx.assert_editor_state(indoc! {"
 7183        fn a() {
 7184            b();
 7185            if c() {
 7186                d(
 7187                    e
 7188                );
 7189        ˇ
 7190            }
 7191        }
 7192    "});
 7193
 7194    cx.set_state(indoc! {"
 7195        fn a() {
 7196            b();
 7197            ˇ
 7198        }
 7199    "});
 7200
 7201    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7202    cx.assert_editor_state(indoc! {"
 7203        fn a() {
 7204            b();
 7205            d(
 7206                e
 7207            );
 7208        ˇ
 7209        }
 7210    "});
 7211}
 7212
 7213#[gpui::test]
 7214fn test_select_all(cx: &mut TestAppContext) {
 7215    init_test(cx, |_| {});
 7216
 7217    let editor = cx.add_window(|window, cx| {
 7218        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7219        build_editor(buffer, window, cx)
 7220    });
 7221    _ = editor.update(cx, |editor, window, cx| {
 7222        editor.select_all(&SelectAll, window, cx);
 7223        assert_eq!(
 7224            editor.selections.display_ranges(cx),
 7225            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7226        );
 7227    });
 7228}
 7229
 7230#[gpui::test]
 7231fn test_select_line(cx: &mut TestAppContext) {
 7232    init_test(cx, |_| {});
 7233
 7234    let editor = cx.add_window(|window, cx| {
 7235        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7236        build_editor(buffer, window, cx)
 7237    });
 7238    _ = editor.update(cx, |editor, window, cx| {
 7239        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7240            s.select_display_ranges([
 7241                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7242                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7243                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7244                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7245            ])
 7246        });
 7247        editor.select_line(&SelectLine, window, cx);
 7248        assert_eq!(
 7249            editor.selections.display_ranges(cx),
 7250            vec![
 7251                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7252                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7253            ]
 7254        );
 7255    });
 7256
 7257    _ = editor.update(cx, |editor, window, cx| {
 7258        editor.select_line(&SelectLine, window, cx);
 7259        assert_eq!(
 7260            editor.selections.display_ranges(cx),
 7261            vec![
 7262                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7263                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7264            ]
 7265        );
 7266    });
 7267
 7268    _ = editor.update(cx, |editor, window, cx| {
 7269        editor.select_line(&SelectLine, window, cx);
 7270        assert_eq!(
 7271            editor.selections.display_ranges(cx),
 7272            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7273        );
 7274    });
 7275}
 7276
 7277#[gpui::test]
 7278async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7279    init_test(cx, |_| {});
 7280    let mut cx = EditorTestContext::new(cx).await;
 7281
 7282    #[track_caller]
 7283    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7284        cx.set_state(initial_state);
 7285        cx.update_editor(|e, window, cx| {
 7286            e.split_selection_into_lines(&Default::default(), window, cx)
 7287        });
 7288        cx.assert_editor_state(expected_state);
 7289    }
 7290
 7291    // Selection starts and ends at the middle of lines, left-to-right
 7292    test(
 7293        &mut cx,
 7294        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7295        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7296    );
 7297    // Same thing, right-to-left
 7298    test(
 7299        &mut cx,
 7300        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7301        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7302    );
 7303
 7304    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7305    test(
 7306        &mut cx,
 7307        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7308        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7309    );
 7310    // Same thing, right-to-left
 7311    test(
 7312        &mut cx,
 7313        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7314        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7315    );
 7316
 7317    // Whole buffer, left-to-right, last line ends with newline
 7318    test(
 7319        &mut cx,
 7320        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7321        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7322    );
 7323    // Same thing, right-to-left
 7324    test(
 7325        &mut cx,
 7326        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7327        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7328    );
 7329
 7330    // Starts at the end of a line, ends at the start of another
 7331    test(
 7332        &mut cx,
 7333        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7334        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7335    );
 7336}
 7337
 7338#[gpui::test]
 7339async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7340    init_test(cx, |_| {});
 7341
 7342    let editor = cx.add_window(|window, cx| {
 7343        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7344        build_editor(buffer, window, cx)
 7345    });
 7346
 7347    // setup
 7348    _ = editor.update(cx, |editor, window, cx| {
 7349        editor.fold_creases(
 7350            vec![
 7351                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7352                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7353                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7354            ],
 7355            true,
 7356            window,
 7357            cx,
 7358        );
 7359        assert_eq!(
 7360            editor.display_text(cx),
 7361            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7362        );
 7363    });
 7364
 7365    _ = editor.update(cx, |editor, window, cx| {
 7366        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7367            s.select_display_ranges([
 7368                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7369                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7370                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7371                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7372            ])
 7373        });
 7374        editor.split_selection_into_lines(&Default::default(), window, cx);
 7375        assert_eq!(
 7376            editor.display_text(cx),
 7377            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7378        );
 7379    });
 7380    EditorTestContext::for_editor(editor, cx)
 7381        .await
 7382        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7383
 7384    _ = editor.update(cx, |editor, window, cx| {
 7385        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7386            s.select_display_ranges([
 7387                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7388            ])
 7389        });
 7390        editor.split_selection_into_lines(&Default::default(), window, cx);
 7391        assert_eq!(
 7392            editor.display_text(cx),
 7393            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7394        );
 7395        assert_eq!(
 7396            editor.selections.display_ranges(cx),
 7397            [
 7398                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7399                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7400                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7401                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7402                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7403                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7404                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7405            ]
 7406        );
 7407    });
 7408    EditorTestContext::for_editor(editor, cx)
 7409        .await
 7410        .assert_editor_state(
 7411            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7412        );
 7413}
 7414
 7415#[gpui::test]
 7416async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7417    init_test(cx, |_| {});
 7418
 7419    let mut cx = EditorTestContext::new(cx).await;
 7420
 7421    cx.set_state(indoc!(
 7422        r#"abc
 7423           defˇghi
 7424
 7425           jk
 7426           nlmo
 7427           "#
 7428    ));
 7429
 7430    cx.update_editor(|editor, window, cx| {
 7431        editor.add_selection_above(&Default::default(), window, cx);
 7432    });
 7433
 7434    cx.assert_editor_state(indoc!(
 7435        r#"abcˇ
 7436           defˇghi
 7437
 7438           jk
 7439           nlmo
 7440           "#
 7441    ));
 7442
 7443    cx.update_editor(|editor, window, cx| {
 7444        editor.add_selection_above(&Default::default(), window, cx);
 7445    });
 7446
 7447    cx.assert_editor_state(indoc!(
 7448        r#"abcˇ
 7449            defˇghi
 7450
 7451            jk
 7452            nlmo
 7453            "#
 7454    ));
 7455
 7456    cx.update_editor(|editor, window, cx| {
 7457        editor.add_selection_below(&Default::default(), window, cx);
 7458    });
 7459
 7460    cx.assert_editor_state(indoc!(
 7461        r#"abc
 7462           defˇghi
 7463
 7464           jk
 7465           nlmo
 7466           "#
 7467    ));
 7468
 7469    cx.update_editor(|editor, window, cx| {
 7470        editor.undo_selection(&Default::default(), window, cx);
 7471    });
 7472
 7473    cx.assert_editor_state(indoc!(
 7474        r#"abcˇ
 7475           defˇghi
 7476
 7477           jk
 7478           nlmo
 7479           "#
 7480    ));
 7481
 7482    cx.update_editor(|editor, window, cx| {
 7483        editor.redo_selection(&Default::default(), window, cx);
 7484    });
 7485
 7486    cx.assert_editor_state(indoc!(
 7487        r#"abc
 7488           defˇghi
 7489
 7490           jk
 7491           nlmo
 7492           "#
 7493    ));
 7494
 7495    cx.update_editor(|editor, window, cx| {
 7496        editor.add_selection_below(&Default::default(), window, cx);
 7497    });
 7498
 7499    cx.assert_editor_state(indoc!(
 7500        r#"abc
 7501           defˇghi
 7502           ˇ
 7503           jk
 7504           nlmo
 7505           "#
 7506    ));
 7507
 7508    cx.update_editor(|editor, window, cx| {
 7509        editor.add_selection_below(&Default::default(), window, cx);
 7510    });
 7511
 7512    cx.assert_editor_state(indoc!(
 7513        r#"abc
 7514           defˇghi
 7515           ˇ
 7516           jkˇ
 7517           nlmo
 7518           "#
 7519    ));
 7520
 7521    cx.update_editor(|editor, window, cx| {
 7522        editor.add_selection_below(&Default::default(), window, cx);
 7523    });
 7524
 7525    cx.assert_editor_state(indoc!(
 7526        r#"abc
 7527           defˇghi
 7528           ˇ
 7529           jkˇ
 7530           nlmˇo
 7531           "#
 7532    ));
 7533
 7534    cx.update_editor(|editor, window, cx| {
 7535        editor.add_selection_below(&Default::default(), window, cx);
 7536    });
 7537
 7538    cx.assert_editor_state(indoc!(
 7539        r#"abc
 7540           defˇghi
 7541           ˇ
 7542           jkˇ
 7543           nlmˇo
 7544           ˇ"#
 7545    ));
 7546
 7547    // change selections
 7548    cx.set_state(indoc!(
 7549        r#"abc
 7550           def«ˇg»hi
 7551
 7552           jk
 7553           nlmo
 7554           "#
 7555    ));
 7556
 7557    cx.update_editor(|editor, window, cx| {
 7558        editor.add_selection_below(&Default::default(), window, cx);
 7559    });
 7560
 7561    cx.assert_editor_state(indoc!(
 7562        r#"abc
 7563           def«ˇg»hi
 7564
 7565           jk
 7566           nlm«ˇo»
 7567           "#
 7568    ));
 7569
 7570    cx.update_editor(|editor, window, cx| {
 7571        editor.add_selection_below(&Default::default(), window, cx);
 7572    });
 7573
 7574    cx.assert_editor_state(indoc!(
 7575        r#"abc
 7576           def«ˇg»hi
 7577
 7578           jk
 7579           nlm«ˇo»
 7580           "#
 7581    ));
 7582
 7583    cx.update_editor(|editor, window, cx| {
 7584        editor.add_selection_above(&Default::default(), window, cx);
 7585    });
 7586
 7587    cx.assert_editor_state(indoc!(
 7588        r#"abc
 7589           def«ˇg»hi
 7590
 7591           jk
 7592           nlmo
 7593           "#
 7594    ));
 7595
 7596    cx.update_editor(|editor, window, cx| {
 7597        editor.add_selection_above(&Default::default(), window, cx);
 7598    });
 7599
 7600    cx.assert_editor_state(indoc!(
 7601        r#"abc
 7602           def«ˇg»hi
 7603
 7604           jk
 7605           nlmo
 7606           "#
 7607    ));
 7608
 7609    // Change selections again
 7610    cx.set_state(indoc!(
 7611        r#"a«bc
 7612           defgˇ»hi
 7613
 7614           jk
 7615           nlmo
 7616           "#
 7617    ));
 7618
 7619    cx.update_editor(|editor, window, cx| {
 7620        editor.add_selection_below(&Default::default(), window, cx);
 7621    });
 7622
 7623    cx.assert_editor_state(indoc!(
 7624        r#"a«bcˇ»
 7625           d«efgˇ»hi
 7626
 7627           j«kˇ»
 7628           nlmo
 7629           "#
 7630    ));
 7631
 7632    cx.update_editor(|editor, window, cx| {
 7633        editor.add_selection_below(&Default::default(), window, cx);
 7634    });
 7635    cx.assert_editor_state(indoc!(
 7636        r#"a«bcˇ»
 7637           d«efgˇ»hi
 7638
 7639           j«kˇ»
 7640           n«lmoˇ»
 7641           "#
 7642    ));
 7643    cx.update_editor(|editor, window, cx| {
 7644        editor.add_selection_above(&Default::default(), window, cx);
 7645    });
 7646
 7647    cx.assert_editor_state(indoc!(
 7648        r#"a«bcˇ»
 7649           d«efgˇ»hi
 7650
 7651           j«kˇ»
 7652           nlmo
 7653           "#
 7654    ));
 7655
 7656    // Change selections again
 7657    cx.set_state(indoc!(
 7658        r#"abc
 7659           d«ˇefghi
 7660
 7661           jk
 7662           nlm»o
 7663           "#
 7664    ));
 7665
 7666    cx.update_editor(|editor, window, cx| {
 7667        editor.add_selection_above(&Default::default(), window, cx);
 7668    });
 7669
 7670    cx.assert_editor_state(indoc!(
 7671        r#"a«ˇbc»
 7672           d«ˇef»ghi
 7673
 7674           j«ˇk»
 7675           n«ˇlm»o
 7676           "#
 7677    ));
 7678
 7679    cx.update_editor(|editor, window, cx| {
 7680        editor.add_selection_below(&Default::default(), window, cx);
 7681    });
 7682
 7683    cx.assert_editor_state(indoc!(
 7684        r#"abc
 7685           d«ˇef»ghi
 7686
 7687           j«ˇk»
 7688           n«ˇlm»o
 7689           "#
 7690    ));
 7691}
 7692
 7693#[gpui::test]
 7694async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7695    init_test(cx, |_| {});
 7696    let mut cx = EditorTestContext::new(cx).await;
 7697
 7698    cx.set_state(indoc!(
 7699        r#"line onˇe
 7700           liˇne two
 7701           line three
 7702           line four"#
 7703    ));
 7704
 7705    cx.update_editor(|editor, window, cx| {
 7706        editor.add_selection_below(&Default::default(), window, cx);
 7707    });
 7708
 7709    // test multiple cursors expand in the same direction
 7710    cx.assert_editor_state(indoc!(
 7711        r#"line onˇe
 7712           liˇne twˇo
 7713           liˇne three
 7714           line four"#
 7715    ));
 7716
 7717    cx.update_editor(|editor, window, cx| {
 7718        editor.add_selection_below(&Default::default(), window, cx);
 7719    });
 7720
 7721    cx.update_editor(|editor, window, cx| {
 7722        editor.add_selection_below(&Default::default(), window, cx);
 7723    });
 7724
 7725    // test multiple cursors expand below overflow
 7726    cx.assert_editor_state(indoc!(
 7727        r#"line onˇe
 7728           liˇne twˇo
 7729           liˇne thˇree
 7730           liˇne foˇur"#
 7731    ));
 7732
 7733    cx.update_editor(|editor, window, cx| {
 7734        editor.add_selection_above(&Default::default(), window, cx);
 7735    });
 7736
 7737    // test multiple cursors retrieves back correctly
 7738    cx.assert_editor_state(indoc!(
 7739        r#"line onˇe
 7740           liˇne twˇo
 7741           liˇne thˇree
 7742           line four"#
 7743    ));
 7744
 7745    cx.update_editor(|editor, window, cx| {
 7746        editor.add_selection_above(&Default::default(), window, cx);
 7747    });
 7748
 7749    cx.update_editor(|editor, window, cx| {
 7750        editor.add_selection_above(&Default::default(), window, cx);
 7751    });
 7752
 7753    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7754    cx.assert_editor_state(indoc!(
 7755        r#"liˇne onˇe
 7756           liˇne two
 7757           line three
 7758           line four"#
 7759    ));
 7760
 7761    cx.update_editor(|editor, window, cx| {
 7762        editor.undo_selection(&Default::default(), window, cx);
 7763    });
 7764
 7765    // test undo
 7766    cx.assert_editor_state(indoc!(
 7767        r#"line onˇe
 7768           liˇne twˇo
 7769           line three
 7770           line four"#
 7771    ));
 7772
 7773    cx.update_editor(|editor, window, cx| {
 7774        editor.redo_selection(&Default::default(), window, cx);
 7775    });
 7776
 7777    // test redo
 7778    cx.assert_editor_state(indoc!(
 7779        r#"liˇne onˇe
 7780           liˇne two
 7781           line three
 7782           line four"#
 7783    ));
 7784
 7785    cx.set_state(indoc!(
 7786        r#"abcd
 7787           ef«ghˇ»
 7788           ijkl
 7789           «mˇ»nop"#
 7790    ));
 7791
 7792    cx.update_editor(|editor, window, cx| {
 7793        editor.add_selection_above(&Default::default(), window, cx);
 7794    });
 7795
 7796    // test multiple selections expand in the same direction
 7797    cx.assert_editor_state(indoc!(
 7798        r#"ab«cdˇ»
 7799           ef«ghˇ»
 7800           «iˇ»jkl
 7801           «mˇ»nop"#
 7802    ));
 7803
 7804    cx.update_editor(|editor, window, cx| {
 7805        editor.add_selection_above(&Default::default(), window, cx);
 7806    });
 7807
 7808    // test multiple selection upward overflow
 7809    cx.assert_editor_state(indoc!(
 7810        r#"ab«cdˇ»
 7811           «eˇ»f«ghˇ»
 7812           «iˇ»jkl
 7813           «mˇ»nop"#
 7814    ));
 7815
 7816    cx.update_editor(|editor, window, cx| {
 7817        editor.add_selection_below(&Default::default(), window, cx);
 7818    });
 7819
 7820    // test multiple selection retrieves back correctly
 7821    cx.assert_editor_state(indoc!(
 7822        r#"abcd
 7823           ef«ghˇ»
 7824           «iˇ»jkl
 7825           «mˇ»nop"#
 7826    ));
 7827
 7828    cx.update_editor(|editor, window, cx| {
 7829        editor.add_selection_below(&Default::default(), window, cx);
 7830    });
 7831
 7832    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7833    cx.assert_editor_state(indoc!(
 7834        r#"abcd
 7835           ef«ghˇ»
 7836           ij«klˇ»
 7837           «mˇ»nop"#
 7838    ));
 7839
 7840    cx.update_editor(|editor, window, cx| {
 7841        editor.undo_selection(&Default::default(), window, cx);
 7842    });
 7843
 7844    // test undo
 7845    cx.assert_editor_state(indoc!(
 7846        r#"abcd
 7847           ef«ghˇ»
 7848           «iˇ»jkl
 7849           «mˇ»nop"#
 7850    ));
 7851
 7852    cx.update_editor(|editor, window, cx| {
 7853        editor.redo_selection(&Default::default(), window, cx);
 7854    });
 7855
 7856    // test redo
 7857    cx.assert_editor_state(indoc!(
 7858        r#"abcd
 7859           ef«ghˇ»
 7860           ij«klˇ»
 7861           «mˇ»nop"#
 7862    ));
 7863}
 7864
 7865#[gpui::test]
 7866async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7867    init_test(cx, |_| {});
 7868    let mut cx = EditorTestContext::new(cx).await;
 7869
 7870    cx.set_state(indoc!(
 7871        r#"line onˇe
 7872           liˇne two
 7873           line three
 7874           line four"#
 7875    ));
 7876
 7877    cx.update_editor(|editor, window, cx| {
 7878        editor.add_selection_below(&Default::default(), window, cx);
 7879        editor.add_selection_below(&Default::default(), window, cx);
 7880        editor.add_selection_below(&Default::default(), window, cx);
 7881    });
 7882
 7883    // initial state with two multi cursor groups
 7884    cx.assert_editor_state(indoc!(
 7885        r#"line onˇe
 7886           liˇne twˇo
 7887           liˇne thˇree
 7888           liˇne foˇur"#
 7889    ));
 7890
 7891    // add single cursor in middle - simulate opt click
 7892    cx.update_editor(|editor, window, cx| {
 7893        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7894        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7895        editor.end_selection(window, cx);
 7896    });
 7897
 7898    cx.assert_editor_state(indoc!(
 7899        r#"line onˇe
 7900           liˇne twˇo
 7901           liˇneˇ thˇree
 7902           liˇne foˇur"#
 7903    ));
 7904
 7905    cx.update_editor(|editor, window, cx| {
 7906        editor.add_selection_above(&Default::default(), window, cx);
 7907    });
 7908
 7909    // test new added selection expands above and existing selection shrinks
 7910    cx.assert_editor_state(indoc!(
 7911        r#"line onˇe
 7912           liˇneˇ twˇo
 7913           liˇneˇ thˇree
 7914           line four"#
 7915    ));
 7916
 7917    cx.update_editor(|editor, window, cx| {
 7918        editor.add_selection_above(&Default::default(), window, cx);
 7919    });
 7920
 7921    // test new added selection expands above and existing selection shrinks
 7922    cx.assert_editor_state(indoc!(
 7923        r#"lineˇ onˇe
 7924           liˇneˇ twˇo
 7925           lineˇ three
 7926           line four"#
 7927    ));
 7928
 7929    // intial state with two selection groups
 7930    cx.set_state(indoc!(
 7931        r#"abcd
 7932           ef«ghˇ»
 7933           ijkl
 7934           «mˇ»nop"#
 7935    ));
 7936
 7937    cx.update_editor(|editor, window, cx| {
 7938        editor.add_selection_above(&Default::default(), window, cx);
 7939        editor.add_selection_above(&Default::default(), window, cx);
 7940    });
 7941
 7942    cx.assert_editor_state(indoc!(
 7943        r#"ab«cdˇ»
 7944           «eˇ»f«ghˇ»
 7945           «iˇ»jkl
 7946           «mˇ»nop"#
 7947    ));
 7948
 7949    // add single selection in middle - simulate opt drag
 7950    cx.update_editor(|editor, window, cx| {
 7951        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7952        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7953        editor.update_selection(
 7954            DisplayPoint::new(DisplayRow(2), 4),
 7955            0,
 7956            gpui::Point::<f32>::default(),
 7957            window,
 7958            cx,
 7959        );
 7960        editor.end_selection(window, cx);
 7961    });
 7962
 7963    cx.assert_editor_state(indoc!(
 7964        r#"ab«cdˇ»
 7965           «eˇ»f«ghˇ»
 7966           «iˇ»jk«lˇ»
 7967           «mˇ»nop"#
 7968    ));
 7969
 7970    cx.update_editor(|editor, window, cx| {
 7971        editor.add_selection_below(&Default::default(), window, cx);
 7972    });
 7973
 7974    // test new added selection expands below, others shrinks from above
 7975    cx.assert_editor_state(indoc!(
 7976        r#"abcd
 7977           ef«ghˇ»
 7978           «iˇ»jk«lˇ»
 7979           «mˇ»no«pˇ»"#
 7980    ));
 7981}
 7982
 7983#[gpui::test]
 7984async fn test_select_next(cx: &mut TestAppContext) {
 7985    init_test(cx, |_| {});
 7986
 7987    let mut cx = EditorTestContext::new(cx).await;
 7988    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7989
 7990    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7991        .unwrap();
 7992    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7993
 7994    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7995        .unwrap();
 7996    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7997
 7998    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7999    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8000
 8001    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8002    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8003
 8004    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8005        .unwrap();
 8006    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8007
 8008    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8009        .unwrap();
 8010    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8011
 8012    // Test selection direction should be preserved
 8013    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8014
 8015    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8016        .unwrap();
 8017    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8018}
 8019
 8020#[gpui::test]
 8021async fn test_select_all_matches(cx: &mut TestAppContext) {
 8022    init_test(cx, |_| {});
 8023
 8024    let mut cx = EditorTestContext::new(cx).await;
 8025
 8026    // Test caret-only selections
 8027    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8028    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8029        .unwrap();
 8030    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8031
 8032    // Test left-to-right selections
 8033    cx.set_state("abc\n«abcˇ»\nabc");
 8034    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8035        .unwrap();
 8036    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8037
 8038    // Test right-to-left selections
 8039    cx.set_state("abc\n«ˇabc»\nabc");
 8040    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8041        .unwrap();
 8042    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8043
 8044    // Test selecting whitespace with caret selection
 8045    cx.set_state("abc\nˇ   abc\nabc");
 8046    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8047        .unwrap();
 8048    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8049
 8050    // Test selecting whitespace with left-to-right selection
 8051    cx.set_state("abc\n«ˇ  »abc\nabc");
 8052    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8053        .unwrap();
 8054    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8055
 8056    // Test no matches with right-to-left selection
 8057    cx.set_state("abc\n«  ˇ»abc\nabc");
 8058    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8059        .unwrap();
 8060    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8061
 8062    // Test with a single word and clip_at_line_ends=true (#29823)
 8063    cx.set_state("aˇbc");
 8064    cx.update_editor(|e, window, cx| {
 8065        e.set_clip_at_line_ends(true, cx);
 8066        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8067        e.set_clip_at_line_ends(false, cx);
 8068    });
 8069    cx.assert_editor_state("«abcˇ»");
 8070}
 8071
 8072#[gpui::test]
 8073async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8074    init_test(cx, |_| {});
 8075
 8076    let mut cx = EditorTestContext::new(cx).await;
 8077
 8078    let large_body_1 = "\nd".repeat(200);
 8079    let large_body_2 = "\ne".repeat(200);
 8080
 8081    cx.set_state(&format!(
 8082        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8083    ));
 8084    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8085        let scroll_position = editor.scroll_position(cx);
 8086        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8087        scroll_position
 8088    });
 8089
 8090    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8091        .unwrap();
 8092    cx.assert_editor_state(&format!(
 8093        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8094    ));
 8095    let scroll_position_after_selection =
 8096        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8097    assert_eq!(
 8098        initial_scroll_position, scroll_position_after_selection,
 8099        "Scroll position should not change after selecting all matches"
 8100    );
 8101}
 8102
 8103#[gpui::test]
 8104async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8105    init_test(cx, |_| {});
 8106
 8107    let mut cx = EditorLspTestContext::new_rust(
 8108        lsp::ServerCapabilities {
 8109            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8110            ..Default::default()
 8111        },
 8112        cx,
 8113    )
 8114    .await;
 8115
 8116    cx.set_state(indoc! {"
 8117        line 1
 8118        line 2
 8119        linˇe 3
 8120        line 4
 8121        line 5
 8122    "});
 8123
 8124    // Make an edit
 8125    cx.update_editor(|editor, window, cx| {
 8126        editor.handle_input("X", window, cx);
 8127    });
 8128
 8129    // Move cursor to a different position
 8130    cx.update_editor(|editor, window, cx| {
 8131        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8132            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8133        });
 8134    });
 8135
 8136    cx.assert_editor_state(indoc! {"
 8137        line 1
 8138        line 2
 8139        linXe 3
 8140        line 4
 8141        liˇne 5
 8142    "});
 8143
 8144    cx.lsp
 8145        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8146            Ok(Some(vec![lsp::TextEdit::new(
 8147                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8148                "PREFIX ".to_string(),
 8149            )]))
 8150        });
 8151
 8152    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8153        .unwrap()
 8154        .await
 8155        .unwrap();
 8156
 8157    cx.assert_editor_state(indoc! {"
 8158        PREFIX line 1
 8159        line 2
 8160        linXe 3
 8161        line 4
 8162        liˇne 5
 8163    "});
 8164
 8165    // Undo formatting
 8166    cx.update_editor(|editor, window, cx| {
 8167        editor.undo(&Default::default(), window, cx);
 8168    });
 8169
 8170    // Verify cursor moved back to position after edit
 8171    cx.assert_editor_state(indoc! {"
 8172        line 1
 8173        line 2
 8174        linXˇe 3
 8175        line 4
 8176        line 5
 8177    "});
 8178}
 8179
 8180#[gpui::test]
 8181async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8182    init_test(cx, |_| {});
 8183
 8184    let mut cx = EditorTestContext::new(cx).await;
 8185
 8186    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8187    cx.update_editor(|editor, window, cx| {
 8188        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8189    });
 8190
 8191    cx.set_state(indoc! {"
 8192        line 1
 8193        line 2
 8194        linˇe 3
 8195        line 4
 8196        line 5
 8197        line 6
 8198        line 7
 8199        line 8
 8200        line 9
 8201        line 10
 8202    "});
 8203
 8204    let snapshot = cx.buffer_snapshot();
 8205    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8206
 8207    cx.update(|_, cx| {
 8208        provider.update(cx, |provider, _| {
 8209            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 8210                id: None,
 8211                edits: vec![(edit_position..edit_position, "X".into())],
 8212                edit_preview: None,
 8213            }))
 8214        })
 8215    });
 8216
 8217    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8218    cx.update_editor(|editor, window, cx| {
 8219        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8220    });
 8221
 8222    cx.assert_editor_state(indoc! {"
 8223        line 1
 8224        line 2
 8225        lineXˇ 3
 8226        line 4
 8227        line 5
 8228        line 6
 8229        line 7
 8230        line 8
 8231        line 9
 8232        line 10
 8233    "});
 8234
 8235    cx.update_editor(|editor, window, cx| {
 8236        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8237            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8238        });
 8239    });
 8240
 8241    cx.assert_editor_state(indoc! {"
 8242        line 1
 8243        line 2
 8244        lineX 3
 8245        line 4
 8246        line 5
 8247        line 6
 8248        line 7
 8249        line 8
 8250        line 9
 8251        liˇne 10
 8252    "});
 8253
 8254    cx.update_editor(|editor, window, cx| {
 8255        editor.undo(&Default::default(), window, cx);
 8256    });
 8257
 8258    cx.assert_editor_state(indoc! {"
 8259        line 1
 8260        line 2
 8261        lineˇ 3
 8262        line 4
 8263        line 5
 8264        line 6
 8265        line 7
 8266        line 8
 8267        line 9
 8268        line 10
 8269    "});
 8270}
 8271
 8272#[gpui::test]
 8273async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8274    init_test(cx, |_| {});
 8275
 8276    let mut cx = EditorTestContext::new(cx).await;
 8277    cx.set_state(
 8278        r#"let foo = 2;
 8279lˇet foo = 2;
 8280let fooˇ = 2;
 8281let foo = 2;
 8282let foo = ˇ2;"#,
 8283    );
 8284
 8285    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8286        .unwrap();
 8287    cx.assert_editor_state(
 8288        r#"let foo = 2;
 8289«letˇ» foo = 2;
 8290let «fooˇ» = 2;
 8291let foo = 2;
 8292let foo = «2ˇ»;"#,
 8293    );
 8294
 8295    // noop for multiple selections with different contents
 8296    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8297        .unwrap();
 8298    cx.assert_editor_state(
 8299        r#"let foo = 2;
 8300«letˇ» foo = 2;
 8301let «fooˇ» = 2;
 8302let foo = 2;
 8303let foo = «2ˇ»;"#,
 8304    );
 8305
 8306    // Test last selection direction should be preserved
 8307    cx.set_state(
 8308        r#"let foo = 2;
 8309let foo = 2;
 8310let «fooˇ» = 2;
 8311let «ˇfoo» = 2;
 8312let foo = 2;"#,
 8313    );
 8314
 8315    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8316        .unwrap();
 8317    cx.assert_editor_state(
 8318        r#"let foo = 2;
 8319let foo = 2;
 8320let «fooˇ» = 2;
 8321let «ˇfoo» = 2;
 8322let «ˇfoo» = 2;"#,
 8323    );
 8324}
 8325
 8326#[gpui::test]
 8327async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8328    init_test(cx, |_| {});
 8329
 8330    let mut cx =
 8331        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8332
 8333    cx.assert_editor_state(indoc! {"
 8334        ˇbbb
 8335        ccc
 8336
 8337        bbb
 8338        ccc
 8339        "});
 8340    cx.dispatch_action(SelectPrevious::default());
 8341    cx.assert_editor_state(indoc! {"
 8342                «bbbˇ»
 8343                ccc
 8344
 8345                bbb
 8346                ccc
 8347                "});
 8348    cx.dispatch_action(SelectPrevious::default());
 8349    cx.assert_editor_state(indoc! {"
 8350                «bbbˇ»
 8351                ccc
 8352
 8353                «bbbˇ»
 8354                ccc
 8355                "});
 8356}
 8357
 8358#[gpui::test]
 8359async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8360    init_test(cx, |_| {});
 8361
 8362    let mut cx = EditorTestContext::new(cx).await;
 8363    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8364
 8365    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8366        .unwrap();
 8367    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8368
 8369    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8370        .unwrap();
 8371    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8372
 8373    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8374    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8375
 8376    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8377    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8378
 8379    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8380        .unwrap();
 8381    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8382
 8383    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8384        .unwrap();
 8385    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8386}
 8387
 8388#[gpui::test]
 8389async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8390    init_test(cx, |_| {});
 8391
 8392    let mut cx = EditorTestContext::new(cx).await;
 8393    cx.set_state("");
 8394
 8395    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8396        .unwrap();
 8397    cx.assert_editor_state("«aˇ»");
 8398    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8399        .unwrap();
 8400    cx.assert_editor_state("«aˇ»");
 8401}
 8402
 8403#[gpui::test]
 8404async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8405    init_test(cx, |_| {});
 8406
 8407    let mut cx = EditorTestContext::new(cx).await;
 8408    cx.set_state(
 8409        r#"let foo = 2;
 8410lˇet foo = 2;
 8411let fooˇ = 2;
 8412let foo = 2;
 8413let foo = ˇ2;"#,
 8414    );
 8415
 8416    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8417        .unwrap();
 8418    cx.assert_editor_state(
 8419        r#"let foo = 2;
 8420«letˇ» foo = 2;
 8421let «fooˇ» = 2;
 8422let foo = 2;
 8423let foo = «2ˇ»;"#,
 8424    );
 8425
 8426    // noop for multiple selections with different contents
 8427    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8428        .unwrap();
 8429    cx.assert_editor_state(
 8430        r#"let foo = 2;
 8431«letˇ» foo = 2;
 8432let «fooˇ» = 2;
 8433let foo = 2;
 8434let foo = «2ˇ»;"#,
 8435    );
 8436}
 8437
 8438#[gpui::test]
 8439async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8440    init_test(cx, |_| {});
 8441
 8442    let mut cx = EditorTestContext::new(cx).await;
 8443    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8444
 8445    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8446        .unwrap();
 8447    // selection direction is preserved
 8448    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8449
 8450    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8451        .unwrap();
 8452    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8453
 8454    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8455    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8456
 8457    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8458    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8459
 8460    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8461        .unwrap();
 8462    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8463
 8464    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8465        .unwrap();
 8466    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8467}
 8468
 8469#[gpui::test]
 8470async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8471    init_test(cx, |_| {});
 8472
 8473    let language = Arc::new(Language::new(
 8474        LanguageConfig::default(),
 8475        Some(tree_sitter_rust::LANGUAGE.into()),
 8476    ));
 8477
 8478    let text = r#"
 8479        use mod1::mod2::{mod3, mod4};
 8480
 8481        fn fn_1(param1: bool, param2: &str) {
 8482            let var1 = "text";
 8483        }
 8484    "#
 8485    .unindent();
 8486
 8487    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8488    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8489    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8490
 8491    editor
 8492        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8493        .await;
 8494
 8495    editor.update_in(cx, |editor, window, cx| {
 8496        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8497            s.select_display_ranges([
 8498                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8499                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8500                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8501            ]);
 8502        });
 8503        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8504    });
 8505    editor.update(cx, |editor, cx| {
 8506        assert_text_with_selections(
 8507            editor,
 8508            indoc! {r#"
 8509                use mod1::mod2::{mod3, «mod4ˇ»};
 8510
 8511                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8512                    let var1 = "«ˇtext»";
 8513                }
 8514            "#},
 8515            cx,
 8516        );
 8517    });
 8518
 8519    editor.update_in(cx, |editor, window, cx| {
 8520        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8521    });
 8522    editor.update(cx, |editor, cx| {
 8523        assert_text_with_selections(
 8524            editor,
 8525            indoc! {r#"
 8526                use mod1::mod2::«{mod3, mod4}ˇ»;
 8527
 8528                «ˇfn fn_1(param1: bool, param2: &str) {
 8529                    let var1 = "text";
 8530 8531            "#},
 8532            cx,
 8533        );
 8534    });
 8535
 8536    editor.update_in(cx, |editor, window, cx| {
 8537        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8538    });
 8539    assert_eq!(
 8540        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8541        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8542    );
 8543
 8544    // Trying to expand the selected syntax node one more time has no effect.
 8545    editor.update_in(cx, |editor, window, cx| {
 8546        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8547    });
 8548    assert_eq!(
 8549        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8550        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8551    );
 8552
 8553    editor.update_in(cx, |editor, window, cx| {
 8554        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8555    });
 8556    editor.update(cx, |editor, cx| {
 8557        assert_text_with_selections(
 8558            editor,
 8559            indoc! {r#"
 8560                use mod1::mod2::«{mod3, mod4}ˇ»;
 8561
 8562                «ˇfn fn_1(param1: bool, param2: &str) {
 8563                    let var1 = "text";
 8564 8565            "#},
 8566            cx,
 8567        );
 8568    });
 8569
 8570    editor.update_in(cx, |editor, window, cx| {
 8571        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8572    });
 8573    editor.update(cx, |editor, cx| {
 8574        assert_text_with_selections(
 8575            editor,
 8576            indoc! {r#"
 8577                use mod1::mod2::{mod3, «mod4ˇ»};
 8578
 8579                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8580                    let var1 = "«ˇtext»";
 8581                }
 8582            "#},
 8583            cx,
 8584        );
 8585    });
 8586
 8587    editor.update_in(cx, |editor, window, cx| {
 8588        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8589    });
 8590    editor.update(cx, |editor, cx| {
 8591        assert_text_with_selections(
 8592            editor,
 8593            indoc! {r#"
 8594                use mod1::mod2::{mod3, moˇd4};
 8595
 8596                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8597                    let var1 = "teˇxt";
 8598                }
 8599            "#},
 8600            cx,
 8601        );
 8602    });
 8603
 8604    // Trying to shrink the selected syntax node one more time has no effect.
 8605    editor.update_in(cx, |editor, window, cx| {
 8606        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8607    });
 8608    editor.update_in(cx, |editor, _, cx| {
 8609        assert_text_with_selections(
 8610            editor,
 8611            indoc! {r#"
 8612                use mod1::mod2::{mod3, moˇd4};
 8613
 8614                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8615                    let var1 = "teˇxt";
 8616                }
 8617            "#},
 8618            cx,
 8619        );
 8620    });
 8621
 8622    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8623    // a fold.
 8624    editor.update_in(cx, |editor, window, cx| {
 8625        editor.fold_creases(
 8626            vec![
 8627                Crease::simple(
 8628                    Point::new(0, 21)..Point::new(0, 24),
 8629                    FoldPlaceholder::test(),
 8630                ),
 8631                Crease::simple(
 8632                    Point::new(3, 20)..Point::new(3, 22),
 8633                    FoldPlaceholder::test(),
 8634                ),
 8635            ],
 8636            true,
 8637            window,
 8638            cx,
 8639        );
 8640        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8641    });
 8642    editor.update(cx, |editor, cx| {
 8643        assert_text_with_selections(
 8644            editor,
 8645            indoc! {r#"
 8646                use mod1::mod2::«{mod3, mod4}ˇ»;
 8647
 8648                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8649                    let var1 = "«ˇtext»";
 8650                }
 8651            "#},
 8652            cx,
 8653        );
 8654    });
 8655}
 8656
 8657#[gpui::test]
 8658async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8659    init_test(cx, |_| {});
 8660
 8661    let language = Arc::new(Language::new(
 8662        LanguageConfig::default(),
 8663        Some(tree_sitter_rust::LANGUAGE.into()),
 8664    ));
 8665
 8666    let text = "let a = 2;";
 8667
 8668    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8669    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8670    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8671
 8672    editor
 8673        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8674        .await;
 8675
 8676    // Test case 1: Cursor at end of word
 8677    editor.update_in(cx, |editor, window, cx| {
 8678        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8679            s.select_display_ranges([
 8680                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8681            ]);
 8682        });
 8683    });
 8684    editor.update(cx, |editor, cx| {
 8685        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8686    });
 8687    editor.update_in(cx, |editor, window, cx| {
 8688        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8689    });
 8690    editor.update(cx, |editor, cx| {
 8691        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8692    });
 8693    editor.update_in(cx, |editor, window, cx| {
 8694        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8695    });
 8696    editor.update(cx, |editor, cx| {
 8697        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8698    });
 8699
 8700    // Test case 2: Cursor at end of statement
 8701    editor.update_in(cx, |editor, window, cx| {
 8702        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8703            s.select_display_ranges([
 8704                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8705            ]);
 8706        });
 8707    });
 8708    editor.update(cx, |editor, cx| {
 8709        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8710    });
 8711    editor.update_in(cx, |editor, window, cx| {
 8712        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8713    });
 8714    editor.update(cx, |editor, cx| {
 8715        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8716    });
 8717}
 8718
 8719#[gpui::test]
 8720async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8721    init_test(cx, |_| {});
 8722
 8723    let language = Arc::new(Language::new(
 8724        LanguageConfig {
 8725            name: "JavaScript".into(),
 8726            ..Default::default()
 8727        },
 8728        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8729    ));
 8730
 8731    let text = r#"
 8732        let a = {
 8733            key: "value",
 8734        };
 8735    "#
 8736    .unindent();
 8737
 8738    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8739    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8740    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8741
 8742    editor
 8743        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8744        .await;
 8745
 8746    // Test case 1: Cursor after '{'
 8747    editor.update_in(cx, |editor, window, cx| {
 8748        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8749            s.select_display_ranges([
 8750                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8751            ]);
 8752        });
 8753    });
 8754    editor.update(cx, |editor, cx| {
 8755        assert_text_with_selections(
 8756            editor,
 8757            indoc! {r#"
 8758                let a = {ˇ
 8759                    key: "value",
 8760                };
 8761            "#},
 8762            cx,
 8763        );
 8764    });
 8765    editor.update_in(cx, |editor, window, cx| {
 8766        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8767    });
 8768    editor.update(cx, |editor, cx| {
 8769        assert_text_with_selections(
 8770            editor,
 8771            indoc! {r#"
 8772                let a = «ˇ{
 8773                    key: "value",
 8774                }»;
 8775            "#},
 8776            cx,
 8777        );
 8778    });
 8779
 8780    // Test case 2: Cursor after ':'
 8781    editor.update_in(cx, |editor, window, cx| {
 8782        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8783            s.select_display_ranges([
 8784                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 8785            ]);
 8786        });
 8787    });
 8788    editor.update(cx, |editor, cx| {
 8789        assert_text_with_selections(
 8790            editor,
 8791            indoc! {r#"
 8792                let a = {
 8793                    key:ˇ "value",
 8794                };
 8795            "#},
 8796            cx,
 8797        );
 8798    });
 8799    editor.update_in(cx, |editor, window, cx| {
 8800        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8801    });
 8802    editor.update(cx, |editor, cx| {
 8803        assert_text_with_selections(
 8804            editor,
 8805            indoc! {r#"
 8806                let a = {
 8807                    «ˇkey: "value"»,
 8808                };
 8809            "#},
 8810            cx,
 8811        );
 8812    });
 8813    editor.update_in(cx, |editor, window, cx| {
 8814        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8815    });
 8816    editor.update(cx, |editor, cx| {
 8817        assert_text_with_selections(
 8818            editor,
 8819            indoc! {r#"
 8820                let a = «ˇ{
 8821                    key: "value",
 8822                }»;
 8823            "#},
 8824            cx,
 8825        );
 8826    });
 8827
 8828    // Test case 3: Cursor after ','
 8829    editor.update_in(cx, |editor, window, cx| {
 8830        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8831            s.select_display_ranges([
 8832                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 8833            ]);
 8834        });
 8835    });
 8836    editor.update(cx, |editor, cx| {
 8837        assert_text_with_selections(
 8838            editor,
 8839            indoc! {r#"
 8840                let a = {
 8841                    key: "value",ˇ
 8842                };
 8843            "#},
 8844            cx,
 8845        );
 8846    });
 8847    editor.update_in(cx, |editor, window, cx| {
 8848        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8849    });
 8850    editor.update(cx, |editor, cx| {
 8851        assert_text_with_selections(
 8852            editor,
 8853            indoc! {r#"
 8854                let a = «ˇ{
 8855                    key: "value",
 8856                }»;
 8857            "#},
 8858            cx,
 8859        );
 8860    });
 8861
 8862    // Test case 4: Cursor after ';'
 8863    editor.update_in(cx, |editor, window, cx| {
 8864        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8865            s.select_display_ranges([
 8866                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 8867            ]);
 8868        });
 8869    });
 8870    editor.update(cx, |editor, cx| {
 8871        assert_text_with_selections(
 8872            editor,
 8873            indoc! {r#"
 8874                let a = {
 8875                    key: "value",
 8876                };ˇ
 8877            "#},
 8878            cx,
 8879        );
 8880    });
 8881    editor.update_in(cx, |editor, window, cx| {
 8882        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8883    });
 8884    editor.update(cx, |editor, cx| {
 8885        assert_text_with_selections(
 8886            editor,
 8887            indoc! {r#"
 8888                «ˇlet a = {
 8889                    key: "value",
 8890                };
 8891                »"#},
 8892            cx,
 8893        );
 8894    });
 8895}
 8896
 8897#[gpui::test]
 8898async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8899    init_test(cx, |_| {});
 8900
 8901    let language = Arc::new(Language::new(
 8902        LanguageConfig::default(),
 8903        Some(tree_sitter_rust::LANGUAGE.into()),
 8904    ));
 8905
 8906    let text = r#"
 8907        use mod1::mod2::{mod3, mod4};
 8908
 8909        fn fn_1(param1: bool, param2: &str) {
 8910            let var1 = "hello world";
 8911        }
 8912    "#
 8913    .unindent();
 8914
 8915    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8916    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8917    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8918
 8919    editor
 8920        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8921        .await;
 8922
 8923    // Test 1: Cursor on a letter of a string word
 8924    editor.update_in(cx, |editor, window, cx| {
 8925        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8926            s.select_display_ranges([
 8927                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8928            ]);
 8929        });
 8930    });
 8931    editor.update_in(cx, |editor, window, cx| {
 8932        assert_text_with_selections(
 8933            editor,
 8934            indoc! {r#"
 8935                use mod1::mod2::{mod3, mod4};
 8936
 8937                fn fn_1(param1: bool, param2: &str) {
 8938                    let var1 = "hˇello world";
 8939                }
 8940            "#},
 8941            cx,
 8942        );
 8943        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8944        assert_text_with_selections(
 8945            editor,
 8946            indoc! {r#"
 8947                use mod1::mod2::{mod3, mod4};
 8948
 8949                fn fn_1(param1: bool, param2: &str) {
 8950                    let var1 = "«ˇhello» world";
 8951                }
 8952            "#},
 8953            cx,
 8954        );
 8955    });
 8956
 8957    // Test 2: Partial selection within a word
 8958    editor.update_in(cx, |editor, window, cx| {
 8959        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8960            s.select_display_ranges([
 8961                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 8962            ]);
 8963        });
 8964    });
 8965    editor.update_in(cx, |editor, window, cx| {
 8966        assert_text_with_selections(
 8967            editor,
 8968            indoc! {r#"
 8969                use mod1::mod2::{mod3, mod4};
 8970
 8971                fn fn_1(param1: bool, param2: &str) {
 8972                    let var1 = "h«elˇ»lo world";
 8973                }
 8974            "#},
 8975            cx,
 8976        );
 8977        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8978        assert_text_with_selections(
 8979            editor,
 8980            indoc! {r#"
 8981                use mod1::mod2::{mod3, mod4};
 8982
 8983                fn fn_1(param1: bool, param2: &str) {
 8984                    let var1 = "«ˇhello» world";
 8985                }
 8986            "#},
 8987            cx,
 8988        );
 8989    });
 8990
 8991    // Test 3: Complete word already selected
 8992    editor.update_in(cx, |editor, window, cx| {
 8993        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8994            s.select_display_ranges([
 8995                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 8996            ]);
 8997        });
 8998    });
 8999    editor.update_in(cx, |editor, window, cx| {
 9000        assert_text_with_selections(
 9001            editor,
 9002            indoc! {r#"
 9003                use mod1::mod2::{mod3, mod4};
 9004
 9005                fn fn_1(param1: bool, param2: &str) {
 9006                    let var1 = "«helloˇ» world";
 9007                }
 9008            "#},
 9009            cx,
 9010        );
 9011        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9012        assert_text_with_selections(
 9013            editor,
 9014            indoc! {r#"
 9015                use mod1::mod2::{mod3, mod4};
 9016
 9017                fn fn_1(param1: bool, param2: &str) {
 9018                    let var1 = "«hello worldˇ»";
 9019                }
 9020            "#},
 9021            cx,
 9022        );
 9023    });
 9024
 9025    // Test 4: Selection spanning across words
 9026    editor.update_in(cx, |editor, window, cx| {
 9027        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9028            s.select_display_ranges([
 9029                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9030            ]);
 9031        });
 9032    });
 9033    editor.update_in(cx, |editor, window, cx| {
 9034        assert_text_with_selections(
 9035            editor,
 9036            indoc! {r#"
 9037                use mod1::mod2::{mod3, mod4};
 9038
 9039                fn fn_1(param1: bool, param2: &str) {
 9040                    let var1 = "hel«lo woˇ»rld";
 9041                }
 9042            "#},
 9043            cx,
 9044        );
 9045        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9046        assert_text_with_selections(
 9047            editor,
 9048            indoc! {r#"
 9049                use mod1::mod2::{mod3, mod4};
 9050
 9051                fn fn_1(param1: bool, param2: &str) {
 9052                    let var1 = "«ˇhello world»";
 9053                }
 9054            "#},
 9055            cx,
 9056        );
 9057    });
 9058
 9059    // Test 5: Expansion beyond string
 9060    editor.update_in(cx, |editor, window, cx| {
 9061        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9062        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9063        assert_text_with_selections(
 9064            editor,
 9065            indoc! {r#"
 9066                use mod1::mod2::{mod3, mod4};
 9067
 9068                fn fn_1(param1: bool, param2: &str) {
 9069                    «ˇlet var1 = "hello world";»
 9070                }
 9071            "#},
 9072            cx,
 9073        );
 9074    });
 9075}
 9076
 9077#[gpui::test]
 9078async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9079    init_test(cx, |_| {});
 9080
 9081    let mut cx = EditorTestContext::new(cx).await;
 9082
 9083    let language = Arc::new(Language::new(
 9084        LanguageConfig::default(),
 9085        Some(tree_sitter_rust::LANGUAGE.into()),
 9086    ));
 9087
 9088    cx.update_buffer(|buffer, cx| {
 9089        buffer.set_language(Some(language), cx);
 9090    });
 9091
 9092    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9093    cx.update_editor(|editor, window, cx| {
 9094        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9095    });
 9096
 9097    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9098}
 9099
 9100#[gpui::test]
 9101async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9102    init_test(cx, |_| {});
 9103
 9104    let base_text = r#"
 9105        impl A {
 9106            // this is an uncommitted comment
 9107
 9108            fn b() {
 9109                c();
 9110            }
 9111
 9112            // this is another uncommitted comment
 9113
 9114            fn d() {
 9115                // e
 9116                // f
 9117            }
 9118        }
 9119
 9120        fn g() {
 9121            // h
 9122        }
 9123    "#
 9124    .unindent();
 9125
 9126    let text = r#"
 9127        ˇimpl A {
 9128
 9129            fn b() {
 9130                c();
 9131            }
 9132
 9133            fn d() {
 9134                // e
 9135                // f
 9136            }
 9137        }
 9138
 9139        fn g() {
 9140            // h
 9141        }
 9142    "#
 9143    .unindent();
 9144
 9145    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9146    cx.set_state(&text);
 9147    cx.set_head_text(&base_text);
 9148    cx.update_editor(|editor, window, cx| {
 9149        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9150    });
 9151
 9152    cx.assert_state_with_diff(
 9153        "
 9154        ˇimpl A {
 9155      -     // this is an uncommitted comment
 9156
 9157            fn b() {
 9158                c();
 9159            }
 9160
 9161      -     // this is another uncommitted comment
 9162      -
 9163            fn d() {
 9164                // e
 9165                // f
 9166            }
 9167        }
 9168
 9169        fn g() {
 9170            // h
 9171        }
 9172    "
 9173        .unindent(),
 9174    );
 9175
 9176    let expected_display_text = "
 9177        impl A {
 9178            // this is an uncommitted comment
 9179
 9180            fn b() {
 9181 9182            }
 9183
 9184            // this is another uncommitted comment
 9185
 9186            fn d() {
 9187 9188            }
 9189        }
 9190
 9191        fn g() {
 9192 9193        }
 9194        "
 9195    .unindent();
 9196
 9197    cx.update_editor(|editor, window, cx| {
 9198        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9199        assert_eq!(editor.display_text(cx), expected_display_text);
 9200    });
 9201}
 9202
 9203#[gpui::test]
 9204async fn test_autoindent(cx: &mut TestAppContext) {
 9205    init_test(cx, |_| {});
 9206
 9207    let language = Arc::new(
 9208        Language::new(
 9209            LanguageConfig {
 9210                brackets: BracketPairConfig {
 9211                    pairs: vec![
 9212                        BracketPair {
 9213                            start: "{".to_string(),
 9214                            end: "}".to_string(),
 9215                            close: false,
 9216                            surround: false,
 9217                            newline: true,
 9218                        },
 9219                        BracketPair {
 9220                            start: "(".to_string(),
 9221                            end: ")".to_string(),
 9222                            close: false,
 9223                            surround: false,
 9224                            newline: true,
 9225                        },
 9226                    ],
 9227                    ..Default::default()
 9228                },
 9229                ..Default::default()
 9230            },
 9231            Some(tree_sitter_rust::LANGUAGE.into()),
 9232        )
 9233        .with_indents_query(
 9234            r#"
 9235                (_ "(" ")" @end) @indent
 9236                (_ "{" "}" @end) @indent
 9237            "#,
 9238        )
 9239        .unwrap(),
 9240    );
 9241
 9242    let text = "fn a() {}";
 9243
 9244    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9245    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9246    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9247    editor
 9248        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9249        .await;
 9250
 9251    editor.update_in(cx, |editor, window, cx| {
 9252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9253            s.select_ranges([5..5, 8..8, 9..9])
 9254        });
 9255        editor.newline(&Newline, window, cx);
 9256        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9257        assert_eq!(
 9258            editor.selections.ranges(cx),
 9259            &[
 9260                Point::new(1, 4)..Point::new(1, 4),
 9261                Point::new(3, 4)..Point::new(3, 4),
 9262                Point::new(5, 0)..Point::new(5, 0)
 9263            ]
 9264        );
 9265    });
 9266}
 9267
 9268#[gpui::test]
 9269async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9270    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9271
 9272    let language = Arc::new(
 9273        Language::new(
 9274            LanguageConfig {
 9275                brackets: BracketPairConfig {
 9276                    pairs: vec![
 9277                        BracketPair {
 9278                            start: "{".to_string(),
 9279                            end: "}".to_string(),
 9280                            close: false,
 9281                            surround: false,
 9282                            newline: true,
 9283                        },
 9284                        BracketPair {
 9285                            start: "(".to_string(),
 9286                            end: ")".to_string(),
 9287                            close: false,
 9288                            surround: false,
 9289                            newline: true,
 9290                        },
 9291                    ],
 9292                    ..Default::default()
 9293                },
 9294                ..Default::default()
 9295            },
 9296            Some(tree_sitter_rust::LANGUAGE.into()),
 9297        )
 9298        .with_indents_query(
 9299            r#"
 9300                (_ "(" ")" @end) @indent
 9301                (_ "{" "}" @end) @indent
 9302            "#,
 9303        )
 9304        .unwrap(),
 9305    );
 9306
 9307    let text = "fn a() {}";
 9308
 9309    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9310    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9311    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9312    editor
 9313        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9314        .await;
 9315
 9316    editor.update_in(cx, |editor, window, cx| {
 9317        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9318            s.select_ranges([5..5, 8..8, 9..9])
 9319        });
 9320        editor.newline(&Newline, window, cx);
 9321        assert_eq!(
 9322            editor.text(cx),
 9323            indoc!(
 9324                "
 9325                fn a(
 9326
 9327                ) {
 9328
 9329                }
 9330                "
 9331            )
 9332        );
 9333        assert_eq!(
 9334            editor.selections.ranges(cx),
 9335            &[
 9336                Point::new(1, 0)..Point::new(1, 0),
 9337                Point::new(3, 0)..Point::new(3, 0),
 9338                Point::new(5, 0)..Point::new(5, 0)
 9339            ]
 9340        );
 9341    });
 9342}
 9343
 9344#[gpui::test]
 9345async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9346    init_test(cx, |settings| {
 9347        settings.defaults.auto_indent = Some(true);
 9348        settings.languages.0.insert(
 9349            "python".into(),
 9350            LanguageSettingsContent {
 9351                auto_indent: Some(false),
 9352                ..Default::default()
 9353            },
 9354        );
 9355    });
 9356
 9357    let mut cx = EditorTestContext::new(cx).await;
 9358
 9359    let injected_language = Arc::new(
 9360        Language::new(
 9361            LanguageConfig {
 9362                brackets: BracketPairConfig {
 9363                    pairs: vec![
 9364                        BracketPair {
 9365                            start: "{".to_string(),
 9366                            end: "}".to_string(),
 9367                            close: false,
 9368                            surround: false,
 9369                            newline: true,
 9370                        },
 9371                        BracketPair {
 9372                            start: "(".to_string(),
 9373                            end: ")".to_string(),
 9374                            close: true,
 9375                            surround: false,
 9376                            newline: true,
 9377                        },
 9378                    ],
 9379                    ..Default::default()
 9380                },
 9381                name: "python".into(),
 9382                ..Default::default()
 9383            },
 9384            Some(tree_sitter_python::LANGUAGE.into()),
 9385        )
 9386        .with_indents_query(
 9387            r#"
 9388                (_ "(" ")" @end) @indent
 9389                (_ "{" "}" @end) @indent
 9390            "#,
 9391        )
 9392        .unwrap(),
 9393    );
 9394
 9395    let language = Arc::new(
 9396        Language::new(
 9397            LanguageConfig {
 9398                brackets: BracketPairConfig {
 9399                    pairs: vec![
 9400                        BracketPair {
 9401                            start: "{".to_string(),
 9402                            end: "}".to_string(),
 9403                            close: false,
 9404                            surround: false,
 9405                            newline: true,
 9406                        },
 9407                        BracketPair {
 9408                            start: "(".to_string(),
 9409                            end: ")".to_string(),
 9410                            close: true,
 9411                            surround: false,
 9412                            newline: true,
 9413                        },
 9414                    ],
 9415                    ..Default::default()
 9416                },
 9417                name: LanguageName::new("rust"),
 9418                ..Default::default()
 9419            },
 9420            Some(tree_sitter_rust::LANGUAGE.into()),
 9421        )
 9422        .with_indents_query(
 9423            r#"
 9424                (_ "(" ")" @end) @indent
 9425                (_ "{" "}" @end) @indent
 9426            "#,
 9427        )
 9428        .unwrap()
 9429        .with_injection_query(
 9430            r#"
 9431            (macro_invocation
 9432                macro: (identifier) @_macro_name
 9433                (token_tree) @injection.content
 9434                (#set! injection.language "python"))
 9435           "#,
 9436        )
 9437        .unwrap(),
 9438    );
 9439
 9440    cx.language_registry().add(injected_language);
 9441    cx.language_registry().add(language.clone());
 9442
 9443    cx.update_buffer(|buffer, cx| {
 9444        buffer.set_language(Some(language), cx);
 9445    });
 9446
 9447    cx.set_state(r#"struct A {ˇ}"#);
 9448
 9449    cx.update_editor(|editor, window, cx| {
 9450        editor.newline(&Default::default(), window, cx);
 9451    });
 9452
 9453    cx.assert_editor_state(indoc!(
 9454        "struct A {
 9455            ˇ
 9456        }"
 9457    ));
 9458
 9459    cx.set_state(r#"select_biased!(ˇ)"#);
 9460
 9461    cx.update_editor(|editor, window, cx| {
 9462        editor.newline(&Default::default(), window, cx);
 9463        editor.handle_input("def ", window, cx);
 9464        editor.handle_input("(", window, cx);
 9465        editor.newline(&Default::default(), window, cx);
 9466        editor.handle_input("a", window, cx);
 9467    });
 9468
 9469    cx.assert_editor_state(indoc!(
 9470        "select_biased!(
 9471        def (
 9472 9473        )
 9474        )"
 9475    ));
 9476}
 9477
 9478#[gpui::test]
 9479async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9480    init_test(cx, |_| {});
 9481
 9482    {
 9483        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9484        cx.set_state(indoc! {"
 9485            impl A {
 9486
 9487                fn b() {}
 9488
 9489            «fn c() {
 9490
 9491            }ˇ»
 9492            }
 9493        "});
 9494
 9495        cx.update_editor(|editor, window, cx| {
 9496            editor.autoindent(&Default::default(), window, cx);
 9497        });
 9498
 9499        cx.assert_editor_state(indoc! {"
 9500            impl A {
 9501
 9502                fn b() {}
 9503
 9504                «fn c() {
 9505
 9506                }ˇ»
 9507            }
 9508        "});
 9509    }
 9510
 9511    {
 9512        let mut cx = EditorTestContext::new_multibuffer(
 9513            cx,
 9514            [indoc! { "
 9515                impl A {
 9516                «
 9517                // a
 9518                fn b(){}
 9519                »
 9520                «
 9521                    }
 9522                    fn c(){}
 9523                »
 9524            "}],
 9525        );
 9526
 9527        let buffer = cx.update_editor(|editor, _, cx| {
 9528            let buffer = editor.buffer().update(cx, |buffer, _| {
 9529                buffer.all_buffers().iter().next().unwrap().clone()
 9530            });
 9531            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9532            buffer
 9533        });
 9534
 9535        cx.run_until_parked();
 9536        cx.update_editor(|editor, window, cx| {
 9537            editor.select_all(&Default::default(), window, cx);
 9538            editor.autoindent(&Default::default(), window, cx)
 9539        });
 9540        cx.run_until_parked();
 9541
 9542        cx.update(|_, cx| {
 9543            assert_eq!(
 9544                buffer.read(cx).text(),
 9545                indoc! { "
 9546                    impl A {
 9547
 9548                        // a
 9549                        fn b(){}
 9550
 9551
 9552                    }
 9553                    fn c(){}
 9554
 9555                " }
 9556            )
 9557        });
 9558    }
 9559}
 9560
 9561#[gpui::test]
 9562async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9563    init_test(cx, |_| {});
 9564
 9565    let mut cx = EditorTestContext::new(cx).await;
 9566
 9567    let language = Arc::new(Language::new(
 9568        LanguageConfig {
 9569            brackets: BracketPairConfig {
 9570                pairs: vec![
 9571                    BracketPair {
 9572                        start: "{".to_string(),
 9573                        end: "}".to_string(),
 9574                        close: true,
 9575                        surround: true,
 9576                        newline: true,
 9577                    },
 9578                    BracketPair {
 9579                        start: "(".to_string(),
 9580                        end: ")".to_string(),
 9581                        close: true,
 9582                        surround: true,
 9583                        newline: true,
 9584                    },
 9585                    BracketPair {
 9586                        start: "/*".to_string(),
 9587                        end: " */".to_string(),
 9588                        close: true,
 9589                        surround: true,
 9590                        newline: true,
 9591                    },
 9592                    BracketPair {
 9593                        start: "[".to_string(),
 9594                        end: "]".to_string(),
 9595                        close: false,
 9596                        surround: false,
 9597                        newline: true,
 9598                    },
 9599                    BracketPair {
 9600                        start: "\"".to_string(),
 9601                        end: "\"".to_string(),
 9602                        close: true,
 9603                        surround: true,
 9604                        newline: false,
 9605                    },
 9606                    BracketPair {
 9607                        start: "<".to_string(),
 9608                        end: ">".to_string(),
 9609                        close: false,
 9610                        surround: true,
 9611                        newline: true,
 9612                    },
 9613                ],
 9614                ..Default::default()
 9615            },
 9616            autoclose_before: "})]".to_string(),
 9617            ..Default::default()
 9618        },
 9619        Some(tree_sitter_rust::LANGUAGE.into()),
 9620    ));
 9621
 9622    cx.language_registry().add(language.clone());
 9623    cx.update_buffer(|buffer, cx| {
 9624        buffer.set_language(Some(language), cx);
 9625    });
 9626
 9627    cx.set_state(
 9628        &r#"
 9629            🏀ˇ
 9630            εˇ
 9631            ❤️ˇ
 9632        "#
 9633        .unindent(),
 9634    );
 9635
 9636    // autoclose multiple nested brackets at multiple cursors
 9637    cx.update_editor(|editor, window, cx| {
 9638        editor.handle_input("{", window, cx);
 9639        editor.handle_input("{", window, cx);
 9640        editor.handle_input("{", window, cx);
 9641    });
 9642    cx.assert_editor_state(
 9643        &"
 9644            🏀{{{ˇ}}}
 9645            ε{{{ˇ}}}
 9646            ❤️{{{ˇ}}}
 9647        "
 9648        .unindent(),
 9649    );
 9650
 9651    // insert a different closing bracket
 9652    cx.update_editor(|editor, window, cx| {
 9653        editor.handle_input(")", window, cx);
 9654    });
 9655    cx.assert_editor_state(
 9656        &"
 9657            🏀{{{)ˇ}}}
 9658            ε{{{)ˇ}}}
 9659            ❤️{{{)ˇ}}}
 9660        "
 9661        .unindent(),
 9662    );
 9663
 9664    // skip over the auto-closed brackets when typing a closing bracket
 9665    cx.update_editor(|editor, window, cx| {
 9666        editor.move_right(&MoveRight, window, cx);
 9667        editor.handle_input("}", window, cx);
 9668        editor.handle_input("}", window, cx);
 9669        editor.handle_input("}", window, cx);
 9670    });
 9671    cx.assert_editor_state(
 9672        &"
 9673            🏀{{{)}}}}ˇ
 9674            ε{{{)}}}}ˇ
 9675            ❤️{{{)}}}}ˇ
 9676        "
 9677        .unindent(),
 9678    );
 9679
 9680    // autoclose multi-character pairs
 9681    cx.set_state(
 9682        &"
 9683            ˇ
 9684            ˇ
 9685        "
 9686        .unindent(),
 9687    );
 9688    cx.update_editor(|editor, window, cx| {
 9689        editor.handle_input("/", window, cx);
 9690        editor.handle_input("*", window, cx);
 9691    });
 9692    cx.assert_editor_state(
 9693        &"
 9694            /*ˇ */
 9695            /*ˇ */
 9696        "
 9697        .unindent(),
 9698    );
 9699
 9700    // one cursor autocloses a multi-character pair, one cursor
 9701    // does not autoclose.
 9702    cx.set_state(
 9703        &"
 9704 9705            ˇ
 9706        "
 9707        .unindent(),
 9708    );
 9709    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9710    cx.assert_editor_state(
 9711        &"
 9712            /*ˇ */
 9713 9714        "
 9715        .unindent(),
 9716    );
 9717
 9718    // Don't autoclose if the next character isn't whitespace and isn't
 9719    // listed in the language's "autoclose_before" section.
 9720    cx.set_state("ˇa b");
 9721    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9722    cx.assert_editor_state("{ˇa b");
 9723
 9724    // Don't autoclose if `close` is false for the bracket pair
 9725    cx.set_state("ˇ");
 9726    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9727    cx.assert_editor_state("");
 9728
 9729    // Surround with brackets if text is selected
 9730    cx.set_state("«aˇ» b");
 9731    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9732    cx.assert_editor_state("{«aˇ»} b");
 9733
 9734    // Autoclose when not immediately after a word character
 9735    cx.set_state("a ˇ");
 9736    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9737    cx.assert_editor_state("a \"ˇ\"");
 9738
 9739    // Autoclose pair where the start and end characters are the same
 9740    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9741    cx.assert_editor_state("a \"\"ˇ");
 9742
 9743    // Don't autoclose when immediately after a word character
 9744    cx.set_state("");
 9745    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9746    cx.assert_editor_state("a\"ˇ");
 9747
 9748    // Do autoclose when after a non-word character
 9749    cx.set_state("");
 9750    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9751    cx.assert_editor_state("{\"ˇ\"");
 9752
 9753    // Non identical pairs autoclose regardless of preceding character
 9754    cx.set_state("");
 9755    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9756    cx.assert_editor_state("a{ˇ}");
 9757
 9758    // Don't autoclose pair if autoclose is disabled
 9759    cx.set_state("ˇ");
 9760    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9761    cx.assert_editor_state("");
 9762
 9763    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9764    cx.set_state("«aˇ» b");
 9765    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9766    cx.assert_editor_state("<«aˇ»> b");
 9767}
 9768
 9769#[gpui::test]
 9770async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9771    init_test(cx, |settings| {
 9772        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9773    });
 9774
 9775    let mut cx = EditorTestContext::new(cx).await;
 9776
 9777    let language = Arc::new(Language::new(
 9778        LanguageConfig {
 9779            brackets: BracketPairConfig {
 9780                pairs: vec![
 9781                    BracketPair {
 9782                        start: "{".to_string(),
 9783                        end: "}".to_string(),
 9784                        close: true,
 9785                        surround: true,
 9786                        newline: true,
 9787                    },
 9788                    BracketPair {
 9789                        start: "(".to_string(),
 9790                        end: ")".to_string(),
 9791                        close: true,
 9792                        surround: true,
 9793                        newline: true,
 9794                    },
 9795                    BracketPair {
 9796                        start: "[".to_string(),
 9797                        end: "]".to_string(),
 9798                        close: false,
 9799                        surround: false,
 9800                        newline: true,
 9801                    },
 9802                ],
 9803                ..Default::default()
 9804            },
 9805            autoclose_before: "})]".to_string(),
 9806            ..Default::default()
 9807        },
 9808        Some(tree_sitter_rust::LANGUAGE.into()),
 9809    ));
 9810
 9811    cx.language_registry().add(language.clone());
 9812    cx.update_buffer(|buffer, cx| {
 9813        buffer.set_language(Some(language), cx);
 9814    });
 9815
 9816    cx.set_state(
 9817        &"
 9818            ˇ
 9819            ˇ
 9820            ˇ
 9821        "
 9822        .unindent(),
 9823    );
 9824
 9825    // ensure only matching closing brackets are skipped over
 9826    cx.update_editor(|editor, window, cx| {
 9827        editor.handle_input("}", window, cx);
 9828        editor.move_left(&MoveLeft, window, cx);
 9829        editor.handle_input(")", window, cx);
 9830        editor.move_left(&MoveLeft, window, cx);
 9831    });
 9832    cx.assert_editor_state(
 9833        &"
 9834            ˇ)}
 9835            ˇ)}
 9836            ˇ)}
 9837        "
 9838        .unindent(),
 9839    );
 9840
 9841    // skip-over closing brackets at multiple cursors
 9842    cx.update_editor(|editor, window, cx| {
 9843        editor.handle_input(")", window, cx);
 9844        editor.handle_input("}", window, cx);
 9845    });
 9846    cx.assert_editor_state(
 9847        &"
 9848            )}ˇ
 9849            )}ˇ
 9850            )}ˇ
 9851        "
 9852        .unindent(),
 9853    );
 9854
 9855    // ignore non-close brackets
 9856    cx.update_editor(|editor, window, cx| {
 9857        editor.handle_input("]", window, cx);
 9858        editor.move_left(&MoveLeft, window, cx);
 9859        editor.handle_input("]", window, cx);
 9860    });
 9861    cx.assert_editor_state(
 9862        &"
 9863            )}]ˇ]
 9864            )}]ˇ]
 9865            )}]ˇ]
 9866        "
 9867        .unindent(),
 9868    );
 9869}
 9870
 9871#[gpui::test]
 9872async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9873    init_test(cx, |_| {});
 9874
 9875    let mut cx = EditorTestContext::new(cx).await;
 9876
 9877    let html_language = Arc::new(
 9878        Language::new(
 9879            LanguageConfig {
 9880                name: "HTML".into(),
 9881                brackets: BracketPairConfig {
 9882                    pairs: vec![
 9883                        BracketPair {
 9884                            start: "<".into(),
 9885                            end: ">".into(),
 9886                            close: true,
 9887                            ..Default::default()
 9888                        },
 9889                        BracketPair {
 9890                            start: "{".into(),
 9891                            end: "}".into(),
 9892                            close: true,
 9893                            ..Default::default()
 9894                        },
 9895                        BracketPair {
 9896                            start: "(".into(),
 9897                            end: ")".into(),
 9898                            close: true,
 9899                            ..Default::default()
 9900                        },
 9901                    ],
 9902                    ..Default::default()
 9903                },
 9904                autoclose_before: "})]>".into(),
 9905                ..Default::default()
 9906            },
 9907            Some(tree_sitter_html::LANGUAGE.into()),
 9908        )
 9909        .with_injection_query(
 9910            r#"
 9911            (script_element
 9912                (raw_text) @injection.content
 9913                (#set! injection.language "javascript"))
 9914            "#,
 9915        )
 9916        .unwrap(),
 9917    );
 9918
 9919    let javascript_language = Arc::new(Language::new(
 9920        LanguageConfig {
 9921            name: "JavaScript".into(),
 9922            brackets: BracketPairConfig {
 9923                pairs: vec![
 9924                    BracketPair {
 9925                        start: "/*".into(),
 9926                        end: " */".into(),
 9927                        close: true,
 9928                        ..Default::default()
 9929                    },
 9930                    BracketPair {
 9931                        start: "{".into(),
 9932                        end: "}".into(),
 9933                        close: true,
 9934                        ..Default::default()
 9935                    },
 9936                    BracketPair {
 9937                        start: "(".into(),
 9938                        end: ")".into(),
 9939                        close: true,
 9940                        ..Default::default()
 9941                    },
 9942                ],
 9943                ..Default::default()
 9944            },
 9945            autoclose_before: "})]>".into(),
 9946            ..Default::default()
 9947        },
 9948        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9949    ));
 9950
 9951    cx.language_registry().add(html_language.clone());
 9952    cx.language_registry().add(javascript_language);
 9953    cx.executor().run_until_parked();
 9954
 9955    cx.update_buffer(|buffer, cx| {
 9956        buffer.set_language(Some(html_language), cx);
 9957    });
 9958
 9959    cx.set_state(
 9960        &r#"
 9961            <body>ˇ
 9962                <script>
 9963                    var x = 1;ˇ
 9964                </script>
 9965            </body>ˇ
 9966        "#
 9967        .unindent(),
 9968    );
 9969
 9970    // Precondition: different languages are active at different locations.
 9971    cx.update_editor(|editor, window, cx| {
 9972        let snapshot = editor.snapshot(window, cx);
 9973        let cursors = editor.selections.ranges::<usize>(cx);
 9974        let languages = cursors
 9975            .iter()
 9976            .map(|c| snapshot.language_at(c.start).unwrap().name())
 9977            .collect::<Vec<_>>();
 9978        assert_eq!(
 9979            languages,
 9980            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 9981        );
 9982    });
 9983
 9984    // Angle brackets autoclose in HTML, but not JavaScript.
 9985    cx.update_editor(|editor, window, cx| {
 9986        editor.handle_input("<", window, cx);
 9987        editor.handle_input("a", window, cx);
 9988    });
 9989    cx.assert_editor_state(
 9990        &r#"
 9991            <body><aˇ>
 9992                <script>
 9993                    var x = 1;<aˇ
 9994                </script>
 9995            </body><aˇ>
 9996        "#
 9997        .unindent(),
 9998    );
 9999
10000    // Curly braces and parens autoclose in both HTML and JavaScript.
10001    cx.update_editor(|editor, window, cx| {
10002        editor.handle_input(" b=", window, cx);
10003        editor.handle_input("{", window, cx);
10004        editor.handle_input("c", window, cx);
10005        editor.handle_input("(", window, cx);
10006    });
10007    cx.assert_editor_state(
10008        &r#"
10009            <body><a b={c(ˇ)}>
10010                <script>
10011                    var x = 1;<a b={c(ˇ)}
10012                </script>
10013            </body><a b={c(ˇ)}>
10014        "#
10015        .unindent(),
10016    );
10017
10018    // Brackets that were already autoclosed are skipped.
10019    cx.update_editor(|editor, window, cx| {
10020        editor.handle_input(")", window, cx);
10021        editor.handle_input("d", window, cx);
10022        editor.handle_input("}", window, cx);
10023    });
10024    cx.assert_editor_state(
10025        &r#"
10026            <body><a b={c()d}ˇ>
10027                <script>
10028                    var x = 1;<a b={c()d}ˇ
10029                </script>
10030            </body><a b={c()d}ˇ>
10031        "#
10032        .unindent(),
10033    );
10034    cx.update_editor(|editor, window, cx| {
10035        editor.handle_input(">", window, cx);
10036    });
10037    cx.assert_editor_state(
10038        &r#"
10039            <body><a b={c()d}>ˇ
10040                <script>
10041                    var x = 1;<a b={c()d}>ˇ
10042                </script>
10043            </body><a b={c()d}>ˇ
10044        "#
10045        .unindent(),
10046    );
10047
10048    // Reset
10049    cx.set_state(
10050        &r#"
10051            <body>ˇ
10052                <script>
10053                    var x = 1;ˇ
10054                </script>
10055            </body>ˇ
10056        "#
10057        .unindent(),
10058    );
10059
10060    cx.update_editor(|editor, window, cx| {
10061        editor.handle_input("<", window, cx);
10062    });
10063    cx.assert_editor_state(
10064        &r#"
10065            <body><ˇ>
10066                <script>
10067                    var x = 1;<ˇ
10068                </script>
10069            </body><ˇ>
10070        "#
10071        .unindent(),
10072    );
10073
10074    // When backspacing, the closing angle brackets are removed.
10075    cx.update_editor(|editor, window, cx| {
10076        editor.backspace(&Backspace, window, cx);
10077    });
10078    cx.assert_editor_state(
10079        &r#"
10080            <body>ˇ
10081                <script>
10082                    var x = 1;ˇ
10083                </script>
10084            </body>ˇ
10085        "#
10086        .unindent(),
10087    );
10088
10089    // Block comments autoclose in JavaScript, but not HTML.
10090    cx.update_editor(|editor, window, cx| {
10091        editor.handle_input("/", window, cx);
10092        editor.handle_input("*", window, cx);
10093    });
10094    cx.assert_editor_state(
10095        &r#"
10096            <body>/*ˇ
10097                <script>
10098                    var x = 1;/*ˇ */
10099                </script>
10100            </body>/*ˇ
10101        "#
10102        .unindent(),
10103    );
10104}
10105
10106#[gpui::test]
10107async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10108    init_test(cx, |_| {});
10109
10110    let mut cx = EditorTestContext::new(cx).await;
10111
10112    let rust_language = Arc::new(
10113        Language::new(
10114            LanguageConfig {
10115                name: "Rust".into(),
10116                brackets: serde_json::from_value(json!([
10117                    { "start": "{", "end": "}", "close": true, "newline": true },
10118                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10119                ]))
10120                .unwrap(),
10121                autoclose_before: "})]>".into(),
10122                ..Default::default()
10123            },
10124            Some(tree_sitter_rust::LANGUAGE.into()),
10125        )
10126        .with_override_query("(string_literal) @string")
10127        .unwrap(),
10128    );
10129
10130    cx.language_registry().add(rust_language.clone());
10131    cx.update_buffer(|buffer, cx| {
10132        buffer.set_language(Some(rust_language), cx);
10133    });
10134
10135    cx.set_state(
10136        &r#"
10137            let x = ˇ
10138        "#
10139        .unindent(),
10140    );
10141
10142    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10143    cx.update_editor(|editor, window, cx| {
10144        editor.handle_input("\"", window, cx);
10145    });
10146    cx.assert_editor_state(
10147        &r#"
10148            let x = "ˇ"
10149        "#
10150        .unindent(),
10151    );
10152
10153    // Inserting another quotation mark. The cursor moves across the existing
10154    // automatically-inserted quotation mark.
10155    cx.update_editor(|editor, window, cx| {
10156        editor.handle_input("\"", window, cx);
10157    });
10158    cx.assert_editor_state(
10159        &r#"
10160            let x = ""ˇ
10161        "#
10162        .unindent(),
10163    );
10164
10165    // Reset
10166    cx.set_state(
10167        &r#"
10168            let x = ˇ
10169        "#
10170        .unindent(),
10171    );
10172
10173    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10174    cx.update_editor(|editor, window, cx| {
10175        editor.handle_input("\"", window, cx);
10176        editor.handle_input(" ", window, cx);
10177        editor.move_left(&Default::default(), window, cx);
10178        editor.handle_input("\\", window, cx);
10179        editor.handle_input("\"", window, cx);
10180    });
10181    cx.assert_editor_state(
10182        &r#"
10183            let x = "\"ˇ "
10184        "#
10185        .unindent(),
10186    );
10187
10188    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10189    // mark. Nothing is inserted.
10190    cx.update_editor(|editor, window, cx| {
10191        editor.move_right(&Default::default(), window, cx);
10192        editor.handle_input("\"", window, cx);
10193    });
10194    cx.assert_editor_state(
10195        &r#"
10196            let x = "\" "ˇ
10197        "#
10198        .unindent(),
10199    );
10200}
10201
10202#[gpui::test]
10203async fn test_surround_with_pair(cx: &mut TestAppContext) {
10204    init_test(cx, |_| {});
10205
10206    let language = Arc::new(Language::new(
10207        LanguageConfig {
10208            brackets: BracketPairConfig {
10209                pairs: vec![
10210                    BracketPair {
10211                        start: "{".to_string(),
10212                        end: "}".to_string(),
10213                        close: true,
10214                        surround: true,
10215                        newline: true,
10216                    },
10217                    BracketPair {
10218                        start: "/* ".to_string(),
10219                        end: "*/".to_string(),
10220                        close: true,
10221                        surround: true,
10222                        ..Default::default()
10223                    },
10224                ],
10225                ..Default::default()
10226            },
10227            ..Default::default()
10228        },
10229        Some(tree_sitter_rust::LANGUAGE.into()),
10230    ));
10231
10232    let text = r#"
10233        a
10234        b
10235        c
10236    "#
10237    .unindent();
10238
10239    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10240    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10241    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10242    editor
10243        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10244        .await;
10245
10246    editor.update_in(cx, |editor, window, cx| {
10247        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10248            s.select_display_ranges([
10249                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10250                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10251                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10252            ])
10253        });
10254
10255        editor.handle_input("{", window, cx);
10256        editor.handle_input("{", window, cx);
10257        editor.handle_input("{", window, cx);
10258        assert_eq!(
10259            editor.text(cx),
10260            "
10261                {{{a}}}
10262                {{{b}}}
10263                {{{c}}}
10264            "
10265            .unindent()
10266        );
10267        assert_eq!(
10268            editor.selections.display_ranges(cx),
10269            [
10270                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10271                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10272                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10273            ]
10274        );
10275
10276        editor.undo(&Undo, window, cx);
10277        editor.undo(&Undo, window, cx);
10278        editor.undo(&Undo, window, cx);
10279        assert_eq!(
10280            editor.text(cx),
10281            "
10282                a
10283                b
10284                c
10285            "
10286            .unindent()
10287        );
10288        assert_eq!(
10289            editor.selections.display_ranges(cx),
10290            [
10291                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10292                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10293                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10294            ]
10295        );
10296
10297        // Ensure inserting the first character of a multi-byte bracket pair
10298        // doesn't surround the selections with the bracket.
10299        editor.handle_input("/", window, cx);
10300        assert_eq!(
10301            editor.text(cx),
10302            "
10303                /
10304                /
10305                /
10306            "
10307            .unindent()
10308        );
10309        assert_eq!(
10310            editor.selections.display_ranges(cx),
10311            [
10312                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10313                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10314                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10315            ]
10316        );
10317
10318        editor.undo(&Undo, window, cx);
10319        assert_eq!(
10320            editor.text(cx),
10321            "
10322                a
10323                b
10324                c
10325            "
10326            .unindent()
10327        );
10328        assert_eq!(
10329            editor.selections.display_ranges(cx),
10330            [
10331                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10332                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10333                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10334            ]
10335        );
10336
10337        // Ensure inserting the last character of a multi-byte bracket pair
10338        // doesn't surround the selections with the bracket.
10339        editor.handle_input("*", window, cx);
10340        assert_eq!(
10341            editor.text(cx),
10342            "
10343                *
10344                *
10345                *
10346            "
10347            .unindent()
10348        );
10349        assert_eq!(
10350            editor.selections.display_ranges(cx),
10351            [
10352                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10353                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10354                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10355            ]
10356        );
10357    });
10358}
10359
10360#[gpui::test]
10361async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10362    init_test(cx, |_| {});
10363
10364    let language = Arc::new(Language::new(
10365        LanguageConfig {
10366            brackets: BracketPairConfig {
10367                pairs: vec![BracketPair {
10368                    start: "{".to_string(),
10369                    end: "}".to_string(),
10370                    close: true,
10371                    surround: true,
10372                    newline: true,
10373                }],
10374                ..Default::default()
10375            },
10376            autoclose_before: "}".to_string(),
10377            ..Default::default()
10378        },
10379        Some(tree_sitter_rust::LANGUAGE.into()),
10380    ));
10381
10382    let text = r#"
10383        a
10384        b
10385        c
10386    "#
10387    .unindent();
10388
10389    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10390    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10391    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10392    editor
10393        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10394        .await;
10395
10396    editor.update_in(cx, |editor, window, cx| {
10397        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10398            s.select_ranges([
10399                Point::new(0, 1)..Point::new(0, 1),
10400                Point::new(1, 1)..Point::new(1, 1),
10401                Point::new(2, 1)..Point::new(2, 1),
10402            ])
10403        });
10404
10405        editor.handle_input("{", window, cx);
10406        editor.handle_input("{", window, cx);
10407        editor.handle_input("_", window, cx);
10408        assert_eq!(
10409            editor.text(cx),
10410            "
10411                a{{_}}
10412                b{{_}}
10413                c{{_}}
10414            "
10415            .unindent()
10416        );
10417        assert_eq!(
10418            editor.selections.ranges::<Point>(cx),
10419            [
10420                Point::new(0, 4)..Point::new(0, 4),
10421                Point::new(1, 4)..Point::new(1, 4),
10422                Point::new(2, 4)..Point::new(2, 4)
10423            ]
10424        );
10425
10426        editor.backspace(&Default::default(), window, cx);
10427        editor.backspace(&Default::default(), window, cx);
10428        assert_eq!(
10429            editor.text(cx),
10430            "
10431                a{}
10432                b{}
10433                c{}
10434            "
10435            .unindent()
10436        );
10437        assert_eq!(
10438            editor.selections.ranges::<Point>(cx),
10439            [
10440                Point::new(0, 2)..Point::new(0, 2),
10441                Point::new(1, 2)..Point::new(1, 2),
10442                Point::new(2, 2)..Point::new(2, 2)
10443            ]
10444        );
10445
10446        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10447        assert_eq!(
10448            editor.text(cx),
10449            "
10450                a
10451                b
10452                c
10453            "
10454            .unindent()
10455        );
10456        assert_eq!(
10457            editor.selections.ranges::<Point>(cx),
10458            [
10459                Point::new(0, 1)..Point::new(0, 1),
10460                Point::new(1, 1)..Point::new(1, 1),
10461                Point::new(2, 1)..Point::new(2, 1)
10462            ]
10463        );
10464    });
10465}
10466
10467#[gpui::test]
10468async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10469    init_test(cx, |settings| {
10470        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10471    });
10472
10473    let mut cx = EditorTestContext::new(cx).await;
10474
10475    let language = Arc::new(Language::new(
10476        LanguageConfig {
10477            brackets: BracketPairConfig {
10478                pairs: vec![
10479                    BracketPair {
10480                        start: "{".to_string(),
10481                        end: "}".to_string(),
10482                        close: true,
10483                        surround: true,
10484                        newline: true,
10485                    },
10486                    BracketPair {
10487                        start: "(".to_string(),
10488                        end: ")".to_string(),
10489                        close: true,
10490                        surround: true,
10491                        newline: true,
10492                    },
10493                    BracketPair {
10494                        start: "[".to_string(),
10495                        end: "]".to_string(),
10496                        close: false,
10497                        surround: true,
10498                        newline: true,
10499                    },
10500                ],
10501                ..Default::default()
10502            },
10503            autoclose_before: "})]".to_string(),
10504            ..Default::default()
10505        },
10506        Some(tree_sitter_rust::LANGUAGE.into()),
10507    ));
10508
10509    cx.language_registry().add(language.clone());
10510    cx.update_buffer(|buffer, cx| {
10511        buffer.set_language(Some(language), cx);
10512    });
10513
10514    cx.set_state(
10515        &"
10516            {(ˇ)}
10517            [[ˇ]]
10518            {(ˇ)}
10519        "
10520        .unindent(),
10521    );
10522
10523    cx.update_editor(|editor, window, cx| {
10524        editor.backspace(&Default::default(), window, cx);
10525        editor.backspace(&Default::default(), window, cx);
10526    });
10527
10528    cx.assert_editor_state(
10529        &"
10530            ˇ
10531            ˇ]]
10532            ˇ
10533        "
10534        .unindent(),
10535    );
10536
10537    cx.update_editor(|editor, window, cx| {
10538        editor.handle_input("{", window, cx);
10539        editor.handle_input("{", window, cx);
10540        editor.move_right(&MoveRight, window, cx);
10541        editor.move_right(&MoveRight, window, cx);
10542        editor.move_left(&MoveLeft, window, cx);
10543        editor.move_left(&MoveLeft, window, cx);
10544        editor.backspace(&Default::default(), window, cx);
10545    });
10546
10547    cx.assert_editor_state(
10548        &"
10549            {ˇ}
10550            {ˇ}]]
10551            {ˇ}
10552        "
10553        .unindent(),
10554    );
10555
10556    cx.update_editor(|editor, window, cx| {
10557        editor.backspace(&Default::default(), window, cx);
10558    });
10559
10560    cx.assert_editor_state(
10561        &"
10562            ˇ
10563            ˇ]]
10564            ˇ
10565        "
10566        .unindent(),
10567    );
10568}
10569
10570#[gpui::test]
10571async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10572    init_test(cx, |_| {});
10573
10574    let language = Arc::new(Language::new(
10575        LanguageConfig::default(),
10576        Some(tree_sitter_rust::LANGUAGE.into()),
10577    ));
10578
10579    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10580    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10581    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10582    editor
10583        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10584        .await;
10585
10586    editor.update_in(cx, |editor, window, cx| {
10587        editor.set_auto_replace_emoji_shortcode(true);
10588
10589        editor.handle_input("Hello ", window, cx);
10590        editor.handle_input(":wave", window, cx);
10591        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10592
10593        editor.handle_input(":", window, cx);
10594        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10595
10596        editor.handle_input(" :smile", window, cx);
10597        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10598
10599        editor.handle_input(":", window, cx);
10600        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10601
10602        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10603        editor.handle_input(":wave", window, cx);
10604        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10605
10606        editor.handle_input(":", window, cx);
10607        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10608
10609        editor.handle_input(":1", window, cx);
10610        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10611
10612        editor.handle_input(":", window, cx);
10613        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10614
10615        // Ensure shortcode does not get replaced when it is part of a word
10616        editor.handle_input(" Test:wave", window, cx);
10617        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10618
10619        editor.handle_input(":", window, cx);
10620        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10621
10622        editor.set_auto_replace_emoji_shortcode(false);
10623
10624        // Ensure shortcode does not get replaced when auto replace is off
10625        editor.handle_input(" :wave", window, cx);
10626        assert_eq!(
10627            editor.text(cx),
10628            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10629        );
10630
10631        editor.handle_input(":", window, cx);
10632        assert_eq!(
10633            editor.text(cx),
10634            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10635        );
10636    });
10637}
10638
10639#[gpui::test]
10640async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10641    init_test(cx, |_| {});
10642
10643    let (text, insertion_ranges) = marked_text_ranges(
10644        indoc! {"
10645            ˇ
10646        "},
10647        false,
10648    );
10649
10650    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10651    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10652
10653    _ = editor.update_in(cx, |editor, window, cx| {
10654        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10655
10656        editor
10657            .insert_snippet(&insertion_ranges, snippet, window, cx)
10658            .unwrap();
10659
10660        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10661            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10662            assert_eq!(editor.text(cx), expected_text);
10663            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10664        }
10665
10666        assert(
10667            editor,
10668            cx,
10669            indoc! {"
10670            type «» =•
10671            "},
10672        );
10673
10674        assert!(editor.context_menu_visible(), "There should be a matches");
10675    });
10676}
10677
10678#[gpui::test]
10679async fn test_snippets(cx: &mut TestAppContext) {
10680    init_test(cx, |_| {});
10681
10682    let mut cx = EditorTestContext::new(cx).await;
10683
10684    cx.set_state(indoc! {"
10685        a.ˇ b
10686        a.ˇ b
10687        a.ˇ b
10688    "});
10689
10690    cx.update_editor(|editor, window, cx| {
10691        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10692        let insertion_ranges = editor
10693            .selections
10694            .all(cx)
10695            .iter()
10696            .map(|s| s.range())
10697            .collect::<Vec<_>>();
10698        editor
10699            .insert_snippet(&insertion_ranges, snippet, window, cx)
10700            .unwrap();
10701    });
10702
10703    cx.assert_editor_state(indoc! {"
10704        a.f(«oneˇ», two, «threeˇ») b
10705        a.f(«oneˇ», two, «threeˇ») b
10706        a.f(«oneˇ», two, «threeˇ») b
10707    "});
10708
10709    // Can't move earlier than the first tab stop
10710    cx.update_editor(|editor, window, cx| {
10711        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10712    });
10713    cx.assert_editor_state(indoc! {"
10714        a.f(«oneˇ», two, «threeˇ») b
10715        a.f(«oneˇ», two, «threeˇ») b
10716        a.f(«oneˇ», two, «threeˇ») b
10717    "});
10718
10719    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10720    cx.assert_editor_state(indoc! {"
10721        a.f(one, «twoˇ», three) b
10722        a.f(one, «twoˇ», three) b
10723        a.f(one, «twoˇ», three) b
10724    "});
10725
10726    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10727    cx.assert_editor_state(indoc! {"
10728        a.f(«oneˇ», two, «threeˇ») b
10729        a.f(«oneˇ», two, «threeˇ») b
10730        a.f(«oneˇ», two, «threeˇ») b
10731    "});
10732
10733    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10734    cx.assert_editor_state(indoc! {"
10735        a.f(one, «twoˇ», three) b
10736        a.f(one, «twoˇ», three) b
10737        a.f(one, «twoˇ», three) b
10738    "});
10739    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10740    cx.assert_editor_state(indoc! {"
10741        a.f(one, two, three)ˇ b
10742        a.f(one, two, three)ˇ b
10743        a.f(one, two, three)ˇ b
10744    "});
10745
10746    // As soon as the last tab stop is reached, snippet state is gone
10747    cx.update_editor(|editor, window, cx| {
10748        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10749    });
10750    cx.assert_editor_state(indoc! {"
10751        a.f(one, two, three)ˇ b
10752        a.f(one, two, three)ˇ b
10753        a.f(one, two, three)ˇ b
10754    "});
10755}
10756
10757#[gpui::test]
10758async fn test_snippet_indentation(cx: &mut TestAppContext) {
10759    init_test(cx, |_| {});
10760
10761    let mut cx = EditorTestContext::new(cx).await;
10762
10763    cx.update_editor(|editor, window, cx| {
10764        let snippet = Snippet::parse(indoc! {"
10765            /*
10766             * Multiline comment with leading indentation
10767             *
10768             * $1
10769             */
10770            $0"})
10771        .unwrap();
10772        let insertion_ranges = editor
10773            .selections
10774            .all(cx)
10775            .iter()
10776            .map(|s| s.range())
10777            .collect::<Vec<_>>();
10778        editor
10779            .insert_snippet(&insertion_ranges, snippet, window, cx)
10780            .unwrap();
10781    });
10782
10783    cx.assert_editor_state(indoc! {"
10784        /*
10785         * Multiline comment with leading indentation
10786         *
10787         * ˇ
10788         */
10789    "});
10790
10791    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10792    cx.assert_editor_state(indoc! {"
10793        /*
10794         * Multiline comment with leading indentation
10795         *
10796         *•
10797         */
10798        ˇ"});
10799}
10800
10801#[gpui::test]
10802async fn test_document_format_during_save(cx: &mut TestAppContext) {
10803    init_test(cx, |_| {});
10804
10805    let fs = FakeFs::new(cx.executor());
10806    fs.insert_file(path!("/file.rs"), Default::default()).await;
10807
10808    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10809
10810    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10811    language_registry.add(rust_lang());
10812    let mut fake_servers = language_registry.register_fake_lsp(
10813        "Rust",
10814        FakeLspAdapter {
10815            capabilities: lsp::ServerCapabilities {
10816                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10817                ..Default::default()
10818            },
10819            ..Default::default()
10820        },
10821    );
10822
10823    let buffer = project
10824        .update(cx, |project, cx| {
10825            project.open_local_buffer(path!("/file.rs"), cx)
10826        })
10827        .await
10828        .unwrap();
10829
10830    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10831    let (editor, cx) = cx.add_window_view(|window, cx| {
10832        build_editor_with_project(project.clone(), buffer, window, cx)
10833    });
10834    editor.update_in(cx, |editor, window, cx| {
10835        editor.set_text("one\ntwo\nthree\n", window, cx)
10836    });
10837    assert!(cx.read(|cx| editor.is_dirty(cx)));
10838
10839    cx.executor().start_waiting();
10840    let fake_server = fake_servers.next().await.unwrap();
10841
10842    {
10843        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10844            move |params, _| async move {
10845                assert_eq!(
10846                    params.text_document.uri,
10847                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10848                );
10849                assert_eq!(params.options.tab_size, 4);
10850                Ok(Some(vec![lsp::TextEdit::new(
10851                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10852                    ", ".to_string(),
10853                )]))
10854            },
10855        );
10856        let save = editor
10857            .update_in(cx, |editor, window, cx| {
10858                editor.save(
10859                    SaveOptions {
10860                        format: true,
10861                        autosave: false,
10862                    },
10863                    project.clone(),
10864                    window,
10865                    cx,
10866                )
10867            })
10868            .unwrap();
10869        cx.executor().start_waiting();
10870        save.await;
10871
10872        assert_eq!(
10873            editor.update(cx, |editor, cx| editor.text(cx)),
10874            "one, two\nthree\n"
10875        );
10876        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10877    }
10878
10879    {
10880        editor.update_in(cx, |editor, window, cx| {
10881            editor.set_text("one\ntwo\nthree\n", window, cx)
10882        });
10883        assert!(cx.read(|cx| editor.is_dirty(cx)));
10884
10885        // Ensure we can still save even if formatting hangs.
10886        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10887            move |params, _| async move {
10888                assert_eq!(
10889                    params.text_document.uri,
10890                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10891                );
10892                futures::future::pending::<()>().await;
10893                unreachable!()
10894            },
10895        );
10896        let save = editor
10897            .update_in(cx, |editor, window, cx| {
10898                editor.save(
10899                    SaveOptions {
10900                        format: true,
10901                        autosave: false,
10902                    },
10903                    project.clone(),
10904                    window,
10905                    cx,
10906                )
10907            })
10908            .unwrap();
10909        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10910        cx.executor().start_waiting();
10911        save.await;
10912        assert_eq!(
10913            editor.update(cx, |editor, cx| editor.text(cx)),
10914            "one\ntwo\nthree\n"
10915        );
10916    }
10917
10918    // Set rust language override and assert overridden tabsize is sent to language server
10919    update_test_language_settings(cx, |settings| {
10920        settings.languages.0.insert(
10921            "Rust".into(),
10922            LanguageSettingsContent {
10923                tab_size: NonZeroU32::new(8),
10924                ..Default::default()
10925            },
10926        );
10927    });
10928
10929    {
10930        editor.update_in(cx, |editor, window, cx| {
10931            editor.set_text("somehting_new\n", window, cx)
10932        });
10933        assert!(cx.read(|cx| editor.is_dirty(cx)));
10934        let _formatting_request_signal = fake_server
10935            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10936                assert_eq!(
10937                    params.text_document.uri,
10938                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10939                );
10940                assert_eq!(params.options.tab_size, 8);
10941                Ok(Some(vec![]))
10942            });
10943        let save = editor
10944            .update_in(cx, |editor, window, cx| {
10945                editor.save(
10946                    SaveOptions {
10947                        format: true,
10948                        autosave: false,
10949                    },
10950                    project.clone(),
10951                    window,
10952                    cx,
10953                )
10954            })
10955            .unwrap();
10956        cx.executor().start_waiting();
10957        save.await;
10958    }
10959}
10960
10961#[gpui::test]
10962async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
10963    init_test(cx, |settings| {
10964        settings.defaults.ensure_final_newline_on_save = Some(false);
10965    });
10966
10967    let fs = FakeFs::new(cx.executor());
10968    fs.insert_file(path!("/file.txt"), "foo".into()).await;
10969
10970    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
10971
10972    let buffer = project
10973        .update(cx, |project, cx| {
10974            project.open_local_buffer(path!("/file.txt"), cx)
10975        })
10976        .await
10977        .unwrap();
10978
10979    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10980    let (editor, cx) = cx.add_window_view(|window, cx| {
10981        build_editor_with_project(project.clone(), buffer, window, cx)
10982    });
10983    editor.update_in(cx, |editor, window, cx| {
10984        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
10985            s.select_ranges([0..0])
10986        });
10987    });
10988    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10989
10990    editor.update_in(cx, |editor, window, cx| {
10991        editor.handle_input("\n", window, cx)
10992    });
10993    cx.run_until_parked();
10994    save(&editor, &project, cx).await;
10995    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10996
10997    editor.update_in(cx, |editor, window, cx| {
10998        editor.undo(&Default::default(), window, cx);
10999    });
11000    save(&editor, &project, cx).await;
11001    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11002
11003    editor.update_in(cx, |editor, window, cx| {
11004        editor.redo(&Default::default(), window, cx);
11005    });
11006    cx.run_until_parked();
11007    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11008
11009    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11010        let save = editor
11011            .update_in(cx, |editor, window, cx| {
11012                editor.save(
11013                    SaveOptions {
11014                        format: true,
11015                        autosave: false,
11016                    },
11017                    project.clone(),
11018                    window,
11019                    cx,
11020                )
11021            })
11022            .unwrap();
11023        cx.executor().start_waiting();
11024        save.await;
11025        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11026    }
11027}
11028
11029#[gpui::test]
11030async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11031    init_test(cx, |_| {});
11032
11033    let cols = 4;
11034    let rows = 10;
11035    let sample_text_1 = sample_text(rows, cols, 'a');
11036    assert_eq!(
11037        sample_text_1,
11038        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11039    );
11040    let sample_text_2 = sample_text(rows, cols, 'l');
11041    assert_eq!(
11042        sample_text_2,
11043        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11044    );
11045    let sample_text_3 = sample_text(rows, cols, 'v');
11046    assert_eq!(
11047        sample_text_3,
11048        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11049    );
11050
11051    let fs = FakeFs::new(cx.executor());
11052    fs.insert_tree(
11053        path!("/a"),
11054        json!({
11055            "main.rs": sample_text_1,
11056            "other.rs": sample_text_2,
11057            "lib.rs": sample_text_3,
11058        }),
11059    )
11060    .await;
11061
11062    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11063    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11064    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11065
11066    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11067    language_registry.add(rust_lang());
11068    let mut fake_servers = language_registry.register_fake_lsp(
11069        "Rust",
11070        FakeLspAdapter {
11071            capabilities: lsp::ServerCapabilities {
11072                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11073                ..Default::default()
11074            },
11075            ..Default::default()
11076        },
11077    );
11078
11079    let worktree = project.update(cx, |project, cx| {
11080        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11081        assert_eq!(worktrees.len(), 1);
11082        worktrees.pop().unwrap()
11083    });
11084    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11085
11086    let buffer_1 = project
11087        .update(cx, |project, cx| {
11088            project.open_buffer((worktree_id, "main.rs"), cx)
11089        })
11090        .await
11091        .unwrap();
11092    let buffer_2 = project
11093        .update(cx, |project, cx| {
11094            project.open_buffer((worktree_id, "other.rs"), cx)
11095        })
11096        .await
11097        .unwrap();
11098    let buffer_3 = project
11099        .update(cx, |project, cx| {
11100            project.open_buffer((worktree_id, "lib.rs"), cx)
11101        })
11102        .await
11103        .unwrap();
11104
11105    let multi_buffer = cx.new(|cx| {
11106        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11107        multi_buffer.push_excerpts(
11108            buffer_1.clone(),
11109            [
11110                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11111                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11112                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11113            ],
11114            cx,
11115        );
11116        multi_buffer.push_excerpts(
11117            buffer_2.clone(),
11118            [
11119                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11120                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11121                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11122            ],
11123            cx,
11124        );
11125        multi_buffer.push_excerpts(
11126            buffer_3.clone(),
11127            [
11128                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11129                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11130                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11131            ],
11132            cx,
11133        );
11134        multi_buffer
11135    });
11136    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11137        Editor::new(
11138            EditorMode::full(),
11139            multi_buffer,
11140            Some(project.clone()),
11141            window,
11142            cx,
11143        )
11144    });
11145
11146    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11147        editor.change_selections(
11148            SelectionEffects::scroll(Autoscroll::Next),
11149            window,
11150            cx,
11151            |s| s.select_ranges(Some(1..2)),
11152        );
11153        editor.insert("|one|two|three|", window, cx);
11154    });
11155    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11156    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11157        editor.change_selections(
11158            SelectionEffects::scroll(Autoscroll::Next),
11159            window,
11160            cx,
11161            |s| s.select_ranges(Some(60..70)),
11162        );
11163        editor.insert("|four|five|six|", window, cx);
11164    });
11165    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11166
11167    // First two buffers should be edited, but not the third one.
11168    assert_eq!(
11169        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11170        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
11171    );
11172    buffer_1.update(cx, |buffer, _| {
11173        assert!(buffer.is_dirty());
11174        assert_eq!(
11175            buffer.text(),
11176            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11177        )
11178    });
11179    buffer_2.update(cx, |buffer, _| {
11180        assert!(buffer.is_dirty());
11181        assert_eq!(
11182            buffer.text(),
11183            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11184        )
11185    });
11186    buffer_3.update(cx, |buffer, _| {
11187        assert!(!buffer.is_dirty());
11188        assert_eq!(buffer.text(), sample_text_3,)
11189    });
11190    cx.executor().run_until_parked();
11191
11192    cx.executor().start_waiting();
11193    let save = multi_buffer_editor
11194        .update_in(cx, |editor, window, cx| {
11195            editor.save(
11196                SaveOptions {
11197                    format: true,
11198                    autosave: false,
11199                },
11200                project.clone(),
11201                window,
11202                cx,
11203            )
11204        })
11205        .unwrap();
11206
11207    let fake_server = fake_servers.next().await.unwrap();
11208    fake_server
11209        .server
11210        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11211            Ok(Some(vec![lsp::TextEdit::new(
11212                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11213                format!("[{} formatted]", params.text_document.uri),
11214            )]))
11215        })
11216        .detach();
11217    save.await;
11218
11219    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11220    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11221    assert_eq!(
11222        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11223        uri!(
11224            "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"
11225        ),
11226    );
11227    buffer_1.update(cx, |buffer, _| {
11228        assert!(!buffer.is_dirty());
11229        assert_eq!(
11230            buffer.text(),
11231            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11232        )
11233    });
11234    buffer_2.update(cx, |buffer, _| {
11235        assert!(!buffer.is_dirty());
11236        assert_eq!(
11237            buffer.text(),
11238            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11239        )
11240    });
11241    buffer_3.update(cx, |buffer, _| {
11242        assert!(!buffer.is_dirty());
11243        assert_eq!(buffer.text(), sample_text_3,)
11244    });
11245}
11246
11247#[gpui::test]
11248async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11249    init_test(cx, |_| {});
11250
11251    let fs = FakeFs::new(cx.executor());
11252    fs.insert_tree(
11253        path!("/dir"),
11254        json!({
11255            "file1.rs": "fn main() { println!(\"hello\"); }",
11256            "file2.rs": "fn test() { println!(\"test\"); }",
11257            "file3.rs": "fn other() { println!(\"other\"); }\n",
11258        }),
11259    )
11260    .await;
11261
11262    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11263    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11264    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11265
11266    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11267    language_registry.add(rust_lang());
11268
11269    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11270    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11271
11272    // Open three buffers
11273    let buffer_1 = project
11274        .update(cx, |project, cx| {
11275            project.open_buffer((worktree_id, "file1.rs"), cx)
11276        })
11277        .await
11278        .unwrap();
11279    let buffer_2 = project
11280        .update(cx, |project, cx| {
11281            project.open_buffer((worktree_id, "file2.rs"), cx)
11282        })
11283        .await
11284        .unwrap();
11285    let buffer_3 = project
11286        .update(cx, |project, cx| {
11287            project.open_buffer((worktree_id, "file3.rs"), cx)
11288        })
11289        .await
11290        .unwrap();
11291
11292    // Create a multi-buffer with all three buffers
11293    let multi_buffer = cx.new(|cx| {
11294        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11295        multi_buffer.push_excerpts(
11296            buffer_1.clone(),
11297            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11298            cx,
11299        );
11300        multi_buffer.push_excerpts(
11301            buffer_2.clone(),
11302            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11303            cx,
11304        );
11305        multi_buffer.push_excerpts(
11306            buffer_3.clone(),
11307            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11308            cx,
11309        );
11310        multi_buffer
11311    });
11312
11313    let editor = cx.new_window_entity(|window, cx| {
11314        Editor::new(
11315            EditorMode::full(),
11316            multi_buffer,
11317            Some(project.clone()),
11318            window,
11319            cx,
11320        )
11321    });
11322
11323    // Edit only the first buffer
11324    editor.update_in(cx, |editor, window, cx| {
11325        editor.change_selections(
11326            SelectionEffects::scroll(Autoscroll::Next),
11327            window,
11328            cx,
11329            |s| s.select_ranges(Some(10..10)),
11330        );
11331        editor.insert("// edited", window, cx);
11332    });
11333
11334    // Verify that only buffer 1 is dirty
11335    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11336    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11337    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11338
11339    // Get write counts after file creation (files were created with initial content)
11340    // We expect each file to have been written once during creation
11341    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11342    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11343    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11344
11345    // Perform autosave
11346    let save_task = editor.update_in(cx, |editor, window, cx| {
11347        editor.save(
11348            SaveOptions {
11349                format: true,
11350                autosave: true,
11351            },
11352            project.clone(),
11353            window,
11354            cx,
11355        )
11356    });
11357    save_task.await.unwrap();
11358
11359    // Only the dirty buffer should have been saved
11360    assert_eq!(
11361        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11362        1,
11363        "Buffer 1 was dirty, so it should have been written once during autosave"
11364    );
11365    assert_eq!(
11366        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11367        0,
11368        "Buffer 2 was clean, so it should not have been written during autosave"
11369    );
11370    assert_eq!(
11371        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11372        0,
11373        "Buffer 3 was clean, so it should not have been written during autosave"
11374    );
11375
11376    // Verify buffer states after autosave
11377    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11378    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11379    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11380
11381    // Now perform a manual save (format = true)
11382    let save_task = editor.update_in(cx, |editor, window, cx| {
11383        editor.save(
11384            SaveOptions {
11385                format: true,
11386                autosave: false,
11387            },
11388            project.clone(),
11389            window,
11390            cx,
11391        )
11392    });
11393    save_task.await.unwrap();
11394
11395    // During manual save, clean buffers don't get written to disk
11396    // They just get did_save called for language server notifications
11397    assert_eq!(
11398        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11399        1,
11400        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11401    );
11402    assert_eq!(
11403        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11404        0,
11405        "Buffer 2 should not have been written at all"
11406    );
11407    assert_eq!(
11408        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11409        0,
11410        "Buffer 3 should not have been written at all"
11411    );
11412}
11413
11414async fn setup_range_format_test(
11415    cx: &mut TestAppContext,
11416) -> (
11417    Entity<Project>,
11418    Entity<Editor>,
11419    &mut gpui::VisualTestContext,
11420    lsp::FakeLanguageServer,
11421) {
11422    init_test(cx, |_| {});
11423
11424    let fs = FakeFs::new(cx.executor());
11425    fs.insert_file(path!("/file.rs"), Default::default()).await;
11426
11427    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11428
11429    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11430    language_registry.add(rust_lang());
11431    let mut fake_servers = language_registry.register_fake_lsp(
11432        "Rust",
11433        FakeLspAdapter {
11434            capabilities: lsp::ServerCapabilities {
11435                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11436                ..lsp::ServerCapabilities::default()
11437            },
11438            ..FakeLspAdapter::default()
11439        },
11440    );
11441
11442    let buffer = project
11443        .update(cx, |project, cx| {
11444            project.open_local_buffer(path!("/file.rs"), cx)
11445        })
11446        .await
11447        .unwrap();
11448
11449    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11450    let (editor, cx) = cx.add_window_view(|window, cx| {
11451        build_editor_with_project(project.clone(), buffer, window, cx)
11452    });
11453
11454    cx.executor().start_waiting();
11455    let fake_server = fake_servers.next().await.unwrap();
11456
11457    (project, editor, cx, fake_server)
11458}
11459
11460#[gpui::test]
11461async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11462    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11463
11464    editor.update_in(cx, |editor, window, cx| {
11465        editor.set_text("one\ntwo\nthree\n", window, cx)
11466    });
11467    assert!(cx.read(|cx| editor.is_dirty(cx)));
11468
11469    let save = editor
11470        .update_in(cx, |editor, window, cx| {
11471            editor.save(
11472                SaveOptions {
11473                    format: true,
11474                    autosave: false,
11475                },
11476                project.clone(),
11477                window,
11478                cx,
11479            )
11480        })
11481        .unwrap();
11482    fake_server
11483        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11484            assert_eq!(
11485                params.text_document.uri,
11486                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11487            );
11488            assert_eq!(params.options.tab_size, 4);
11489            Ok(Some(vec![lsp::TextEdit::new(
11490                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11491                ", ".to_string(),
11492            )]))
11493        })
11494        .next()
11495        .await;
11496    cx.executor().start_waiting();
11497    save.await;
11498    assert_eq!(
11499        editor.update(cx, |editor, cx| editor.text(cx)),
11500        "one, two\nthree\n"
11501    );
11502    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11503}
11504
11505#[gpui::test]
11506async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11507    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11508
11509    editor.update_in(cx, |editor, window, cx| {
11510        editor.set_text("one\ntwo\nthree\n", window, cx)
11511    });
11512    assert!(cx.read(|cx| editor.is_dirty(cx)));
11513
11514    // Test that save still works when formatting hangs
11515    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11516        move |params, _| async move {
11517            assert_eq!(
11518                params.text_document.uri,
11519                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11520            );
11521            futures::future::pending::<()>().await;
11522            unreachable!()
11523        },
11524    );
11525    let save = editor
11526        .update_in(cx, |editor, window, cx| {
11527            editor.save(
11528                SaveOptions {
11529                    format: true,
11530                    autosave: false,
11531                },
11532                project.clone(),
11533                window,
11534                cx,
11535            )
11536        })
11537        .unwrap();
11538    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11539    cx.executor().start_waiting();
11540    save.await;
11541    assert_eq!(
11542        editor.update(cx, |editor, cx| editor.text(cx)),
11543        "one\ntwo\nthree\n"
11544    );
11545    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11546}
11547
11548#[gpui::test]
11549async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11550    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11551
11552    // Buffer starts clean, no formatting should be requested
11553    let save = editor
11554        .update_in(cx, |editor, window, cx| {
11555            editor.save(
11556                SaveOptions {
11557                    format: false,
11558                    autosave: false,
11559                },
11560                project.clone(),
11561                window,
11562                cx,
11563            )
11564        })
11565        .unwrap();
11566    let _pending_format_request = fake_server
11567        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11568            panic!("Should not be invoked");
11569        })
11570        .next();
11571    cx.executor().start_waiting();
11572    save.await;
11573    cx.run_until_parked();
11574}
11575
11576#[gpui::test]
11577async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11578    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11579
11580    // Set Rust language override and assert overridden tabsize is sent to language server
11581    update_test_language_settings(cx, |settings| {
11582        settings.languages.0.insert(
11583            "Rust".into(),
11584            LanguageSettingsContent {
11585                tab_size: NonZeroU32::new(8),
11586                ..Default::default()
11587            },
11588        );
11589    });
11590
11591    editor.update_in(cx, |editor, window, cx| {
11592        editor.set_text("something_new\n", window, cx)
11593    });
11594    assert!(cx.read(|cx| editor.is_dirty(cx)));
11595    let save = editor
11596        .update_in(cx, |editor, window, cx| {
11597            editor.save(
11598                SaveOptions {
11599                    format: true,
11600                    autosave: false,
11601                },
11602                project.clone(),
11603                window,
11604                cx,
11605            )
11606        })
11607        .unwrap();
11608    fake_server
11609        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11610            assert_eq!(
11611                params.text_document.uri,
11612                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11613            );
11614            assert_eq!(params.options.tab_size, 8);
11615            Ok(Some(Vec::new()))
11616        })
11617        .next()
11618        .await;
11619    save.await;
11620}
11621
11622#[gpui::test]
11623async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11624    init_test(cx, |settings| {
11625        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11626            Formatter::LanguageServer { name: None },
11627        )))
11628    });
11629
11630    let fs = FakeFs::new(cx.executor());
11631    fs.insert_file(path!("/file.rs"), Default::default()).await;
11632
11633    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11634
11635    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11636    language_registry.add(Arc::new(Language::new(
11637        LanguageConfig {
11638            name: "Rust".into(),
11639            matcher: LanguageMatcher {
11640                path_suffixes: vec!["rs".to_string()],
11641                ..Default::default()
11642            },
11643            ..LanguageConfig::default()
11644        },
11645        Some(tree_sitter_rust::LANGUAGE.into()),
11646    )));
11647    update_test_language_settings(cx, |settings| {
11648        // Enable Prettier formatting for the same buffer, and ensure
11649        // LSP is called instead of Prettier.
11650        settings.defaults.prettier = Some(PrettierSettings {
11651            allowed: true,
11652            ..PrettierSettings::default()
11653        });
11654    });
11655    let mut fake_servers = language_registry.register_fake_lsp(
11656        "Rust",
11657        FakeLspAdapter {
11658            capabilities: lsp::ServerCapabilities {
11659                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11660                ..Default::default()
11661            },
11662            ..Default::default()
11663        },
11664    );
11665
11666    let buffer = project
11667        .update(cx, |project, cx| {
11668            project.open_local_buffer(path!("/file.rs"), cx)
11669        })
11670        .await
11671        .unwrap();
11672
11673    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11674    let (editor, cx) = cx.add_window_view(|window, cx| {
11675        build_editor_with_project(project.clone(), buffer, window, cx)
11676    });
11677    editor.update_in(cx, |editor, window, cx| {
11678        editor.set_text("one\ntwo\nthree\n", window, cx)
11679    });
11680
11681    cx.executor().start_waiting();
11682    let fake_server = fake_servers.next().await.unwrap();
11683
11684    let format = editor
11685        .update_in(cx, |editor, window, cx| {
11686            editor.perform_format(
11687                project.clone(),
11688                FormatTrigger::Manual,
11689                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11690                window,
11691                cx,
11692            )
11693        })
11694        .unwrap();
11695    fake_server
11696        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11697            assert_eq!(
11698                params.text_document.uri,
11699                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11700            );
11701            assert_eq!(params.options.tab_size, 4);
11702            Ok(Some(vec![lsp::TextEdit::new(
11703                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11704                ", ".to_string(),
11705            )]))
11706        })
11707        .next()
11708        .await;
11709    cx.executor().start_waiting();
11710    format.await;
11711    assert_eq!(
11712        editor.update(cx, |editor, cx| editor.text(cx)),
11713        "one, two\nthree\n"
11714    );
11715
11716    editor.update_in(cx, |editor, window, cx| {
11717        editor.set_text("one\ntwo\nthree\n", window, cx)
11718    });
11719    // Ensure we don't lock if formatting hangs.
11720    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11721        move |params, _| async move {
11722            assert_eq!(
11723                params.text_document.uri,
11724                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11725            );
11726            futures::future::pending::<()>().await;
11727            unreachable!()
11728        },
11729    );
11730    let format = editor
11731        .update_in(cx, |editor, window, cx| {
11732            editor.perform_format(
11733                project,
11734                FormatTrigger::Manual,
11735                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11736                window,
11737                cx,
11738            )
11739        })
11740        .unwrap();
11741    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11742    cx.executor().start_waiting();
11743    format.await;
11744    assert_eq!(
11745        editor.update(cx, |editor, cx| editor.text(cx)),
11746        "one\ntwo\nthree\n"
11747    );
11748}
11749
11750#[gpui::test]
11751async fn test_multiple_formatters(cx: &mut TestAppContext) {
11752    init_test(cx, |settings| {
11753        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11754        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11755            Formatter::LanguageServer { name: None },
11756            Formatter::CodeActions(
11757                [
11758                    ("code-action-1".into(), true),
11759                    ("code-action-2".into(), true),
11760                ]
11761                .into_iter()
11762                .collect(),
11763            ),
11764        ])))
11765    });
11766
11767    let fs = FakeFs::new(cx.executor());
11768    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11769        .await;
11770
11771    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11772    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11773    language_registry.add(rust_lang());
11774
11775    let mut fake_servers = language_registry.register_fake_lsp(
11776        "Rust",
11777        FakeLspAdapter {
11778            capabilities: lsp::ServerCapabilities {
11779                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11780                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11781                    commands: vec!["the-command-for-code-action-1".into()],
11782                    ..Default::default()
11783                }),
11784                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11785                ..Default::default()
11786            },
11787            ..Default::default()
11788        },
11789    );
11790
11791    let buffer = project
11792        .update(cx, |project, cx| {
11793            project.open_local_buffer(path!("/file.rs"), cx)
11794        })
11795        .await
11796        .unwrap();
11797
11798    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11799    let (editor, cx) = cx.add_window_view(|window, cx| {
11800        build_editor_with_project(project.clone(), buffer, window, cx)
11801    });
11802
11803    cx.executor().start_waiting();
11804
11805    let fake_server = fake_servers.next().await.unwrap();
11806    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11807        move |_params, _| async move {
11808            Ok(Some(vec![lsp::TextEdit::new(
11809                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11810                "applied-formatting\n".to_string(),
11811            )]))
11812        },
11813    );
11814    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11815        move |params, _| async move {
11816            assert_eq!(
11817                params.context.only,
11818                Some(vec!["code-action-1".into(), "code-action-2".into()])
11819            );
11820            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11821            Ok(Some(vec![
11822                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11823                    kind: Some("code-action-1".into()),
11824                    edit: Some(lsp::WorkspaceEdit::new(
11825                        [(
11826                            uri.clone(),
11827                            vec![lsp::TextEdit::new(
11828                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11829                                "applied-code-action-1-edit\n".to_string(),
11830                            )],
11831                        )]
11832                        .into_iter()
11833                        .collect(),
11834                    )),
11835                    command: Some(lsp::Command {
11836                        command: "the-command-for-code-action-1".into(),
11837                        ..Default::default()
11838                    }),
11839                    ..Default::default()
11840                }),
11841                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11842                    kind: Some("code-action-2".into()),
11843                    edit: Some(lsp::WorkspaceEdit::new(
11844                        [(
11845                            uri,
11846                            vec![lsp::TextEdit::new(
11847                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11848                                "applied-code-action-2-edit\n".to_string(),
11849                            )],
11850                        )]
11851                        .into_iter()
11852                        .collect(),
11853                    )),
11854                    ..Default::default()
11855                }),
11856            ]))
11857        },
11858    );
11859
11860    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11861        move |params, _| async move { Ok(params) }
11862    });
11863
11864    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11865    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11866        let fake = fake_server.clone();
11867        let lock = command_lock.clone();
11868        move |params, _| {
11869            assert_eq!(params.command, "the-command-for-code-action-1");
11870            let fake = fake.clone();
11871            let lock = lock.clone();
11872            async move {
11873                lock.lock().await;
11874                fake.server
11875                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11876                        label: None,
11877                        edit: lsp::WorkspaceEdit {
11878                            changes: Some(
11879                                [(
11880                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11881                                    vec![lsp::TextEdit {
11882                                        range: lsp::Range::new(
11883                                            lsp::Position::new(0, 0),
11884                                            lsp::Position::new(0, 0),
11885                                        ),
11886                                        new_text: "applied-code-action-1-command\n".into(),
11887                                    }],
11888                                )]
11889                                .into_iter()
11890                                .collect(),
11891                            ),
11892                            ..Default::default()
11893                        },
11894                    })
11895                    .await
11896                    .into_response()
11897                    .unwrap();
11898                Ok(Some(json!(null)))
11899            }
11900        }
11901    });
11902
11903    cx.executor().start_waiting();
11904    editor
11905        .update_in(cx, |editor, window, cx| {
11906            editor.perform_format(
11907                project.clone(),
11908                FormatTrigger::Manual,
11909                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11910                window,
11911                cx,
11912            )
11913        })
11914        .unwrap()
11915        .await;
11916    editor.update(cx, |editor, cx| {
11917        assert_eq!(
11918            editor.text(cx),
11919            r#"
11920                applied-code-action-2-edit
11921                applied-code-action-1-command
11922                applied-code-action-1-edit
11923                applied-formatting
11924                one
11925                two
11926                three
11927            "#
11928            .unindent()
11929        );
11930    });
11931
11932    editor.update_in(cx, |editor, window, cx| {
11933        editor.undo(&Default::default(), window, cx);
11934        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11935    });
11936
11937    // Perform a manual edit while waiting for an LSP command
11938    // that's being run as part of a formatting code action.
11939    let lock_guard = command_lock.lock().await;
11940    let format = editor
11941        .update_in(cx, |editor, window, cx| {
11942            editor.perform_format(
11943                project.clone(),
11944                FormatTrigger::Manual,
11945                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11946                window,
11947                cx,
11948            )
11949        })
11950        .unwrap();
11951    cx.run_until_parked();
11952    editor.update(cx, |editor, cx| {
11953        assert_eq!(
11954            editor.text(cx),
11955            r#"
11956                applied-code-action-1-edit
11957                applied-formatting
11958                one
11959                two
11960                three
11961            "#
11962            .unindent()
11963        );
11964
11965        editor.buffer.update(cx, |buffer, cx| {
11966            let ix = buffer.len(cx);
11967            buffer.edit([(ix..ix, "edited\n")], None, cx);
11968        });
11969    });
11970
11971    // Allow the LSP command to proceed. Because the buffer was edited,
11972    // the second code action will not be run.
11973    drop(lock_guard);
11974    format.await;
11975    editor.update_in(cx, |editor, window, cx| {
11976        assert_eq!(
11977            editor.text(cx),
11978            r#"
11979                applied-code-action-1-command
11980                applied-code-action-1-edit
11981                applied-formatting
11982                one
11983                two
11984                three
11985                edited
11986            "#
11987            .unindent()
11988        );
11989
11990        // The manual edit is undone first, because it is the last thing the user did
11991        // (even though the command completed afterwards).
11992        editor.undo(&Default::default(), window, cx);
11993        assert_eq!(
11994            editor.text(cx),
11995            r#"
11996                applied-code-action-1-command
11997                applied-code-action-1-edit
11998                applied-formatting
11999                one
12000                two
12001                three
12002            "#
12003            .unindent()
12004        );
12005
12006        // All the formatting (including the command, which completed after the manual edit)
12007        // is undone together.
12008        editor.undo(&Default::default(), window, cx);
12009        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12010    });
12011}
12012
12013#[gpui::test]
12014async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12015    init_test(cx, |settings| {
12016        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12017            Formatter::LanguageServer { name: None },
12018        ])))
12019    });
12020
12021    let fs = FakeFs::new(cx.executor());
12022    fs.insert_file(path!("/file.ts"), Default::default()).await;
12023
12024    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12025
12026    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12027    language_registry.add(Arc::new(Language::new(
12028        LanguageConfig {
12029            name: "TypeScript".into(),
12030            matcher: LanguageMatcher {
12031                path_suffixes: vec!["ts".to_string()],
12032                ..Default::default()
12033            },
12034            ..LanguageConfig::default()
12035        },
12036        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12037    )));
12038    update_test_language_settings(cx, |settings| {
12039        settings.defaults.prettier = Some(PrettierSettings {
12040            allowed: true,
12041            ..PrettierSettings::default()
12042        });
12043    });
12044    let mut fake_servers = language_registry.register_fake_lsp(
12045        "TypeScript",
12046        FakeLspAdapter {
12047            capabilities: lsp::ServerCapabilities {
12048                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12049                ..Default::default()
12050            },
12051            ..Default::default()
12052        },
12053    );
12054
12055    let buffer = project
12056        .update(cx, |project, cx| {
12057            project.open_local_buffer(path!("/file.ts"), cx)
12058        })
12059        .await
12060        .unwrap();
12061
12062    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12063    let (editor, cx) = cx.add_window_view(|window, cx| {
12064        build_editor_with_project(project.clone(), buffer, window, cx)
12065    });
12066    editor.update_in(cx, |editor, window, cx| {
12067        editor.set_text(
12068            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12069            window,
12070            cx,
12071        )
12072    });
12073
12074    cx.executor().start_waiting();
12075    let fake_server = fake_servers.next().await.unwrap();
12076
12077    let format = editor
12078        .update_in(cx, |editor, window, cx| {
12079            editor.perform_code_action_kind(
12080                project.clone(),
12081                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12082                window,
12083                cx,
12084            )
12085        })
12086        .unwrap();
12087    fake_server
12088        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12089            assert_eq!(
12090                params.text_document.uri,
12091                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12092            );
12093            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12094                lsp::CodeAction {
12095                    title: "Organize Imports".to_string(),
12096                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12097                    edit: Some(lsp::WorkspaceEdit {
12098                        changes: Some(
12099                            [(
12100                                params.text_document.uri.clone(),
12101                                vec![lsp::TextEdit::new(
12102                                    lsp::Range::new(
12103                                        lsp::Position::new(1, 0),
12104                                        lsp::Position::new(2, 0),
12105                                    ),
12106                                    "".to_string(),
12107                                )],
12108                            )]
12109                            .into_iter()
12110                            .collect(),
12111                        ),
12112                        ..Default::default()
12113                    }),
12114                    ..Default::default()
12115                },
12116            )]))
12117        })
12118        .next()
12119        .await;
12120    cx.executor().start_waiting();
12121    format.await;
12122    assert_eq!(
12123        editor.update(cx, |editor, cx| editor.text(cx)),
12124        "import { a } from 'module';\n\nconst x = a;\n"
12125    );
12126
12127    editor.update_in(cx, |editor, window, cx| {
12128        editor.set_text(
12129            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12130            window,
12131            cx,
12132        )
12133    });
12134    // Ensure we don't lock if code action hangs.
12135    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12136        move |params, _| async move {
12137            assert_eq!(
12138                params.text_document.uri,
12139                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12140            );
12141            futures::future::pending::<()>().await;
12142            unreachable!()
12143        },
12144    );
12145    let format = editor
12146        .update_in(cx, |editor, window, cx| {
12147            editor.perform_code_action_kind(
12148                project,
12149                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12150                window,
12151                cx,
12152            )
12153        })
12154        .unwrap();
12155    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12156    cx.executor().start_waiting();
12157    format.await;
12158    assert_eq!(
12159        editor.update(cx, |editor, cx| editor.text(cx)),
12160        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12161    );
12162}
12163
12164#[gpui::test]
12165async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12166    init_test(cx, |_| {});
12167
12168    let mut cx = EditorLspTestContext::new_rust(
12169        lsp::ServerCapabilities {
12170            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12171            ..Default::default()
12172        },
12173        cx,
12174    )
12175    .await;
12176
12177    cx.set_state(indoc! {"
12178        one.twoˇ
12179    "});
12180
12181    // The format request takes a long time. When it completes, it inserts
12182    // a newline and an indent before the `.`
12183    cx.lsp
12184        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12185            let executor = cx.background_executor().clone();
12186            async move {
12187                executor.timer(Duration::from_millis(100)).await;
12188                Ok(Some(vec![lsp::TextEdit {
12189                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12190                    new_text: "\n    ".into(),
12191                }]))
12192            }
12193        });
12194
12195    // Submit a format request.
12196    let format_1 = cx
12197        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12198        .unwrap();
12199    cx.executor().run_until_parked();
12200
12201    // Submit a second format request.
12202    let format_2 = cx
12203        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12204        .unwrap();
12205    cx.executor().run_until_parked();
12206
12207    // Wait for both format requests to complete
12208    cx.executor().advance_clock(Duration::from_millis(200));
12209    cx.executor().start_waiting();
12210    format_1.await.unwrap();
12211    cx.executor().start_waiting();
12212    format_2.await.unwrap();
12213
12214    // The formatting edits only happens once.
12215    cx.assert_editor_state(indoc! {"
12216        one
12217            .twoˇ
12218    "});
12219}
12220
12221#[gpui::test]
12222async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12223    init_test(cx, |settings| {
12224        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12225    });
12226
12227    let mut cx = EditorLspTestContext::new_rust(
12228        lsp::ServerCapabilities {
12229            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12230            ..Default::default()
12231        },
12232        cx,
12233    )
12234    .await;
12235
12236    // Set up a buffer white some trailing whitespace and no trailing newline.
12237    cx.set_state(
12238        &[
12239            "one ",   //
12240            "twoˇ",   //
12241            "three ", //
12242            "four",   //
12243        ]
12244        .join("\n"),
12245    );
12246
12247    // Submit a format request.
12248    let format = cx
12249        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12250        .unwrap();
12251
12252    // Record which buffer changes have been sent to the language server
12253    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12254    cx.lsp
12255        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12256            let buffer_changes = buffer_changes.clone();
12257            move |params, _| {
12258                buffer_changes.lock().extend(
12259                    params
12260                        .content_changes
12261                        .into_iter()
12262                        .map(|e| (e.range.unwrap(), e.text)),
12263                );
12264            }
12265        });
12266
12267    // Handle formatting requests to the language server.
12268    cx.lsp
12269        .set_request_handler::<lsp::request::Formatting, _, _>({
12270            let buffer_changes = buffer_changes.clone();
12271            move |_, _| {
12272                // When formatting is requested, trailing whitespace has already been stripped,
12273                // and the trailing newline has already been added.
12274                assert_eq!(
12275                    &buffer_changes.lock()[1..],
12276                    &[
12277                        (
12278                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12279                            "".into()
12280                        ),
12281                        (
12282                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12283                            "".into()
12284                        ),
12285                        (
12286                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12287                            "\n".into()
12288                        ),
12289                    ]
12290                );
12291
12292                // Insert blank lines between each line of the buffer.
12293                async move {
12294                    Ok(Some(vec![
12295                        lsp::TextEdit {
12296                            range: lsp::Range::new(
12297                                lsp::Position::new(1, 0),
12298                                lsp::Position::new(1, 0),
12299                            ),
12300                            new_text: "\n".into(),
12301                        },
12302                        lsp::TextEdit {
12303                            range: lsp::Range::new(
12304                                lsp::Position::new(2, 0),
12305                                lsp::Position::new(2, 0),
12306                            ),
12307                            new_text: "\n".into(),
12308                        },
12309                    ]))
12310                }
12311            }
12312        });
12313
12314    // After formatting the buffer, the trailing whitespace is stripped,
12315    // a newline is appended, and the edits provided by the language server
12316    // have been applied.
12317    format.await.unwrap();
12318    cx.assert_editor_state(
12319        &[
12320            "one",   //
12321            "",      //
12322            "twoˇ",  //
12323            "",      //
12324            "three", //
12325            "four",  //
12326            "",      //
12327        ]
12328        .join("\n"),
12329    );
12330
12331    // Undoing the formatting undoes the trailing whitespace removal, the
12332    // trailing newline, and the LSP edits.
12333    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12334    cx.assert_editor_state(
12335        &[
12336            "one ",   //
12337            "twoˇ",   //
12338            "three ", //
12339            "four",   //
12340        ]
12341        .join("\n"),
12342    );
12343}
12344
12345#[gpui::test]
12346async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12347    cx: &mut TestAppContext,
12348) {
12349    init_test(cx, |_| {});
12350
12351    cx.update(|cx| {
12352        cx.update_global::<SettingsStore, _>(|settings, cx| {
12353            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12354                settings.auto_signature_help = Some(true);
12355            });
12356        });
12357    });
12358
12359    let mut cx = EditorLspTestContext::new_rust(
12360        lsp::ServerCapabilities {
12361            signature_help_provider: Some(lsp::SignatureHelpOptions {
12362                ..Default::default()
12363            }),
12364            ..Default::default()
12365        },
12366        cx,
12367    )
12368    .await;
12369
12370    let language = Language::new(
12371        LanguageConfig {
12372            name: "Rust".into(),
12373            brackets: BracketPairConfig {
12374                pairs: vec![
12375                    BracketPair {
12376                        start: "{".to_string(),
12377                        end: "}".to_string(),
12378                        close: true,
12379                        surround: true,
12380                        newline: true,
12381                    },
12382                    BracketPair {
12383                        start: "(".to_string(),
12384                        end: ")".to_string(),
12385                        close: true,
12386                        surround: true,
12387                        newline: true,
12388                    },
12389                    BracketPair {
12390                        start: "/*".to_string(),
12391                        end: " */".to_string(),
12392                        close: true,
12393                        surround: true,
12394                        newline: true,
12395                    },
12396                    BracketPair {
12397                        start: "[".to_string(),
12398                        end: "]".to_string(),
12399                        close: false,
12400                        surround: false,
12401                        newline: true,
12402                    },
12403                    BracketPair {
12404                        start: "\"".to_string(),
12405                        end: "\"".to_string(),
12406                        close: true,
12407                        surround: true,
12408                        newline: false,
12409                    },
12410                    BracketPair {
12411                        start: "<".to_string(),
12412                        end: ">".to_string(),
12413                        close: false,
12414                        surround: true,
12415                        newline: true,
12416                    },
12417                ],
12418                ..Default::default()
12419            },
12420            autoclose_before: "})]".to_string(),
12421            ..Default::default()
12422        },
12423        Some(tree_sitter_rust::LANGUAGE.into()),
12424    );
12425    let language = Arc::new(language);
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        &r#"
12434            fn main() {
12435                sampleˇ
12436            }
12437        "#
12438        .unindent(),
12439    );
12440
12441    cx.update_editor(|editor, window, cx| {
12442        editor.handle_input("(", window, cx);
12443    });
12444    cx.assert_editor_state(
12445        &"
12446            fn main() {
12447                sample(ˇ)
12448            }
12449        "
12450        .unindent(),
12451    );
12452
12453    let mocked_response = lsp::SignatureHelp {
12454        signatures: vec![lsp::SignatureInformation {
12455            label: "fn sample(param1: u8, param2: u8)".to_string(),
12456            documentation: None,
12457            parameters: Some(vec![
12458                lsp::ParameterInformation {
12459                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12460                    documentation: None,
12461                },
12462                lsp::ParameterInformation {
12463                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12464                    documentation: None,
12465                },
12466            ]),
12467            active_parameter: None,
12468        }],
12469        active_signature: Some(0),
12470        active_parameter: Some(0),
12471    };
12472    handle_signature_help_request(&mut cx, mocked_response).await;
12473
12474    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12475        .await;
12476
12477    cx.editor(|editor, _, _| {
12478        let signature_help_state = editor.signature_help_state.popover().cloned();
12479        let signature = signature_help_state.unwrap();
12480        assert_eq!(
12481            signature.signatures[signature.current_signature].label,
12482            "fn sample(param1: u8, param2: u8)"
12483        );
12484    });
12485}
12486
12487#[gpui::test]
12488async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12489    init_test(cx, |_| {});
12490
12491    cx.update(|cx| {
12492        cx.update_global::<SettingsStore, _>(|settings, cx| {
12493            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12494                settings.auto_signature_help = Some(false);
12495                settings.show_signature_help_after_edits = Some(false);
12496            });
12497        });
12498    });
12499
12500    let mut cx = EditorLspTestContext::new_rust(
12501        lsp::ServerCapabilities {
12502            signature_help_provider: Some(lsp::SignatureHelpOptions {
12503                ..Default::default()
12504            }),
12505            ..Default::default()
12506        },
12507        cx,
12508    )
12509    .await;
12510
12511    let language = Language::new(
12512        LanguageConfig {
12513            name: "Rust".into(),
12514            brackets: BracketPairConfig {
12515                pairs: vec![
12516                    BracketPair {
12517                        start: "{".to_string(),
12518                        end: "}".to_string(),
12519                        close: true,
12520                        surround: true,
12521                        newline: true,
12522                    },
12523                    BracketPair {
12524                        start: "(".to_string(),
12525                        end: ")".to_string(),
12526                        close: true,
12527                        surround: true,
12528                        newline: true,
12529                    },
12530                    BracketPair {
12531                        start: "/*".to_string(),
12532                        end: " */".to_string(),
12533                        close: true,
12534                        surround: true,
12535                        newline: true,
12536                    },
12537                    BracketPair {
12538                        start: "[".to_string(),
12539                        end: "]".to_string(),
12540                        close: false,
12541                        surround: false,
12542                        newline: true,
12543                    },
12544                    BracketPair {
12545                        start: "\"".to_string(),
12546                        end: "\"".to_string(),
12547                        close: true,
12548                        surround: true,
12549                        newline: false,
12550                    },
12551                    BracketPair {
12552                        start: "<".to_string(),
12553                        end: ">".to_string(),
12554                        close: false,
12555                        surround: true,
12556                        newline: true,
12557                    },
12558                ],
12559                ..Default::default()
12560            },
12561            autoclose_before: "})]".to_string(),
12562            ..Default::default()
12563        },
12564        Some(tree_sitter_rust::LANGUAGE.into()),
12565    );
12566    let language = Arc::new(language);
12567
12568    cx.language_registry().add(language.clone());
12569    cx.update_buffer(|buffer, cx| {
12570        buffer.set_language(Some(language), cx);
12571    });
12572
12573    // Ensure that signature_help is not called when no signature help is enabled.
12574    cx.set_state(
12575        &r#"
12576            fn main() {
12577                sampleˇ
12578            }
12579        "#
12580        .unindent(),
12581    );
12582    cx.update_editor(|editor, window, cx| {
12583        editor.handle_input("(", window, cx);
12584    });
12585    cx.assert_editor_state(
12586        &"
12587            fn main() {
12588                sample(ˇ)
12589            }
12590        "
12591        .unindent(),
12592    );
12593    cx.editor(|editor, _, _| {
12594        assert!(editor.signature_help_state.task().is_none());
12595    });
12596
12597    let mocked_response = lsp::SignatureHelp {
12598        signatures: vec![lsp::SignatureInformation {
12599            label: "fn sample(param1: u8, param2: u8)".to_string(),
12600            documentation: None,
12601            parameters: Some(vec![
12602                lsp::ParameterInformation {
12603                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12604                    documentation: None,
12605                },
12606                lsp::ParameterInformation {
12607                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12608                    documentation: None,
12609                },
12610            ]),
12611            active_parameter: None,
12612        }],
12613        active_signature: Some(0),
12614        active_parameter: Some(0),
12615    };
12616
12617    // Ensure that signature_help is called when enabled afte edits
12618    cx.update(|_, cx| {
12619        cx.update_global::<SettingsStore, _>(|settings, cx| {
12620            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12621                settings.auto_signature_help = Some(false);
12622                settings.show_signature_help_after_edits = Some(true);
12623            });
12624        });
12625    });
12626    cx.set_state(
12627        &r#"
12628            fn main() {
12629                sampleˇ
12630            }
12631        "#
12632        .unindent(),
12633    );
12634    cx.update_editor(|editor, window, cx| {
12635        editor.handle_input("(", window, cx);
12636    });
12637    cx.assert_editor_state(
12638        &"
12639            fn main() {
12640                sample(ˇ)
12641            }
12642        "
12643        .unindent(),
12644    );
12645    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12646    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12647        .await;
12648    cx.update_editor(|editor, _, _| {
12649        let signature_help_state = editor.signature_help_state.popover().cloned();
12650        assert!(signature_help_state.is_some());
12651        let signature = signature_help_state.unwrap();
12652        assert_eq!(
12653            signature.signatures[signature.current_signature].label,
12654            "fn sample(param1: u8, param2: u8)"
12655        );
12656        editor.signature_help_state = SignatureHelpState::default();
12657    });
12658
12659    // Ensure that signature_help is called when auto signature help override is enabled
12660    cx.update(|_, cx| {
12661        cx.update_global::<SettingsStore, _>(|settings, cx| {
12662            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12663                settings.auto_signature_help = Some(true);
12664                settings.show_signature_help_after_edits = Some(false);
12665            });
12666        });
12667    });
12668    cx.set_state(
12669        &r#"
12670            fn main() {
12671                sampleˇ
12672            }
12673        "#
12674        .unindent(),
12675    );
12676    cx.update_editor(|editor, window, cx| {
12677        editor.handle_input("(", window, cx);
12678    });
12679    cx.assert_editor_state(
12680        &"
12681            fn main() {
12682                sample(ˇ)
12683            }
12684        "
12685        .unindent(),
12686    );
12687    handle_signature_help_request(&mut cx, mocked_response).await;
12688    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12689        .await;
12690    cx.editor(|editor, _, _| {
12691        let signature_help_state = editor.signature_help_state.popover().cloned();
12692        assert!(signature_help_state.is_some());
12693        let signature = signature_help_state.unwrap();
12694        assert_eq!(
12695            signature.signatures[signature.current_signature].label,
12696            "fn sample(param1: u8, param2: u8)"
12697        );
12698    });
12699}
12700
12701#[gpui::test]
12702async fn test_signature_help(cx: &mut TestAppContext) {
12703    init_test(cx, |_| {});
12704    cx.update(|cx| {
12705        cx.update_global::<SettingsStore, _>(|settings, cx| {
12706            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12707                settings.auto_signature_help = Some(true);
12708            });
12709        });
12710    });
12711
12712    let mut cx = EditorLspTestContext::new_rust(
12713        lsp::ServerCapabilities {
12714            signature_help_provider: Some(lsp::SignatureHelpOptions {
12715                ..Default::default()
12716            }),
12717            ..Default::default()
12718        },
12719        cx,
12720    )
12721    .await;
12722
12723    // A test that directly calls `show_signature_help`
12724    cx.update_editor(|editor, window, cx| {
12725        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12726    });
12727
12728    let mocked_response = lsp::SignatureHelp {
12729        signatures: vec![lsp::SignatureInformation {
12730            label: "fn sample(param1: u8, param2: u8)".to_string(),
12731            documentation: None,
12732            parameters: Some(vec![
12733                lsp::ParameterInformation {
12734                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12735                    documentation: None,
12736                },
12737                lsp::ParameterInformation {
12738                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12739                    documentation: None,
12740                },
12741            ]),
12742            active_parameter: None,
12743        }],
12744        active_signature: Some(0),
12745        active_parameter: Some(0),
12746    };
12747    handle_signature_help_request(&mut cx, mocked_response).await;
12748
12749    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12750        .await;
12751
12752    cx.editor(|editor, _, _| {
12753        let signature_help_state = editor.signature_help_state.popover().cloned();
12754        assert!(signature_help_state.is_some());
12755        let signature = signature_help_state.unwrap();
12756        assert_eq!(
12757            signature.signatures[signature.current_signature].label,
12758            "fn sample(param1: u8, param2: u8)"
12759        );
12760    });
12761
12762    // When exiting outside from inside the brackets, `signature_help` is closed.
12763    cx.set_state(indoc! {"
12764        fn main() {
12765            sample(ˇ);
12766        }
12767
12768        fn sample(param1: u8, param2: u8) {}
12769    "});
12770
12771    cx.update_editor(|editor, window, cx| {
12772        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12773            s.select_ranges([0..0])
12774        });
12775    });
12776
12777    let mocked_response = lsp::SignatureHelp {
12778        signatures: Vec::new(),
12779        active_signature: None,
12780        active_parameter: None,
12781    };
12782    handle_signature_help_request(&mut cx, mocked_response).await;
12783
12784    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12785        .await;
12786
12787    cx.editor(|editor, _, _| {
12788        assert!(!editor.signature_help_state.is_shown());
12789    });
12790
12791    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12792    cx.set_state(indoc! {"
12793        fn main() {
12794            sample(ˇ);
12795        }
12796
12797        fn sample(param1: u8, param2: u8) {}
12798    "});
12799
12800    let mocked_response = lsp::SignatureHelp {
12801        signatures: vec![lsp::SignatureInformation {
12802            label: "fn sample(param1: u8, param2: u8)".to_string(),
12803            documentation: None,
12804            parameters: Some(vec![
12805                lsp::ParameterInformation {
12806                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12807                    documentation: None,
12808                },
12809                lsp::ParameterInformation {
12810                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12811                    documentation: None,
12812                },
12813            ]),
12814            active_parameter: None,
12815        }],
12816        active_signature: Some(0),
12817        active_parameter: Some(0),
12818    };
12819    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12820    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12821        .await;
12822    cx.editor(|editor, _, _| {
12823        assert!(editor.signature_help_state.is_shown());
12824    });
12825
12826    // Restore the popover with more parameter input
12827    cx.set_state(indoc! {"
12828        fn main() {
12829            sample(param1, param2ˇ);
12830        }
12831
12832        fn sample(param1: u8, param2: u8) {}
12833    "});
12834
12835    let mocked_response = lsp::SignatureHelp {
12836        signatures: vec![lsp::SignatureInformation {
12837            label: "fn sample(param1: u8, param2: u8)".to_string(),
12838            documentation: None,
12839            parameters: Some(vec![
12840                lsp::ParameterInformation {
12841                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12842                    documentation: None,
12843                },
12844                lsp::ParameterInformation {
12845                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12846                    documentation: None,
12847                },
12848            ]),
12849            active_parameter: None,
12850        }],
12851        active_signature: Some(0),
12852        active_parameter: Some(1),
12853    };
12854    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12855    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12856        .await;
12857
12858    // When selecting a range, the popover is gone.
12859    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12860    cx.update_editor(|editor, window, cx| {
12861        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12862            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12863        })
12864    });
12865    cx.assert_editor_state(indoc! {"
12866        fn main() {
12867            sample(param1, «ˇparam2»);
12868        }
12869
12870        fn sample(param1: u8, param2: u8) {}
12871    "});
12872    cx.editor(|editor, _, _| {
12873        assert!(!editor.signature_help_state.is_shown());
12874    });
12875
12876    // When unselecting again, the popover is back if within the brackets.
12877    cx.update_editor(|editor, window, cx| {
12878        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12879            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12880        })
12881    });
12882    cx.assert_editor_state(indoc! {"
12883        fn main() {
12884            sample(param1, ˇparam2);
12885        }
12886
12887        fn sample(param1: u8, param2: u8) {}
12888    "});
12889    handle_signature_help_request(&mut cx, mocked_response).await;
12890    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12891        .await;
12892    cx.editor(|editor, _, _| {
12893        assert!(editor.signature_help_state.is_shown());
12894    });
12895
12896    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
12897    cx.update_editor(|editor, window, cx| {
12898        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12899            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
12900            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12901        })
12902    });
12903    cx.assert_editor_state(indoc! {"
12904        fn main() {
12905            sample(param1, ˇparam2);
12906        }
12907
12908        fn sample(param1: u8, param2: u8) {}
12909    "});
12910
12911    let mocked_response = lsp::SignatureHelp {
12912        signatures: vec![lsp::SignatureInformation {
12913            label: "fn sample(param1: u8, param2: u8)".to_string(),
12914            documentation: None,
12915            parameters: Some(vec![
12916                lsp::ParameterInformation {
12917                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12918                    documentation: None,
12919                },
12920                lsp::ParameterInformation {
12921                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12922                    documentation: None,
12923                },
12924            ]),
12925            active_parameter: None,
12926        }],
12927        active_signature: Some(0),
12928        active_parameter: Some(1),
12929    };
12930    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12931    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12932        .await;
12933    cx.update_editor(|editor, _, cx| {
12934        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
12935    });
12936    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12937        .await;
12938    cx.update_editor(|editor, window, cx| {
12939        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12940            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12941        })
12942    });
12943    cx.assert_editor_state(indoc! {"
12944        fn main() {
12945            sample(param1, «ˇparam2»);
12946        }
12947
12948        fn sample(param1: u8, param2: u8) {}
12949    "});
12950    cx.update_editor(|editor, window, cx| {
12951        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12952            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12953        })
12954    });
12955    cx.assert_editor_state(indoc! {"
12956        fn main() {
12957            sample(param1, ˇparam2);
12958        }
12959
12960        fn sample(param1: u8, param2: u8) {}
12961    "});
12962    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
12963        .await;
12964}
12965
12966#[gpui::test]
12967async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
12968    init_test(cx, |_| {});
12969
12970    let mut cx = EditorLspTestContext::new_rust(
12971        lsp::ServerCapabilities {
12972            signature_help_provider: Some(lsp::SignatureHelpOptions {
12973                ..Default::default()
12974            }),
12975            ..Default::default()
12976        },
12977        cx,
12978    )
12979    .await;
12980
12981    cx.set_state(indoc! {"
12982        fn main() {
12983            overloadedˇ
12984        }
12985    "});
12986
12987    cx.update_editor(|editor, window, cx| {
12988        editor.handle_input("(", window, cx);
12989        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12990    });
12991
12992    // Mock response with 3 signatures
12993    let mocked_response = lsp::SignatureHelp {
12994        signatures: vec![
12995            lsp::SignatureInformation {
12996                label: "fn overloaded(x: i32)".to_string(),
12997                documentation: None,
12998                parameters: Some(vec![lsp::ParameterInformation {
12999                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13000                    documentation: None,
13001                }]),
13002                active_parameter: None,
13003            },
13004            lsp::SignatureInformation {
13005                label: "fn overloaded(x: i32, y: i32)".to_string(),
13006                documentation: None,
13007                parameters: Some(vec![
13008                    lsp::ParameterInformation {
13009                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13010                        documentation: None,
13011                    },
13012                    lsp::ParameterInformation {
13013                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13014                        documentation: None,
13015                    },
13016                ]),
13017                active_parameter: None,
13018            },
13019            lsp::SignatureInformation {
13020                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13021                documentation: None,
13022                parameters: Some(vec![
13023                    lsp::ParameterInformation {
13024                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13025                        documentation: None,
13026                    },
13027                    lsp::ParameterInformation {
13028                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13029                        documentation: None,
13030                    },
13031                    lsp::ParameterInformation {
13032                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13033                        documentation: None,
13034                    },
13035                ]),
13036                active_parameter: None,
13037            },
13038        ],
13039        active_signature: Some(1),
13040        active_parameter: Some(0),
13041    };
13042    handle_signature_help_request(&mut cx, mocked_response).await;
13043
13044    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13045        .await;
13046
13047    // Verify we have multiple signatures and the right one is selected
13048    cx.editor(|editor, _, _| {
13049        let popover = editor.signature_help_state.popover().cloned().unwrap();
13050        assert_eq!(popover.signatures.len(), 3);
13051        // active_signature was 1, so that should be the current
13052        assert_eq!(popover.current_signature, 1);
13053        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13054        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13055        assert_eq!(
13056            popover.signatures[2].label,
13057            "fn overloaded(x: i32, y: i32, z: i32)"
13058        );
13059    });
13060
13061    // Test navigation functionality
13062    cx.update_editor(|editor, window, cx| {
13063        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13064    });
13065
13066    cx.editor(|editor, _, _| {
13067        let popover = editor.signature_help_state.popover().cloned().unwrap();
13068        assert_eq!(popover.current_signature, 2);
13069    });
13070
13071    // Test wrap around
13072    cx.update_editor(|editor, window, cx| {
13073        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13074    });
13075
13076    cx.editor(|editor, _, _| {
13077        let popover = editor.signature_help_state.popover().cloned().unwrap();
13078        assert_eq!(popover.current_signature, 0);
13079    });
13080
13081    // Test previous navigation
13082    cx.update_editor(|editor, window, cx| {
13083        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13084    });
13085
13086    cx.editor(|editor, _, _| {
13087        let popover = editor.signature_help_state.popover().cloned().unwrap();
13088        assert_eq!(popover.current_signature, 2);
13089    });
13090}
13091
13092#[gpui::test]
13093async fn test_completion_mode(cx: &mut TestAppContext) {
13094    init_test(cx, |_| {});
13095    let mut cx = EditorLspTestContext::new_rust(
13096        lsp::ServerCapabilities {
13097            completion_provider: Some(lsp::CompletionOptions {
13098                resolve_provider: Some(true),
13099                ..Default::default()
13100            }),
13101            ..Default::default()
13102        },
13103        cx,
13104    )
13105    .await;
13106
13107    struct Run {
13108        run_description: &'static str,
13109        initial_state: String,
13110        buffer_marked_text: String,
13111        completion_label: &'static str,
13112        completion_text: &'static str,
13113        expected_with_insert_mode: String,
13114        expected_with_replace_mode: String,
13115        expected_with_replace_subsequence_mode: String,
13116        expected_with_replace_suffix_mode: String,
13117    }
13118
13119    let runs = [
13120        Run {
13121            run_description: "Start of word matches completion text",
13122            initial_state: "before ediˇ after".into(),
13123            buffer_marked_text: "before <edi|> after".into(),
13124            completion_label: "editor",
13125            completion_text: "editor",
13126            expected_with_insert_mode: "before editorˇ after".into(),
13127            expected_with_replace_mode: "before editorˇ after".into(),
13128            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13129            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13130        },
13131        Run {
13132            run_description: "Accept same text at the middle of the word",
13133            initial_state: "before ediˇtor after".into(),
13134            buffer_marked_text: "before <edi|tor> after".into(),
13135            completion_label: "editor",
13136            completion_text: "editor",
13137            expected_with_insert_mode: "before editorˇtor after".into(),
13138            expected_with_replace_mode: "before editorˇ after".into(),
13139            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13140            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13141        },
13142        Run {
13143            run_description: "End of word matches completion text -- cursor at end",
13144            initial_state: "before torˇ after".into(),
13145            buffer_marked_text: "before <tor|> after".into(),
13146            completion_label: "editor",
13147            completion_text: "editor",
13148            expected_with_insert_mode: "before editorˇ after".into(),
13149            expected_with_replace_mode: "before editorˇ after".into(),
13150            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13151            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13152        },
13153        Run {
13154            run_description: "End of word matches completion text -- cursor at start",
13155            initial_state: "before ˇtor after".into(),
13156            buffer_marked_text: "before <|tor> after".into(),
13157            completion_label: "editor",
13158            completion_text: "editor",
13159            expected_with_insert_mode: "before editorˇtor after".into(),
13160            expected_with_replace_mode: "before editorˇ after".into(),
13161            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13162            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13163        },
13164        Run {
13165            run_description: "Prepend text containing whitespace",
13166            initial_state: "pˇfield: bool".into(),
13167            buffer_marked_text: "<p|field>: bool".into(),
13168            completion_label: "pub ",
13169            completion_text: "pub ",
13170            expected_with_insert_mode: "pub ˇfield: bool".into(),
13171            expected_with_replace_mode: "pub ˇ: bool".into(),
13172            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13173            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13174        },
13175        Run {
13176            run_description: "Add element to start of list",
13177            initial_state: "[element_ˇelement_2]".into(),
13178            buffer_marked_text: "[<element_|element_2>]".into(),
13179            completion_label: "element_1",
13180            completion_text: "element_1",
13181            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13182            expected_with_replace_mode: "[element_1ˇ]".into(),
13183            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13184            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13185        },
13186        Run {
13187            run_description: "Add element to start of list -- first and second elements are equal",
13188            initial_state: "[elˇelement]".into(),
13189            buffer_marked_text: "[<el|element>]".into(),
13190            completion_label: "element",
13191            completion_text: "element",
13192            expected_with_insert_mode: "[elementˇelement]".into(),
13193            expected_with_replace_mode: "[elementˇ]".into(),
13194            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13195            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13196        },
13197        Run {
13198            run_description: "Ends with matching suffix",
13199            initial_state: "SubˇError".into(),
13200            buffer_marked_text: "<Sub|Error>".into(),
13201            completion_label: "SubscriptionError",
13202            completion_text: "SubscriptionError",
13203            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13204            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13205            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13206            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13207        },
13208        Run {
13209            run_description: "Suffix is a subsequence -- contiguous",
13210            initial_state: "SubˇErr".into(),
13211            buffer_marked_text: "<Sub|Err>".into(),
13212            completion_label: "SubscriptionError",
13213            completion_text: "SubscriptionError",
13214            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13215            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13216            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13217            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13218        },
13219        Run {
13220            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13221            initial_state: "Suˇscrirr".into(),
13222            buffer_marked_text: "<Su|scrirr>".into(),
13223            completion_label: "SubscriptionError",
13224            completion_text: "SubscriptionError",
13225            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13226            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13227            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13228            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13229        },
13230        Run {
13231            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13232            initial_state: "foo(indˇix)".into(),
13233            buffer_marked_text: "foo(<ind|ix>)".into(),
13234            completion_label: "node_index",
13235            completion_text: "node_index",
13236            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13237            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13238            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13239            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13240        },
13241        Run {
13242            run_description: "Replace range ends before cursor - should extend to cursor",
13243            initial_state: "before editˇo after".into(),
13244            buffer_marked_text: "before <{ed}>it|o after".into(),
13245            completion_label: "editor",
13246            completion_text: "editor",
13247            expected_with_insert_mode: "before editorˇo after".into(),
13248            expected_with_replace_mode: "before editorˇo after".into(),
13249            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13250            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13251        },
13252        Run {
13253            run_description: "Uses label for suffix matching",
13254            initial_state: "before ediˇtor after".into(),
13255            buffer_marked_text: "before <edi|tor> after".into(),
13256            completion_label: "editor",
13257            completion_text: "editor()",
13258            expected_with_insert_mode: "before editor()ˇtor after".into(),
13259            expected_with_replace_mode: "before editor()ˇ after".into(),
13260            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13261            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13262        },
13263        Run {
13264            run_description: "Case insensitive subsequence and suffix matching",
13265            initial_state: "before EDiˇtoR after".into(),
13266            buffer_marked_text: "before <EDi|toR> after".into(),
13267            completion_label: "editor",
13268            completion_text: "editor",
13269            expected_with_insert_mode: "before editorˇtoR after".into(),
13270            expected_with_replace_mode: "before editorˇ after".into(),
13271            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13272            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13273        },
13274    ];
13275
13276    for run in runs {
13277        let run_variations = [
13278            (LspInsertMode::Insert, run.expected_with_insert_mode),
13279            (LspInsertMode::Replace, run.expected_with_replace_mode),
13280            (
13281                LspInsertMode::ReplaceSubsequence,
13282                run.expected_with_replace_subsequence_mode,
13283            ),
13284            (
13285                LspInsertMode::ReplaceSuffix,
13286                run.expected_with_replace_suffix_mode,
13287            ),
13288        ];
13289
13290        for (lsp_insert_mode, expected_text) in run_variations {
13291            eprintln!(
13292                "run = {:?}, mode = {lsp_insert_mode:.?}",
13293                run.run_description,
13294            );
13295
13296            update_test_language_settings(&mut cx, |settings| {
13297                settings.defaults.completions = Some(CompletionSettings {
13298                    lsp_insert_mode,
13299                    words: WordsCompletionMode::Disabled,
13300                    words_min_length: 0,
13301                    lsp: true,
13302                    lsp_fetch_timeout_ms: 0,
13303                });
13304            });
13305
13306            cx.set_state(&run.initial_state);
13307            cx.update_editor(|editor, window, cx| {
13308                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13309            });
13310
13311            let counter = Arc::new(AtomicUsize::new(0));
13312            handle_completion_request_with_insert_and_replace(
13313                &mut cx,
13314                &run.buffer_marked_text,
13315                vec![(run.completion_label, run.completion_text)],
13316                counter.clone(),
13317            )
13318            .await;
13319            cx.condition(|editor, _| editor.context_menu_visible())
13320                .await;
13321            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13322
13323            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13324                editor
13325                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13326                    .unwrap()
13327            });
13328            cx.assert_editor_state(&expected_text);
13329            handle_resolve_completion_request(&mut cx, None).await;
13330            apply_additional_edits.await.unwrap();
13331        }
13332    }
13333}
13334
13335#[gpui::test]
13336async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13337    init_test(cx, |_| {});
13338    let mut cx = EditorLspTestContext::new_rust(
13339        lsp::ServerCapabilities {
13340            completion_provider: Some(lsp::CompletionOptions {
13341                resolve_provider: Some(true),
13342                ..Default::default()
13343            }),
13344            ..Default::default()
13345        },
13346        cx,
13347    )
13348    .await;
13349
13350    let initial_state = "SubˇError";
13351    let buffer_marked_text = "<Sub|Error>";
13352    let completion_text = "SubscriptionError";
13353    let expected_with_insert_mode = "SubscriptionErrorˇError";
13354    let expected_with_replace_mode = "SubscriptionErrorˇ";
13355
13356    update_test_language_settings(&mut cx, |settings| {
13357        settings.defaults.completions = Some(CompletionSettings {
13358            words: WordsCompletionMode::Disabled,
13359            words_min_length: 0,
13360            // set the opposite here to ensure that the action is overriding the default behavior
13361            lsp_insert_mode: LspInsertMode::Insert,
13362            lsp: true,
13363            lsp_fetch_timeout_ms: 0,
13364        });
13365    });
13366
13367    cx.set_state(initial_state);
13368    cx.update_editor(|editor, window, cx| {
13369        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13370    });
13371
13372    let counter = Arc::new(AtomicUsize::new(0));
13373    handle_completion_request_with_insert_and_replace(
13374        &mut cx,
13375        buffer_marked_text,
13376        vec![(completion_text, completion_text)],
13377        counter.clone(),
13378    )
13379    .await;
13380    cx.condition(|editor, _| editor.context_menu_visible())
13381        .await;
13382    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13383
13384    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13385        editor
13386            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13387            .unwrap()
13388    });
13389    cx.assert_editor_state(expected_with_replace_mode);
13390    handle_resolve_completion_request(&mut cx, None).await;
13391    apply_additional_edits.await.unwrap();
13392
13393    update_test_language_settings(&mut cx, |settings| {
13394        settings.defaults.completions = Some(CompletionSettings {
13395            words: WordsCompletionMode::Disabled,
13396            words_min_length: 0,
13397            // set the opposite here to ensure that the action is overriding the default behavior
13398            lsp_insert_mode: LspInsertMode::Replace,
13399            lsp: true,
13400            lsp_fetch_timeout_ms: 0,
13401        });
13402    });
13403
13404    cx.set_state(initial_state);
13405    cx.update_editor(|editor, window, cx| {
13406        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13407    });
13408    handle_completion_request_with_insert_and_replace(
13409        &mut cx,
13410        buffer_marked_text,
13411        vec![(completion_text, completion_text)],
13412        counter.clone(),
13413    )
13414    .await;
13415    cx.condition(|editor, _| editor.context_menu_visible())
13416        .await;
13417    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13418
13419    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13420        editor
13421            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13422            .unwrap()
13423    });
13424    cx.assert_editor_state(expected_with_insert_mode);
13425    handle_resolve_completion_request(&mut cx, None).await;
13426    apply_additional_edits.await.unwrap();
13427}
13428
13429#[gpui::test]
13430async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13431    init_test(cx, |_| {});
13432    let mut cx = EditorLspTestContext::new_rust(
13433        lsp::ServerCapabilities {
13434            completion_provider: Some(lsp::CompletionOptions {
13435                resolve_provider: Some(true),
13436                ..Default::default()
13437            }),
13438            ..Default::default()
13439        },
13440        cx,
13441    )
13442    .await;
13443
13444    // scenario: surrounding text matches completion text
13445    let completion_text = "to_offset";
13446    let initial_state = indoc! {"
13447        1. buf.to_offˇsuffix
13448        2. buf.to_offˇsuf
13449        3. buf.to_offˇfix
13450        4. buf.to_offˇ
13451        5. into_offˇensive
13452        6. ˇsuffix
13453        7. let ˇ //
13454        8. aaˇzz
13455        9. buf.to_off«zzzzzˇ»suffix
13456        10. buf.«ˇzzzzz»suffix
13457        11. to_off«ˇzzzzz»
13458
13459        buf.to_offˇsuffix  // newest cursor
13460    "};
13461    let completion_marked_buffer = indoc! {"
13462        1. buf.to_offsuffix
13463        2. buf.to_offsuf
13464        3. buf.to_offfix
13465        4. buf.to_off
13466        5. into_offensive
13467        6. suffix
13468        7. let  //
13469        8. aazz
13470        9. buf.to_offzzzzzsuffix
13471        10. buf.zzzzzsuffix
13472        11. to_offzzzzz
13473
13474        buf.<to_off|suffix>  // newest cursor
13475    "};
13476    let expected = indoc! {"
13477        1. buf.to_offsetˇ
13478        2. buf.to_offsetˇsuf
13479        3. buf.to_offsetˇfix
13480        4. buf.to_offsetˇ
13481        5. into_offsetˇensive
13482        6. to_offsetˇsuffix
13483        7. let to_offsetˇ //
13484        8. aato_offsetˇzz
13485        9. buf.to_offsetˇ
13486        10. buf.to_offsetˇsuffix
13487        11. to_offsetˇ
13488
13489        buf.to_offsetˇ  // newest cursor
13490    "};
13491    cx.set_state(initial_state);
13492    cx.update_editor(|editor, window, cx| {
13493        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13494    });
13495    handle_completion_request_with_insert_and_replace(
13496        &mut cx,
13497        completion_marked_buffer,
13498        vec![(completion_text, completion_text)],
13499        Arc::new(AtomicUsize::new(0)),
13500    )
13501    .await;
13502    cx.condition(|editor, _| editor.context_menu_visible())
13503        .await;
13504    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13505        editor
13506            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13507            .unwrap()
13508    });
13509    cx.assert_editor_state(expected);
13510    handle_resolve_completion_request(&mut cx, None).await;
13511    apply_additional_edits.await.unwrap();
13512
13513    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13514    let completion_text = "foo_and_bar";
13515    let initial_state = indoc! {"
13516        1. ooanbˇ
13517        2. zooanbˇ
13518        3. ooanbˇz
13519        4. zooanbˇz
13520        5. ooanˇ
13521        6. oanbˇ
13522
13523        ooanbˇ
13524    "};
13525    let completion_marked_buffer = indoc! {"
13526        1. ooanb
13527        2. zooanb
13528        3. ooanbz
13529        4. zooanbz
13530        5. ooan
13531        6. oanb
13532
13533        <ooanb|>
13534    "};
13535    let expected = indoc! {"
13536        1. foo_and_barˇ
13537        2. zfoo_and_barˇ
13538        3. foo_and_barˇz
13539        4. zfoo_and_barˇz
13540        5. ooanfoo_and_barˇ
13541        6. oanbfoo_and_barˇ
13542
13543        foo_and_barˇ
13544    "};
13545    cx.set_state(initial_state);
13546    cx.update_editor(|editor, window, cx| {
13547        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13548    });
13549    handle_completion_request_with_insert_and_replace(
13550        &mut cx,
13551        completion_marked_buffer,
13552        vec![(completion_text, completion_text)],
13553        Arc::new(AtomicUsize::new(0)),
13554    )
13555    .await;
13556    cx.condition(|editor, _| editor.context_menu_visible())
13557        .await;
13558    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13559        editor
13560            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13561            .unwrap()
13562    });
13563    cx.assert_editor_state(expected);
13564    handle_resolve_completion_request(&mut cx, None).await;
13565    apply_additional_edits.await.unwrap();
13566
13567    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13568    // (expects the same as if it was inserted at the end)
13569    let completion_text = "foo_and_bar";
13570    let initial_state = indoc! {"
13571        1. ooˇanb
13572        2. zooˇanb
13573        3. ooˇanbz
13574        4. zooˇanbz
13575
13576        ooˇanb
13577    "};
13578    let completion_marked_buffer = indoc! {"
13579        1. ooanb
13580        2. zooanb
13581        3. ooanbz
13582        4. zooanbz
13583
13584        <oo|anb>
13585    "};
13586    let expected = indoc! {"
13587        1. foo_and_barˇ
13588        2. zfoo_and_barˇ
13589        3. foo_and_barˇz
13590        4. zfoo_and_barˇz
13591
13592        foo_and_barˇ
13593    "};
13594    cx.set_state(initial_state);
13595    cx.update_editor(|editor, window, cx| {
13596        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13597    });
13598    handle_completion_request_with_insert_and_replace(
13599        &mut cx,
13600        completion_marked_buffer,
13601        vec![(completion_text, completion_text)],
13602        Arc::new(AtomicUsize::new(0)),
13603    )
13604    .await;
13605    cx.condition(|editor, _| editor.context_menu_visible())
13606        .await;
13607    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13608        editor
13609            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13610            .unwrap()
13611    });
13612    cx.assert_editor_state(expected);
13613    handle_resolve_completion_request(&mut cx, None).await;
13614    apply_additional_edits.await.unwrap();
13615}
13616
13617// This used to crash
13618#[gpui::test]
13619async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13620    init_test(cx, |_| {});
13621
13622    let buffer_text = indoc! {"
13623        fn main() {
13624            10.satu;
13625
13626            //
13627            // separate cursors so they open in different excerpts (manually reproducible)
13628            //
13629
13630            10.satu20;
13631        }
13632    "};
13633    let multibuffer_text_with_selections = indoc! {"
13634        fn main() {
13635            10.satuˇ;
13636
13637            //
13638
13639            //
13640
13641            10.satuˇ20;
13642        }
13643    "};
13644    let expected_multibuffer = indoc! {"
13645        fn main() {
13646            10.saturating_sub()ˇ;
13647
13648            //
13649
13650            //
13651
13652            10.saturating_sub()ˇ;
13653        }
13654    "};
13655
13656    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13657    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13658
13659    let fs = FakeFs::new(cx.executor());
13660    fs.insert_tree(
13661        path!("/a"),
13662        json!({
13663            "main.rs": buffer_text,
13664        }),
13665    )
13666    .await;
13667
13668    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13669    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13670    language_registry.add(rust_lang());
13671    let mut fake_servers = language_registry.register_fake_lsp(
13672        "Rust",
13673        FakeLspAdapter {
13674            capabilities: lsp::ServerCapabilities {
13675                completion_provider: Some(lsp::CompletionOptions {
13676                    resolve_provider: None,
13677                    ..lsp::CompletionOptions::default()
13678                }),
13679                ..lsp::ServerCapabilities::default()
13680            },
13681            ..FakeLspAdapter::default()
13682        },
13683    );
13684    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13685    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13686    let buffer = project
13687        .update(cx, |project, cx| {
13688            project.open_local_buffer(path!("/a/main.rs"), cx)
13689        })
13690        .await
13691        .unwrap();
13692
13693    let multi_buffer = cx.new(|cx| {
13694        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13695        multi_buffer.push_excerpts(
13696            buffer.clone(),
13697            [ExcerptRange::new(0..first_excerpt_end)],
13698            cx,
13699        );
13700        multi_buffer.push_excerpts(
13701            buffer.clone(),
13702            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13703            cx,
13704        );
13705        multi_buffer
13706    });
13707
13708    let editor = workspace
13709        .update(cx, |_, window, cx| {
13710            cx.new(|cx| {
13711                Editor::new(
13712                    EditorMode::Full {
13713                        scale_ui_elements_with_buffer_font_size: false,
13714                        show_active_line_background: false,
13715                        sized_by_content: false,
13716                    },
13717                    multi_buffer.clone(),
13718                    Some(project.clone()),
13719                    window,
13720                    cx,
13721                )
13722            })
13723        })
13724        .unwrap();
13725
13726    let pane = workspace
13727        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13728        .unwrap();
13729    pane.update_in(cx, |pane, window, cx| {
13730        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13731    });
13732
13733    let fake_server = fake_servers.next().await.unwrap();
13734
13735    editor.update_in(cx, |editor, window, cx| {
13736        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13737            s.select_ranges([
13738                Point::new(1, 11)..Point::new(1, 11),
13739                Point::new(7, 11)..Point::new(7, 11),
13740            ])
13741        });
13742
13743        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13744    });
13745
13746    editor.update_in(cx, |editor, window, cx| {
13747        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13748    });
13749
13750    fake_server
13751        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13752            let completion_item = lsp::CompletionItem {
13753                label: "saturating_sub()".into(),
13754                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13755                    lsp::InsertReplaceEdit {
13756                        new_text: "saturating_sub()".to_owned(),
13757                        insert: lsp::Range::new(
13758                            lsp::Position::new(7, 7),
13759                            lsp::Position::new(7, 11),
13760                        ),
13761                        replace: lsp::Range::new(
13762                            lsp::Position::new(7, 7),
13763                            lsp::Position::new(7, 13),
13764                        ),
13765                    },
13766                )),
13767                ..lsp::CompletionItem::default()
13768            };
13769
13770            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13771        })
13772        .next()
13773        .await
13774        .unwrap();
13775
13776    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13777        .await;
13778
13779    editor
13780        .update_in(cx, |editor, window, cx| {
13781            editor
13782                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13783                .unwrap()
13784        })
13785        .await
13786        .unwrap();
13787
13788    editor.update(cx, |editor, cx| {
13789        assert_text_with_selections(editor, expected_multibuffer, cx);
13790    })
13791}
13792
13793#[gpui::test]
13794async fn test_completion(cx: &mut TestAppContext) {
13795    init_test(cx, |_| {});
13796
13797    let mut cx = EditorLspTestContext::new_rust(
13798        lsp::ServerCapabilities {
13799            completion_provider: Some(lsp::CompletionOptions {
13800                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13801                resolve_provider: Some(true),
13802                ..Default::default()
13803            }),
13804            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13805            ..Default::default()
13806        },
13807        cx,
13808    )
13809    .await;
13810    let counter = Arc::new(AtomicUsize::new(0));
13811
13812    cx.set_state(indoc! {"
13813        oneˇ
13814        two
13815        three
13816    "});
13817    cx.simulate_keystroke(".");
13818    handle_completion_request(
13819        indoc! {"
13820            one.|<>
13821            two
13822            three
13823        "},
13824        vec!["first_completion", "second_completion"],
13825        true,
13826        counter.clone(),
13827        &mut cx,
13828    )
13829    .await;
13830    cx.condition(|editor, _| editor.context_menu_visible())
13831        .await;
13832    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13833
13834    let _handler = handle_signature_help_request(
13835        &mut cx,
13836        lsp::SignatureHelp {
13837            signatures: vec![lsp::SignatureInformation {
13838                label: "test signature".to_string(),
13839                documentation: None,
13840                parameters: Some(vec![lsp::ParameterInformation {
13841                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13842                    documentation: None,
13843                }]),
13844                active_parameter: None,
13845            }],
13846            active_signature: None,
13847            active_parameter: None,
13848        },
13849    );
13850    cx.update_editor(|editor, window, cx| {
13851        assert!(
13852            !editor.signature_help_state.is_shown(),
13853            "No signature help was called for"
13854        );
13855        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13856    });
13857    cx.run_until_parked();
13858    cx.update_editor(|editor, _, _| {
13859        assert!(
13860            !editor.signature_help_state.is_shown(),
13861            "No signature help should be shown when completions menu is open"
13862        );
13863    });
13864
13865    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13866        editor.context_menu_next(&Default::default(), window, cx);
13867        editor
13868            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13869            .unwrap()
13870    });
13871    cx.assert_editor_state(indoc! {"
13872        one.second_completionˇ
13873        two
13874        three
13875    "});
13876
13877    handle_resolve_completion_request(
13878        &mut cx,
13879        Some(vec![
13880            (
13881                //This overlaps with the primary completion edit which is
13882                //misbehavior from the LSP spec, test that we filter it out
13883                indoc! {"
13884                    one.second_ˇcompletion
13885                    two
13886                    threeˇ
13887                "},
13888                "overlapping additional edit",
13889            ),
13890            (
13891                indoc! {"
13892                    one.second_completion
13893                    two
13894                    threeˇ
13895                "},
13896                "\nadditional edit",
13897            ),
13898        ]),
13899    )
13900    .await;
13901    apply_additional_edits.await.unwrap();
13902    cx.assert_editor_state(indoc! {"
13903        one.second_completionˇ
13904        two
13905        three
13906        additional edit
13907    "});
13908
13909    cx.set_state(indoc! {"
13910        one.second_completion
13911        twoˇ
13912        threeˇ
13913        additional edit
13914    "});
13915    cx.simulate_keystroke(" ");
13916    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13917    cx.simulate_keystroke("s");
13918    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13919
13920    cx.assert_editor_state(indoc! {"
13921        one.second_completion
13922        two sˇ
13923        three sˇ
13924        additional edit
13925    "});
13926    handle_completion_request(
13927        indoc! {"
13928            one.second_completion
13929            two s
13930            three <s|>
13931            additional edit
13932        "},
13933        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13934        true,
13935        counter.clone(),
13936        &mut cx,
13937    )
13938    .await;
13939    cx.condition(|editor, _| editor.context_menu_visible())
13940        .await;
13941    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13942
13943    cx.simulate_keystroke("i");
13944
13945    handle_completion_request(
13946        indoc! {"
13947            one.second_completion
13948            two si
13949            three <si|>
13950            additional edit
13951        "},
13952        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13953        true,
13954        counter.clone(),
13955        &mut cx,
13956    )
13957    .await;
13958    cx.condition(|editor, _| editor.context_menu_visible())
13959        .await;
13960    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13961
13962    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13963        editor
13964            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13965            .unwrap()
13966    });
13967    cx.assert_editor_state(indoc! {"
13968        one.second_completion
13969        two sixth_completionˇ
13970        three sixth_completionˇ
13971        additional edit
13972    "});
13973
13974    apply_additional_edits.await.unwrap();
13975
13976    update_test_language_settings(&mut cx, |settings| {
13977        settings.defaults.show_completions_on_input = Some(false);
13978    });
13979    cx.set_state("editorˇ");
13980    cx.simulate_keystroke(".");
13981    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13982    cx.simulate_keystrokes("c l o");
13983    cx.assert_editor_state("editor.cloˇ");
13984    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13985    cx.update_editor(|editor, window, cx| {
13986        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13987    });
13988    handle_completion_request(
13989        "editor.<clo|>",
13990        vec!["close", "clobber"],
13991        true,
13992        counter.clone(),
13993        &mut cx,
13994    )
13995    .await;
13996    cx.condition(|editor, _| editor.context_menu_visible())
13997        .await;
13998    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
13999
14000    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14001        editor
14002            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14003            .unwrap()
14004    });
14005    cx.assert_editor_state("editor.clobberˇ");
14006    handle_resolve_completion_request(&mut cx, None).await;
14007    apply_additional_edits.await.unwrap();
14008}
14009
14010#[gpui::test]
14011async fn test_completion_reuse(cx: &mut TestAppContext) {
14012    init_test(cx, |_| {});
14013
14014    let mut cx = EditorLspTestContext::new_rust(
14015        lsp::ServerCapabilities {
14016            completion_provider: Some(lsp::CompletionOptions {
14017                trigger_characters: Some(vec![".".to_string()]),
14018                ..Default::default()
14019            }),
14020            ..Default::default()
14021        },
14022        cx,
14023    )
14024    .await;
14025
14026    let counter = Arc::new(AtomicUsize::new(0));
14027    cx.set_state("objˇ");
14028    cx.simulate_keystroke(".");
14029
14030    // Initial completion request returns complete results
14031    let is_incomplete = false;
14032    handle_completion_request(
14033        "obj.|<>",
14034        vec!["a", "ab", "abc"],
14035        is_incomplete,
14036        counter.clone(),
14037        &mut cx,
14038    )
14039    .await;
14040    cx.run_until_parked();
14041    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14042    cx.assert_editor_state("obj.ˇ");
14043    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14044
14045    // Type "a" - filters existing completions
14046    cx.simulate_keystroke("a");
14047    cx.run_until_parked();
14048    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14049    cx.assert_editor_state("obj.aˇ");
14050    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14051
14052    // Type "b" - filters existing completions
14053    cx.simulate_keystroke("b");
14054    cx.run_until_parked();
14055    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14056    cx.assert_editor_state("obj.abˇ");
14057    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14058
14059    // Type "c" - filters existing completions
14060    cx.simulate_keystroke("c");
14061    cx.run_until_parked();
14062    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14063    cx.assert_editor_state("obj.abcˇ");
14064    check_displayed_completions(vec!["abc"], &mut cx);
14065
14066    // Backspace to delete "c" - filters existing completions
14067    cx.update_editor(|editor, window, cx| {
14068        editor.backspace(&Backspace, window, cx);
14069    });
14070    cx.run_until_parked();
14071    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14072    cx.assert_editor_state("obj.abˇ");
14073    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14074
14075    // Moving cursor to the left dismisses menu.
14076    cx.update_editor(|editor, window, cx| {
14077        editor.move_left(&MoveLeft, window, cx);
14078    });
14079    cx.run_until_parked();
14080    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14081    cx.assert_editor_state("obj.aˇb");
14082    cx.update_editor(|editor, _, _| {
14083        assert_eq!(editor.context_menu_visible(), false);
14084    });
14085
14086    // Type "b" - new request
14087    cx.simulate_keystroke("b");
14088    let is_incomplete = false;
14089    handle_completion_request(
14090        "obj.<ab|>a",
14091        vec!["ab", "abc"],
14092        is_incomplete,
14093        counter.clone(),
14094        &mut cx,
14095    )
14096    .await;
14097    cx.run_until_parked();
14098    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14099    cx.assert_editor_state("obj.abˇb");
14100    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14101
14102    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14103    cx.update_editor(|editor, window, cx| {
14104        editor.backspace(&Backspace, window, cx);
14105    });
14106    let is_incomplete = false;
14107    handle_completion_request(
14108        "obj.<a|>b",
14109        vec!["a", "ab", "abc"],
14110        is_incomplete,
14111        counter.clone(),
14112        &mut cx,
14113    )
14114    .await;
14115    cx.run_until_parked();
14116    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14117    cx.assert_editor_state("obj.aˇb");
14118    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14119
14120    // Backspace to delete "a" - dismisses menu.
14121    cx.update_editor(|editor, window, cx| {
14122        editor.backspace(&Backspace, window, cx);
14123    });
14124    cx.run_until_parked();
14125    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14126    cx.assert_editor_state("obj.ˇb");
14127    cx.update_editor(|editor, _, _| {
14128        assert_eq!(editor.context_menu_visible(), false);
14129    });
14130}
14131
14132#[gpui::test]
14133async fn test_word_completion(cx: &mut TestAppContext) {
14134    let lsp_fetch_timeout_ms = 10;
14135    init_test(cx, |language_settings| {
14136        language_settings.defaults.completions = Some(CompletionSettings {
14137            words: WordsCompletionMode::Fallback,
14138            words_min_length: 0,
14139            lsp: true,
14140            lsp_fetch_timeout_ms: 10,
14141            lsp_insert_mode: LspInsertMode::Insert,
14142        });
14143    });
14144
14145    let mut cx = EditorLspTestContext::new_rust(
14146        lsp::ServerCapabilities {
14147            completion_provider: Some(lsp::CompletionOptions {
14148                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14149                ..lsp::CompletionOptions::default()
14150            }),
14151            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14152            ..lsp::ServerCapabilities::default()
14153        },
14154        cx,
14155    )
14156    .await;
14157
14158    let throttle_completions = Arc::new(AtomicBool::new(false));
14159
14160    let lsp_throttle_completions = throttle_completions.clone();
14161    let _completion_requests_handler =
14162        cx.lsp
14163            .server
14164            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14165                let lsp_throttle_completions = lsp_throttle_completions.clone();
14166                let cx = cx.clone();
14167                async move {
14168                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14169                        cx.background_executor()
14170                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14171                            .await;
14172                    }
14173                    Ok(Some(lsp::CompletionResponse::Array(vec![
14174                        lsp::CompletionItem {
14175                            label: "first".into(),
14176                            ..lsp::CompletionItem::default()
14177                        },
14178                        lsp::CompletionItem {
14179                            label: "last".into(),
14180                            ..lsp::CompletionItem::default()
14181                        },
14182                    ])))
14183                }
14184            });
14185
14186    cx.set_state(indoc! {"
14187        oneˇ
14188        two
14189        three
14190    "});
14191    cx.simulate_keystroke(".");
14192    cx.executor().run_until_parked();
14193    cx.condition(|editor, _| editor.context_menu_visible())
14194        .await;
14195    cx.update_editor(|editor, window, cx| {
14196        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14197        {
14198            assert_eq!(
14199                completion_menu_entries(menu),
14200                &["first", "last"],
14201                "When LSP server is fast to reply, no fallback word completions are used"
14202            );
14203        } else {
14204            panic!("expected completion menu to be open");
14205        }
14206        editor.cancel(&Cancel, window, cx);
14207    });
14208    cx.executor().run_until_parked();
14209    cx.condition(|editor, _| !editor.context_menu_visible())
14210        .await;
14211
14212    throttle_completions.store(true, atomic::Ordering::Release);
14213    cx.simulate_keystroke(".");
14214    cx.executor()
14215        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14216    cx.executor().run_until_parked();
14217    cx.condition(|editor, _| editor.context_menu_visible())
14218        .await;
14219    cx.update_editor(|editor, _, _| {
14220        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14221        {
14222            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14223                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14224        } else {
14225            panic!("expected completion menu to be open");
14226        }
14227    });
14228}
14229
14230#[gpui::test]
14231async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14232    init_test(cx, |language_settings| {
14233        language_settings.defaults.completions = Some(CompletionSettings {
14234            words: WordsCompletionMode::Enabled,
14235            words_min_length: 0,
14236            lsp: true,
14237            lsp_fetch_timeout_ms: 0,
14238            lsp_insert_mode: LspInsertMode::Insert,
14239        });
14240    });
14241
14242    let mut cx = EditorLspTestContext::new_rust(
14243        lsp::ServerCapabilities {
14244            completion_provider: Some(lsp::CompletionOptions {
14245                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14246                ..lsp::CompletionOptions::default()
14247            }),
14248            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14249            ..lsp::ServerCapabilities::default()
14250        },
14251        cx,
14252    )
14253    .await;
14254
14255    let _completion_requests_handler =
14256        cx.lsp
14257            .server
14258            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14259                Ok(Some(lsp::CompletionResponse::Array(vec![
14260                    lsp::CompletionItem {
14261                        label: "first".into(),
14262                        ..lsp::CompletionItem::default()
14263                    },
14264                    lsp::CompletionItem {
14265                        label: "last".into(),
14266                        ..lsp::CompletionItem::default()
14267                    },
14268                ])))
14269            });
14270
14271    cx.set_state(indoc! {"ˇ
14272        first
14273        last
14274        second
14275    "});
14276    cx.simulate_keystroke(".");
14277    cx.executor().run_until_parked();
14278    cx.condition(|editor, _| editor.context_menu_visible())
14279        .await;
14280    cx.update_editor(|editor, _, _| {
14281        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14282        {
14283            assert_eq!(
14284                completion_menu_entries(menu),
14285                &["first", "last", "second"],
14286                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14287            );
14288        } else {
14289            panic!("expected completion menu to be open");
14290        }
14291    });
14292}
14293
14294#[gpui::test]
14295async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14296    init_test(cx, |language_settings| {
14297        language_settings.defaults.completions = Some(CompletionSettings {
14298            words: WordsCompletionMode::Disabled,
14299            words_min_length: 0,
14300            lsp: true,
14301            lsp_fetch_timeout_ms: 0,
14302            lsp_insert_mode: LspInsertMode::Insert,
14303        });
14304    });
14305
14306    let mut cx = EditorLspTestContext::new_rust(
14307        lsp::ServerCapabilities {
14308            completion_provider: Some(lsp::CompletionOptions {
14309                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14310                ..lsp::CompletionOptions::default()
14311            }),
14312            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14313            ..lsp::ServerCapabilities::default()
14314        },
14315        cx,
14316    )
14317    .await;
14318
14319    let _completion_requests_handler =
14320        cx.lsp
14321            .server
14322            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14323                panic!("LSP completions should not be queried when dealing with word completions")
14324            });
14325
14326    cx.set_state(indoc! {"ˇ
14327        first
14328        last
14329        second
14330    "});
14331    cx.update_editor(|editor, window, cx| {
14332        editor.show_word_completions(&ShowWordCompletions, window, cx);
14333    });
14334    cx.executor().run_until_parked();
14335    cx.condition(|editor, _| editor.context_menu_visible())
14336        .await;
14337    cx.update_editor(|editor, _, _| {
14338        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14339        {
14340            assert_eq!(
14341                completion_menu_entries(menu),
14342                &["first", "last", "second"],
14343                "`ShowWordCompletions` action should show word completions"
14344            );
14345        } else {
14346            panic!("expected completion menu to be open");
14347        }
14348    });
14349
14350    cx.simulate_keystroke("l");
14351    cx.executor().run_until_parked();
14352    cx.condition(|editor, _| editor.context_menu_visible())
14353        .await;
14354    cx.update_editor(|editor, _, _| {
14355        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14356        {
14357            assert_eq!(
14358                completion_menu_entries(menu),
14359                &["last"],
14360                "After showing word completions, further editing should filter them and not query the LSP"
14361            );
14362        } else {
14363            panic!("expected completion menu to be open");
14364        }
14365    });
14366}
14367
14368#[gpui::test]
14369async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14370    init_test(cx, |language_settings| {
14371        language_settings.defaults.completions = Some(CompletionSettings {
14372            words: WordsCompletionMode::Fallback,
14373            words_min_length: 0,
14374            lsp: false,
14375            lsp_fetch_timeout_ms: 0,
14376            lsp_insert_mode: LspInsertMode::Insert,
14377        });
14378    });
14379
14380    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14381
14382    cx.set_state(indoc! {"ˇ
14383        0_usize
14384        let
14385        33
14386        4.5f32
14387    "});
14388    cx.update_editor(|editor, window, cx| {
14389        editor.show_completions(&ShowCompletions::default(), window, cx);
14390    });
14391    cx.executor().run_until_parked();
14392    cx.condition(|editor, _| editor.context_menu_visible())
14393        .await;
14394    cx.update_editor(|editor, window, cx| {
14395        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14396        {
14397            assert_eq!(
14398                completion_menu_entries(menu),
14399                &["let"],
14400                "With no digits in the completion query, no digits should be in the word completions"
14401            );
14402        } else {
14403            panic!("expected completion menu to be open");
14404        }
14405        editor.cancel(&Cancel, window, cx);
14406    });
14407
14408    cx.set_state(indoc! {"14409        0_usize
14410        let
14411        3
14412        33.35f32
14413    "});
14414    cx.update_editor(|editor, window, cx| {
14415        editor.show_completions(&ShowCompletions::default(), window, cx);
14416    });
14417    cx.executor().run_until_parked();
14418    cx.condition(|editor, _| editor.context_menu_visible())
14419        .await;
14420    cx.update_editor(|editor, _, _| {
14421        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14422        {
14423            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14424                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14425        } else {
14426            panic!("expected completion menu to be open");
14427        }
14428    });
14429}
14430
14431#[gpui::test]
14432async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14433    init_test(cx, |language_settings| {
14434        language_settings.defaults.completions = Some(CompletionSettings {
14435            words: WordsCompletionMode::Enabled,
14436            words_min_length: 3,
14437            lsp: true,
14438            lsp_fetch_timeout_ms: 0,
14439            lsp_insert_mode: LspInsertMode::Insert,
14440        });
14441    });
14442
14443    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14444    cx.set_state(indoc! {"ˇ
14445        wow
14446        wowen
14447        wowser
14448    "});
14449    cx.simulate_keystroke("w");
14450    cx.executor().run_until_parked();
14451    cx.update_editor(|editor, _, _| {
14452        if editor.context_menu.borrow_mut().is_some() {
14453            panic!(
14454                "expected completion menu to be hidden, as words completion threshold is not met"
14455            );
14456        }
14457    });
14458
14459    cx.update_editor(|editor, window, cx| {
14460        editor.show_word_completions(&ShowWordCompletions, window, cx);
14461    });
14462    cx.executor().run_until_parked();
14463    cx.update_editor(|editor, window, cx| {
14464        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14465        {
14466            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");
14467        } else {
14468            panic!("expected completion menu to be open after the word completions are called with an action");
14469        }
14470
14471        editor.cancel(&Cancel, window, cx);
14472    });
14473    cx.update_editor(|editor, _, _| {
14474        if editor.context_menu.borrow_mut().is_some() {
14475            panic!("expected completion menu to be hidden after canceling");
14476        }
14477    });
14478
14479    cx.simulate_keystroke("o");
14480    cx.executor().run_until_parked();
14481    cx.update_editor(|editor, _, _| {
14482        if editor.context_menu.borrow_mut().is_some() {
14483            panic!(
14484                "expected completion menu to be hidden, as words completion threshold is not met still"
14485            );
14486        }
14487    });
14488
14489    cx.simulate_keystroke("w");
14490    cx.executor().run_until_parked();
14491    cx.update_editor(|editor, _, _| {
14492        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14493        {
14494            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14495        } else {
14496            panic!("expected completion menu to be open after the word completions threshold is met");
14497        }
14498    });
14499}
14500
14501#[gpui::test]
14502async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14503    init_test(cx, |language_settings| {
14504        language_settings.defaults.completions = Some(CompletionSettings {
14505            words: WordsCompletionMode::Enabled,
14506            words_min_length: 0,
14507            lsp: true,
14508            lsp_fetch_timeout_ms: 0,
14509            lsp_insert_mode: LspInsertMode::Insert,
14510        });
14511    });
14512
14513    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14514    cx.update_editor(|editor, _, _| {
14515        editor.disable_word_completions();
14516    });
14517    cx.set_state(indoc! {"ˇ
14518        wow
14519        wowen
14520        wowser
14521    "});
14522    cx.simulate_keystroke("w");
14523    cx.executor().run_until_parked();
14524    cx.update_editor(|editor, _, _| {
14525        if editor.context_menu.borrow_mut().is_some() {
14526            panic!(
14527                "expected completion menu to be hidden, as words completion are disabled for this editor"
14528            );
14529        }
14530    });
14531
14532    cx.update_editor(|editor, window, cx| {
14533        editor.show_word_completions(&ShowWordCompletions, window, cx);
14534    });
14535    cx.executor().run_until_parked();
14536    cx.update_editor(|editor, _, _| {
14537        if editor.context_menu.borrow_mut().is_some() {
14538            panic!(
14539                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14540            );
14541        }
14542    });
14543}
14544
14545fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14546    let position = || lsp::Position {
14547        line: params.text_document_position.position.line,
14548        character: params.text_document_position.position.character,
14549    };
14550    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14551        range: lsp::Range {
14552            start: position(),
14553            end: position(),
14554        },
14555        new_text: text.to_string(),
14556    }))
14557}
14558
14559#[gpui::test]
14560async fn test_multiline_completion(cx: &mut TestAppContext) {
14561    init_test(cx, |_| {});
14562
14563    let fs = FakeFs::new(cx.executor());
14564    fs.insert_tree(
14565        path!("/a"),
14566        json!({
14567            "main.ts": "a",
14568        }),
14569    )
14570    .await;
14571
14572    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14573    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14574    let typescript_language = Arc::new(Language::new(
14575        LanguageConfig {
14576            name: "TypeScript".into(),
14577            matcher: LanguageMatcher {
14578                path_suffixes: vec!["ts".to_string()],
14579                ..LanguageMatcher::default()
14580            },
14581            line_comments: vec!["// ".into()],
14582            ..LanguageConfig::default()
14583        },
14584        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14585    ));
14586    language_registry.add(typescript_language.clone());
14587    let mut fake_servers = language_registry.register_fake_lsp(
14588        "TypeScript",
14589        FakeLspAdapter {
14590            capabilities: lsp::ServerCapabilities {
14591                completion_provider: Some(lsp::CompletionOptions {
14592                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14593                    ..lsp::CompletionOptions::default()
14594                }),
14595                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14596                ..lsp::ServerCapabilities::default()
14597            },
14598            // Emulate vtsls label generation
14599            label_for_completion: Some(Box::new(|item, _| {
14600                let text = if let Some(description) = item
14601                    .label_details
14602                    .as_ref()
14603                    .and_then(|label_details| label_details.description.as_ref())
14604                {
14605                    format!("{} {}", item.label, description)
14606                } else if let Some(detail) = &item.detail {
14607                    format!("{} {}", item.label, detail)
14608                } else {
14609                    item.label.clone()
14610                };
14611                let len = text.len();
14612                Some(language::CodeLabel {
14613                    text,
14614                    runs: Vec::new(),
14615                    filter_range: 0..len,
14616                })
14617            })),
14618            ..FakeLspAdapter::default()
14619        },
14620    );
14621    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14622    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14623    let worktree_id = workspace
14624        .update(cx, |workspace, _window, cx| {
14625            workspace.project().update(cx, |project, cx| {
14626                project.worktrees(cx).next().unwrap().read(cx).id()
14627            })
14628        })
14629        .unwrap();
14630    let _buffer = project
14631        .update(cx, |project, cx| {
14632            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14633        })
14634        .await
14635        .unwrap();
14636    let editor = workspace
14637        .update(cx, |workspace, window, cx| {
14638            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14639        })
14640        .unwrap()
14641        .await
14642        .unwrap()
14643        .downcast::<Editor>()
14644        .unwrap();
14645    let fake_server = fake_servers.next().await.unwrap();
14646
14647    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14648    let multiline_label_2 = "a\nb\nc\n";
14649    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14650    let multiline_description = "d\ne\nf\n";
14651    let multiline_detail_2 = "g\nh\ni\n";
14652
14653    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14654        move |params, _| async move {
14655            Ok(Some(lsp::CompletionResponse::Array(vec![
14656                lsp::CompletionItem {
14657                    label: multiline_label.to_string(),
14658                    text_edit: gen_text_edit(&params, "new_text_1"),
14659                    ..lsp::CompletionItem::default()
14660                },
14661                lsp::CompletionItem {
14662                    label: "single line label 1".to_string(),
14663                    detail: Some(multiline_detail.to_string()),
14664                    text_edit: gen_text_edit(&params, "new_text_2"),
14665                    ..lsp::CompletionItem::default()
14666                },
14667                lsp::CompletionItem {
14668                    label: "single line label 2".to_string(),
14669                    label_details: Some(lsp::CompletionItemLabelDetails {
14670                        description: Some(multiline_description.to_string()),
14671                        detail: None,
14672                    }),
14673                    text_edit: gen_text_edit(&params, "new_text_2"),
14674                    ..lsp::CompletionItem::default()
14675                },
14676                lsp::CompletionItem {
14677                    label: multiline_label_2.to_string(),
14678                    detail: Some(multiline_detail_2.to_string()),
14679                    text_edit: gen_text_edit(&params, "new_text_3"),
14680                    ..lsp::CompletionItem::default()
14681                },
14682                lsp::CompletionItem {
14683                    label: "Label with many     spaces and \t but without newlines".to_string(),
14684                    detail: Some(
14685                        "Details with many     spaces and \t but without newlines".to_string(),
14686                    ),
14687                    text_edit: gen_text_edit(&params, "new_text_4"),
14688                    ..lsp::CompletionItem::default()
14689                },
14690            ])))
14691        },
14692    );
14693
14694    editor.update_in(cx, |editor, window, cx| {
14695        cx.focus_self(window);
14696        editor.move_to_end(&MoveToEnd, window, cx);
14697        editor.handle_input(".", window, cx);
14698    });
14699    cx.run_until_parked();
14700    completion_handle.next().await.unwrap();
14701
14702    editor.update(cx, |editor, _| {
14703        assert!(editor.context_menu_visible());
14704        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14705        {
14706            let completion_labels = menu
14707                .completions
14708                .borrow()
14709                .iter()
14710                .map(|c| c.label.text.clone())
14711                .collect::<Vec<_>>();
14712            assert_eq!(
14713                completion_labels,
14714                &[
14715                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14716                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14717                    "single line label 2 d e f ",
14718                    "a b c g h i ",
14719                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14720                ],
14721                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14722            );
14723
14724            for completion in menu
14725                .completions
14726                .borrow()
14727                .iter() {
14728                    assert_eq!(
14729                        completion.label.filter_range,
14730                        0..completion.label.text.len(),
14731                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14732                    );
14733                }
14734        } else {
14735            panic!("expected completion menu to be open");
14736        }
14737    });
14738}
14739
14740#[gpui::test]
14741async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14742    init_test(cx, |_| {});
14743    let mut cx = EditorLspTestContext::new_rust(
14744        lsp::ServerCapabilities {
14745            completion_provider: Some(lsp::CompletionOptions {
14746                trigger_characters: Some(vec![".".to_string()]),
14747                ..Default::default()
14748            }),
14749            ..Default::default()
14750        },
14751        cx,
14752    )
14753    .await;
14754    cx.lsp
14755        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14756            Ok(Some(lsp::CompletionResponse::Array(vec![
14757                lsp::CompletionItem {
14758                    label: "first".into(),
14759                    ..Default::default()
14760                },
14761                lsp::CompletionItem {
14762                    label: "last".into(),
14763                    ..Default::default()
14764                },
14765            ])))
14766        });
14767    cx.set_state("variableˇ");
14768    cx.simulate_keystroke(".");
14769    cx.executor().run_until_parked();
14770
14771    cx.update_editor(|editor, _, _| {
14772        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14773        {
14774            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14775        } else {
14776            panic!("expected completion menu to be open");
14777        }
14778    });
14779
14780    cx.update_editor(|editor, window, cx| {
14781        editor.move_page_down(&MovePageDown::default(), window, cx);
14782        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14783        {
14784            assert!(
14785                menu.selected_item == 1,
14786                "expected PageDown to select the last item from the context menu"
14787            );
14788        } else {
14789            panic!("expected completion menu to stay open after PageDown");
14790        }
14791    });
14792
14793    cx.update_editor(|editor, window, cx| {
14794        editor.move_page_up(&MovePageUp::default(), window, cx);
14795        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14796        {
14797            assert!(
14798                menu.selected_item == 0,
14799                "expected PageUp to select the first item from the context menu"
14800            );
14801        } else {
14802            panic!("expected completion menu to stay open after PageUp");
14803        }
14804    });
14805}
14806
14807#[gpui::test]
14808async fn test_as_is_completions(cx: &mut TestAppContext) {
14809    init_test(cx, |_| {});
14810    let mut cx = EditorLspTestContext::new_rust(
14811        lsp::ServerCapabilities {
14812            completion_provider: Some(lsp::CompletionOptions {
14813                ..Default::default()
14814            }),
14815            ..Default::default()
14816        },
14817        cx,
14818    )
14819    .await;
14820    cx.lsp
14821        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14822            Ok(Some(lsp::CompletionResponse::Array(vec![
14823                lsp::CompletionItem {
14824                    label: "unsafe".into(),
14825                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14826                        range: lsp::Range {
14827                            start: lsp::Position {
14828                                line: 1,
14829                                character: 2,
14830                            },
14831                            end: lsp::Position {
14832                                line: 1,
14833                                character: 3,
14834                            },
14835                        },
14836                        new_text: "unsafe".to_string(),
14837                    })),
14838                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14839                    ..Default::default()
14840                },
14841            ])))
14842        });
14843    cx.set_state("fn a() {}\n");
14844    cx.executor().run_until_parked();
14845    cx.update_editor(|editor, window, cx| {
14846        editor.show_completions(
14847            &ShowCompletions {
14848                trigger: Some("\n".into()),
14849            },
14850            window,
14851            cx,
14852        );
14853    });
14854    cx.executor().run_until_parked();
14855
14856    cx.update_editor(|editor, window, cx| {
14857        editor.confirm_completion(&Default::default(), window, cx)
14858    });
14859    cx.executor().run_until_parked();
14860    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14861}
14862
14863#[gpui::test]
14864async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14865    init_test(cx, |_| {});
14866    let language =
14867        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14868    let mut cx = EditorLspTestContext::new(
14869        language,
14870        lsp::ServerCapabilities {
14871            completion_provider: Some(lsp::CompletionOptions {
14872                ..lsp::CompletionOptions::default()
14873            }),
14874            ..lsp::ServerCapabilities::default()
14875        },
14876        cx,
14877    )
14878    .await;
14879
14880    cx.set_state(
14881        "#ifndef BAR_H
14882#define BAR_H
14883
14884#include <stdbool.h>
14885
14886int fn_branch(bool do_branch1, bool do_branch2);
14887
14888#endif // BAR_H
14889ˇ",
14890    );
14891    cx.executor().run_until_parked();
14892    cx.update_editor(|editor, window, cx| {
14893        editor.handle_input("#", window, cx);
14894    });
14895    cx.executor().run_until_parked();
14896    cx.update_editor(|editor, window, cx| {
14897        editor.handle_input("i", window, cx);
14898    });
14899    cx.executor().run_until_parked();
14900    cx.update_editor(|editor, window, cx| {
14901        editor.handle_input("n", window, cx);
14902    });
14903    cx.executor().run_until_parked();
14904    cx.assert_editor_state(
14905        "#ifndef BAR_H
14906#define BAR_H
14907
14908#include <stdbool.h>
14909
14910int fn_branch(bool do_branch1, bool do_branch2);
14911
14912#endif // BAR_H
14913#inˇ",
14914    );
14915
14916    cx.lsp
14917        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14918            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14919                is_incomplete: false,
14920                item_defaults: None,
14921                items: vec![lsp::CompletionItem {
14922                    kind: Some(lsp::CompletionItemKind::SNIPPET),
14923                    label_details: Some(lsp::CompletionItemLabelDetails {
14924                        detail: Some("header".to_string()),
14925                        description: None,
14926                    }),
14927                    label: " include".to_string(),
14928                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14929                        range: lsp::Range {
14930                            start: lsp::Position {
14931                                line: 8,
14932                                character: 1,
14933                            },
14934                            end: lsp::Position {
14935                                line: 8,
14936                                character: 1,
14937                            },
14938                        },
14939                        new_text: "include \"$0\"".to_string(),
14940                    })),
14941                    sort_text: Some("40b67681include".to_string()),
14942                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14943                    filter_text: Some("include".to_string()),
14944                    insert_text: Some("include \"$0\"".to_string()),
14945                    ..lsp::CompletionItem::default()
14946                }],
14947            })))
14948        });
14949    cx.update_editor(|editor, window, cx| {
14950        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14951    });
14952    cx.executor().run_until_parked();
14953    cx.update_editor(|editor, window, cx| {
14954        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14955    });
14956    cx.executor().run_until_parked();
14957    cx.assert_editor_state(
14958        "#ifndef BAR_H
14959#define BAR_H
14960
14961#include <stdbool.h>
14962
14963int fn_branch(bool do_branch1, bool do_branch2);
14964
14965#endif // BAR_H
14966#include \"ˇ\"",
14967    );
14968
14969    cx.lsp
14970        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14971            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14972                is_incomplete: true,
14973                item_defaults: None,
14974                items: vec![lsp::CompletionItem {
14975                    kind: Some(lsp::CompletionItemKind::FILE),
14976                    label: "AGL/".to_string(),
14977                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14978                        range: lsp::Range {
14979                            start: lsp::Position {
14980                                line: 8,
14981                                character: 10,
14982                            },
14983                            end: lsp::Position {
14984                                line: 8,
14985                                character: 11,
14986                            },
14987                        },
14988                        new_text: "AGL/".to_string(),
14989                    })),
14990                    sort_text: Some("40b67681AGL/".to_string()),
14991                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14992                    filter_text: Some("AGL/".to_string()),
14993                    insert_text: Some("AGL/".to_string()),
14994                    ..lsp::CompletionItem::default()
14995                }],
14996            })))
14997        });
14998    cx.update_editor(|editor, window, cx| {
14999        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15000    });
15001    cx.executor().run_until_parked();
15002    cx.update_editor(|editor, window, cx| {
15003        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15004    });
15005    cx.executor().run_until_parked();
15006    cx.assert_editor_state(
15007        r##"#ifndef BAR_H
15008#define BAR_H
15009
15010#include <stdbool.h>
15011
15012int fn_branch(bool do_branch1, bool do_branch2);
15013
15014#endif // BAR_H
15015#include "AGL/ˇ"##,
15016    );
15017
15018    cx.update_editor(|editor, window, cx| {
15019        editor.handle_input("\"", window, cx);
15020    });
15021    cx.executor().run_until_parked();
15022    cx.assert_editor_state(
15023        r##"#ifndef BAR_H
15024#define BAR_H
15025
15026#include <stdbool.h>
15027
15028int fn_branch(bool do_branch1, bool do_branch2);
15029
15030#endif // BAR_H
15031#include "AGL/"ˇ"##,
15032    );
15033}
15034
15035#[gpui::test]
15036async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15037    init_test(cx, |_| {});
15038
15039    let mut cx = EditorLspTestContext::new_rust(
15040        lsp::ServerCapabilities {
15041            completion_provider: Some(lsp::CompletionOptions {
15042                trigger_characters: Some(vec![".".to_string()]),
15043                resolve_provider: Some(true),
15044                ..Default::default()
15045            }),
15046            ..Default::default()
15047        },
15048        cx,
15049    )
15050    .await;
15051
15052    cx.set_state("fn main() { let a = 2ˇ; }");
15053    cx.simulate_keystroke(".");
15054    let completion_item = lsp::CompletionItem {
15055        label: "Some".into(),
15056        kind: Some(lsp::CompletionItemKind::SNIPPET),
15057        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15058        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15059            kind: lsp::MarkupKind::Markdown,
15060            value: "```rust\nSome(2)\n```".to_string(),
15061        })),
15062        deprecated: Some(false),
15063        sort_text: Some("Some".to_string()),
15064        filter_text: Some("Some".to_string()),
15065        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15066        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15067            range: lsp::Range {
15068                start: lsp::Position {
15069                    line: 0,
15070                    character: 22,
15071                },
15072                end: lsp::Position {
15073                    line: 0,
15074                    character: 22,
15075                },
15076            },
15077            new_text: "Some(2)".to_string(),
15078        })),
15079        additional_text_edits: Some(vec![lsp::TextEdit {
15080            range: lsp::Range {
15081                start: lsp::Position {
15082                    line: 0,
15083                    character: 20,
15084                },
15085                end: lsp::Position {
15086                    line: 0,
15087                    character: 22,
15088                },
15089            },
15090            new_text: "".to_string(),
15091        }]),
15092        ..Default::default()
15093    };
15094
15095    let closure_completion_item = completion_item.clone();
15096    let counter = Arc::new(AtomicUsize::new(0));
15097    let counter_clone = counter.clone();
15098    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15099        let task_completion_item = closure_completion_item.clone();
15100        counter_clone.fetch_add(1, atomic::Ordering::Release);
15101        async move {
15102            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15103                is_incomplete: true,
15104                item_defaults: None,
15105                items: vec![task_completion_item],
15106            })))
15107        }
15108    });
15109
15110    cx.condition(|editor, _| editor.context_menu_visible())
15111        .await;
15112    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15113    assert!(request.next().await.is_some());
15114    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15115
15116    cx.simulate_keystrokes("S o m");
15117    cx.condition(|editor, _| editor.context_menu_visible())
15118        .await;
15119    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15120    assert!(request.next().await.is_some());
15121    assert!(request.next().await.is_some());
15122    assert!(request.next().await.is_some());
15123    request.close();
15124    assert!(request.next().await.is_none());
15125    assert_eq!(
15126        counter.load(atomic::Ordering::Acquire),
15127        4,
15128        "With the completions menu open, only one LSP request should happen per input"
15129    );
15130}
15131
15132#[gpui::test]
15133async fn test_toggle_comment(cx: &mut TestAppContext) {
15134    init_test(cx, |_| {});
15135    let mut cx = EditorTestContext::new(cx).await;
15136    let language = Arc::new(Language::new(
15137        LanguageConfig {
15138            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15139            ..Default::default()
15140        },
15141        Some(tree_sitter_rust::LANGUAGE.into()),
15142    ));
15143    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15144
15145    // If multiple selections intersect a line, the line is only toggled once.
15146    cx.set_state(indoc! {"
15147        fn a() {
15148            «//b();
15149            ˇ»// «c();
15150            //ˇ»  d();
15151        }
15152    "});
15153
15154    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15155
15156    cx.assert_editor_state(indoc! {"
15157        fn a() {
15158            «b();
15159            c();
15160            ˇ» d();
15161        }
15162    "});
15163
15164    // The comment prefix is inserted at the same column for every line in a
15165    // selection.
15166    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15167
15168    cx.assert_editor_state(indoc! {"
15169        fn a() {
15170            // «b();
15171            // c();
15172            ˇ»//  d();
15173        }
15174    "});
15175
15176    // If a selection ends at the beginning of a line, that line is not toggled.
15177    cx.set_selections_state(indoc! {"
15178        fn a() {
15179            // b();
15180            «// c();
15181        ˇ»    //  d();
15182        }
15183    "});
15184
15185    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15186
15187    cx.assert_editor_state(indoc! {"
15188        fn a() {
15189            // b();
15190            «c();
15191        ˇ»    //  d();
15192        }
15193    "});
15194
15195    // If a selection span a single line and is empty, the line is toggled.
15196    cx.set_state(indoc! {"
15197        fn a() {
15198            a();
15199            b();
15200        ˇ
15201        }
15202    "});
15203
15204    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15205
15206    cx.assert_editor_state(indoc! {"
15207        fn a() {
15208            a();
15209            b();
15210        //•ˇ
15211        }
15212    "});
15213
15214    // If a selection span multiple lines, empty lines are not toggled.
15215    cx.set_state(indoc! {"
15216        fn a() {
15217            «a();
15218
15219            c();ˇ»
15220        }
15221    "});
15222
15223    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15224
15225    cx.assert_editor_state(indoc! {"
15226        fn a() {
15227            // «a();
15228
15229            // c();ˇ»
15230        }
15231    "});
15232
15233    // If a selection includes multiple comment prefixes, all lines are uncommented.
15234    cx.set_state(indoc! {"
15235        fn a() {
15236            «// a();
15237            /// b();
15238            //! c();ˇ»
15239        }
15240    "});
15241
15242    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15243
15244    cx.assert_editor_state(indoc! {"
15245        fn a() {
15246            «a();
15247            b();
15248            c();ˇ»
15249        }
15250    "});
15251}
15252
15253#[gpui::test]
15254async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15255    init_test(cx, |_| {});
15256    let mut cx = EditorTestContext::new(cx).await;
15257    let language = Arc::new(Language::new(
15258        LanguageConfig {
15259            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15260            ..Default::default()
15261        },
15262        Some(tree_sitter_rust::LANGUAGE.into()),
15263    ));
15264    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15265
15266    let toggle_comments = &ToggleComments {
15267        advance_downwards: false,
15268        ignore_indent: true,
15269    };
15270
15271    // If multiple selections intersect a line, the line is only toggled once.
15272    cx.set_state(indoc! {"
15273        fn a() {
15274        //    «b();
15275        //    c();
15276        //    ˇ» d();
15277        }
15278    "});
15279
15280    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15281
15282    cx.assert_editor_state(indoc! {"
15283        fn a() {
15284            «b();
15285            c();
15286            ˇ» d();
15287        }
15288    "});
15289
15290    // The comment prefix is inserted at the beginning of each line
15291    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15292
15293    cx.assert_editor_state(indoc! {"
15294        fn a() {
15295        //    «b();
15296        //    c();
15297        //    ˇ» d();
15298        }
15299    "});
15300
15301    // If a selection ends at the beginning of a line, that line is not toggled.
15302    cx.set_selections_state(indoc! {"
15303        fn a() {
15304        //    b();
15305        //    «c();
15306        ˇ»//     d();
15307        }
15308    "});
15309
15310    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15311
15312    cx.assert_editor_state(indoc! {"
15313        fn a() {
15314        //    b();
15315            «c();
15316        ˇ»//     d();
15317        }
15318    "});
15319
15320    // If a selection span a single line and is empty, the line is toggled.
15321    cx.set_state(indoc! {"
15322        fn a() {
15323            a();
15324            b();
15325        ˇ
15326        }
15327    "});
15328
15329    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15330
15331    cx.assert_editor_state(indoc! {"
15332        fn a() {
15333            a();
15334            b();
15335        //ˇ
15336        }
15337    "});
15338
15339    // If a selection span multiple lines, empty lines are not toggled.
15340    cx.set_state(indoc! {"
15341        fn a() {
15342            «a();
15343
15344            c();ˇ»
15345        }
15346    "});
15347
15348    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15349
15350    cx.assert_editor_state(indoc! {"
15351        fn a() {
15352        //    «a();
15353
15354        //    c();ˇ»
15355        }
15356    "});
15357
15358    // If a selection includes multiple comment prefixes, all lines are uncommented.
15359    cx.set_state(indoc! {"
15360        fn a() {
15361        //    «a();
15362        ///    b();
15363        //!    c();ˇ»
15364        }
15365    "});
15366
15367    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15368
15369    cx.assert_editor_state(indoc! {"
15370        fn a() {
15371            «a();
15372            b();
15373            c();ˇ»
15374        }
15375    "});
15376}
15377
15378#[gpui::test]
15379async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15380    init_test(cx, |_| {});
15381
15382    let language = Arc::new(Language::new(
15383        LanguageConfig {
15384            line_comments: vec!["// ".into()],
15385            ..Default::default()
15386        },
15387        Some(tree_sitter_rust::LANGUAGE.into()),
15388    ));
15389
15390    let mut cx = EditorTestContext::new(cx).await;
15391
15392    cx.language_registry().add(language.clone());
15393    cx.update_buffer(|buffer, cx| {
15394        buffer.set_language(Some(language), cx);
15395    });
15396
15397    let toggle_comments = &ToggleComments {
15398        advance_downwards: true,
15399        ignore_indent: false,
15400    };
15401
15402    // Single cursor on one line -> advance
15403    // Cursor moves horizontally 3 characters as well on non-blank line
15404    cx.set_state(indoc!(
15405        "fn a() {
15406             ˇdog();
15407             cat();
15408        }"
15409    ));
15410    cx.update_editor(|editor, window, cx| {
15411        editor.toggle_comments(toggle_comments, window, cx);
15412    });
15413    cx.assert_editor_state(indoc!(
15414        "fn a() {
15415             // dog();
15416             catˇ();
15417        }"
15418    ));
15419
15420    // Single selection on one line -> don't advance
15421    cx.set_state(indoc!(
15422        "fn a() {
15423             «dog()ˇ»;
15424             cat();
15425        }"
15426    ));
15427    cx.update_editor(|editor, window, cx| {
15428        editor.toggle_comments(toggle_comments, window, cx);
15429    });
15430    cx.assert_editor_state(indoc!(
15431        "fn a() {
15432             // «dog()ˇ»;
15433             cat();
15434        }"
15435    ));
15436
15437    // Multiple cursors on one line -> advance
15438    cx.set_state(indoc!(
15439        "fn a() {
15440             ˇdˇog();
15441             cat();
15442        }"
15443    ));
15444    cx.update_editor(|editor, window, cx| {
15445        editor.toggle_comments(toggle_comments, window, cx);
15446    });
15447    cx.assert_editor_state(indoc!(
15448        "fn a() {
15449             // dog();
15450             catˇ(ˇ);
15451        }"
15452    ));
15453
15454    // Multiple cursors on one line, with selection -> don't advance
15455    cx.set_state(indoc!(
15456        "fn a() {
15457             ˇdˇog«()ˇ»;
15458             cat();
15459        }"
15460    ));
15461    cx.update_editor(|editor, window, cx| {
15462        editor.toggle_comments(toggle_comments, window, cx);
15463    });
15464    cx.assert_editor_state(indoc!(
15465        "fn a() {
15466             // ˇdˇog«()ˇ»;
15467             cat();
15468        }"
15469    ));
15470
15471    // Single cursor on one line -> advance
15472    // Cursor moves to column 0 on blank line
15473    cx.set_state(indoc!(
15474        "fn a() {
15475             ˇdog();
15476
15477             cat();
15478        }"
15479    ));
15480    cx.update_editor(|editor, window, cx| {
15481        editor.toggle_comments(toggle_comments, window, cx);
15482    });
15483    cx.assert_editor_state(indoc!(
15484        "fn a() {
15485             // dog();
15486        ˇ
15487             cat();
15488        }"
15489    ));
15490
15491    // Single cursor on one line -> advance
15492    // Cursor starts and ends at column 0
15493    cx.set_state(indoc!(
15494        "fn a() {
15495         ˇ    dog();
15496             cat();
15497        }"
15498    ));
15499    cx.update_editor(|editor, window, cx| {
15500        editor.toggle_comments(toggle_comments, window, cx);
15501    });
15502    cx.assert_editor_state(indoc!(
15503        "fn a() {
15504             // dog();
15505         ˇ    cat();
15506        }"
15507    ));
15508}
15509
15510#[gpui::test]
15511async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15512    init_test(cx, |_| {});
15513
15514    let mut cx = EditorTestContext::new(cx).await;
15515
15516    let html_language = Arc::new(
15517        Language::new(
15518            LanguageConfig {
15519                name: "HTML".into(),
15520                block_comment: Some(BlockCommentConfig {
15521                    start: "<!-- ".into(),
15522                    prefix: "".into(),
15523                    end: " -->".into(),
15524                    tab_size: 0,
15525                }),
15526                ..Default::default()
15527            },
15528            Some(tree_sitter_html::LANGUAGE.into()),
15529        )
15530        .with_injection_query(
15531            r#"
15532            (script_element
15533                (raw_text) @injection.content
15534                (#set! injection.language "javascript"))
15535            "#,
15536        )
15537        .unwrap(),
15538    );
15539
15540    let javascript_language = Arc::new(Language::new(
15541        LanguageConfig {
15542            name: "JavaScript".into(),
15543            line_comments: vec!["// ".into()],
15544            ..Default::default()
15545        },
15546        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15547    ));
15548
15549    cx.language_registry().add(html_language.clone());
15550    cx.language_registry().add(javascript_language);
15551    cx.update_buffer(|buffer, cx| {
15552        buffer.set_language(Some(html_language), cx);
15553    });
15554
15555    // Toggle comments for empty selections
15556    cx.set_state(
15557        &r#"
15558            <p>A</p>ˇ
15559            <p>B</p>ˇ
15560            <p>C</p>ˇ
15561        "#
15562        .unindent(),
15563    );
15564    cx.update_editor(|editor, window, cx| {
15565        editor.toggle_comments(&ToggleComments::default(), window, cx)
15566    });
15567    cx.assert_editor_state(
15568        &r#"
15569            <!-- <p>A</p>ˇ -->
15570            <!-- <p>B</p>ˇ -->
15571            <!-- <p>C</p>ˇ -->
15572        "#
15573        .unindent(),
15574    );
15575    cx.update_editor(|editor, window, cx| {
15576        editor.toggle_comments(&ToggleComments::default(), window, cx)
15577    });
15578    cx.assert_editor_state(
15579        &r#"
15580            <p>A</p>ˇ
15581            <p>B</p>ˇ
15582            <p>C</p>ˇ
15583        "#
15584        .unindent(),
15585    );
15586
15587    // Toggle comments for mixture of empty and non-empty selections, where
15588    // multiple selections occupy a given line.
15589    cx.set_state(
15590        &r#"
15591            <p>A«</p>
15592            <p>ˇ»B</p>ˇ
15593            <p>C«</p>
15594            <p>ˇ»D</p>ˇ
15595        "#
15596        .unindent(),
15597    );
15598
15599    cx.update_editor(|editor, window, cx| {
15600        editor.toggle_comments(&ToggleComments::default(), window, cx)
15601    });
15602    cx.assert_editor_state(
15603        &r#"
15604            <!-- <p>A«</p>
15605            <p>ˇ»B</p>ˇ -->
15606            <!-- <p>C«</p>
15607            <p>ˇ»D</p>ˇ -->
15608        "#
15609        .unindent(),
15610    );
15611    cx.update_editor(|editor, window, cx| {
15612        editor.toggle_comments(&ToggleComments::default(), window, cx)
15613    });
15614    cx.assert_editor_state(
15615        &r#"
15616            <p>A«</p>
15617            <p>ˇ»B</p>ˇ
15618            <p>C«</p>
15619            <p>ˇ»D</p>ˇ
15620        "#
15621        .unindent(),
15622    );
15623
15624    // Toggle comments when different languages are active for different
15625    // selections.
15626    cx.set_state(
15627        &r#"
15628            ˇ<script>
15629                ˇvar x = new Y();
15630            ˇ</script>
15631        "#
15632        .unindent(),
15633    );
15634    cx.executor().run_until_parked();
15635    cx.update_editor(|editor, window, cx| {
15636        editor.toggle_comments(&ToggleComments::default(), window, cx)
15637    });
15638    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15639    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15640    cx.assert_editor_state(
15641        &r#"
15642            <!-- ˇ<script> -->
15643                // ˇvar x = new Y();
15644            <!-- ˇ</script> -->
15645        "#
15646        .unindent(),
15647    );
15648}
15649
15650#[gpui::test]
15651fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15652    init_test(cx, |_| {});
15653
15654    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15655    let multibuffer = cx.new(|cx| {
15656        let mut multibuffer = MultiBuffer::new(ReadWrite);
15657        multibuffer.push_excerpts(
15658            buffer.clone(),
15659            [
15660                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15661                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15662            ],
15663            cx,
15664        );
15665        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15666        multibuffer
15667    });
15668
15669    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15670    editor.update_in(cx, |editor, window, cx| {
15671        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15672        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15673            s.select_ranges([
15674                Point::new(0, 0)..Point::new(0, 0),
15675                Point::new(1, 0)..Point::new(1, 0),
15676            ])
15677        });
15678
15679        editor.handle_input("X", window, cx);
15680        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15681        assert_eq!(
15682            editor.selections.ranges(cx),
15683            [
15684                Point::new(0, 1)..Point::new(0, 1),
15685                Point::new(1, 1)..Point::new(1, 1),
15686            ]
15687        );
15688
15689        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15690        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15691            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15692        });
15693        editor.backspace(&Default::default(), window, cx);
15694        assert_eq!(editor.text(cx), "Xa\nbbb");
15695        assert_eq!(
15696            editor.selections.ranges(cx),
15697            [Point::new(1, 0)..Point::new(1, 0)]
15698        );
15699
15700        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15701            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15702        });
15703        editor.backspace(&Default::default(), window, cx);
15704        assert_eq!(editor.text(cx), "X\nbb");
15705        assert_eq!(
15706            editor.selections.ranges(cx),
15707            [Point::new(0, 1)..Point::new(0, 1)]
15708        );
15709    });
15710}
15711
15712#[gpui::test]
15713fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15714    init_test(cx, |_| {});
15715
15716    let markers = vec![('[', ']').into(), ('(', ')').into()];
15717    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15718        indoc! {"
15719            [aaaa
15720            (bbbb]
15721            cccc)",
15722        },
15723        markers.clone(),
15724    );
15725    let excerpt_ranges = markers.into_iter().map(|marker| {
15726        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15727        ExcerptRange::new(context)
15728    });
15729    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15730    let multibuffer = cx.new(|cx| {
15731        let mut multibuffer = MultiBuffer::new(ReadWrite);
15732        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15733        multibuffer
15734    });
15735
15736    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15737    editor.update_in(cx, |editor, window, cx| {
15738        let (expected_text, selection_ranges) = marked_text_ranges(
15739            indoc! {"
15740                aaaa
15741                bˇbbb
15742                bˇbbˇb
15743                cccc"
15744            },
15745            true,
15746        );
15747        assert_eq!(editor.text(cx), expected_text);
15748        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15749            s.select_ranges(selection_ranges)
15750        });
15751
15752        editor.handle_input("X", window, cx);
15753
15754        let (expected_text, expected_selections) = marked_text_ranges(
15755            indoc! {"
15756                aaaa
15757                bXˇbbXb
15758                bXˇbbXˇb
15759                cccc"
15760            },
15761            false,
15762        );
15763        assert_eq!(editor.text(cx), expected_text);
15764        assert_eq!(editor.selections.ranges(cx), expected_selections);
15765
15766        editor.newline(&Newline, window, cx);
15767        let (expected_text, expected_selections) = marked_text_ranges(
15768            indoc! {"
15769                aaaa
15770                bX
15771                ˇbbX
15772                b
15773                bX
15774                ˇbbX
15775                ˇb
15776                cccc"
15777            },
15778            false,
15779        );
15780        assert_eq!(editor.text(cx), expected_text);
15781        assert_eq!(editor.selections.ranges(cx), expected_selections);
15782    });
15783}
15784
15785#[gpui::test]
15786fn test_refresh_selections(cx: &mut TestAppContext) {
15787    init_test(cx, |_| {});
15788
15789    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15790    let mut excerpt1_id = None;
15791    let multibuffer = cx.new(|cx| {
15792        let mut multibuffer = MultiBuffer::new(ReadWrite);
15793        excerpt1_id = multibuffer
15794            .push_excerpts(
15795                buffer.clone(),
15796                [
15797                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15798                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15799                ],
15800                cx,
15801            )
15802            .into_iter()
15803            .next();
15804        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15805        multibuffer
15806    });
15807
15808    let editor = cx.add_window(|window, cx| {
15809        let mut editor = build_editor(multibuffer.clone(), window, cx);
15810        let snapshot = editor.snapshot(window, cx);
15811        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15812            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15813        });
15814        editor.begin_selection(
15815            Point::new(2, 1).to_display_point(&snapshot),
15816            true,
15817            1,
15818            window,
15819            cx,
15820        );
15821        assert_eq!(
15822            editor.selections.ranges(cx),
15823            [
15824                Point::new(1, 3)..Point::new(1, 3),
15825                Point::new(2, 1)..Point::new(2, 1),
15826            ]
15827        );
15828        editor
15829    });
15830
15831    // Refreshing selections is a no-op when excerpts haven't changed.
15832    _ = editor.update(cx, |editor, window, cx| {
15833        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15834        assert_eq!(
15835            editor.selections.ranges(cx),
15836            [
15837                Point::new(1, 3)..Point::new(1, 3),
15838                Point::new(2, 1)..Point::new(2, 1),
15839            ]
15840        );
15841    });
15842
15843    multibuffer.update(cx, |multibuffer, cx| {
15844        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15845    });
15846    _ = editor.update(cx, |editor, window, cx| {
15847        // Removing an excerpt causes the first selection to become degenerate.
15848        assert_eq!(
15849            editor.selections.ranges(cx),
15850            [
15851                Point::new(0, 0)..Point::new(0, 0),
15852                Point::new(0, 1)..Point::new(0, 1)
15853            ]
15854        );
15855
15856        // Refreshing selections will relocate the first selection to the original buffer
15857        // location.
15858        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15859        assert_eq!(
15860            editor.selections.ranges(cx),
15861            [
15862                Point::new(0, 1)..Point::new(0, 1),
15863                Point::new(0, 3)..Point::new(0, 3)
15864            ]
15865        );
15866        assert!(editor.selections.pending_anchor().is_some());
15867    });
15868}
15869
15870#[gpui::test]
15871fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15872    init_test(cx, |_| {});
15873
15874    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15875    let mut excerpt1_id = None;
15876    let multibuffer = cx.new(|cx| {
15877        let mut multibuffer = MultiBuffer::new(ReadWrite);
15878        excerpt1_id = multibuffer
15879            .push_excerpts(
15880                buffer.clone(),
15881                [
15882                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15883                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15884                ],
15885                cx,
15886            )
15887            .into_iter()
15888            .next();
15889        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15890        multibuffer
15891    });
15892
15893    let editor = cx.add_window(|window, cx| {
15894        let mut editor = build_editor(multibuffer.clone(), window, cx);
15895        let snapshot = editor.snapshot(window, cx);
15896        editor.begin_selection(
15897            Point::new(1, 3).to_display_point(&snapshot),
15898            false,
15899            1,
15900            window,
15901            cx,
15902        );
15903        assert_eq!(
15904            editor.selections.ranges(cx),
15905            [Point::new(1, 3)..Point::new(1, 3)]
15906        );
15907        editor
15908    });
15909
15910    multibuffer.update(cx, |multibuffer, cx| {
15911        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15912    });
15913    _ = editor.update(cx, |editor, window, cx| {
15914        assert_eq!(
15915            editor.selections.ranges(cx),
15916            [Point::new(0, 0)..Point::new(0, 0)]
15917        );
15918
15919        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
15920        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15921        assert_eq!(
15922            editor.selections.ranges(cx),
15923            [Point::new(0, 3)..Point::new(0, 3)]
15924        );
15925        assert!(editor.selections.pending_anchor().is_some());
15926    });
15927}
15928
15929#[gpui::test]
15930async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
15931    init_test(cx, |_| {});
15932
15933    let language = Arc::new(
15934        Language::new(
15935            LanguageConfig {
15936                brackets: BracketPairConfig {
15937                    pairs: vec![
15938                        BracketPair {
15939                            start: "{".to_string(),
15940                            end: "}".to_string(),
15941                            close: true,
15942                            surround: true,
15943                            newline: true,
15944                        },
15945                        BracketPair {
15946                            start: "/* ".to_string(),
15947                            end: " */".to_string(),
15948                            close: true,
15949                            surround: true,
15950                            newline: true,
15951                        },
15952                    ],
15953                    ..Default::default()
15954                },
15955                ..Default::default()
15956            },
15957            Some(tree_sitter_rust::LANGUAGE.into()),
15958        )
15959        .with_indents_query("")
15960        .unwrap(),
15961    );
15962
15963    let text = concat!(
15964        "{   }\n",     //
15965        "  x\n",       //
15966        "  /*   */\n", //
15967        "x\n",         //
15968        "{{} }\n",     //
15969    );
15970
15971    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15972    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15973    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15974    editor
15975        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
15976        .await;
15977
15978    editor.update_in(cx, |editor, window, cx| {
15979        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15980            s.select_display_ranges([
15981                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
15982                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
15983                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
15984            ])
15985        });
15986        editor.newline(&Newline, window, cx);
15987
15988        assert_eq!(
15989            editor.buffer().read(cx).read(cx).text(),
15990            concat!(
15991                "{ \n",    // Suppress rustfmt
15992                "\n",      //
15993                "}\n",     //
15994                "  x\n",   //
15995                "  /* \n", //
15996                "  \n",    //
15997                "  */\n",  //
15998                "x\n",     //
15999                "{{} \n",  //
16000                "}\n",     //
16001            )
16002        );
16003    });
16004}
16005
16006#[gpui::test]
16007fn test_highlighted_ranges(cx: &mut TestAppContext) {
16008    init_test(cx, |_| {});
16009
16010    let editor = cx.add_window(|window, cx| {
16011        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16012        build_editor(buffer, window, cx)
16013    });
16014
16015    _ = editor.update(cx, |editor, window, cx| {
16016        struct Type1;
16017        struct Type2;
16018
16019        let buffer = editor.buffer.read(cx).snapshot(cx);
16020
16021        let anchor_range =
16022            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16023
16024        editor.highlight_background::<Type1>(
16025            &[
16026                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16027                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16028                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16029                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16030            ],
16031            |_| Hsla::red(),
16032            cx,
16033        );
16034        editor.highlight_background::<Type2>(
16035            &[
16036                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16037                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16038                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16039                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16040            ],
16041            |_| Hsla::green(),
16042            cx,
16043        );
16044
16045        let snapshot = editor.snapshot(window, cx);
16046        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16047            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16048            &snapshot,
16049            cx.theme(),
16050        );
16051        assert_eq!(
16052            highlighted_ranges,
16053            &[
16054                (
16055                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16056                    Hsla::green(),
16057                ),
16058                (
16059                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16060                    Hsla::red(),
16061                ),
16062                (
16063                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16064                    Hsla::green(),
16065                ),
16066                (
16067                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16068                    Hsla::red(),
16069                ),
16070            ]
16071        );
16072        assert_eq!(
16073            editor.sorted_background_highlights_in_range(
16074                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16075                &snapshot,
16076                cx.theme(),
16077            ),
16078            &[(
16079                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16080                Hsla::red(),
16081            )]
16082        );
16083    });
16084}
16085
16086#[gpui::test]
16087async fn test_following(cx: &mut TestAppContext) {
16088    init_test(cx, |_| {});
16089
16090    let fs = FakeFs::new(cx.executor());
16091    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16092
16093    let buffer = project.update(cx, |project, cx| {
16094        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16095        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16096    });
16097    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16098    let follower = cx.update(|cx| {
16099        cx.open_window(
16100            WindowOptions {
16101                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16102                    gpui::Point::new(px(0.), px(0.)),
16103                    gpui::Point::new(px(10.), px(80.)),
16104                ))),
16105                ..Default::default()
16106            },
16107            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16108        )
16109        .unwrap()
16110    });
16111
16112    let is_still_following = Rc::new(RefCell::new(true));
16113    let follower_edit_event_count = Rc::new(RefCell::new(0));
16114    let pending_update = Rc::new(RefCell::new(None));
16115    let leader_entity = leader.root(cx).unwrap();
16116    let follower_entity = follower.root(cx).unwrap();
16117    _ = follower.update(cx, {
16118        let update = pending_update.clone();
16119        let is_still_following = is_still_following.clone();
16120        let follower_edit_event_count = follower_edit_event_count.clone();
16121        |_, window, cx| {
16122            cx.subscribe_in(
16123                &leader_entity,
16124                window,
16125                move |_, leader, event, window, cx| {
16126                    leader.read(cx).add_event_to_update_proto(
16127                        event,
16128                        &mut update.borrow_mut(),
16129                        window,
16130                        cx,
16131                    );
16132                },
16133            )
16134            .detach();
16135
16136            cx.subscribe_in(
16137                &follower_entity,
16138                window,
16139                move |_, _, event: &EditorEvent, _window, _cx| {
16140                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16141                        *is_still_following.borrow_mut() = false;
16142                    }
16143
16144                    if let EditorEvent::BufferEdited = event {
16145                        *follower_edit_event_count.borrow_mut() += 1;
16146                    }
16147                },
16148            )
16149            .detach();
16150        }
16151    });
16152
16153    // Update the selections only
16154    _ = leader.update(cx, |leader, window, cx| {
16155        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16156            s.select_ranges([1..1])
16157        });
16158    });
16159    follower
16160        .update(cx, |follower, window, cx| {
16161            follower.apply_update_proto(
16162                &project,
16163                pending_update.borrow_mut().take().unwrap(),
16164                window,
16165                cx,
16166            )
16167        })
16168        .unwrap()
16169        .await
16170        .unwrap();
16171    _ = follower.update(cx, |follower, _, cx| {
16172        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16173    });
16174    assert!(*is_still_following.borrow());
16175    assert_eq!(*follower_edit_event_count.borrow(), 0);
16176
16177    // Update the scroll position only
16178    _ = leader.update(cx, |leader, window, cx| {
16179        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16180    });
16181    follower
16182        .update(cx, |follower, window, cx| {
16183            follower.apply_update_proto(
16184                &project,
16185                pending_update.borrow_mut().take().unwrap(),
16186                window,
16187                cx,
16188            )
16189        })
16190        .unwrap()
16191        .await
16192        .unwrap();
16193    assert_eq!(
16194        follower
16195            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16196            .unwrap(),
16197        gpui::Point::new(1.5, 3.5)
16198    );
16199    assert!(*is_still_following.borrow());
16200    assert_eq!(*follower_edit_event_count.borrow(), 0);
16201
16202    // Update the selections and scroll position. The follower's scroll position is updated
16203    // via autoscroll, not via the leader's exact scroll position.
16204    _ = leader.update(cx, |leader, window, cx| {
16205        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16206            s.select_ranges([0..0])
16207        });
16208        leader.request_autoscroll(Autoscroll::newest(), cx);
16209        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16210    });
16211    follower
16212        .update(cx, |follower, window, cx| {
16213            follower.apply_update_proto(
16214                &project,
16215                pending_update.borrow_mut().take().unwrap(),
16216                window,
16217                cx,
16218            )
16219        })
16220        .unwrap()
16221        .await
16222        .unwrap();
16223    _ = follower.update(cx, |follower, _, cx| {
16224        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16225        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16226    });
16227    assert!(*is_still_following.borrow());
16228
16229    // Creating a pending selection that precedes another selection
16230    _ = leader.update(cx, |leader, window, cx| {
16231        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16232            s.select_ranges([1..1])
16233        });
16234        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16235    });
16236    follower
16237        .update(cx, |follower, window, cx| {
16238            follower.apply_update_proto(
16239                &project,
16240                pending_update.borrow_mut().take().unwrap(),
16241                window,
16242                cx,
16243            )
16244        })
16245        .unwrap()
16246        .await
16247        .unwrap();
16248    _ = follower.update(cx, |follower, _, cx| {
16249        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16250    });
16251    assert!(*is_still_following.borrow());
16252
16253    // Extend the pending selection so that it surrounds another selection
16254    _ = leader.update(cx, |leader, window, cx| {
16255        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16256    });
16257    follower
16258        .update(cx, |follower, window, cx| {
16259            follower.apply_update_proto(
16260                &project,
16261                pending_update.borrow_mut().take().unwrap(),
16262                window,
16263                cx,
16264            )
16265        })
16266        .unwrap()
16267        .await
16268        .unwrap();
16269    _ = follower.update(cx, |follower, _, cx| {
16270        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16271    });
16272
16273    // Scrolling locally breaks the follow
16274    _ = follower.update(cx, |follower, window, cx| {
16275        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16276        follower.set_scroll_anchor(
16277            ScrollAnchor {
16278                anchor: top_anchor,
16279                offset: gpui::Point::new(0.0, 0.5),
16280            },
16281            window,
16282            cx,
16283        );
16284    });
16285    assert!(!(*is_still_following.borrow()));
16286}
16287
16288#[gpui::test]
16289async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16290    init_test(cx, |_| {});
16291
16292    let fs = FakeFs::new(cx.executor());
16293    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16294    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16295    let pane = workspace
16296        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16297        .unwrap();
16298
16299    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16300
16301    let leader = pane.update_in(cx, |_, window, cx| {
16302        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16303        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16304    });
16305
16306    // Start following the editor when it has no excerpts.
16307    let mut state_message =
16308        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16309    let workspace_entity = workspace.root(cx).unwrap();
16310    let follower_1 = cx
16311        .update_window(*workspace.deref(), |_, window, cx| {
16312            Editor::from_state_proto(
16313                workspace_entity,
16314                ViewId {
16315                    creator: CollaboratorId::PeerId(PeerId::default()),
16316                    id: 0,
16317                },
16318                &mut state_message,
16319                window,
16320                cx,
16321            )
16322        })
16323        .unwrap()
16324        .unwrap()
16325        .await
16326        .unwrap();
16327
16328    let update_message = Rc::new(RefCell::new(None));
16329    follower_1.update_in(cx, {
16330        let update = update_message.clone();
16331        |_, window, cx| {
16332            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16333                leader.read(cx).add_event_to_update_proto(
16334                    event,
16335                    &mut update.borrow_mut(),
16336                    window,
16337                    cx,
16338                );
16339            })
16340            .detach();
16341        }
16342    });
16343
16344    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16345        (
16346            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16347            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16348        )
16349    });
16350
16351    // Insert some excerpts.
16352    leader.update(cx, |leader, cx| {
16353        leader.buffer.update(cx, |multibuffer, cx| {
16354            multibuffer.set_excerpts_for_path(
16355                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
16356                buffer_1.clone(),
16357                vec![
16358                    Point::row_range(0..3),
16359                    Point::row_range(1..6),
16360                    Point::row_range(12..15),
16361                ],
16362                0,
16363                cx,
16364            );
16365            multibuffer.set_excerpts_for_path(
16366                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
16367                buffer_2.clone(),
16368                vec![Point::row_range(0..6), Point::row_range(8..12)],
16369                0,
16370                cx,
16371            );
16372        });
16373    });
16374
16375    // Apply the update of adding the excerpts.
16376    follower_1
16377        .update_in(cx, |follower, window, cx| {
16378            follower.apply_update_proto(
16379                &project,
16380                update_message.borrow().clone().unwrap(),
16381                window,
16382                cx,
16383            )
16384        })
16385        .await
16386        .unwrap();
16387    assert_eq!(
16388        follower_1.update(cx, |editor, cx| editor.text(cx)),
16389        leader.update(cx, |editor, cx| editor.text(cx))
16390    );
16391    update_message.borrow_mut().take();
16392
16393    // Start following separately after it already has excerpts.
16394    let mut state_message =
16395        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16396    let workspace_entity = workspace.root(cx).unwrap();
16397    let follower_2 = cx
16398        .update_window(*workspace.deref(), |_, window, cx| {
16399            Editor::from_state_proto(
16400                workspace_entity,
16401                ViewId {
16402                    creator: CollaboratorId::PeerId(PeerId::default()),
16403                    id: 0,
16404                },
16405                &mut state_message,
16406                window,
16407                cx,
16408            )
16409        })
16410        .unwrap()
16411        .unwrap()
16412        .await
16413        .unwrap();
16414    assert_eq!(
16415        follower_2.update(cx, |editor, cx| editor.text(cx)),
16416        leader.update(cx, |editor, cx| editor.text(cx))
16417    );
16418
16419    // Remove some excerpts.
16420    leader.update(cx, |leader, cx| {
16421        leader.buffer.update(cx, |multibuffer, cx| {
16422            let excerpt_ids = multibuffer.excerpt_ids();
16423            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16424            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16425        });
16426    });
16427
16428    // Apply the update of removing the excerpts.
16429    follower_1
16430        .update_in(cx, |follower, window, cx| {
16431            follower.apply_update_proto(
16432                &project,
16433                update_message.borrow().clone().unwrap(),
16434                window,
16435                cx,
16436            )
16437        })
16438        .await
16439        .unwrap();
16440    follower_2
16441        .update_in(cx, |follower, window, cx| {
16442            follower.apply_update_proto(
16443                &project,
16444                update_message.borrow().clone().unwrap(),
16445                window,
16446                cx,
16447            )
16448        })
16449        .await
16450        .unwrap();
16451    update_message.borrow_mut().take();
16452    assert_eq!(
16453        follower_1.update(cx, |editor, cx| editor.text(cx)),
16454        leader.update(cx, |editor, cx| editor.text(cx))
16455    );
16456}
16457
16458#[gpui::test]
16459async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16460    init_test(cx, |_| {});
16461
16462    let mut cx = EditorTestContext::new(cx).await;
16463    let lsp_store =
16464        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16465
16466    cx.set_state(indoc! {"
16467        ˇfn func(abc def: i32) -> u32 {
16468        }
16469    "});
16470
16471    cx.update(|_, cx| {
16472        lsp_store.update(cx, |lsp_store, cx| {
16473            lsp_store
16474                .update_diagnostics(
16475                    LanguageServerId(0),
16476                    lsp::PublishDiagnosticsParams {
16477                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16478                        version: None,
16479                        diagnostics: vec![
16480                            lsp::Diagnostic {
16481                                range: lsp::Range::new(
16482                                    lsp::Position::new(0, 11),
16483                                    lsp::Position::new(0, 12),
16484                                ),
16485                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16486                                ..Default::default()
16487                            },
16488                            lsp::Diagnostic {
16489                                range: lsp::Range::new(
16490                                    lsp::Position::new(0, 12),
16491                                    lsp::Position::new(0, 15),
16492                                ),
16493                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16494                                ..Default::default()
16495                            },
16496                            lsp::Diagnostic {
16497                                range: lsp::Range::new(
16498                                    lsp::Position::new(0, 25),
16499                                    lsp::Position::new(0, 28),
16500                                ),
16501                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16502                                ..Default::default()
16503                            },
16504                        ],
16505                    },
16506                    None,
16507                    DiagnosticSourceKind::Pushed,
16508                    &[],
16509                    cx,
16510                )
16511                .unwrap()
16512        });
16513    });
16514
16515    executor.run_until_parked();
16516
16517    cx.update_editor(|editor, window, cx| {
16518        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16519    });
16520
16521    cx.assert_editor_state(indoc! {"
16522        fn func(abc def: i32) -> ˇu32 {
16523        }
16524    "});
16525
16526    cx.update_editor(|editor, window, cx| {
16527        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16528    });
16529
16530    cx.assert_editor_state(indoc! {"
16531        fn func(abc ˇdef: i32) -> u32 {
16532        }
16533    "});
16534
16535    cx.update_editor(|editor, window, cx| {
16536        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16537    });
16538
16539    cx.assert_editor_state(indoc! {"
16540        fn func(abcˇ def: i32) -> u32 {
16541        }
16542    "});
16543
16544    cx.update_editor(|editor, window, cx| {
16545        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16546    });
16547
16548    cx.assert_editor_state(indoc! {"
16549        fn func(abc def: i32) -> ˇu32 {
16550        }
16551    "});
16552}
16553
16554#[gpui::test]
16555async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16556    init_test(cx, |_| {});
16557
16558    let mut cx = EditorTestContext::new(cx).await;
16559
16560    let diff_base = r#"
16561        use some::mod;
16562
16563        const A: u32 = 42;
16564
16565        fn main() {
16566            println!("hello");
16567
16568            println!("world");
16569        }
16570        "#
16571    .unindent();
16572
16573    // Edits are modified, removed, modified, added
16574    cx.set_state(
16575        &r#"
16576        use some::modified;
16577
16578        ˇ
16579        fn main() {
16580            println!("hello there");
16581
16582            println!("around the");
16583            println!("world");
16584        }
16585        "#
16586        .unindent(),
16587    );
16588
16589    cx.set_head_text(&diff_base);
16590    executor.run_until_parked();
16591
16592    cx.update_editor(|editor, window, cx| {
16593        //Wrap around the bottom of the buffer
16594        for _ in 0..3 {
16595            editor.go_to_next_hunk(&GoToHunk, window, cx);
16596        }
16597    });
16598
16599    cx.assert_editor_state(
16600        &r#"
16601        ˇuse some::modified;
16602
16603
16604        fn main() {
16605            println!("hello there");
16606
16607            println!("around the");
16608            println!("world");
16609        }
16610        "#
16611        .unindent(),
16612    );
16613
16614    cx.update_editor(|editor, window, cx| {
16615        //Wrap around the top of the buffer
16616        for _ in 0..2 {
16617            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16618        }
16619    });
16620
16621    cx.assert_editor_state(
16622        &r#"
16623        use some::modified;
16624
16625
16626        fn main() {
16627        ˇ    println!("hello there");
16628
16629            println!("around the");
16630            println!("world");
16631        }
16632        "#
16633        .unindent(),
16634    );
16635
16636    cx.update_editor(|editor, window, cx| {
16637        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16638    });
16639
16640    cx.assert_editor_state(
16641        &r#"
16642        use some::modified;
16643
16644        ˇ
16645        fn main() {
16646            println!("hello there");
16647
16648            println!("around the");
16649            println!("world");
16650        }
16651        "#
16652        .unindent(),
16653    );
16654
16655    cx.update_editor(|editor, window, cx| {
16656        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16657    });
16658
16659    cx.assert_editor_state(
16660        &r#"
16661        ˇuse some::modified;
16662
16663
16664        fn main() {
16665            println!("hello there");
16666
16667            println!("around the");
16668            println!("world");
16669        }
16670        "#
16671        .unindent(),
16672    );
16673
16674    cx.update_editor(|editor, window, cx| {
16675        for _ in 0..2 {
16676            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16677        }
16678    });
16679
16680    cx.assert_editor_state(
16681        &r#"
16682        use some::modified;
16683
16684
16685        fn main() {
16686        ˇ    println!("hello there");
16687
16688            println!("around the");
16689            println!("world");
16690        }
16691        "#
16692        .unindent(),
16693    );
16694
16695    cx.update_editor(|editor, window, cx| {
16696        editor.fold(&Fold, window, cx);
16697    });
16698
16699    cx.update_editor(|editor, window, cx| {
16700        editor.go_to_next_hunk(&GoToHunk, window, cx);
16701    });
16702
16703    cx.assert_editor_state(
16704        &r#"
16705        ˇuse some::modified;
16706
16707
16708        fn main() {
16709            println!("hello there");
16710
16711            println!("around the");
16712            println!("world");
16713        }
16714        "#
16715        .unindent(),
16716    );
16717}
16718
16719#[test]
16720fn test_split_words() {
16721    fn split(text: &str) -> Vec<&str> {
16722        split_words(text).collect()
16723    }
16724
16725    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16726    assert_eq!(split("hello_world"), &["hello_", "world"]);
16727    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16728    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16729    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16730    assert_eq!(split("helloworld"), &["helloworld"]);
16731
16732    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16733}
16734
16735#[gpui::test]
16736async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16737    init_test(cx, |_| {});
16738
16739    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16740    let mut assert = |before, after| {
16741        let _state_context = cx.set_state(before);
16742        cx.run_until_parked();
16743        cx.update_editor(|editor, window, cx| {
16744            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16745        });
16746        cx.run_until_parked();
16747        cx.assert_editor_state(after);
16748    };
16749
16750    // Outside bracket jumps to outside of matching bracket
16751    assert("console.logˇ(var);", "console.log(var)ˇ;");
16752    assert("console.log(var)ˇ;", "console.logˇ(var);");
16753
16754    // Inside bracket jumps to inside of matching bracket
16755    assert("console.log(ˇvar);", "console.log(varˇ);");
16756    assert("console.log(varˇ);", "console.log(ˇvar);");
16757
16758    // When outside a bracket and inside, favor jumping to the inside bracket
16759    assert(
16760        "console.log('foo', [1, 2, 3]ˇ);",
16761        "console.log(ˇ'foo', [1, 2, 3]);",
16762    );
16763    assert(
16764        "console.log(ˇ'foo', [1, 2, 3]);",
16765        "console.log('foo', [1, 2, 3]ˇ);",
16766    );
16767
16768    // Bias forward if two options are equally likely
16769    assert(
16770        "let result = curried_fun()ˇ();",
16771        "let result = curried_fun()()ˇ;",
16772    );
16773
16774    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16775    assert(
16776        indoc! {"
16777            function test() {
16778                console.log('test')ˇ
16779            }"},
16780        indoc! {"
16781            function test() {
16782                console.logˇ('test')
16783            }"},
16784    );
16785}
16786
16787#[gpui::test]
16788async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16789    init_test(cx, |_| {});
16790
16791    let fs = FakeFs::new(cx.executor());
16792    fs.insert_tree(
16793        path!("/a"),
16794        json!({
16795            "main.rs": "fn main() { let a = 5; }",
16796            "other.rs": "// Test file",
16797        }),
16798    )
16799    .await;
16800    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16801
16802    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16803    language_registry.add(Arc::new(Language::new(
16804        LanguageConfig {
16805            name: "Rust".into(),
16806            matcher: LanguageMatcher {
16807                path_suffixes: vec!["rs".to_string()],
16808                ..Default::default()
16809            },
16810            brackets: BracketPairConfig {
16811                pairs: vec![BracketPair {
16812                    start: "{".to_string(),
16813                    end: "}".to_string(),
16814                    close: true,
16815                    surround: true,
16816                    newline: true,
16817                }],
16818                disabled_scopes_by_bracket_ix: Vec::new(),
16819            },
16820            ..Default::default()
16821        },
16822        Some(tree_sitter_rust::LANGUAGE.into()),
16823    )));
16824    let mut fake_servers = language_registry.register_fake_lsp(
16825        "Rust",
16826        FakeLspAdapter {
16827            capabilities: lsp::ServerCapabilities {
16828                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16829                    first_trigger_character: "{".to_string(),
16830                    more_trigger_character: None,
16831                }),
16832                ..Default::default()
16833            },
16834            ..Default::default()
16835        },
16836    );
16837
16838    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16839
16840    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16841
16842    let worktree_id = workspace
16843        .update(cx, |workspace, _, cx| {
16844            workspace.project().update(cx, |project, cx| {
16845                project.worktrees(cx).next().unwrap().read(cx).id()
16846            })
16847        })
16848        .unwrap();
16849
16850    let buffer = project
16851        .update(cx, |project, cx| {
16852            project.open_local_buffer(path!("/a/main.rs"), cx)
16853        })
16854        .await
16855        .unwrap();
16856    let editor_handle = workspace
16857        .update(cx, |workspace, window, cx| {
16858            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16859        })
16860        .unwrap()
16861        .await
16862        .unwrap()
16863        .downcast::<Editor>()
16864        .unwrap();
16865
16866    cx.executor().start_waiting();
16867    let fake_server = fake_servers.next().await.unwrap();
16868
16869    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16870        |params, _| async move {
16871            assert_eq!(
16872                params.text_document_position.text_document.uri,
16873                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16874            );
16875            assert_eq!(
16876                params.text_document_position.position,
16877                lsp::Position::new(0, 21),
16878            );
16879
16880            Ok(Some(vec![lsp::TextEdit {
16881                new_text: "]".to_string(),
16882                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16883            }]))
16884        },
16885    );
16886
16887    editor_handle.update_in(cx, |editor, window, cx| {
16888        window.focus(&editor.focus_handle(cx));
16889        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16890            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16891        });
16892        editor.handle_input("{", window, cx);
16893    });
16894
16895    cx.executor().run_until_parked();
16896
16897    buffer.update(cx, |buffer, _| {
16898        assert_eq!(
16899            buffer.text(),
16900            "fn main() { let a = {5}; }",
16901            "No extra braces from on type formatting should appear in the buffer"
16902        )
16903    });
16904}
16905
16906#[gpui::test(iterations = 20, seeds(31))]
16907async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
16908    init_test(cx, |_| {});
16909
16910    let mut cx = EditorLspTestContext::new_rust(
16911        lsp::ServerCapabilities {
16912            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16913                first_trigger_character: ".".to_string(),
16914                more_trigger_character: None,
16915            }),
16916            ..Default::default()
16917        },
16918        cx,
16919    )
16920    .await;
16921
16922    cx.update_buffer(|buffer, _| {
16923        // This causes autoindent to be async.
16924        buffer.set_sync_parse_timeout(Duration::ZERO)
16925    });
16926
16927    cx.set_state("fn c() {\n    d()ˇ\n}\n");
16928    cx.simulate_keystroke("\n");
16929    cx.run_until_parked();
16930
16931    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
16932    let mut request =
16933        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
16934            let buffer_cloned = buffer_cloned.clone();
16935            async move {
16936                buffer_cloned.update(&mut cx, |buffer, _| {
16937                    assert_eq!(
16938                        buffer.text(),
16939                        "fn c() {\n    d()\n        .\n}\n",
16940                        "OnTypeFormatting should triggered after autoindent applied"
16941                    )
16942                })?;
16943
16944                Ok(Some(vec![]))
16945            }
16946        });
16947
16948    cx.simulate_keystroke(".");
16949    cx.run_until_parked();
16950
16951    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
16952    assert!(request.next().await.is_some());
16953    request.close();
16954    assert!(request.next().await.is_none());
16955}
16956
16957#[gpui::test]
16958async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
16959    init_test(cx, |_| {});
16960
16961    let fs = FakeFs::new(cx.executor());
16962    fs.insert_tree(
16963        path!("/a"),
16964        json!({
16965            "main.rs": "fn main() { let a = 5; }",
16966            "other.rs": "// Test file",
16967        }),
16968    )
16969    .await;
16970
16971    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16972
16973    let server_restarts = Arc::new(AtomicUsize::new(0));
16974    let closure_restarts = Arc::clone(&server_restarts);
16975    let language_server_name = "test language server";
16976    let language_name: LanguageName = "Rust".into();
16977
16978    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16979    language_registry.add(Arc::new(Language::new(
16980        LanguageConfig {
16981            name: language_name.clone(),
16982            matcher: LanguageMatcher {
16983                path_suffixes: vec!["rs".to_string()],
16984                ..Default::default()
16985            },
16986            ..Default::default()
16987        },
16988        Some(tree_sitter_rust::LANGUAGE.into()),
16989    )));
16990    let mut fake_servers = language_registry.register_fake_lsp(
16991        "Rust",
16992        FakeLspAdapter {
16993            name: language_server_name,
16994            initialization_options: Some(json!({
16995                "testOptionValue": true
16996            })),
16997            initializer: Some(Box::new(move |fake_server| {
16998                let task_restarts = Arc::clone(&closure_restarts);
16999                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17000                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17001                    futures::future::ready(Ok(()))
17002                });
17003            })),
17004            ..Default::default()
17005        },
17006    );
17007
17008    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17009    let _buffer = project
17010        .update(cx, |project, cx| {
17011            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17012        })
17013        .await
17014        .unwrap();
17015    let _fake_server = fake_servers.next().await.unwrap();
17016    update_test_language_settings(cx, |language_settings| {
17017        language_settings.languages.0.insert(
17018            language_name.clone(),
17019            LanguageSettingsContent {
17020                tab_size: NonZeroU32::new(8),
17021                ..Default::default()
17022            },
17023        );
17024    });
17025    cx.executor().run_until_parked();
17026    assert_eq!(
17027        server_restarts.load(atomic::Ordering::Acquire),
17028        0,
17029        "Should not restart LSP server on an unrelated change"
17030    );
17031
17032    update_test_project_settings(cx, |project_settings| {
17033        project_settings.lsp.insert(
17034            "Some other server name".into(),
17035            LspSettings {
17036                binary: None,
17037                settings: None,
17038                initialization_options: Some(json!({
17039                    "some other init value": false
17040                })),
17041                enable_lsp_tasks: false,
17042                fetch: None,
17043            },
17044        );
17045    });
17046    cx.executor().run_until_parked();
17047    assert_eq!(
17048        server_restarts.load(atomic::Ordering::Acquire),
17049        0,
17050        "Should not restart LSP server on an unrelated LSP settings change"
17051    );
17052
17053    update_test_project_settings(cx, |project_settings| {
17054        project_settings.lsp.insert(
17055            language_server_name.into(),
17056            LspSettings {
17057                binary: None,
17058                settings: None,
17059                initialization_options: Some(json!({
17060                    "anotherInitValue": false
17061                })),
17062                enable_lsp_tasks: false,
17063                fetch: None,
17064            },
17065        );
17066    });
17067    cx.executor().run_until_parked();
17068    assert_eq!(
17069        server_restarts.load(atomic::Ordering::Acquire),
17070        1,
17071        "Should restart LSP server on a related LSP settings change"
17072    );
17073
17074    update_test_project_settings(cx, |project_settings| {
17075        project_settings.lsp.insert(
17076            language_server_name.into(),
17077            LspSettings {
17078                binary: None,
17079                settings: None,
17080                initialization_options: Some(json!({
17081                    "anotherInitValue": false
17082                })),
17083                enable_lsp_tasks: false,
17084                fetch: None,
17085            },
17086        );
17087    });
17088    cx.executor().run_until_parked();
17089    assert_eq!(
17090        server_restarts.load(atomic::Ordering::Acquire),
17091        1,
17092        "Should not restart LSP server on a related LSP settings change that is the same"
17093    );
17094
17095    update_test_project_settings(cx, |project_settings| {
17096        project_settings.lsp.insert(
17097            language_server_name.into(),
17098            LspSettings {
17099                binary: None,
17100                settings: None,
17101                initialization_options: None,
17102                enable_lsp_tasks: false,
17103                fetch: None,
17104            },
17105        );
17106    });
17107    cx.executor().run_until_parked();
17108    assert_eq!(
17109        server_restarts.load(atomic::Ordering::Acquire),
17110        2,
17111        "Should restart LSP server on another related LSP settings change"
17112    );
17113}
17114
17115#[gpui::test]
17116async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17117    init_test(cx, |_| {});
17118
17119    let mut cx = EditorLspTestContext::new_rust(
17120        lsp::ServerCapabilities {
17121            completion_provider: Some(lsp::CompletionOptions {
17122                trigger_characters: Some(vec![".".to_string()]),
17123                resolve_provider: Some(true),
17124                ..Default::default()
17125            }),
17126            ..Default::default()
17127        },
17128        cx,
17129    )
17130    .await;
17131
17132    cx.set_state("fn main() { let a = 2ˇ; }");
17133    cx.simulate_keystroke(".");
17134    let completion_item = lsp::CompletionItem {
17135        label: "some".into(),
17136        kind: Some(lsp::CompletionItemKind::SNIPPET),
17137        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17138        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17139            kind: lsp::MarkupKind::Markdown,
17140            value: "```rust\nSome(2)\n```".to_string(),
17141        })),
17142        deprecated: Some(false),
17143        sort_text: Some("fffffff2".to_string()),
17144        filter_text: Some("some".to_string()),
17145        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17146        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17147            range: lsp::Range {
17148                start: lsp::Position {
17149                    line: 0,
17150                    character: 22,
17151                },
17152                end: lsp::Position {
17153                    line: 0,
17154                    character: 22,
17155                },
17156            },
17157            new_text: "Some(2)".to_string(),
17158        })),
17159        additional_text_edits: Some(vec![lsp::TextEdit {
17160            range: lsp::Range {
17161                start: lsp::Position {
17162                    line: 0,
17163                    character: 20,
17164                },
17165                end: lsp::Position {
17166                    line: 0,
17167                    character: 22,
17168                },
17169            },
17170            new_text: "".to_string(),
17171        }]),
17172        ..Default::default()
17173    };
17174
17175    let closure_completion_item = completion_item.clone();
17176    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17177        let task_completion_item = closure_completion_item.clone();
17178        async move {
17179            Ok(Some(lsp::CompletionResponse::Array(vec![
17180                task_completion_item,
17181            ])))
17182        }
17183    });
17184
17185    request.next().await;
17186
17187    cx.condition(|editor, _| editor.context_menu_visible())
17188        .await;
17189    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17190        editor
17191            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17192            .unwrap()
17193    });
17194    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17195
17196    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17197        let task_completion_item = completion_item.clone();
17198        async move { Ok(task_completion_item) }
17199    })
17200    .next()
17201    .await
17202    .unwrap();
17203    apply_additional_edits.await.unwrap();
17204    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17205}
17206
17207#[gpui::test]
17208async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17209    init_test(cx, |_| {});
17210
17211    let mut cx = EditorLspTestContext::new_rust(
17212        lsp::ServerCapabilities {
17213            completion_provider: Some(lsp::CompletionOptions {
17214                trigger_characters: Some(vec![".".to_string()]),
17215                resolve_provider: Some(true),
17216                ..Default::default()
17217            }),
17218            ..Default::default()
17219        },
17220        cx,
17221    )
17222    .await;
17223
17224    cx.set_state("fn main() { let a = 2ˇ; }");
17225    cx.simulate_keystroke(".");
17226
17227    let item1 = lsp::CompletionItem {
17228        label: "method id()".to_string(),
17229        filter_text: Some("id".to_string()),
17230        detail: None,
17231        documentation: None,
17232        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17233            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17234            new_text: ".id".to_string(),
17235        })),
17236        ..lsp::CompletionItem::default()
17237    };
17238
17239    let item2 = lsp::CompletionItem {
17240        label: "other".to_string(),
17241        filter_text: Some("other".to_string()),
17242        detail: None,
17243        documentation: None,
17244        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17245            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17246            new_text: ".other".to_string(),
17247        })),
17248        ..lsp::CompletionItem::default()
17249    };
17250
17251    let item1 = item1.clone();
17252    cx.set_request_handler::<lsp::request::Completion, _, _>({
17253        let item1 = item1.clone();
17254        move |_, _, _| {
17255            let item1 = item1.clone();
17256            let item2 = item2.clone();
17257            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17258        }
17259    })
17260    .next()
17261    .await;
17262
17263    cx.condition(|editor, _| editor.context_menu_visible())
17264        .await;
17265    cx.update_editor(|editor, _, _| {
17266        let context_menu = editor.context_menu.borrow_mut();
17267        let context_menu = context_menu
17268            .as_ref()
17269            .expect("Should have the context menu deployed");
17270        match context_menu {
17271            CodeContextMenu::Completions(completions_menu) => {
17272                let completions = completions_menu.completions.borrow_mut();
17273                assert_eq!(
17274                    completions
17275                        .iter()
17276                        .map(|completion| &completion.label.text)
17277                        .collect::<Vec<_>>(),
17278                    vec!["method id()", "other"]
17279                )
17280            }
17281            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17282        }
17283    });
17284
17285    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17286        let item1 = item1.clone();
17287        move |_, item_to_resolve, _| {
17288            let item1 = item1.clone();
17289            async move {
17290                if item1 == item_to_resolve {
17291                    Ok(lsp::CompletionItem {
17292                        label: "method id()".to_string(),
17293                        filter_text: Some("id".to_string()),
17294                        detail: Some("Now resolved!".to_string()),
17295                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17296                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17297                            range: lsp::Range::new(
17298                                lsp::Position::new(0, 22),
17299                                lsp::Position::new(0, 22),
17300                            ),
17301                            new_text: ".id".to_string(),
17302                        })),
17303                        ..lsp::CompletionItem::default()
17304                    })
17305                } else {
17306                    Ok(item_to_resolve)
17307                }
17308            }
17309        }
17310    })
17311    .next()
17312    .await
17313    .unwrap();
17314    cx.run_until_parked();
17315
17316    cx.update_editor(|editor, window, cx| {
17317        editor.context_menu_next(&Default::default(), window, cx);
17318    });
17319
17320    cx.update_editor(|editor, _, _| {
17321        let context_menu = editor.context_menu.borrow_mut();
17322        let context_menu = context_menu
17323            .as_ref()
17324            .expect("Should have the context menu deployed");
17325        match context_menu {
17326            CodeContextMenu::Completions(completions_menu) => {
17327                let completions = completions_menu.completions.borrow_mut();
17328                assert_eq!(
17329                    completions
17330                        .iter()
17331                        .map(|completion| &completion.label.text)
17332                        .collect::<Vec<_>>(),
17333                    vec!["method id() Now resolved!", "other"],
17334                    "Should update first completion label, but not second as the filter text did not match."
17335                );
17336            }
17337            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17338        }
17339    });
17340}
17341
17342#[gpui::test]
17343async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17344    init_test(cx, |_| {});
17345    let mut cx = EditorLspTestContext::new_rust(
17346        lsp::ServerCapabilities {
17347            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17348            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17349            completion_provider: Some(lsp::CompletionOptions {
17350                resolve_provider: Some(true),
17351                ..Default::default()
17352            }),
17353            ..Default::default()
17354        },
17355        cx,
17356    )
17357    .await;
17358    cx.set_state(indoc! {"
17359        struct TestStruct {
17360            field: i32
17361        }
17362
17363        fn mainˇ() {
17364            let unused_var = 42;
17365            let test_struct = TestStruct { field: 42 };
17366        }
17367    "});
17368    let symbol_range = cx.lsp_range(indoc! {"
17369        struct TestStruct {
17370            field: i32
17371        }
17372
17373        «fn main»() {
17374            let unused_var = 42;
17375            let test_struct = TestStruct { field: 42 };
17376        }
17377    "});
17378    let mut hover_requests =
17379        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17380            Ok(Some(lsp::Hover {
17381                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17382                    kind: lsp::MarkupKind::Markdown,
17383                    value: "Function documentation".to_string(),
17384                }),
17385                range: Some(symbol_range),
17386            }))
17387        });
17388
17389    // Case 1: Test that code action menu hide hover popover
17390    cx.dispatch_action(Hover);
17391    hover_requests.next().await;
17392    cx.condition(|editor, _| editor.hover_state.visible()).await;
17393    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17394        move |_, _, _| async move {
17395            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17396                lsp::CodeAction {
17397                    title: "Remove unused variable".to_string(),
17398                    kind: Some(CodeActionKind::QUICKFIX),
17399                    edit: Some(lsp::WorkspaceEdit {
17400                        changes: Some(
17401                            [(
17402                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17403                                vec![lsp::TextEdit {
17404                                    range: lsp::Range::new(
17405                                        lsp::Position::new(5, 4),
17406                                        lsp::Position::new(5, 27),
17407                                    ),
17408                                    new_text: "".to_string(),
17409                                }],
17410                            )]
17411                            .into_iter()
17412                            .collect(),
17413                        ),
17414                        ..Default::default()
17415                    }),
17416                    ..Default::default()
17417                },
17418            )]))
17419        },
17420    );
17421    cx.update_editor(|editor, window, cx| {
17422        editor.toggle_code_actions(
17423            &ToggleCodeActions {
17424                deployed_from: None,
17425                quick_launch: false,
17426            },
17427            window,
17428            cx,
17429        );
17430    });
17431    code_action_requests.next().await;
17432    cx.run_until_parked();
17433    cx.condition(|editor, _| editor.context_menu_visible())
17434        .await;
17435    cx.update_editor(|editor, _, _| {
17436        assert!(
17437            !editor.hover_state.visible(),
17438            "Hover popover should be hidden when code action menu is shown"
17439        );
17440        // Hide code actions
17441        editor.context_menu.take();
17442    });
17443
17444    // Case 2: Test that code completions hide hover popover
17445    cx.dispatch_action(Hover);
17446    hover_requests.next().await;
17447    cx.condition(|editor, _| editor.hover_state.visible()).await;
17448    let counter = Arc::new(AtomicUsize::new(0));
17449    let mut completion_requests =
17450        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17451            let counter = counter.clone();
17452            async move {
17453                counter.fetch_add(1, atomic::Ordering::Release);
17454                Ok(Some(lsp::CompletionResponse::Array(vec![
17455                    lsp::CompletionItem {
17456                        label: "main".into(),
17457                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17458                        detail: Some("() -> ()".to_string()),
17459                        ..Default::default()
17460                    },
17461                    lsp::CompletionItem {
17462                        label: "TestStruct".into(),
17463                        kind: Some(lsp::CompletionItemKind::STRUCT),
17464                        detail: Some("struct TestStruct".to_string()),
17465                        ..Default::default()
17466                    },
17467                ])))
17468            }
17469        });
17470    cx.update_editor(|editor, window, cx| {
17471        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17472    });
17473    completion_requests.next().await;
17474    cx.condition(|editor, _| editor.context_menu_visible())
17475        .await;
17476    cx.update_editor(|editor, _, _| {
17477        assert!(
17478            !editor.hover_state.visible(),
17479            "Hover popover should be hidden when completion menu is shown"
17480        );
17481    });
17482}
17483
17484#[gpui::test]
17485async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17486    init_test(cx, |_| {});
17487
17488    let mut cx = EditorLspTestContext::new_rust(
17489        lsp::ServerCapabilities {
17490            completion_provider: Some(lsp::CompletionOptions {
17491                trigger_characters: Some(vec![".".to_string()]),
17492                resolve_provider: Some(true),
17493                ..Default::default()
17494            }),
17495            ..Default::default()
17496        },
17497        cx,
17498    )
17499    .await;
17500
17501    cx.set_state("fn main() { let a = 2ˇ; }");
17502    cx.simulate_keystroke(".");
17503
17504    let unresolved_item_1 = lsp::CompletionItem {
17505        label: "id".to_string(),
17506        filter_text: Some("id".to_string()),
17507        detail: None,
17508        documentation: None,
17509        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17510            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17511            new_text: ".id".to_string(),
17512        })),
17513        ..lsp::CompletionItem::default()
17514    };
17515    let resolved_item_1 = lsp::CompletionItem {
17516        additional_text_edits: Some(vec![lsp::TextEdit {
17517            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17518            new_text: "!!".to_string(),
17519        }]),
17520        ..unresolved_item_1.clone()
17521    };
17522    let unresolved_item_2 = lsp::CompletionItem {
17523        label: "other".to_string(),
17524        filter_text: Some("other".to_string()),
17525        detail: None,
17526        documentation: None,
17527        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17528            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17529            new_text: ".other".to_string(),
17530        })),
17531        ..lsp::CompletionItem::default()
17532    };
17533    let resolved_item_2 = lsp::CompletionItem {
17534        additional_text_edits: Some(vec![lsp::TextEdit {
17535            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17536            new_text: "??".to_string(),
17537        }]),
17538        ..unresolved_item_2.clone()
17539    };
17540
17541    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17542    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17543    cx.lsp
17544        .server
17545        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17546            let unresolved_item_1 = unresolved_item_1.clone();
17547            let resolved_item_1 = resolved_item_1.clone();
17548            let unresolved_item_2 = unresolved_item_2.clone();
17549            let resolved_item_2 = resolved_item_2.clone();
17550            let resolve_requests_1 = resolve_requests_1.clone();
17551            let resolve_requests_2 = resolve_requests_2.clone();
17552            move |unresolved_request, _| {
17553                let unresolved_item_1 = unresolved_item_1.clone();
17554                let resolved_item_1 = resolved_item_1.clone();
17555                let unresolved_item_2 = unresolved_item_2.clone();
17556                let resolved_item_2 = resolved_item_2.clone();
17557                let resolve_requests_1 = resolve_requests_1.clone();
17558                let resolve_requests_2 = resolve_requests_2.clone();
17559                async move {
17560                    if unresolved_request == unresolved_item_1 {
17561                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17562                        Ok(resolved_item_1.clone())
17563                    } else if unresolved_request == unresolved_item_2 {
17564                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17565                        Ok(resolved_item_2.clone())
17566                    } else {
17567                        panic!("Unexpected completion item {unresolved_request:?}")
17568                    }
17569                }
17570            }
17571        })
17572        .detach();
17573
17574    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17575        let unresolved_item_1 = unresolved_item_1.clone();
17576        let unresolved_item_2 = unresolved_item_2.clone();
17577        async move {
17578            Ok(Some(lsp::CompletionResponse::Array(vec![
17579                unresolved_item_1,
17580                unresolved_item_2,
17581            ])))
17582        }
17583    })
17584    .next()
17585    .await;
17586
17587    cx.condition(|editor, _| editor.context_menu_visible())
17588        .await;
17589    cx.update_editor(|editor, _, _| {
17590        let context_menu = editor.context_menu.borrow_mut();
17591        let context_menu = context_menu
17592            .as_ref()
17593            .expect("Should have the context menu deployed");
17594        match context_menu {
17595            CodeContextMenu::Completions(completions_menu) => {
17596                let completions = completions_menu.completions.borrow_mut();
17597                assert_eq!(
17598                    completions
17599                        .iter()
17600                        .map(|completion| &completion.label.text)
17601                        .collect::<Vec<_>>(),
17602                    vec!["id", "other"]
17603                )
17604            }
17605            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17606        }
17607    });
17608    cx.run_until_parked();
17609
17610    cx.update_editor(|editor, window, cx| {
17611        editor.context_menu_next(&ContextMenuNext, window, cx);
17612    });
17613    cx.run_until_parked();
17614    cx.update_editor(|editor, window, cx| {
17615        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17616    });
17617    cx.run_until_parked();
17618    cx.update_editor(|editor, window, cx| {
17619        editor.context_menu_next(&ContextMenuNext, window, cx);
17620    });
17621    cx.run_until_parked();
17622    cx.update_editor(|editor, window, cx| {
17623        editor
17624            .compose_completion(&ComposeCompletion::default(), window, cx)
17625            .expect("No task returned")
17626    })
17627    .await
17628    .expect("Completion failed");
17629    cx.run_until_parked();
17630
17631    cx.update_editor(|editor, _, cx| {
17632        assert_eq!(
17633            resolve_requests_1.load(atomic::Ordering::Acquire),
17634            1,
17635            "Should always resolve once despite multiple selections"
17636        );
17637        assert_eq!(
17638            resolve_requests_2.load(atomic::Ordering::Acquire),
17639            1,
17640            "Should always resolve once after multiple selections and applying the completion"
17641        );
17642        assert_eq!(
17643            editor.text(cx),
17644            "fn main() { let a = ??.other; }",
17645            "Should use resolved data when applying the completion"
17646        );
17647    });
17648}
17649
17650#[gpui::test]
17651async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17652    init_test(cx, |_| {});
17653
17654    let item_0 = lsp::CompletionItem {
17655        label: "abs".into(),
17656        insert_text: Some("abs".into()),
17657        data: Some(json!({ "very": "special"})),
17658        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17659        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17660            lsp::InsertReplaceEdit {
17661                new_text: "abs".to_string(),
17662                insert: lsp::Range::default(),
17663                replace: lsp::Range::default(),
17664            },
17665        )),
17666        ..lsp::CompletionItem::default()
17667    };
17668    let items = iter::once(item_0.clone())
17669        .chain((11..51).map(|i| lsp::CompletionItem {
17670            label: format!("item_{}", i),
17671            insert_text: Some(format!("item_{}", i)),
17672            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17673            ..lsp::CompletionItem::default()
17674        }))
17675        .collect::<Vec<_>>();
17676
17677    let default_commit_characters = vec!["?".to_string()];
17678    let default_data = json!({ "default": "data"});
17679    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17680    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17681    let default_edit_range = lsp::Range {
17682        start: lsp::Position {
17683            line: 0,
17684            character: 5,
17685        },
17686        end: lsp::Position {
17687            line: 0,
17688            character: 5,
17689        },
17690    };
17691
17692    let mut cx = EditorLspTestContext::new_rust(
17693        lsp::ServerCapabilities {
17694            completion_provider: Some(lsp::CompletionOptions {
17695                trigger_characters: Some(vec![".".to_string()]),
17696                resolve_provider: Some(true),
17697                ..Default::default()
17698            }),
17699            ..Default::default()
17700        },
17701        cx,
17702    )
17703    .await;
17704
17705    cx.set_state("fn main() { let a = 2ˇ; }");
17706    cx.simulate_keystroke(".");
17707
17708    let completion_data = default_data.clone();
17709    let completion_characters = default_commit_characters.clone();
17710    let completion_items = items.clone();
17711    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17712        let default_data = completion_data.clone();
17713        let default_commit_characters = completion_characters.clone();
17714        let items = completion_items.clone();
17715        async move {
17716            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17717                items,
17718                item_defaults: Some(lsp::CompletionListItemDefaults {
17719                    data: Some(default_data.clone()),
17720                    commit_characters: Some(default_commit_characters.clone()),
17721                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17722                        default_edit_range,
17723                    )),
17724                    insert_text_format: Some(default_insert_text_format),
17725                    insert_text_mode: Some(default_insert_text_mode),
17726                }),
17727                ..lsp::CompletionList::default()
17728            })))
17729        }
17730    })
17731    .next()
17732    .await;
17733
17734    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17735    cx.lsp
17736        .server
17737        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17738            let closure_resolved_items = resolved_items.clone();
17739            move |item_to_resolve, _| {
17740                let closure_resolved_items = closure_resolved_items.clone();
17741                async move {
17742                    closure_resolved_items.lock().push(item_to_resolve.clone());
17743                    Ok(item_to_resolve)
17744                }
17745            }
17746        })
17747        .detach();
17748
17749    cx.condition(|editor, _| editor.context_menu_visible())
17750        .await;
17751    cx.run_until_parked();
17752    cx.update_editor(|editor, _, _| {
17753        let menu = editor.context_menu.borrow_mut();
17754        match menu.as_ref().expect("should have the completions menu") {
17755            CodeContextMenu::Completions(completions_menu) => {
17756                assert_eq!(
17757                    completions_menu
17758                        .entries
17759                        .borrow()
17760                        .iter()
17761                        .map(|mat| mat.string.clone())
17762                        .collect::<Vec<String>>(),
17763                    items
17764                        .iter()
17765                        .map(|completion| completion.label.clone())
17766                        .collect::<Vec<String>>()
17767                );
17768            }
17769            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17770        }
17771    });
17772    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17773    // with 4 from the end.
17774    assert_eq!(
17775        *resolved_items.lock(),
17776        [&items[0..16], &items[items.len() - 4..items.len()]]
17777            .concat()
17778            .iter()
17779            .cloned()
17780            .map(|mut item| {
17781                if item.data.is_none() {
17782                    item.data = Some(default_data.clone());
17783                }
17784                item
17785            })
17786            .collect::<Vec<lsp::CompletionItem>>(),
17787        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17788    );
17789    resolved_items.lock().clear();
17790
17791    cx.update_editor(|editor, window, cx| {
17792        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17793    });
17794    cx.run_until_parked();
17795    // Completions that have already been resolved are skipped.
17796    assert_eq!(
17797        *resolved_items.lock(),
17798        items[items.len() - 17..items.len() - 4]
17799            .iter()
17800            .cloned()
17801            .map(|mut item| {
17802                if item.data.is_none() {
17803                    item.data = Some(default_data.clone());
17804                }
17805                item
17806            })
17807            .collect::<Vec<lsp::CompletionItem>>()
17808    );
17809    resolved_items.lock().clear();
17810}
17811
17812#[gpui::test]
17813async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17814    init_test(cx, |_| {});
17815
17816    let mut cx = EditorLspTestContext::new(
17817        Language::new(
17818            LanguageConfig {
17819                matcher: LanguageMatcher {
17820                    path_suffixes: vec!["jsx".into()],
17821                    ..Default::default()
17822                },
17823                overrides: [(
17824                    "element".into(),
17825                    LanguageConfigOverride {
17826                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17827                        ..Default::default()
17828                    },
17829                )]
17830                .into_iter()
17831                .collect(),
17832                ..Default::default()
17833            },
17834            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17835        )
17836        .with_override_query("(jsx_self_closing_element) @element")
17837        .unwrap(),
17838        lsp::ServerCapabilities {
17839            completion_provider: Some(lsp::CompletionOptions {
17840                trigger_characters: Some(vec![":".to_string()]),
17841                ..Default::default()
17842            }),
17843            ..Default::default()
17844        },
17845        cx,
17846    )
17847    .await;
17848
17849    cx.lsp
17850        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17851            Ok(Some(lsp::CompletionResponse::Array(vec![
17852                lsp::CompletionItem {
17853                    label: "bg-blue".into(),
17854                    ..Default::default()
17855                },
17856                lsp::CompletionItem {
17857                    label: "bg-red".into(),
17858                    ..Default::default()
17859                },
17860                lsp::CompletionItem {
17861                    label: "bg-yellow".into(),
17862                    ..Default::default()
17863                },
17864            ])))
17865        });
17866
17867    cx.set_state(r#"<p class="bgˇ" />"#);
17868
17869    // Trigger completion when typing a dash, because the dash is an extra
17870    // word character in the 'element' scope, which contains the cursor.
17871    cx.simulate_keystroke("-");
17872    cx.executor().run_until_parked();
17873    cx.update_editor(|editor, _, _| {
17874        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17875        {
17876            assert_eq!(
17877                completion_menu_entries(menu),
17878                &["bg-blue", "bg-red", "bg-yellow"]
17879            );
17880        } else {
17881            panic!("expected completion menu to be open");
17882        }
17883    });
17884
17885    cx.simulate_keystroke("l");
17886    cx.executor().run_until_parked();
17887    cx.update_editor(|editor, _, _| {
17888        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17889        {
17890            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17891        } else {
17892            panic!("expected completion menu to be open");
17893        }
17894    });
17895
17896    // When filtering completions, consider the character after the '-' to
17897    // be the start of a subword.
17898    cx.set_state(r#"<p class="yelˇ" />"#);
17899    cx.simulate_keystroke("l");
17900    cx.executor().run_until_parked();
17901    cx.update_editor(|editor, _, _| {
17902        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17903        {
17904            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
17905        } else {
17906            panic!("expected completion menu to be open");
17907        }
17908    });
17909}
17910
17911fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
17912    let entries = menu.entries.borrow();
17913    entries.iter().map(|mat| mat.string.clone()).collect()
17914}
17915
17916#[gpui::test]
17917async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
17918    init_test(cx, |settings| {
17919        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
17920            Formatter::Prettier,
17921        )))
17922    });
17923
17924    let fs = FakeFs::new(cx.executor());
17925    fs.insert_file(path!("/file.ts"), Default::default()).await;
17926
17927    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
17928    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17929
17930    language_registry.add(Arc::new(Language::new(
17931        LanguageConfig {
17932            name: "TypeScript".into(),
17933            matcher: LanguageMatcher {
17934                path_suffixes: vec!["ts".to_string()],
17935                ..Default::default()
17936            },
17937            ..Default::default()
17938        },
17939        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17940    )));
17941    update_test_language_settings(cx, |settings| {
17942        settings.defaults.prettier = Some(PrettierSettings {
17943            allowed: true,
17944            ..PrettierSettings::default()
17945        });
17946    });
17947
17948    let test_plugin = "test_plugin";
17949    let _ = language_registry.register_fake_lsp(
17950        "TypeScript",
17951        FakeLspAdapter {
17952            prettier_plugins: vec![test_plugin],
17953            ..Default::default()
17954        },
17955    );
17956
17957    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
17958    let buffer = project
17959        .update(cx, |project, cx| {
17960            project.open_local_buffer(path!("/file.ts"), cx)
17961        })
17962        .await
17963        .unwrap();
17964
17965    let buffer_text = "one\ntwo\nthree\n";
17966    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17967    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17968    editor.update_in(cx, |editor, window, cx| {
17969        editor.set_text(buffer_text, window, cx)
17970    });
17971
17972    editor
17973        .update_in(cx, |editor, window, cx| {
17974            editor.perform_format(
17975                project.clone(),
17976                FormatTrigger::Manual,
17977                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
17978                window,
17979                cx,
17980            )
17981        })
17982        .unwrap()
17983        .await;
17984    assert_eq!(
17985        editor.update(cx, |editor, cx| editor.text(cx)),
17986        buffer_text.to_string() + prettier_format_suffix,
17987        "Test prettier formatting was not applied to the original buffer text",
17988    );
17989
17990    update_test_language_settings(cx, |settings| {
17991        settings.defaults.formatter = Some(SelectedFormatter::Auto)
17992    });
17993    let format = editor.update_in(cx, |editor, window, cx| {
17994        editor.perform_format(
17995            project.clone(),
17996            FormatTrigger::Manual,
17997            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
17998            window,
17999            cx,
18000        )
18001    });
18002    format.await.unwrap();
18003    assert_eq!(
18004        editor.update(cx, |editor, cx| editor.text(cx)),
18005        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18006        "Autoformatting (via test prettier) was not applied to the original buffer text",
18007    );
18008}
18009
18010#[gpui::test]
18011async fn test_addition_reverts(cx: &mut TestAppContext) {
18012    init_test(cx, |_| {});
18013    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18014    let base_text = indoc! {r#"
18015        struct Row;
18016        struct Row1;
18017        struct Row2;
18018
18019        struct Row4;
18020        struct Row5;
18021        struct Row6;
18022
18023        struct Row8;
18024        struct Row9;
18025        struct Row10;"#};
18026
18027    // When addition hunks are not adjacent to carets, no hunk revert is performed
18028    assert_hunk_revert(
18029        indoc! {r#"struct Row;
18030                   struct Row1;
18031                   struct Row1.1;
18032                   struct Row1.2;
18033                   struct Row2;ˇ
18034
18035                   struct Row4;
18036                   struct Row5;
18037                   struct Row6;
18038
18039                   struct Row8;
18040                   ˇstruct Row9;
18041                   struct Row9.1;
18042                   struct Row9.2;
18043                   struct Row9.3;
18044                   struct Row10;"#},
18045        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18046        indoc! {r#"struct Row;
18047                   struct Row1;
18048                   struct Row1.1;
18049                   struct Row1.2;
18050                   struct Row2;ˇ
18051
18052                   struct Row4;
18053                   struct Row5;
18054                   struct Row6;
18055
18056                   struct Row8;
18057                   ˇstruct Row9;
18058                   struct Row9.1;
18059                   struct Row9.2;
18060                   struct Row9.3;
18061                   struct Row10;"#},
18062        base_text,
18063        &mut cx,
18064    );
18065    // Same for selections
18066    assert_hunk_revert(
18067        indoc! {r#"struct Row;
18068                   struct Row1;
18069                   struct Row2;
18070                   struct Row2.1;
18071                   struct Row2.2;
18072                   «ˇ
18073                   struct Row4;
18074                   struct» Row5;
18075                   «struct Row6;
18076                   ˇ»
18077                   struct Row9.1;
18078                   struct Row9.2;
18079                   struct Row9.3;
18080                   struct Row8;
18081                   struct Row9;
18082                   struct Row10;"#},
18083        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18084        indoc! {r#"struct Row;
18085                   struct Row1;
18086                   struct Row2;
18087                   struct Row2.1;
18088                   struct Row2.2;
18089                   «ˇ
18090                   struct Row4;
18091                   struct» Row5;
18092                   «struct Row6;
18093                   ˇ»
18094                   struct Row9.1;
18095                   struct Row9.2;
18096                   struct Row9.3;
18097                   struct Row8;
18098                   struct Row9;
18099                   struct Row10;"#},
18100        base_text,
18101        &mut cx,
18102    );
18103
18104    // When carets and selections intersect the addition hunks, those are reverted.
18105    // Adjacent carets got merged.
18106    assert_hunk_revert(
18107        indoc! {r#"struct Row;
18108                   ˇ// something on the top
18109                   struct Row1;
18110                   struct Row2;
18111                   struct Roˇw3.1;
18112                   struct Row2.2;
18113                   struct Row2.3;ˇ
18114
18115                   struct Row4;
18116                   struct ˇRow5.1;
18117                   struct Row5.2;
18118                   struct «Rowˇ»5.3;
18119                   struct Row5;
18120                   struct Row6;
18121                   ˇ
18122                   struct Row9.1;
18123                   struct «Rowˇ»9.2;
18124                   struct «ˇRow»9.3;
18125                   struct Row8;
18126                   struct Row9;
18127                   «ˇ// something on bottom»
18128                   struct Row10;"#},
18129        vec![
18130            DiffHunkStatusKind::Added,
18131            DiffHunkStatusKind::Added,
18132            DiffHunkStatusKind::Added,
18133            DiffHunkStatusKind::Added,
18134            DiffHunkStatusKind::Added,
18135        ],
18136        indoc! {r#"struct Row;
18137                   ˇstruct Row1;
18138                   struct Row2;
18139                   ˇ
18140                   struct Row4;
18141                   ˇstruct Row5;
18142                   struct Row6;
18143                   ˇ
18144                   ˇstruct Row8;
18145                   struct Row9;
18146                   ˇstruct Row10;"#},
18147        base_text,
18148        &mut cx,
18149    );
18150}
18151
18152#[gpui::test]
18153async fn test_modification_reverts(cx: &mut TestAppContext) {
18154    init_test(cx, |_| {});
18155    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18156    let base_text = indoc! {r#"
18157        struct Row;
18158        struct Row1;
18159        struct Row2;
18160
18161        struct Row4;
18162        struct Row5;
18163        struct Row6;
18164
18165        struct Row8;
18166        struct Row9;
18167        struct Row10;"#};
18168
18169    // Modification hunks behave the same as the addition ones.
18170    assert_hunk_revert(
18171        indoc! {r#"struct Row;
18172                   struct Row1;
18173                   struct Row33;
18174                   ˇ
18175                   struct Row4;
18176                   struct Row5;
18177                   struct Row6;
18178                   ˇ
18179                   struct Row99;
18180                   struct Row9;
18181                   struct Row10;"#},
18182        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18183        indoc! {r#"struct Row;
18184                   struct Row1;
18185                   struct Row33;
18186                   ˇ
18187                   struct Row4;
18188                   struct Row5;
18189                   struct Row6;
18190                   ˇ
18191                   struct Row99;
18192                   struct Row9;
18193                   struct Row10;"#},
18194        base_text,
18195        &mut cx,
18196    );
18197    assert_hunk_revert(
18198        indoc! {r#"struct Row;
18199                   struct Row1;
18200                   struct Row33;
18201                   «ˇ
18202                   struct Row4;
18203                   struct» Row5;
18204                   «struct Row6;
18205                   ˇ»
18206                   struct Row99;
18207                   struct Row9;
18208                   struct Row10;"#},
18209        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18210        indoc! {r#"struct Row;
18211                   struct Row1;
18212                   struct Row33;
18213                   «ˇ
18214                   struct Row4;
18215                   struct» Row5;
18216                   «struct Row6;
18217                   ˇ»
18218                   struct Row99;
18219                   struct Row9;
18220                   struct Row10;"#},
18221        base_text,
18222        &mut cx,
18223    );
18224
18225    assert_hunk_revert(
18226        indoc! {r#"ˇstruct Row1.1;
18227                   struct Row1;
18228                   «ˇstr»uct Row22;
18229
18230                   struct ˇRow44;
18231                   struct Row5;
18232                   struct «Rˇ»ow66;ˇ
18233
18234                   «struˇ»ct Row88;
18235                   struct Row9;
18236                   struct Row1011;ˇ"#},
18237        vec![
18238            DiffHunkStatusKind::Modified,
18239            DiffHunkStatusKind::Modified,
18240            DiffHunkStatusKind::Modified,
18241            DiffHunkStatusKind::Modified,
18242            DiffHunkStatusKind::Modified,
18243            DiffHunkStatusKind::Modified,
18244        ],
18245        indoc! {r#"struct Row;
18246                   ˇstruct Row1;
18247                   struct Row2;
18248                   ˇ
18249                   struct Row4;
18250                   ˇstruct Row5;
18251                   struct Row6;
18252                   ˇ
18253                   struct Row8;
18254                   ˇstruct Row9;
18255                   struct Row10;ˇ"#},
18256        base_text,
18257        &mut cx,
18258    );
18259}
18260
18261#[gpui::test]
18262async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18263    init_test(cx, |_| {});
18264    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18265    let base_text = indoc! {r#"
18266        one
18267
18268        two
18269        three
18270        "#};
18271
18272    cx.set_head_text(base_text);
18273    cx.set_state("\nˇ\n");
18274    cx.executor().run_until_parked();
18275    cx.update_editor(|editor, _window, cx| {
18276        editor.expand_selected_diff_hunks(cx);
18277    });
18278    cx.executor().run_until_parked();
18279    cx.update_editor(|editor, window, cx| {
18280        editor.backspace(&Default::default(), window, cx);
18281    });
18282    cx.run_until_parked();
18283    cx.assert_state_with_diff(
18284        indoc! {r#"
18285
18286        - two
18287        - threeˇ
18288        +
18289        "#}
18290        .to_string(),
18291    );
18292}
18293
18294#[gpui::test]
18295async fn test_deletion_reverts(cx: &mut TestAppContext) {
18296    init_test(cx, |_| {});
18297    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18298    let base_text = indoc! {r#"struct Row;
18299struct Row1;
18300struct Row2;
18301
18302struct Row4;
18303struct Row5;
18304struct Row6;
18305
18306struct Row8;
18307struct Row9;
18308struct Row10;"#};
18309
18310    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18311    assert_hunk_revert(
18312        indoc! {r#"struct Row;
18313                   struct Row2;
18314
18315                   ˇstruct Row4;
18316                   struct Row5;
18317                   struct Row6;
18318                   ˇ
18319                   struct Row8;
18320                   struct Row10;"#},
18321        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18322        indoc! {r#"struct Row;
18323                   struct Row2;
18324
18325                   ˇstruct Row4;
18326                   struct Row5;
18327                   struct Row6;
18328                   ˇ
18329                   struct Row8;
18330                   struct Row10;"#},
18331        base_text,
18332        &mut cx,
18333    );
18334    assert_hunk_revert(
18335        indoc! {r#"struct Row;
18336                   struct Row2;
18337
18338                   «ˇstruct Row4;
18339                   struct» Row5;
18340                   «struct Row6;
18341                   ˇ»
18342                   struct Row8;
18343                   struct Row10;"#},
18344        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18345        indoc! {r#"struct Row;
18346                   struct Row2;
18347
18348                   «ˇstruct Row4;
18349                   struct» Row5;
18350                   «struct Row6;
18351                   ˇ»
18352                   struct Row8;
18353                   struct Row10;"#},
18354        base_text,
18355        &mut cx,
18356    );
18357
18358    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18359    assert_hunk_revert(
18360        indoc! {r#"struct Row;
18361                   ˇstruct Row2;
18362
18363                   struct Row4;
18364                   struct Row5;
18365                   struct Row6;
18366
18367                   struct Row8;ˇ
18368                   struct Row10;"#},
18369        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18370        indoc! {r#"struct Row;
18371                   struct Row1;
18372                   ˇstruct Row2;
18373
18374                   struct Row4;
18375                   struct Row5;
18376                   struct Row6;
18377
18378                   struct Row8;ˇ
18379                   struct Row9;
18380                   struct Row10;"#},
18381        base_text,
18382        &mut cx,
18383    );
18384    assert_hunk_revert(
18385        indoc! {r#"struct Row;
18386                   struct Row2«ˇ;
18387                   struct Row4;
18388                   struct» Row5;
18389                   «struct Row6;
18390
18391                   struct Row8;ˇ»
18392                   struct Row10;"#},
18393        vec![
18394            DiffHunkStatusKind::Deleted,
18395            DiffHunkStatusKind::Deleted,
18396            DiffHunkStatusKind::Deleted,
18397        ],
18398        indoc! {r#"struct Row;
18399                   struct Row1;
18400                   struct Row2«ˇ;
18401
18402                   struct Row4;
18403                   struct» Row5;
18404                   «struct Row6;
18405
18406                   struct Row8;ˇ»
18407                   struct Row9;
18408                   struct Row10;"#},
18409        base_text,
18410        &mut cx,
18411    );
18412}
18413
18414#[gpui::test]
18415async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18416    init_test(cx, |_| {});
18417
18418    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18419    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18420    let base_text_3 =
18421        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18422
18423    let text_1 = edit_first_char_of_every_line(base_text_1);
18424    let text_2 = edit_first_char_of_every_line(base_text_2);
18425    let text_3 = edit_first_char_of_every_line(base_text_3);
18426
18427    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18428    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18429    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18430
18431    let multibuffer = cx.new(|cx| {
18432        let mut multibuffer = MultiBuffer::new(ReadWrite);
18433        multibuffer.push_excerpts(
18434            buffer_1.clone(),
18435            [
18436                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18437                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18438                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18439            ],
18440            cx,
18441        );
18442        multibuffer.push_excerpts(
18443            buffer_2.clone(),
18444            [
18445                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18446                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18447                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18448            ],
18449            cx,
18450        );
18451        multibuffer.push_excerpts(
18452            buffer_3.clone(),
18453            [
18454                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18455                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18456                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18457            ],
18458            cx,
18459        );
18460        multibuffer
18461    });
18462
18463    let fs = FakeFs::new(cx.executor());
18464    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18465    let (editor, cx) = cx
18466        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18467    editor.update_in(cx, |editor, _window, cx| {
18468        for (buffer, diff_base) in [
18469            (buffer_1.clone(), base_text_1),
18470            (buffer_2.clone(), base_text_2),
18471            (buffer_3.clone(), base_text_3),
18472        ] {
18473            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18474            editor
18475                .buffer
18476                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18477        }
18478    });
18479    cx.executor().run_until_parked();
18480
18481    editor.update_in(cx, |editor, window, cx| {
18482        assert_eq!(editor.text(cx), "Xaaa\nXbbb\nXccc\n\nXfff\nXggg\n\nXjjj\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}");
18483        editor.select_all(&SelectAll, window, cx);
18484        editor.git_restore(&Default::default(), window, cx);
18485    });
18486    cx.executor().run_until_parked();
18487
18488    // When all ranges are selected, all buffer hunks are reverted.
18489    editor.update(cx, |editor, cx| {
18490        assert_eq!(editor.text(cx), "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nllll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu\n\n\nvvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}\n\n");
18491    });
18492    buffer_1.update(cx, |buffer, _| {
18493        assert_eq!(buffer.text(), base_text_1);
18494    });
18495    buffer_2.update(cx, |buffer, _| {
18496        assert_eq!(buffer.text(), base_text_2);
18497    });
18498    buffer_3.update(cx, |buffer, _| {
18499        assert_eq!(buffer.text(), base_text_3);
18500    });
18501
18502    editor.update_in(cx, |editor, window, cx| {
18503        editor.undo(&Default::default(), window, cx);
18504    });
18505
18506    editor.update_in(cx, |editor, window, cx| {
18507        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18508            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18509        });
18510        editor.git_restore(&Default::default(), window, cx);
18511    });
18512
18513    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18514    // but not affect buffer_2 and its related excerpts.
18515    editor.update(cx, |editor, cx| {
18516        assert_eq!(
18517            editor.text(cx),
18518            "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n\n\nXlll\nXmmm\nXnnn\n\nXqqq\nXrrr\n\nXuuu\nXvvv\nXwww\nXxxx\n\nX{{{\nX|||\n\nX\u{7f}\u{7f}\u{7f}"
18519        );
18520    });
18521    buffer_1.update(cx, |buffer, _| {
18522        assert_eq!(buffer.text(), base_text_1);
18523    });
18524    buffer_2.update(cx, |buffer, _| {
18525        assert_eq!(
18526            buffer.text(),
18527            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18528        );
18529    });
18530    buffer_3.update(cx, |buffer, _| {
18531        assert_eq!(
18532            buffer.text(),
18533            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18534        );
18535    });
18536
18537    fn edit_first_char_of_every_line(text: &str) -> String {
18538        text.split('\n')
18539            .map(|line| format!("X{}", &line[1..]))
18540            .collect::<Vec<_>>()
18541            .join("\n")
18542    }
18543}
18544
18545#[gpui::test]
18546async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18547    init_test(cx, |_| {});
18548
18549    let cols = 4;
18550    let rows = 10;
18551    let sample_text_1 = sample_text(rows, cols, 'a');
18552    assert_eq!(
18553        sample_text_1,
18554        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18555    );
18556    let sample_text_2 = sample_text(rows, cols, 'l');
18557    assert_eq!(
18558        sample_text_2,
18559        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18560    );
18561    let sample_text_3 = sample_text(rows, cols, 'v');
18562    assert_eq!(
18563        sample_text_3,
18564        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18565    );
18566
18567    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18568    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18569    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18570
18571    let multi_buffer = cx.new(|cx| {
18572        let mut multibuffer = MultiBuffer::new(ReadWrite);
18573        multibuffer.push_excerpts(
18574            buffer_1.clone(),
18575            [
18576                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18577                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18578                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18579            ],
18580            cx,
18581        );
18582        multibuffer.push_excerpts(
18583            buffer_2.clone(),
18584            [
18585                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18586                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18587                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18588            ],
18589            cx,
18590        );
18591        multibuffer.push_excerpts(
18592            buffer_3.clone(),
18593            [
18594                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18595                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18596                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18597            ],
18598            cx,
18599        );
18600        multibuffer
18601    });
18602
18603    let fs = FakeFs::new(cx.executor());
18604    fs.insert_tree(
18605        "/a",
18606        json!({
18607            "main.rs": sample_text_1,
18608            "other.rs": sample_text_2,
18609            "lib.rs": sample_text_3,
18610        }),
18611    )
18612    .await;
18613    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18614    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18615    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18616    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18617        Editor::new(
18618            EditorMode::full(),
18619            multi_buffer,
18620            Some(project.clone()),
18621            window,
18622            cx,
18623        )
18624    });
18625    let multibuffer_item_id = workspace
18626        .update(cx, |workspace, window, cx| {
18627            assert!(
18628                workspace.active_item(cx).is_none(),
18629                "active item should be None before the first item is added"
18630            );
18631            workspace.add_item_to_active_pane(
18632                Box::new(multi_buffer_editor.clone()),
18633                None,
18634                true,
18635                window,
18636                cx,
18637            );
18638            let active_item = workspace
18639                .active_item(cx)
18640                .expect("should have an active item after adding the multi buffer");
18641            assert!(
18642                !active_item.is_singleton(cx),
18643                "A multi buffer was expected to active after adding"
18644            );
18645            active_item.item_id()
18646        })
18647        .unwrap();
18648    cx.executor().run_until_parked();
18649
18650    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18651        editor.change_selections(
18652            SelectionEffects::scroll(Autoscroll::Next),
18653            window,
18654            cx,
18655            |s| s.select_ranges(Some(1..2)),
18656        );
18657        editor.open_excerpts(&OpenExcerpts, window, cx);
18658    });
18659    cx.executor().run_until_parked();
18660    let first_item_id = workspace
18661        .update(cx, |workspace, window, cx| {
18662            let active_item = workspace
18663                .active_item(cx)
18664                .expect("should have an active item after navigating into the 1st buffer");
18665            let first_item_id = active_item.item_id();
18666            assert_ne!(
18667                first_item_id, multibuffer_item_id,
18668                "Should navigate into the 1st buffer and activate it"
18669            );
18670            assert!(
18671                active_item.is_singleton(cx),
18672                "New active item should be a singleton buffer"
18673            );
18674            assert_eq!(
18675                active_item
18676                    .act_as::<Editor>(cx)
18677                    .expect("should have navigated into an editor for the 1st buffer")
18678                    .read(cx)
18679                    .text(cx),
18680                sample_text_1
18681            );
18682
18683            workspace
18684                .go_back(workspace.active_pane().downgrade(), window, cx)
18685                .detach_and_log_err(cx);
18686
18687            first_item_id
18688        })
18689        .unwrap();
18690    cx.executor().run_until_parked();
18691    workspace
18692        .update(cx, |workspace, _, cx| {
18693            let active_item = workspace
18694                .active_item(cx)
18695                .expect("should have an active item after navigating back");
18696            assert_eq!(
18697                active_item.item_id(),
18698                multibuffer_item_id,
18699                "Should navigate back to the multi buffer"
18700            );
18701            assert!(!active_item.is_singleton(cx));
18702        })
18703        .unwrap();
18704
18705    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18706        editor.change_selections(
18707            SelectionEffects::scroll(Autoscroll::Next),
18708            window,
18709            cx,
18710            |s| s.select_ranges(Some(39..40)),
18711        );
18712        editor.open_excerpts(&OpenExcerpts, window, cx);
18713    });
18714    cx.executor().run_until_parked();
18715    let second_item_id = workspace
18716        .update(cx, |workspace, window, cx| {
18717            let active_item = workspace
18718                .active_item(cx)
18719                .expect("should have an active item after navigating into the 2nd buffer");
18720            let second_item_id = active_item.item_id();
18721            assert_ne!(
18722                second_item_id, multibuffer_item_id,
18723                "Should navigate away from the multibuffer"
18724            );
18725            assert_ne!(
18726                second_item_id, first_item_id,
18727                "Should navigate into the 2nd buffer and activate it"
18728            );
18729            assert!(
18730                active_item.is_singleton(cx),
18731                "New active item should be a singleton buffer"
18732            );
18733            assert_eq!(
18734                active_item
18735                    .act_as::<Editor>(cx)
18736                    .expect("should have navigated into an editor")
18737                    .read(cx)
18738                    .text(cx),
18739                sample_text_2
18740            );
18741
18742            workspace
18743                .go_back(workspace.active_pane().downgrade(), window, cx)
18744                .detach_and_log_err(cx);
18745
18746            second_item_id
18747        })
18748        .unwrap();
18749    cx.executor().run_until_parked();
18750    workspace
18751        .update(cx, |workspace, _, cx| {
18752            let active_item = workspace
18753                .active_item(cx)
18754                .expect("should have an active item after navigating back from the 2nd buffer");
18755            assert_eq!(
18756                active_item.item_id(),
18757                multibuffer_item_id,
18758                "Should navigate back from the 2nd buffer to the multi buffer"
18759            );
18760            assert!(!active_item.is_singleton(cx));
18761        })
18762        .unwrap();
18763
18764    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18765        editor.change_selections(
18766            SelectionEffects::scroll(Autoscroll::Next),
18767            window,
18768            cx,
18769            |s| s.select_ranges(Some(70..70)),
18770        );
18771        editor.open_excerpts(&OpenExcerpts, window, cx);
18772    });
18773    cx.executor().run_until_parked();
18774    workspace
18775        .update(cx, |workspace, window, cx| {
18776            let active_item = workspace
18777                .active_item(cx)
18778                .expect("should have an active item after navigating into the 3rd buffer");
18779            let third_item_id = active_item.item_id();
18780            assert_ne!(
18781                third_item_id, multibuffer_item_id,
18782                "Should navigate into the 3rd buffer and activate it"
18783            );
18784            assert_ne!(third_item_id, first_item_id);
18785            assert_ne!(third_item_id, second_item_id);
18786            assert!(
18787                active_item.is_singleton(cx),
18788                "New active item should be a singleton buffer"
18789            );
18790            assert_eq!(
18791                active_item
18792                    .act_as::<Editor>(cx)
18793                    .expect("should have navigated into an editor")
18794                    .read(cx)
18795                    .text(cx),
18796                sample_text_3
18797            );
18798
18799            workspace
18800                .go_back(workspace.active_pane().downgrade(), window, cx)
18801                .detach_and_log_err(cx);
18802        })
18803        .unwrap();
18804    cx.executor().run_until_parked();
18805    workspace
18806        .update(cx, |workspace, _, cx| {
18807            let active_item = workspace
18808                .active_item(cx)
18809                .expect("should have an active item after navigating back from the 3rd buffer");
18810            assert_eq!(
18811                active_item.item_id(),
18812                multibuffer_item_id,
18813                "Should navigate back from the 3rd buffer to the multi buffer"
18814            );
18815            assert!(!active_item.is_singleton(cx));
18816        })
18817        .unwrap();
18818}
18819
18820#[gpui::test]
18821async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18822    init_test(cx, |_| {});
18823
18824    let mut cx = EditorTestContext::new(cx).await;
18825
18826    let diff_base = r#"
18827        use some::mod;
18828
18829        const A: u32 = 42;
18830
18831        fn main() {
18832            println!("hello");
18833
18834            println!("world");
18835        }
18836        "#
18837    .unindent();
18838
18839    cx.set_state(
18840        &r#"
18841        use some::modified;
18842
18843        ˇ
18844        fn main() {
18845            println!("hello there");
18846
18847            println!("around the");
18848            println!("world");
18849        }
18850        "#
18851        .unindent(),
18852    );
18853
18854    cx.set_head_text(&diff_base);
18855    executor.run_until_parked();
18856
18857    cx.update_editor(|editor, window, cx| {
18858        editor.go_to_next_hunk(&GoToHunk, window, cx);
18859        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18860    });
18861    executor.run_until_parked();
18862    cx.assert_state_with_diff(
18863        r#"
18864          use some::modified;
18865
18866
18867          fn main() {
18868        -     println!("hello");
18869        + ˇ    println!("hello there");
18870
18871              println!("around the");
18872              println!("world");
18873          }
18874        "#
18875        .unindent(),
18876    );
18877
18878    cx.update_editor(|editor, window, cx| {
18879        for _ in 0..2 {
18880            editor.go_to_next_hunk(&GoToHunk, window, cx);
18881            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18882        }
18883    });
18884    executor.run_until_parked();
18885    cx.assert_state_with_diff(
18886        r#"
18887        - use some::mod;
18888        + ˇuse some::modified;
18889
18890
18891          fn main() {
18892        -     println!("hello");
18893        +     println!("hello there");
18894
18895        +     println!("around the");
18896              println!("world");
18897          }
18898        "#
18899        .unindent(),
18900    );
18901
18902    cx.update_editor(|editor, window, cx| {
18903        editor.go_to_next_hunk(&GoToHunk, window, cx);
18904        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18905    });
18906    executor.run_until_parked();
18907    cx.assert_state_with_diff(
18908        r#"
18909        - use some::mod;
18910        + use some::modified;
18911
18912        - const A: u32 = 42;
18913          ˇ
18914          fn main() {
18915        -     println!("hello");
18916        +     println!("hello there");
18917
18918        +     println!("around the");
18919              println!("world");
18920          }
18921        "#
18922        .unindent(),
18923    );
18924
18925    cx.update_editor(|editor, window, cx| {
18926        editor.cancel(&Cancel, window, cx);
18927    });
18928
18929    cx.assert_state_with_diff(
18930        r#"
18931          use some::modified;
18932
18933          ˇ
18934          fn main() {
18935              println!("hello there");
18936
18937              println!("around the");
18938              println!("world");
18939          }
18940        "#
18941        .unindent(),
18942    );
18943}
18944
18945#[gpui::test]
18946async fn test_diff_base_change_with_expanded_diff_hunks(
18947    executor: BackgroundExecutor,
18948    cx: &mut TestAppContext,
18949) {
18950    init_test(cx, |_| {});
18951
18952    let mut cx = EditorTestContext::new(cx).await;
18953
18954    let diff_base = r#"
18955        use some::mod1;
18956        use some::mod2;
18957
18958        const A: u32 = 42;
18959        const B: u32 = 42;
18960        const C: u32 = 42;
18961
18962        fn main() {
18963            println!("hello");
18964
18965            println!("world");
18966        }
18967        "#
18968    .unindent();
18969
18970    cx.set_state(
18971        &r#"
18972        use some::mod2;
18973
18974        const A: u32 = 42;
18975        const C: u32 = 42;
18976
18977        fn main(ˇ) {
18978            //println!("hello");
18979
18980            println!("world");
18981            //
18982            //
18983        }
18984        "#
18985        .unindent(),
18986    );
18987
18988    cx.set_head_text(&diff_base);
18989    executor.run_until_parked();
18990
18991    cx.update_editor(|editor, window, cx| {
18992        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18993    });
18994    executor.run_until_parked();
18995    cx.assert_state_with_diff(
18996        r#"
18997        - use some::mod1;
18998          use some::mod2;
18999
19000          const A: u32 = 42;
19001        - const B: u32 = 42;
19002          const C: u32 = 42;
19003
19004          fn main(ˇ) {
19005        -     println!("hello");
19006        +     //println!("hello");
19007
19008              println!("world");
19009        +     //
19010        +     //
19011          }
19012        "#
19013        .unindent(),
19014    );
19015
19016    cx.set_head_text("new diff base!");
19017    executor.run_until_parked();
19018    cx.assert_state_with_diff(
19019        r#"
19020        - new diff base!
19021        + use some::mod2;
19022        +
19023        + const A: u32 = 42;
19024        + const C: u32 = 42;
19025        +
19026        + fn main(ˇ) {
19027        +     //println!("hello");
19028        +
19029        +     println!("world");
19030        +     //
19031        +     //
19032        + }
19033        "#
19034        .unindent(),
19035    );
19036}
19037
19038#[gpui::test]
19039async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19040    init_test(cx, |_| {});
19041
19042    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19043    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19044    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19045    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19046    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19047    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19048
19049    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19050    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19051    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19052
19053    let multi_buffer = cx.new(|cx| {
19054        let mut multibuffer = MultiBuffer::new(ReadWrite);
19055        multibuffer.push_excerpts(
19056            buffer_1.clone(),
19057            [
19058                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19059                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19060                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19061            ],
19062            cx,
19063        );
19064        multibuffer.push_excerpts(
19065            buffer_2.clone(),
19066            [
19067                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19068                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19069                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19070            ],
19071            cx,
19072        );
19073        multibuffer.push_excerpts(
19074            buffer_3.clone(),
19075            [
19076                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19077                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19078                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19079            ],
19080            cx,
19081        );
19082        multibuffer
19083    });
19084
19085    let editor =
19086        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19087    editor
19088        .update(cx, |editor, _window, cx| {
19089            for (buffer, diff_base) in [
19090                (buffer_1.clone(), file_1_old),
19091                (buffer_2.clone(), file_2_old),
19092                (buffer_3.clone(), file_3_old),
19093            ] {
19094                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19095                editor
19096                    .buffer
19097                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19098            }
19099        })
19100        .unwrap();
19101
19102    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19103    cx.run_until_parked();
19104
19105    cx.assert_editor_state(
19106        &"
19107            ˇaaa
19108            ccc
19109            ddd
19110
19111            ggg
19112            hhh
19113
19114
19115            lll
19116            mmm
19117            NNN
19118
19119            qqq
19120            rrr
19121
19122            uuu
19123            111
19124            222
19125            333
19126
19127            666
19128            777
19129
19130            000
19131            !!!"
19132        .unindent(),
19133    );
19134
19135    cx.update_editor(|editor, window, cx| {
19136        editor.select_all(&SelectAll, window, cx);
19137        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19138    });
19139    cx.executor().run_until_parked();
19140
19141    cx.assert_state_with_diff(
19142        "
19143            «aaa
19144          - bbb
19145            ccc
19146            ddd
19147
19148            ggg
19149            hhh
19150
19151
19152            lll
19153            mmm
19154          - nnn
19155          + NNN
19156
19157            qqq
19158            rrr
19159
19160            uuu
19161            111
19162            222
19163            333
19164
19165          + 666
19166            777
19167
19168            000
19169            !!!ˇ»"
19170            .unindent(),
19171    );
19172}
19173
19174#[gpui::test]
19175async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19176    init_test(cx, |_| {});
19177
19178    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19179    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19180
19181    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19182    let multi_buffer = cx.new(|cx| {
19183        let mut multibuffer = MultiBuffer::new(ReadWrite);
19184        multibuffer.push_excerpts(
19185            buffer.clone(),
19186            [
19187                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19188                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19189                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19190            ],
19191            cx,
19192        );
19193        multibuffer
19194    });
19195
19196    let editor =
19197        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19198    editor
19199        .update(cx, |editor, _window, cx| {
19200            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19201            editor
19202                .buffer
19203                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19204        })
19205        .unwrap();
19206
19207    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19208    cx.run_until_parked();
19209
19210    cx.update_editor(|editor, window, cx| {
19211        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19212    });
19213    cx.executor().run_until_parked();
19214
19215    // When the start of a hunk coincides with the start of its excerpt,
19216    // the hunk is expanded. When the start of a a hunk is earlier than
19217    // the start of its excerpt, the hunk is not expanded.
19218    cx.assert_state_with_diff(
19219        "
19220            ˇaaa
19221          - bbb
19222          + BBB
19223
19224          - ddd
19225          - eee
19226          + DDD
19227          + EEE
19228            fff
19229
19230            iii
19231        "
19232        .unindent(),
19233    );
19234}
19235
19236#[gpui::test]
19237async fn test_edits_around_expanded_insertion_hunks(
19238    executor: BackgroundExecutor,
19239    cx: &mut TestAppContext,
19240) {
19241    init_test(cx, |_| {});
19242
19243    let mut cx = EditorTestContext::new(cx).await;
19244
19245    let diff_base = r#"
19246        use some::mod1;
19247        use some::mod2;
19248
19249        const A: u32 = 42;
19250
19251        fn main() {
19252            println!("hello");
19253
19254            println!("world");
19255        }
19256        "#
19257    .unindent();
19258    executor.run_until_parked();
19259    cx.set_state(
19260        &r#"
19261        use some::mod1;
19262        use some::mod2;
19263
19264        const A: u32 = 42;
19265        const B: u32 = 42;
19266        const C: u32 = 42;
19267        ˇ
19268
19269        fn main() {
19270            println!("hello");
19271
19272            println!("world");
19273        }
19274        "#
19275        .unindent(),
19276    );
19277
19278    cx.set_head_text(&diff_base);
19279    executor.run_until_parked();
19280
19281    cx.update_editor(|editor, window, cx| {
19282        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19283    });
19284    executor.run_until_parked();
19285
19286    cx.assert_state_with_diff(
19287        r#"
19288        use some::mod1;
19289        use some::mod2;
19290
19291        const A: u32 = 42;
19292      + const B: u32 = 42;
19293      + const C: u32 = 42;
19294      + ˇ
19295
19296        fn main() {
19297            println!("hello");
19298
19299            println!("world");
19300        }
19301      "#
19302        .unindent(),
19303    );
19304
19305    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19306    executor.run_until_parked();
19307
19308    cx.assert_state_with_diff(
19309        r#"
19310        use some::mod1;
19311        use some::mod2;
19312
19313        const A: u32 = 42;
19314      + const B: u32 = 42;
19315      + const C: u32 = 42;
19316      + const D: u32 = 42;
19317      + ˇ
19318
19319        fn main() {
19320            println!("hello");
19321
19322            println!("world");
19323        }
19324      "#
19325        .unindent(),
19326    );
19327
19328    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19329    executor.run_until_parked();
19330
19331    cx.assert_state_with_diff(
19332        r#"
19333        use some::mod1;
19334        use some::mod2;
19335
19336        const A: u32 = 42;
19337      + const B: u32 = 42;
19338      + const C: u32 = 42;
19339      + const D: u32 = 42;
19340      + const E: u32 = 42;
19341      + ˇ
19342
19343        fn main() {
19344            println!("hello");
19345
19346            println!("world");
19347        }
19348      "#
19349        .unindent(),
19350    );
19351
19352    cx.update_editor(|editor, window, cx| {
19353        editor.delete_line(&DeleteLine, window, cx);
19354    });
19355    executor.run_until_parked();
19356
19357    cx.assert_state_with_diff(
19358        r#"
19359        use some::mod1;
19360        use some::mod2;
19361
19362        const A: u32 = 42;
19363      + const B: u32 = 42;
19364      + const C: u32 = 42;
19365      + const D: u32 = 42;
19366      + const E: u32 = 42;
19367        ˇ
19368        fn main() {
19369            println!("hello");
19370
19371            println!("world");
19372        }
19373      "#
19374        .unindent(),
19375    );
19376
19377    cx.update_editor(|editor, window, cx| {
19378        editor.move_up(&MoveUp, window, cx);
19379        editor.delete_line(&DeleteLine, window, cx);
19380        editor.move_up(&MoveUp, window, cx);
19381        editor.delete_line(&DeleteLine, window, cx);
19382        editor.move_up(&MoveUp, window, cx);
19383        editor.delete_line(&DeleteLine, window, cx);
19384    });
19385    executor.run_until_parked();
19386    cx.assert_state_with_diff(
19387        r#"
19388        use some::mod1;
19389        use some::mod2;
19390
19391        const A: u32 = 42;
19392      + const B: u32 = 42;
19393        ˇ
19394        fn main() {
19395            println!("hello");
19396
19397            println!("world");
19398        }
19399      "#
19400        .unindent(),
19401    );
19402
19403    cx.update_editor(|editor, window, cx| {
19404        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19405        editor.delete_line(&DeleteLine, window, cx);
19406    });
19407    executor.run_until_parked();
19408    cx.assert_state_with_diff(
19409        r#"
19410        ˇ
19411        fn main() {
19412            println!("hello");
19413
19414            println!("world");
19415        }
19416      "#
19417        .unindent(),
19418    );
19419}
19420
19421#[gpui::test]
19422async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19423    init_test(cx, |_| {});
19424
19425    let mut cx = EditorTestContext::new(cx).await;
19426    cx.set_head_text(indoc! { "
19427        one
19428        two
19429        three
19430        four
19431        five
19432        "
19433    });
19434    cx.set_state(indoc! { "
19435        one
19436        ˇthree
19437        five
19438    "});
19439    cx.run_until_parked();
19440    cx.update_editor(|editor, window, cx| {
19441        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19442    });
19443    cx.assert_state_with_diff(
19444        indoc! { "
19445        one
19446      - two
19447        ˇthree
19448      - four
19449        five
19450    "}
19451        .to_string(),
19452    );
19453    cx.update_editor(|editor, window, cx| {
19454        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19455    });
19456
19457    cx.assert_state_with_diff(
19458        indoc! { "
19459        one
19460        ˇthree
19461        five
19462    "}
19463        .to_string(),
19464    );
19465
19466    cx.set_state(indoc! { "
19467        one
19468        ˇTWO
19469        three
19470        four
19471        five
19472    "});
19473    cx.run_until_parked();
19474    cx.update_editor(|editor, window, cx| {
19475        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19476    });
19477
19478    cx.assert_state_with_diff(
19479        indoc! { "
19480            one
19481          - two
19482          + ˇTWO
19483            three
19484            four
19485            five
19486        "}
19487        .to_string(),
19488    );
19489    cx.update_editor(|editor, window, cx| {
19490        editor.move_up(&Default::default(), window, cx);
19491        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19492    });
19493    cx.assert_state_with_diff(
19494        indoc! { "
19495            one
19496            ˇTWO
19497            three
19498            four
19499            five
19500        "}
19501        .to_string(),
19502    );
19503}
19504
19505#[gpui::test]
19506async fn test_edits_around_expanded_deletion_hunks(
19507    executor: BackgroundExecutor,
19508    cx: &mut TestAppContext,
19509) {
19510    init_test(cx, |_| {});
19511
19512    let mut cx = EditorTestContext::new(cx).await;
19513
19514    let diff_base = r#"
19515        use some::mod1;
19516        use some::mod2;
19517
19518        const A: u32 = 42;
19519        const B: u32 = 42;
19520        const C: u32 = 42;
19521
19522
19523        fn main() {
19524            println!("hello");
19525
19526            println!("world");
19527        }
19528    "#
19529    .unindent();
19530    executor.run_until_parked();
19531    cx.set_state(
19532        &r#"
19533        use some::mod1;
19534        use some::mod2;
19535
19536        ˇconst B: u32 = 42;
19537        const C: u32 = 42;
19538
19539
19540        fn main() {
19541            println!("hello");
19542
19543            println!("world");
19544        }
19545        "#
19546        .unindent(),
19547    );
19548
19549    cx.set_head_text(&diff_base);
19550    executor.run_until_parked();
19551
19552    cx.update_editor(|editor, window, cx| {
19553        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19554    });
19555    executor.run_until_parked();
19556
19557    cx.assert_state_with_diff(
19558        r#"
19559        use some::mod1;
19560        use some::mod2;
19561
19562      - const A: u32 = 42;
19563        ˇconst B: u32 = 42;
19564        const C: u32 = 42;
19565
19566
19567        fn main() {
19568            println!("hello");
19569
19570            println!("world");
19571        }
19572      "#
19573        .unindent(),
19574    );
19575
19576    cx.update_editor(|editor, window, cx| {
19577        editor.delete_line(&DeleteLine, window, cx);
19578    });
19579    executor.run_until_parked();
19580    cx.assert_state_with_diff(
19581        r#"
19582        use some::mod1;
19583        use some::mod2;
19584
19585      - const A: u32 = 42;
19586      - const B: u32 = 42;
19587        ˇconst C: u32 = 42;
19588
19589
19590        fn main() {
19591            println!("hello");
19592
19593            println!("world");
19594        }
19595      "#
19596        .unindent(),
19597    );
19598
19599    cx.update_editor(|editor, window, cx| {
19600        editor.delete_line(&DeleteLine, window, cx);
19601    });
19602    executor.run_until_parked();
19603    cx.assert_state_with_diff(
19604        r#"
19605        use some::mod1;
19606        use some::mod2;
19607
19608      - const A: u32 = 42;
19609      - const B: u32 = 42;
19610      - const C: u32 = 42;
19611        ˇ
19612
19613        fn main() {
19614            println!("hello");
19615
19616            println!("world");
19617        }
19618      "#
19619        .unindent(),
19620    );
19621
19622    cx.update_editor(|editor, window, cx| {
19623        editor.handle_input("replacement", window, cx);
19624    });
19625    executor.run_until_parked();
19626    cx.assert_state_with_diff(
19627        r#"
19628        use some::mod1;
19629        use some::mod2;
19630
19631      - const A: u32 = 42;
19632      - const B: u32 = 42;
19633      - const C: u32 = 42;
19634      -
19635      + replacementˇ
19636
19637        fn main() {
19638            println!("hello");
19639
19640            println!("world");
19641        }
19642      "#
19643        .unindent(),
19644    );
19645}
19646
19647#[gpui::test]
19648async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19649    init_test(cx, |_| {});
19650
19651    let mut cx = EditorTestContext::new(cx).await;
19652
19653    let base_text = r#"
19654        one
19655        two
19656        three
19657        four
19658        five
19659    "#
19660    .unindent();
19661    executor.run_until_parked();
19662    cx.set_state(
19663        &r#"
19664        one
19665        two
19666        fˇour
19667        five
19668        "#
19669        .unindent(),
19670    );
19671
19672    cx.set_head_text(&base_text);
19673    executor.run_until_parked();
19674
19675    cx.update_editor(|editor, window, cx| {
19676        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19677    });
19678    executor.run_until_parked();
19679
19680    cx.assert_state_with_diff(
19681        r#"
19682          one
19683          two
19684        - three
19685          fˇour
19686          five
19687        "#
19688        .unindent(),
19689    );
19690
19691    cx.update_editor(|editor, window, cx| {
19692        editor.backspace(&Backspace, window, cx);
19693        editor.backspace(&Backspace, window, cx);
19694    });
19695    executor.run_until_parked();
19696    cx.assert_state_with_diff(
19697        r#"
19698          one
19699          two
19700        - threeˇ
19701        - four
19702        + our
19703          five
19704        "#
19705        .unindent(),
19706    );
19707}
19708
19709#[gpui::test]
19710async fn test_edit_after_expanded_modification_hunk(
19711    executor: BackgroundExecutor,
19712    cx: &mut TestAppContext,
19713) {
19714    init_test(cx, |_| {});
19715
19716    let mut cx = EditorTestContext::new(cx).await;
19717
19718    let diff_base = r#"
19719        use some::mod1;
19720        use some::mod2;
19721
19722        const A: u32 = 42;
19723        const B: u32 = 42;
19724        const C: u32 = 42;
19725        const D: u32 = 42;
19726
19727
19728        fn main() {
19729            println!("hello");
19730
19731            println!("world");
19732        }"#
19733    .unindent();
19734
19735    cx.set_state(
19736        &r#"
19737        use some::mod1;
19738        use some::mod2;
19739
19740        const A: u32 = 42;
19741        const B: u32 = 42;
19742        const C: u32 = 43ˇ
19743        const D: u32 = 42;
19744
19745
19746        fn main() {
19747            println!("hello");
19748
19749            println!("world");
19750        }"#
19751        .unindent(),
19752    );
19753
19754    cx.set_head_text(&diff_base);
19755    executor.run_until_parked();
19756    cx.update_editor(|editor, window, cx| {
19757        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19758    });
19759    executor.run_until_parked();
19760
19761    cx.assert_state_with_diff(
19762        r#"
19763        use some::mod1;
19764        use some::mod2;
19765
19766        const A: u32 = 42;
19767        const B: u32 = 42;
19768      - const C: u32 = 42;
19769      + const C: u32 = 43ˇ
19770        const D: u32 = 42;
19771
19772
19773        fn main() {
19774            println!("hello");
19775
19776            println!("world");
19777        }"#
19778        .unindent(),
19779    );
19780
19781    cx.update_editor(|editor, window, cx| {
19782        editor.handle_input("\nnew_line\n", window, cx);
19783    });
19784    executor.run_until_parked();
19785
19786    cx.assert_state_with_diff(
19787        r#"
19788        use some::mod1;
19789        use some::mod2;
19790
19791        const A: u32 = 42;
19792        const B: u32 = 42;
19793      - const C: u32 = 42;
19794      + const C: u32 = 43
19795      + new_line
19796      + ˇ
19797        const D: u32 = 42;
19798
19799
19800        fn main() {
19801            println!("hello");
19802
19803            println!("world");
19804        }"#
19805        .unindent(),
19806    );
19807}
19808
19809#[gpui::test]
19810async fn test_stage_and_unstage_added_file_hunk(
19811    executor: BackgroundExecutor,
19812    cx: &mut TestAppContext,
19813) {
19814    init_test(cx, |_| {});
19815
19816    let mut cx = EditorTestContext::new(cx).await;
19817    cx.update_editor(|editor, _, cx| {
19818        editor.set_expand_all_diff_hunks(cx);
19819    });
19820
19821    let working_copy = r#"
19822            ˇfn main() {
19823                println!("hello, world!");
19824            }
19825        "#
19826    .unindent();
19827
19828    cx.set_state(&working_copy);
19829    executor.run_until_parked();
19830
19831    cx.assert_state_with_diff(
19832        r#"
19833            + ˇfn main() {
19834            +     println!("hello, world!");
19835            + }
19836        "#
19837        .unindent(),
19838    );
19839    cx.assert_index_text(None);
19840
19841    cx.update_editor(|editor, window, cx| {
19842        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19843    });
19844    executor.run_until_parked();
19845    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19846    cx.assert_state_with_diff(
19847        r#"
19848            + ˇfn main() {
19849            +     println!("hello, world!");
19850            + }
19851        "#
19852        .unindent(),
19853    );
19854
19855    cx.update_editor(|editor, window, cx| {
19856        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19857    });
19858    executor.run_until_parked();
19859    cx.assert_index_text(None);
19860}
19861
19862async fn setup_indent_guides_editor(
19863    text: &str,
19864    cx: &mut TestAppContext,
19865) -> (BufferId, EditorTestContext) {
19866    init_test(cx, |_| {});
19867
19868    let mut cx = EditorTestContext::new(cx).await;
19869
19870    let buffer_id = cx.update_editor(|editor, window, cx| {
19871        editor.set_text(text, window, cx);
19872        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19873
19874        buffer_ids[0]
19875    });
19876
19877    (buffer_id, cx)
19878}
19879
19880fn assert_indent_guides(
19881    range: Range<u32>,
19882    expected: Vec<IndentGuide>,
19883    active_indices: Option<Vec<usize>>,
19884    cx: &mut EditorTestContext,
19885) {
19886    let indent_guides = cx.update_editor(|editor, window, cx| {
19887        let snapshot = editor.snapshot(window, cx).display_snapshot;
19888        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19889            editor,
19890            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19891            true,
19892            &snapshot,
19893            cx,
19894        );
19895
19896        indent_guides.sort_by(|a, b| {
19897            a.depth.cmp(&b.depth).then(
19898                a.start_row
19899                    .cmp(&b.start_row)
19900                    .then(a.end_row.cmp(&b.end_row)),
19901            )
19902        });
19903        indent_guides
19904    });
19905
19906    if let Some(expected) = active_indices {
19907        let active_indices = cx.update_editor(|editor, window, cx| {
19908            let snapshot = editor.snapshot(window, cx).display_snapshot;
19909            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
19910        });
19911
19912        assert_eq!(
19913            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
19914            expected,
19915            "Active indent guide indices do not match"
19916        );
19917    }
19918
19919    assert_eq!(indent_guides, expected, "Indent guides do not match");
19920}
19921
19922fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
19923    IndentGuide {
19924        buffer_id,
19925        start_row: MultiBufferRow(start_row),
19926        end_row: MultiBufferRow(end_row),
19927        depth,
19928        tab_size: 4,
19929        settings: IndentGuideSettings {
19930            enabled: true,
19931            line_width: 1,
19932            active_line_width: 1,
19933            ..Default::default()
19934        },
19935    }
19936}
19937
19938#[gpui::test]
19939async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
19940    let (buffer_id, mut cx) = setup_indent_guides_editor(
19941        &"
19942        fn main() {
19943            let a = 1;
19944        }"
19945        .unindent(),
19946        cx,
19947    )
19948    .await;
19949
19950    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19951}
19952
19953#[gpui::test]
19954async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
19955    let (buffer_id, mut cx) = setup_indent_guides_editor(
19956        &"
19957        fn main() {
19958            let a = 1;
19959            let b = 2;
19960        }"
19961        .unindent(),
19962        cx,
19963    )
19964    .await;
19965
19966    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
19967}
19968
19969#[gpui::test]
19970async fn test_indent_guide_nested(cx: &mut TestAppContext) {
19971    let (buffer_id, mut cx) = setup_indent_guides_editor(
19972        &"
19973        fn main() {
19974            let a = 1;
19975            if a == 3 {
19976                let b = 2;
19977            } else {
19978                let c = 3;
19979            }
19980        }"
19981        .unindent(),
19982        cx,
19983    )
19984    .await;
19985
19986    assert_indent_guides(
19987        0..8,
19988        vec![
19989            indent_guide(buffer_id, 1, 6, 0),
19990            indent_guide(buffer_id, 3, 3, 1),
19991            indent_guide(buffer_id, 5, 5, 1),
19992        ],
19993        None,
19994        &mut cx,
19995    );
19996}
19997
19998#[gpui::test]
19999async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20000    let (buffer_id, mut cx) = setup_indent_guides_editor(
20001        &"
20002        fn main() {
20003            let a = 1;
20004                let b = 2;
20005            let c = 3;
20006        }"
20007        .unindent(),
20008        cx,
20009    )
20010    .await;
20011
20012    assert_indent_guides(
20013        0..5,
20014        vec![
20015            indent_guide(buffer_id, 1, 3, 0),
20016            indent_guide(buffer_id, 2, 2, 1),
20017        ],
20018        None,
20019        &mut cx,
20020    );
20021}
20022
20023#[gpui::test]
20024async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20025    let (buffer_id, mut cx) = setup_indent_guides_editor(
20026        &"
20027        fn main() {
20028            let a = 1;
20029
20030            let c = 3;
20031        }"
20032        .unindent(),
20033        cx,
20034    )
20035    .await;
20036
20037    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20038}
20039
20040#[gpui::test]
20041async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20042    let (buffer_id, mut cx) = setup_indent_guides_editor(
20043        &"
20044        fn main() {
20045            let a = 1;
20046
20047            let c = 3;
20048
20049            if a == 3 {
20050                let b = 2;
20051            } else {
20052                let c = 3;
20053            }
20054        }"
20055        .unindent(),
20056        cx,
20057    )
20058    .await;
20059
20060    assert_indent_guides(
20061        0..11,
20062        vec![
20063            indent_guide(buffer_id, 1, 9, 0),
20064            indent_guide(buffer_id, 6, 6, 1),
20065            indent_guide(buffer_id, 8, 8, 1),
20066        ],
20067        None,
20068        &mut cx,
20069    );
20070}
20071
20072#[gpui::test]
20073async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20074    let (buffer_id, mut cx) = setup_indent_guides_editor(
20075        &"
20076        fn main() {
20077            let a = 1;
20078
20079            let c = 3;
20080
20081            if a == 3 {
20082                let b = 2;
20083            } else {
20084                let c = 3;
20085            }
20086        }"
20087        .unindent(),
20088        cx,
20089    )
20090    .await;
20091
20092    assert_indent_guides(
20093        1..11,
20094        vec![
20095            indent_guide(buffer_id, 1, 9, 0),
20096            indent_guide(buffer_id, 6, 6, 1),
20097            indent_guide(buffer_id, 8, 8, 1),
20098        ],
20099        None,
20100        &mut cx,
20101    );
20102}
20103
20104#[gpui::test]
20105async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20106    let (buffer_id, mut cx) = setup_indent_guides_editor(
20107        &"
20108        fn main() {
20109            let a = 1;
20110
20111            let c = 3;
20112
20113            if a == 3 {
20114                let b = 2;
20115            } else {
20116                let c = 3;
20117            }
20118        }"
20119        .unindent(),
20120        cx,
20121    )
20122    .await;
20123
20124    assert_indent_guides(
20125        1..10,
20126        vec![
20127            indent_guide(buffer_id, 1, 9, 0),
20128            indent_guide(buffer_id, 6, 6, 1),
20129            indent_guide(buffer_id, 8, 8, 1),
20130        ],
20131        None,
20132        &mut cx,
20133    );
20134}
20135
20136#[gpui::test]
20137async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20138    let (buffer_id, mut cx) = setup_indent_guides_editor(
20139        &"
20140        fn main() {
20141            if a {
20142                b(
20143                    c,
20144                    d,
20145                )
20146            } else {
20147                e(
20148                    f
20149                )
20150            }
20151        }"
20152        .unindent(),
20153        cx,
20154    )
20155    .await;
20156
20157    assert_indent_guides(
20158        0..11,
20159        vec![
20160            indent_guide(buffer_id, 1, 10, 0),
20161            indent_guide(buffer_id, 2, 5, 1),
20162            indent_guide(buffer_id, 7, 9, 1),
20163            indent_guide(buffer_id, 3, 4, 2),
20164            indent_guide(buffer_id, 8, 8, 2),
20165        ],
20166        None,
20167        &mut cx,
20168    );
20169
20170    cx.update_editor(|editor, window, cx| {
20171        editor.fold_at(MultiBufferRow(2), window, cx);
20172        assert_eq!(
20173            editor.display_text(cx),
20174            "
20175            fn main() {
20176                if a {
20177                    b(⋯
20178                    )
20179                } else {
20180                    e(
20181                        f
20182                    )
20183                }
20184            }"
20185            .unindent()
20186        );
20187    });
20188
20189    assert_indent_guides(
20190        0..11,
20191        vec![
20192            indent_guide(buffer_id, 1, 10, 0),
20193            indent_guide(buffer_id, 2, 5, 1),
20194            indent_guide(buffer_id, 7, 9, 1),
20195            indent_guide(buffer_id, 8, 8, 2),
20196        ],
20197        None,
20198        &mut cx,
20199    );
20200}
20201
20202#[gpui::test]
20203async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20204    let (buffer_id, mut cx) = setup_indent_guides_editor(
20205        &"
20206        block1
20207            block2
20208                block3
20209                    block4
20210            block2
20211        block1
20212        block1"
20213            .unindent(),
20214        cx,
20215    )
20216    .await;
20217
20218    assert_indent_guides(
20219        1..10,
20220        vec![
20221            indent_guide(buffer_id, 1, 4, 0),
20222            indent_guide(buffer_id, 2, 3, 1),
20223            indent_guide(buffer_id, 3, 3, 2),
20224        ],
20225        None,
20226        &mut cx,
20227    );
20228}
20229
20230#[gpui::test]
20231async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20232    let (buffer_id, mut cx) = setup_indent_guides_editor(
20233        &"
20234        block1
20235            block2
20236                block3
20237
20238        block1
20239        block1"
20240            .unindent(),
20241        cx,
20242    )
20243    .await;
20244
20245    assert_indent_guides(
20246        0..6,
20247        vec![
20248            indent_guide(buffer_id, 1, 2, 0),
20249            indent_guide(buffer_id, 2, 2, 1),
20250        ],
20251        None,
20252        &mut cx,
20253    );
20254}
20255
20256#[gpui::test]
20257async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20258    let (buffer_id, mut cx) = setup_indent_guides_editor(
20259        &"
20260        function component() {
20261        \treturn (
20262        \t\t\t
20263        \t\t<div>
20264        \t\t\t<abc></abc>
20265        \t\t</div>
20266        \t)
20267        }"
20268        .unindent(),
20269        cx,
20270    )
20271    .await;
20272
20273    assert_indent_guides(
20274        0..8,
20275        vec![
20276            indent_guide(buffer_id, 1, 6, 0),
20277            indent_guide(buffer_id, 2, 5, 1),
20278            indent_guide(buffer_id, 4, 4, 2),
20279        ],
20280        None,
20281        &mut cx,
20282    );
20283}
20284
20285#[gpui::test]
20286async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20287    let (buffer_id, mut cx) = setup_indent_guides_editor(
20288        &"
20289        function component() {
20290        \treturn (
20291        \t
20292        \t\t<div>
20293        \t\t\t<abc></abc>
20294        \t\t</div>
20295        \t)
20296        }"
20297        .unindent(),
20298        cx,
20299    )
20300    .await;
20301
20302    assert_indent_guides(
20303        0..8,
20304        vec![
20305            indent_guide(buffer_id, 1, 6, 0),
20306            indent_guide(buffer_id, 2, 5, 1),
20307            indent_guide(buffer_id, 4, 4, 2),
20308        ],
20309        None,
20310        &mut cx,
20311    );
20312}
20313
20314#[gpui::test]
20315async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20316    let (buffer_id, mut cx) = setup_indent_guides_editor(
20317        &"
20318        block1
20319
20320
20321
20322            block2
20323        "
20324        .unindent(),
20325        cx,
20326    )
20327    .await;
20328
20329    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20330}
20331
20332#[gpui::test]
20333async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20334    let (buffer_id, mut cx) = setup_indent_guides_editor(
20335        &"
20336        def a:
20337        \tb = 3
20338        \tif True:
20339        \t\tc = 4
20340        \t\td = 5
20341        \tprint(b)
20342        "
20343        .unindent(),
20344        cx,
20345    )
20346    .await;
20347
20348    assert_indent_guides(
20349        0..6,
20350        vec![
20351            indent_guide(buffer_id, 1, 5, 0),
20352            indent_guide(buffer_id, 3, 4, 1),
20353        ],
20354        None,
20355        &mut cx,
20356    );
20357}
20358
20359#[gpui::test]
20360async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20361    let (buffer_id, mut cx) = setup_indent_guides_editor(
20362        &"
20363    fn main() {
20364        let a = 1;
20365    }"
20366        .unindent(),
20367        cx,
20368    )
20369    .await;
20370
20371    cx.update_editor(|editor, window, cx| {
20372        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20373            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20374        });
20375    });
20376
20377    assert_indent_guides(
20378        0..3,
20379        vec![indent_guide(buffer_id, 1, 1, 0)],
20380        Some(vec![0]),
20381        &mut cx,
20382    );
20383}
20384
20385#[gpui::test]
20386async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20387    let (buffer_id, mut cx) = setup_indent_guides_editor(
20388        &"
20389    fn main() {
20390        if 1 == 2 {
20391            let a = 1;
20392        }
20393    }"
20394        .unindent(),
20395        cx,
20396    )
20397    .await;
20398
20399    cx.update_editor(|editor, window, cx| {
20400        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20401            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20402        });
20403    });
20404
20405    assert_indent_guides(
20406        0..4,
20407        vec![
20408            indent_guide(buffer_id, 1, 3, 0),
20409            indent_guide(buffer_id, 2, 2, 1),
20410        ],
20411        Some(vec![1]),
20412        &mut cx,
20413    );
20414
20415    cx.update_editor(|editor, window, cx| {
20416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20417            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20418        });
20419    });
20420
20421    assert_indent_guides(
20422        0..4,
20423        vec![
20424            indent_guide(buffer_id, 1, 3, 0),
20425            indent_guide(buffer_id, 2, 2, 1),
20426        ],
20427        Some(vec![1]),
20428        &mut cx,
20429    );
20430
20431    cx.update_editor(|editor, window, cx| {
20432        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20433            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20434        });
20435    });
20436
20437    assert_indent_guides(
20438        0..4,
20439        vec![
20440            indent_guide(buffer_id, 1, 3, 0),
20441            indent_guide(buffer_id, 2, 2, 1),
20442        ],
20443        Some(vec![0]),
20444        &mut cx,
20445    );
20446}
20447
20448#[gpui::test]
20449async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20450    let (buffer_id, mut cx) = setup_indent_guides_editor(
20451        &"
20452    fn main() {
20453        let a = 1;
20454
20455        let b = 2;
20456    }"
20457        .unindent(),
20458        cx,
20459    )
20460    .await;
20461
20462    cx.update_editor(|editor, window, cx| {
20463        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20464            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20465        });
20466    });
20467
20468    assert_indent_guides(
20469        0..5,
20470        vec![indent_guide(buffer_id, 1, 3, 0)],
20471        Some(vec![0]),
20472        &mut cx,
20473    );
20474}
20475
20476#[gpui::test]
20477async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20478    let (buffer_id, mut cx) = setup_indent_guides_editor(
20479        &"
20480    def m:
20481        a = 1
20482        pass"
20483            .unindent(),
20484        cx,
20485    )
20486    .await;
20487
20488    cx.update_editor(|editor, window, cx| {
20489        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20490            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20491        });
20492    });
20493
20494    assert_indent_guides(
20495        0..3,
20496        vec![indent_guide(buffer_id, 1, 2, 0)],
20497        Some(vec![0]),
20498        &mut cx,
20499    );
20500}
20501
20502#[gpui::test]
20503async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20504    init_test(cx, |_| {});
20505    let mut cx = EditorTestContext::new(cx).await;
20506    let text = indoc! {
20507        "
20508        impl A {
20509            fn b() {
20510                0;
20511                3;
20512                5;
20513                6;
20514                7;
20515            }
20516        }
20517        "
20518    };
20519    let base_text = indoc! {
20520        "
20521        impl A {
20522            fn b() {
20523                0;
20524                1;
20525                2;
20526                3;
20527                4;
20528            }
20529            fn c() {
20530                5;
20531                6;
20532                7;
20533            }
20534        }
20535        "
20536    };
20537
20538    cx.update_editor(|editor, window, cx| {
20539        editor.set_text(text, window, cx);
20540
20541        editor.buffer().update(cx, |multibuffer, cx| {
20542            let buffer = multibuffer.as_singleton().unwrap();
20543            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20544
20545            multibuffer.set_all_diff_hunks_expanded(cx);
20546            multibuffer.add_diff(diff, cx);
20547
20548            buffer.read(cx).remote_id()
20549        })
20550    });
20551    cx.run_until_parked();
20552
20553    cx.assert_state_with_diff(
20554        indoc! { "
20555          impl A {
20556              fn b() {
20557                  0;
20558        -         1;
20559        -         2;
20560                  3;
20561        -         4;
20562        -     }
20563        -     fn c() {
20564                  5;
20565                  6;
20566                  7;
20567              }
20568          }
20569          ˇ"
20570        }
20571        .to_string(),
20572    );
20573
20574    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20575        editor
20576            .snapshot(window, cx)
20577            .buffer_snapshot
20578            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20579            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20580            .collect::<Vec<_>>()
20581    });
20582    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20583    assert_eq!(
20584        actual_guides,
20585        vec![
20586            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20587            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20588            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20589        ]
20590    );
20591}
20592
20593#[gpui::test]
20594async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20595    init_test(cx, |_| {});
20596    let mut cx = EditorTestContext::new(cx).await;
20597
20598    let diff_base = r#"
20599        a
20600        b
20601        c
20602        "#
20603    .unindent();
20604
20605    cx.set_state(
20606        &r#"
20607        ˇA
20608        b
20609        C
20610        "#
20611        .unindent(),
20612    );
20613    cx.set_head_text(&diff_base);
20614    cx.update_editor(|editor, window, cx| {
20615        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20616    });
20617    executor.run_until_parked();
20618
20619    let both_hunks_expanded = r#"
20620        - a
20621        + ˇA
20622          b
20623        - c
20624        + C
20625        "#
20626    .unindent();
20627
20628    cx.assert_state_with_diff(both_hunks_expanded.clone());
20629
20630    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20631        let snapshot = editor.snapshot(window, cx);
20632        let hunks = editor
20633            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20634            .collect::<Vec<_>>();
20635        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20636        let buffer_id = hunks[0].buffer_id;
20637        hunks
20638            .into_iter()
20639            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20640            .collect::<Vec<_>>()
20641    });
20642    assert_eq!(hunk_ranges.len(), 2);
20643
20644    cx.update_editor(|editor, _, cx| {
20645        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20646    });
20647    executor.run_until_parked();
20648
20649    let second_hunk_expanded = r#"
20650          ˇA
20651          b
20652        - c
20653        + C
20654        "#
20655    .unindent();
20656
20657    cx.assert_state_with_diff(second_hunk_expanded);
20658
20659    cx.update_editor(|editor, _, cx| {
20660        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20661    });
20662    executor.run_until_parked();
20663
20664    cx.assert_state_with_diff(both_hunks_expanded.clone());
20665
20666    cx.update_editor(|editor, _, cx| {
20667        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20668    });
20669    executor.run_until_parked();
20670
20671    let first_hunk_expanded = r#"
20672        - a
20673        + ˇA
20674          b
20675          C
20676        "#
20677    .unindent();
20678
20679    cx.assert_state_with_diff(first_hunk_expanded);
20680
20681    cx.update_editor(|editor, _, cx| {
20682        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20683    });
20684    executor.run_until_parked();
20685
20686    cx.assert_state_with_diff(both_hunks_expanded);
20687
20688    cx.set_state(
20689        &r#"
20690        ˇA
20691        b
20692        "#
20693        .unindent(),
20694    );
20695    cx.run_until_parked();
20696
20697    // TODO this cursor position seems bad
20698    cx.assert_state_with_diff(
20699        r#"
20700        - ˇa
20701        + A
20702          b
20703        "#
20704        .unindent(),
20705    );
20706
20707    cx.update_editor(|editor, window, cx| {
20708        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20709    });
20710
20711    cx.assert_state_with_diff(
20712        r#"
20713            - ˇa
20714            + A
20715              b
20716            - c
20717            "#
20718        .unindent(),
20719    );
20720
20721    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20722        let snapshot = editor.snapshot(window, cx);
20723        let hunks = editor
20724            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20725            .collect::<Vec<_>>();
20726        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20727        let buffer_id = hunks[0].buffer_id;
20728        hunks
20729            .into_iter()
20730            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20731            .collect::<Vec<_>>()
20732    });
20733    assert_eq!(hunk_ranges.len(), 2);
20734
20735    cx.update_editor(|editor, _, cx| {
20736        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20737    });
20738    executor.run_until_parked();
20739
20740    cx.assert_state_with_diff(
20741        r#"
20742        - ˇa
20743        + A
20744          b
20745        "#
20746        .unindent(),
20747    );
20748}
20749
20750#[gpui::test]
20751async fn test_toggle_deletion_hunk_at_start_of_file(
20752    executor: BackgroundExecutor,
20753    cx: &mut TestAppContext,
20754) {
20755    init_test(cx, |_| {});
20756    let mut cx = EditorTestContext::new(cx).await;
20757
20758    let diff_base = r#"
20759        a
20760        b
20761        c
20762        "#
20763    .unindent();
20764
20765    cx.set_state(
20766        &r#"
20767        ˇb
20768        c
20769        "#
20770        .unindent(),
20771    );
20772    cx.set_head_text(&diff_base);
20773    cx.update_editor(|editor, window, cx| {
20774        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20775    });
20776    executor.run_until_parked();
20777
20778    let hunk_expanded = r#"
20779        - a
20780          ˇb
20781          c
20782        "#
20783    .unindent();
20784
20785    cx.assert_state_with_diff(hunk_expanded.clone());
20786
20787    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20788        let snapshot = editor.snapshot(window, cx);
20789        let hunks = editor
20790            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20791            .collect::<Vec<_>>();
20792        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20793        let buffer_id = hunks[0].buffer_id;
20794        hunks
20795            .into_iter()
20796            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20797            .collect::<Vec<_>>()
20798    });
20799    assert_eq!(hunk_ranges.len(), 1);
20800
20801    cx.update_editor(|editor, _, cx| {
20802        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20803    });
20804    executor.run_until_parked();
20805
20806    let hunk_collapsed = r#"
20807          ˇb
20808          c
20809        "#
20810    .unindent();
20811
20812    cx.assert_state_with_diff(hunk_collapsed);
20813
20814    cx.update_editor(|editor, _, cx| {
20815        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20816    });
20817    executor.run_until_parked();
20818
20819    cx.assert_state_with_diff(hunk_expanded);
20820}
20821
20822#[gpui::test]
20823async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20824    init_test(cx, |_| {});
20825
20826    let fs = FakeFs::new(cx.executor());
20827    fs.insert_tree(
20828        path!("/test"),
20829        json!({
20830            ".git": {},
20831            "file-1": "ONE\n",
20832            "file-2": "TWO\n",
20833            "file-3": "THREE\n",
20834        }),
20835    )
20836    .await;
20837
20838    fs.set_head_for_repo(
20839        path!("/test/.git").as_ref(),
20840        &[
20841            ("file-1".into(), "one\n".into()),
20842            ("file-2".into(), "two\n".into()),
20843            ("file-3".into(), "three\n".into()),
20844        ],
20845        "deadbeef",
20846    );
20847
20848    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20849    let mut buffers = vec![];
20850    for i in 1..=3 {
20851        let buffer = project
20852            .update(cx, |project, cx| {
20853                let path = format!(path!("/test/file-{}"), i);
20854                project.open_local_buffer(path, cx)
20855            })
20856            .await
20857            .unwrap();
20858        buffers.push(buffer);
20859    }
20860
20861    let multibuffer = cx.new(|cx| {
20862        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20863        multibuffer.set_all_diff_hunks_expanded(cx);
20864        for buffer in &buffers {
20865            let snapshot = buffer.read(cx).snapshot();
20866            multibuffer.set_excerpts_for_path(
20867                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20868                buffer.clone(),
20869                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20870                2,
20871                cx,
20872            );
20873        }
20874        multibuffer
20875    });
20876
20877    let editor = cx.add_window(|window, cx| {
20878        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20879    });
20880    cx.run_until_parked();
20881
20882    let snapshot = editor
20883        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20884        .unwrap();
20885    let hunks = snapshot
20886        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20887        .map(|hunk| match hunk {
20888            DisplayDiffHunk::Unfolded {
20889                display_row_range, ..
20890            } => display_row_range,
20891            DisplayDiffHunk::Folded { .. } => unreachable!(),
20892        })
20893        .collect::<Vec<_>>();
20894    assert_eq!(
20895        hunks,
20896        [
20897            DisplayRow(2)..DisplayRow(4),
20898            DisplayRow(7)..DisplayRow(9),
20899            DisplayRow(12)..DisplayRow(14),
20900        ]
20901    );
20902}
20903
20904#[gpui::test]
20905async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
20906    init_test(cx, |_| {});
20907
20908    let mut cx = EditorTestContext::new(cx).await;
20909    cx.set_head_text(indoc! { "
20910        one
20911        two
20912        three
20913        four
20914        five
20915        "
20916    });
20917    cx.set_index_text(indoc! { "
20918        one
20919        two
20920        three
20921        four
20922        five
20923        "
20924    });
20925    cx.set_state(indoc! {"
20926        one
20927        TWO
20928        ˇTHREE
20929        FOUR
20930        five
20931    "});
20932    cx.run_until_parked();
20933    cx.update_editor(|editor, window, cx| {
20934        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20935    });
20936    cx.run_until_parked();
20937    cx.assert_index_text(Some(indoc! {"
20938        one
20939        TWO
20940        THREE
20941        FOUR
20942        five
20943    "}));
20944    cx.set_state(indoc! { "
20945        one
20946        TWO
20947        ˇTHREE-HUNDRED
20948        FOUR
20949        five
20950    "});
20951    cx.run_until_parked();
20952    cx.update_editor(|editor, window, cx| {
20953        let snapshot = editor.snapshot(window, cx);
20954        let hunks = editor
20955            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20956            .collect::<Vec<_>>();
20957        assert_eq!(hunks.len(), 1);
20958        assert_eq!(
20959            hunks[0].status(),
20960            DiffHunkStatus {
20961                kind: DiffHunkStatusKind::Modified,
20962                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
20963            }
20964        );
20965
20966        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20967    });
20968    cx.run_until_parked();
20969    cx.assert_index_text(Some(indoc! {"
20970        one
20971        TWO
20972        THREE-HUNDRED
20973        FOUR
20974        five
20975    "}));
20976}
20977
20978#[gpui::test]
20979fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
20980    init_test(cx, |_| {});
20981
20982    let editor = cx.add_window(|window, cx| {
20983        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
20984        build_editor(buffer, window, cx)
20985    });
20986
20987    let render_args = Arc::new(Mutex::new(None));
20988    let snapshot = editor
20989        .update(cx, |editor, window, cx| {
20990            let snapshot = editor.buffer().read(cx).snapshot(cx);
20991            let range =
20992                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
20993
20994            struct RenderArgs {
20995                row: MultiBufferRow,
20996                folded: bool,
20997                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
20998            }
20999
21000            let crease = Crease::inline(
21001                range,
21002                FoldPlaceholder::test(),
21003                {
21004                    let toggle_callback = render_args.clone();
21005                    move |row, folded, callback, _window, _cx| {
21006                        *toggle_callback.lock() = Some(RenderArgs {
21007                            row,
21008                            folded,
21009                            callback,
21010                        });
21011                        div()
21012                    }
21013                },
21014                |_row, _folded, _window, _cx| div(),
21015            );
21016
21017            editor.insert_creases(Some(crease), cx);
21018            let snapshot = editor.snapshot(window, cx);
21019            let _div =
21020                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21021            snapshot
21022        })
21023        .unwrap();
21024
21025    let render_args = render_args.lock().take().unwrap();
21026    assert_eq!(render_args.row, MultiBufferRow(1));
21027    assert!(!render_args.folded);
21028    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21029
21030    cx.update_window(*editor, |_, window, cx| {
21031        (render_args.callback)(true, window, cx)
21032    })
21033    .unwrap();
21034    let snapshot = editor
21035        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21036        .unwrap();
21037    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21038
21039    cx.update_window(*editor, |_, window, cx| {
21040        (render_args.callback)(false, window, cx)
21041    })
21042    .unwrap();
21043    let snapshot = editor
21044        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21045        .unwrap();
21046    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21047}
21048
21049#[gpui::test]
21050async fn test_input_text(cx: &mut TestAppContext) {
21051    init_test(cx, |_| {});
21052    let mut cx = EditorTestContext::new(cx).await;
21053
21054    cx.set_state(
21055        &r#"ˇone
21056        two
21057
21058        three
21059        fourˇ
21060        five
21061
21062        siˇx"#
21063            .unindent(),
21064    );
21065
21066    cx.dispatch_action(HandleInput(String::new()));
21067    cx.assert_editor_state(
21068        &r#"ˇone
21069        two
21070
21071        three
21072        fourˇ
21073        five
21074
21075        siˇx"#
21076            .unindent(),
21077    );
21078
21079    cx.dispatch_action(HandleInput("AAAA".to_string()));
21080    cx.assert_editor_state(
21081        &r#"AAAAˇone
21082        two
21083
21084        three
21085        fourAAAAˇ
21086        five
21087
21088        siAAAAˇx"#
21089            .unindent(),
21090    );
21091}
21092
21093#[gpui::test]
21094async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21095    init_test(cx, |_| {});
21096
21097    let mut cx = EditorTestContext::new(cx).await;
21098    cx.set_state(
21099        r#"let foo = 1;
21100let foo = 2;
21101let foo = 3;
21102let fooˇ = 4;
21103let foo = 5;
21104let foo = 6;
21105let foo = 7;
21106let foo = 8;
21107let foo = 9;
21108let foo = 10;
21109let foo = 11;
21110let foo = 12;
21111let foo = 13;
21112let foo = 14;
21113let foo = 15;"#,
21114    );
21115
21116    cx.update_editor(|e, window, cx| {
21117        assert_eq!(
21118            e.next_scroll_position,
21119            NextScrollCursorCenterTopBottom::Center,
21120            "Default next scroll direction is center",
21121        );
21122
21123        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21124        assert_eq!(
21125            e.next_scroll_position,
21126            NextScrollCursorCenterTopBottom::Top,
21127            "After center, next scroll direction should be top",
21128        );
21129
21130        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21131        assert_eq!(
21132            e.next_scroll_position,
21133            NextScrollCursorCenterTopBottom::Bottom,
21134            "After top, next scroll direction should be bottom",
21135        );
21136
21137        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21138        assert_eq!(
21139            e.next_scroll_position,
21140            NextScrollCursorCenterTopBottom::Center,
21141            "After bottom, scrolling should start over",
21142        );
21143
21144        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21145        assert_eq!(
21146            e.next_scroll_position,
21147            NextScrollCursorCenterTopBottom::Top,
21148            "Scrolling continues if retriggered fast enough"
21149        );
21150    });
21151
21152    cx.executor()
21153        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21154    cx.executor().run_until_parked();
21155    cx.update_editor(|e, _, _| {
21156        assert_eq!(
21157            e.next_scroll_position,
21158            NextScrollCursorCenterTopBottom::Center,
21159            "If scrolling is not triggered fast enough, it should reset"
21160        );
21161    });
21162}
21163
21164#[gpui::test]
21165async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21166    init_test(cx, |_| {});
21167    let mut cx = EditorLspTestContext::new_rust(
21168        lsp::ServerCapabilities {
21169            definition_provider: Some(lsp::OneOf::Left(true)),
21170            references_provider: Some(lsp::OneOf::Left(true)),
21171            ..lsp::ServerCapabilities::default()
21172        },
21173        cx,
21174    )
21175    .await;
21176
21177    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21178        let go_to_definition = cx
21179            .lsp
21180            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21181                move |params, _| async move {
21182                    if empty_go_to_definition {
21183                        Ok(None)
21184                    } else {
21185                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21186                            uri: params.text_document_position_params.text_document.uri,
21187                            range: lsp::Range::new(
21188                                lsp::Position::new(4, 3),
21189                                lsp::Position::new(4, 6),
21190                            ),
21191                        })))
21192                    }
21193                },
21194            );
21195        let references = cx
21196            .lsp
21197            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21198                Ok(Some(vec![lsp::Location {
21199                    uri: params.text_document_position.text_document.uri,
21200                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21201                }]))
21202            });
21203        (go_to_definition, references)
21204    };
21205
21206    cx.set_state(
21207        &r#"fn one() {
21208            let mut a = ˇtwo();
21209        }
21210
21211        fn two() {}"#
21212            .unindent(),
21213    );
21214    set_up_lsp_handlers(false, &mut cx);
21215    let navigated = cx
21216        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21217        .await
21218        .expect("Failed to navigate to definition");
21219    assert_eq!(
21220        navigated,
21221        Navigated::Yes,
21222        "Should have navigated to definition from the GetDefinition response"
21223    );
21224    cx.assert_editor_state(
21225        &r#"fn one() {
21226            let mut a = two();
21227        }
21228
21229        fn «twoˇ»() {}"#
21230            .unindent(),
21231    );
21232
21233    let editors = cx.update_workspace(|workspace, _, cx| {
21234        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21235    });
21236    cx.update_editor(|_, _, test_editor_cx| {
21237        assert_eq!(
21238            editors.len(),
21239            1,
21240            "Initially, only one, test, editor should be open in the workspace"
21241        );
21242        assert_eq!(
21243            test_editor_cx.entity(),
21244            editors.last().expect("Asserted len is 1").clone()
21245        );
21246    });
21247
21248    set_up_lsp_handlers(true, &mut cx);
21249    let navigated = cx
21250        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21251        .await
21252        .expect("Failed to navigate to lookup references");
21253    assert_eq!(
21254        navigated,
21255        Navigated::Yes,
21256        "Should have navigated to references as a fallback after empty GoToDefinition response"
21257    );
21258    // We should not change the selections in the existing file,
21259    // if opening another milti buffer with the references
21260    cx.assert_editor_state(
21261        &r#"fn one() {
21262            let mut a = two();
21263        }
21264
21265        fn «twoˇ»() {}"#
21266            .unindent(),
21267    );
21268    let editors = cx.update_workspace(|workspace, _, cx| {
21269        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21270    });
21271    cx.update_editor(|_, _, test_editor_cx| {
21272        assert_eq!(
21273            editors.len(),
21274            2,
21275            "After falling back to references search, we open a new editor with the results"
21276        );
21277        let references_fallback_text = editors
21278            .into_iter()
21279            .find(|new_editor| *new_editor != test_editor_cx.entity())
21280            .expect("Should have one non-test editor now")
21281            .read(test_editor_cx)
21282            .text(test_editor_cx);
21283        assert_eq!(
21284            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21285            "Should use the range from the references response and not the GoToDefinition one"
21286        );
21287    });
21288}
21289
21290#[gpui::test]
21291async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21292    init_test(cx, |_| {});
21293    cx.update(|cx| {
21294        let mut editor_settings = EditorSettings::get_global(cx).clone();
21295        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21296        EditorSettings::override_global(editor_settings, cx);
21297    });
21298    let mut cx = EditorLspTestContext::new_rust(
21299        lsp::ServerCapabilities {
21300            definition_provider: Some(lsp::OneOf::Left(true)),
21301            references_provider: Some(lsp::OneOf::Left(true)),
21302            ..lsp::ServerCapabilities::default()
21303        },
21304        cx,
21305    )
21306    .await;
21307    let original_state = r#"fn one() {
21308        let mut a = ˇtwo();
21309    }
21310
21311    fn two() {}"#
21312        .unindent();
21313    cx.set_state(&original_state);
21314
21315    let mut go_to_definition = cx
21316        .lsp
21317        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21318            move |_, _| async move { Ok(None) },
21319        );
21320    let _references = cx
21321        .lsp
21322        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21323            panic!("Should not call for references with no go to definition fallback")
21324        });
21325
21326    let navigated = cx
21327        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21328        .await
21329        .expect("Failed to navigate to lookup references");
21330    go_to_definition
21331        .next()
21332        .await
21333        .expect("Should have called the go_to_definition handler");
21334
21335    assert_eq!(
21336        navigated,
21337        Navigated::No,
21338        "Should have navigated to references as a fallback after empty GoToDefinition response"
21339    );
21340    cx.assert_editor_state(&original_state);
21341    let editors = cx.update_workspace(|workspace, _, cx| {
21342        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21343    });
21344    cx.update_editor(|_, _, _| {
21345        assert_eq!(
21346            editors.len(),
21347            1,
21348            "After unsuccessful fallback, no other editor should have been opened"
21349        );
21350    });
21351}
21352
21353#[gpui::test]
21354async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21355    init_test(cx, |_| {});
21356
21357    let language = Arc::new(Language::new(
21358        LanguageConfig::default(),
21359        Some(tree_sitter_rust::LANGUAGE.into()),
21360    ));
21361
21362    let text = r#"
21363        #[cfg(test)]
21364        mod tests() {
21365            #[test]
21366            fn runnable_1() {
21367                let a = 1;
21368            }
21369
21370            #[test]
21371            fn runnable_2() {
21372                let a = 1;
21373                let b = 2;
21374            }
21375        }
21376    "#
21377    .unindent();
21378
21379    let fs = FakeFs::new(cx.executor());
21380    fs.insert_file("/file.rs", Default::default()).await;
21381
21382    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21383    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21384    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21385    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21386    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21387
21388    let editor = cx.new_window_entity(|window, cx| {
21389        Editor::new(
21390            EditorMode::full(),
21391            multi_buffer,
21392            Some(project.clone()),
21393            window,
21394            cx,
21395        )
21396    });
21397
21398    editor.update_in(cx, |editor, window, cx| {
21399        let snapshot = editor.buffer().read(cx).snapshot(cx);
21400        editor.tasks.insert(
21401            (buffer.read(cx).remote_id(), 3),
21402            RunnableTasks {
21403                templates: vec![],
21404                offset: snapshot.anchor_before(43),
21405                column: 0,
21406                extra_variables: HashMap::default(),
21407                context_range: BufferOffset(43)..BufferOffset(85),
21408            },
21409        );
21410        editor.tasks.insert(
21411            (buffer.read(cx).remote_id(), 8),
21412            RunnableTasks {
21413                templates: vec![],
21414                offset: snapshot.anchor_before(86),
21415                column: 0,
21416                extra_variables: HashMap::default(),
21417                context_range: BufferOffset(86)..BufferOffset(191),
21418            },
21419        );
21420
21421        // Test finding task when cursor is inside function body
21422        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21423            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21424        });
21425        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21426        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21427
21428        // Test finding task when cursor is on function name
21429        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21430            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21431        });
21432        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21433        assert_eq!(row, 8, "Should find task when cursor is on function name");
21434    });
21435}
21436
21437#[gpui::test]
21438async fn test_folding_buffers(cx: &mut TestAppContext) {
21439    init_test(cx, |_| {});
21440
21441    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21442    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21443    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21444
21445    let fs = FakeFs::new(cx.executor());
21446    fs.insert_tree(
21447        path!("/a"),
21448        json!({
21449            "first.rs": sample_text_1,
21450            "second.rs": sample_text_2,
21451            "third.rs": sample_text_3,
21452        }),
21453    )
21454    .await;
21455    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21456    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21457    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21458    let worktree = project.update(cx, |project, cx| {
21459        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21460        assert_eq!(worktrees.len(), 1);
21461        worktrees.pop().unwrap()
21462    });
21463    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21464
21465    let buffer_1 = project
21466        .update(cx, |project, cx| {
21467            project.open_buffer((worktree_id, "first.rs"), cx)
21468        })
21469        .await
21470        .unwrap();
21471    let buffer_2 = project
21472        .update(cx, |project, cx| {
21473            project.open_buffer((worktree_id, "second.rs"), cx)
21474        })
21475        .await
21476        .unwrap();
21477    let buffer_3 = project
21478        .update(cx, |project, cx| {
21479            project.open_buffer((worktree_id, "third.rs"), cx)
21480        })
21481        .await
21482        .unwrap();
21483
21484    let multi_buffer = cx.new(|cx| {
21485        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21486        multi_buffer.push_excerpts(
21487            buffer_1.clone(),
21488            [
21489                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21490                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21491                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21492            ],
21493            cx,
21494        );
21495        multi_buffer.push_excerpts(
21496            buffer_2.clone(),
21497            [
21498                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21499                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21500                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21501            ],
21502            cx,
21503        );
21504        multi_buffer.push_excerpts(
21505            buffer_3.clone(),
21506            [
21507                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21508                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21509                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21510            ],
21511            cx,
21512        );
21513        multi_buffer
21514    });
21515    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21516        Editor::new(
21517            EditorMode::full(),
21518            multi_buffer.clone(),
21519            Some(project.clone()),
21520            window,
21521            cx,
21522        )
21523    });
21524
21525    assert_eq!(
21526        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21527        "\n\naaaa\nbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21528    );
21529
21530    multi_buffer_editor.update(cx, |editor, cx| {
21531        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21532    });
21533    assert_eq!(
21534        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21535        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21536        "After folding the first buffer, its text should not be displayed"
21537    );
21538
21539    multi_buffer_editor.update(cx, |editor, cx| {
21540        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21541    });
21542    assert_eq!(
21543        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21544        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21545        "After folding the second buffer, its text should not be displayed"
21546    );
21547
21548    multi_buffer_editor.update(cx, |editor, cx| {
21549        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21550    });
21551    assert_eq!(
21552        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21553        "\n\n\n\n\n",
21554        "After folding the third buffer, its text should not be displayed"
21555    );
21556
21557    // Emulate selection inside the fold logic, that should work
21558    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21559        editor
21560            .snapshot(window, cx)
21561            .next_line_boundary(Point::new(0, 4));
21562    });
21563
21564    multi_buffer_editor.update(cx, |editor, cx| {
21565        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21566    });
21567    assert_eq!(
21568        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21569        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21570        "After unfolding the second buffer, its text should be displayed"
21571    );
21572
21573    // Typing inside of buffer 1 causes that buffer to be unfolded.
21574    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21575        assert_eq!(
21576            multi_buffer
21577                .read(cx)
21578                .snapshot(cx)
21579                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21580                .collect::<String>(),
21581            "bbbb"
21582        );
21583        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21584            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21585        });
21586        editor.handle_input("B", window, cx);
21587    });
21588
21589    assert_eq!(
21590        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21591        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21592        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21593    );
21594
21595    multi_buffer_editor.update(cx, |editor, cx| {
21596        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21597    });
21598    assert_eq!(
21599        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21600        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21601        "After unfolding the all buffers, all original text should be displayed"
21602    );
21603}
21604
21605#[gpui::test]
21606async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21607    init_test(cx, |_| {});
21608
21609    let sample_text_1 = "1111\n2222\n3333".to_string();
21610    let sample_text_2 = "4444\n5555\n6666".to_string();
21611    let sample_text_3 = "7777\n8888\n9999".to_string();
21612
21613    let fs = FakeFs::new(cx.executor());
21614    fs.insert_tree(
21615        path!("/a"),
21616        json!({
21617            "first.rs": sample_text_1,
21618            "second.rs": sample_text_2,
21619            "third.rs": sample_text_3,
21620        }),
21621    )
21622    .await;
21623    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21624    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21625    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21626    let worktree = project.update(cx, |project, cx| {
21627        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21628        assert_eq!(worktrees.len(), 1);
21629        worktrees.pop().unwrap()
21630    });
21631    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21632
21633    let buffer_1 = project
21634        .update(cx, |project, cx| {
21635            project.open_buffer((worktree_id, "first.rs"), cx)
21636        })
21637        .await
21638        .unwrap();
21639    let buffer_2 = project
21640        .update(cx, |project, cx| {
21641            project.open_buffer((worktree_id, "second.rs"), cx)
21642        })
21643        .await
21644        .unwrap();
21645    let buffer_3 = project
21646        .update(cx, |project, cx| {
21647            project.open_buffer((worktree_id, "third.rs"), cx)
21648        })
21649        .await
21650        .unwrap();
21651
21652    let multi_buffer = cx.new(|cx| {
21653        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21654        multi_buffer.push_excerpts(
21655            buffer_1.clone(),
21656            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21657            cx,
21658        );
21659        multi_buffer.push_excerpts(
21660            buffer_2.clone(),
21661            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21662            cx,
21663        );
21664        multi_buffer.push_excerpts(
21665            buffer_3.clone(),
21666            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21667            cx,
21668        );
21669        multi_buffer
21670    });
21671
21672    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21673        Editor::new(
21674            EditorMode::full(),
21675            multi_buffer,
21676            Some(project.clone()),
21677            window,
21678            cx,
21679        )
21680    });
21681
21682    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21683    assert_eq!(
21684        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21685        full_text,
21686    );
21687
21688    multi_buffer_editor.update(cx, |editor, cx| {
21689        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21690    });
21691    assert_eq!(
21692        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21693        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21694        "After folding the first buffer, its text should not be displayed"
21695    );
21696
21697    multi_buffer_editor.update(cx, |editor, cx| {
21698        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21699    });
21700
21701    assert_eq!(
21702        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21703        "\n\n\n\n\n\n7777\n8888\n9999",
21704        "After folding the second buffer, its text should not be displayed"
21705    );
21706
21707    multi_buffer_editor.update(cx, |editor, cx| {
21708        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21709    });
21710    assert_eq!(
21711        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21712        "\n\n\n\n\n",
21713        "After folding the third buffer, its text should not be displayed"
21714    );
21715
21716    multi_buffer_editor.update(cx, |editor, cx| {
21717        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21718    });
21719    assert_eq!(
21720        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21721        "\n\n\n\n4444\n5555\n6666\n\n",
21722        "After unfolding the second buffer, its text should be displayed"
21723    );
21724
21725    multi_buffer_editor.update(cx, |editor, cx| {
21726        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21727    });
21728    assert_eq!(
21729        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21730        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21731        "After unfolding the first buffer, its text should be displayed"
21732    );
21733
21734    multi_buffer_editor.update(cx, |editor, cx| {
21735        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21736    });
21737    assert_eq!(
21738        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21739        full_text,
21740        "After unfolding all buffers, all original text should be displayed"
21741    );
21742}
21743
21744#[gpui::test]
21745async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21746    init_test(cx, |_| {});
21747
21748    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21749
21750    let fs = FakeFs::new(cx.executor());
21751    fs.insert_tree(
21752        path!("/a"),
21753        json!({
21754            "main.rs": sample_text,
21755        }),
21756    )
21757    .await;
21758    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21759    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21760    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21761    let worktree = project.update(cx, |project, cx| {
21762        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21763        assert_eq!(worktrees.len(), 1);
21764        worktrees.pop().unwrap()
21765    });
21766    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21767
21768    let buffer_1 = project
21769        .update(cx, |project, cx| {
21770            project.open_buffer((worktree_id, "main.rs"), cx)
21771        })
21772        .await
21773        .unwrap();
21774
21775    let multi_buffer = cx.new(|cx| {
21776        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21777        multi_buffer.push_excerpts(
21778            buffer_1.clone(),
21779            [ExcerptRange::new(
21780                Point::new(0, 0)
21781                    ..Point::new(
21782                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
21783                        0,
21784                    ),
21785            )],
21786            cx,
21787        );
21788        multi_buffer
21789    });
21790    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21791        Editor::new(
21792            EditorMode::full(),
21793            multi_buffer,
21794            Some(project.clone()),
21795            window,
21796            cx,
21797        )
21798    });
21799
21800    let selection_range = Point::new(1, 0)..Point::new(2, 0);
21801    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21802        enum TestHighlight {}
21803        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
21804        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
21805        editor.highlight_text::<TestHighlight>(
21806            vec![highlight_range.clone()],
21807            HighlightStyle::color(Hsla::green()),
21808            cx,
21809        );
21810        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21811            s.select_ranges(Some(highlight_range))
21812        });
21813    });
21814
21815    let full_text = format!("\n\n{sample_text}");
21816    assert_eq!(
21817        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21818        full_text,
21819    );
21820}
21821
21822#[gpui::test]
21823async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
21824    init_test(cx, |_| {});
21825    cx.update(|cx| {
21826        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
21827            "keymaps/default-linux.json",
21828            cx,
21829        )
21830        .unwrap();
21831        cx.bind_keys(default_key_bindings);
21832    });
21833
21834    let (editor, cx) = cx.add_window_view(|window, cx| {
21835        let multi_buffer = MultiBuffer::build_multi(
21836            [
21837                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
21838                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
21839                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
21840                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
21841            ],
21842            cx,
21843        );
21844        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
21845
21846        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
21847        // fold all but the second buffer, so that we test navigating between two
21848        // adjacent folded buffers, as well as folded buffers at the start and
21849        // end the multibuffer
21850        editor.fold_buffer(buffer_ids[0], cx);
21851        editor.fold_buffer(buffer_ids[2], cx);
21852        editor.fold_buffer(buffer_ids[3], cx);
21853
21854        editor
21855    });
21856    cx.simulate_resize(size(px(1000.), px(1000.)));
21857
21858    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
21859    cx.assert_excerpts_with_selections(indoc! {"
21860        [EXCERPT]
21861        ˇ[FOLDED]
21862        [EXCERPT]
21863        a1
21864        b1
21865        [EXCERPT]
21866        [FOLDED]
21867        [EXCERPT]
21868        [FOLDED]
21869        "
21870    });
21871    cx.simulate_keystroke("down");
21872    cx.assert_excerpts_with_selections(indoc! {"
21873        [EXCERPT]
21874        [FOLDED]
21875        [EXCERPT]
21876        ˇa1
21877        b1
21878        [EXCERPT]
21879        [FOLDED]
21880        [EXCERPT]
21881        [FOLDED]
21882        "
21883    });
21884    cx.simulate_keystroke("down");
21885    cx.assert_excerpts_with_selections(indoc! {"
21886        [EXCERPT]
21887        [FOLDED]
21888        [EXCERPT]
21889        a1
21890        ˇb1
21891        [EXCERPT]
21892        [FOLDED]
21893        [EXCERPT]
21894        [FOLDED]
21895        "
21896    });
21897    cx.simulate_keystroke("down");
21898    cx.assert_excerpts_with_selections(indoc! {"
21899        [EXCERPT]
21900        [FOLDED]
21901        [EXCERPT]
21902        a1
21903        b1
21904        ˇ[EXCERPT]
21905        [FOLDED]
21906        [EXCERPT]
21907        [FOLDED]
21908        "
21909    });
21910    cx.simulate_keystroke("down");
21911    cx.assert_excerpts_with_selections(indoc! {"
21912        [EXCERPT]
21913        [FOLDED]
21914        [EXCERPT]
21915        a1
21916        b1
21917        [EXCERPT]
21918        ˇ[FOLDED]
21919        [EXCERPT]
21920        [FOLDED]
21921        "
21922    });
21923    for _ in 0..5 {
21924        cx.simulate_keystroke("down");
21925        cx.assert_excerpts_with_selections(indoc! {"
21926            [EXCERPT]
21927            [FOLDED]
21928            [EXCERPT]
21929            a1
21930            b1
21931            [EXCERPT]
21932            [FOLDED]
21933            [EXCERPT]
21934            ˇ[FOLDED]
21935            "
21936        });
21937    }
21938
21939    cx.simulate_keystroke("up");
21940    cx.assert_excerpts_with_selections(indoc! {"
21941        [EXCERPT]
21942        [FOLDED]
21943        [EXCERPT]
21944        a1
21945        b1
21946        [EXCERPT]
21947        ˇ[FOLDED]
21948        [EXCERPT]
21949        [FOLDED]
21950        "
21951    });
21952    cx.simulate_keystroke("up");
21953    cx.assert_excerpts_with_selections(indoc! {"
21954        [EXCERPT]
21955        [FOLDED]
21956        [EXCERPT]
21957        a1
21958        b1
21959        ˇ[EXCERPT]
21960        [FOLDED]
21961        [EXCERPT]
21962        [FOLDED]
21963        "
21964    });
21965    cx.simulate_keystroke("up");
21966    cx.assert_excerpts_with_selections(indoc! {"
21967        [EXCERPT]
21968        [FOLDED]
21969        [EXCERPT]
21970        a1
21971        ˇb1
21972        [EXCERPT]
21973        [FOLDED]
21974        [EXCERPT]
21975        [FOLDED]
21976        "
21977    });
21978    cx.simulate_keystroke("up");
21979    cx.assert_excerpts_with_selections(indoc! {"
21980        [EXCERPT]
21981        [FOLDED]
21982        [EXCERPT]
21983        ˇa1
21984        b1
21985        [EXCERPT]
21986        [FOLDED]
21987        [EXCERPT]
21988        [FOLDED]
21989        "
21990    });
21991    for _ in 0..5 {
21992        cx.simulate_keystroke("up");
21993        cx.assert_excerpts_with_selections(indoc! {"
21994            [EXCERPT]
21995            ˇ[FOLDED]
21996            [EXCERPT]
21997            a1
21998            b1
21999            [EXCERPT]
22000            [FOLDED]
22001            [EXCERPT]
22002            [FOLDED]
22003            "
22004        });
22005    }
22006}
22007
22008#[gpui::test]
22009async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22010    init_test(cx, |_| {});
22011
22012    // Simple insertion
22013    assert_highlighted_edits(
22014        "Hello, world!",
22015        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22016        true,
22017        cx,
22018        |highlighted_edits, cx| {
22019            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22020            assert_eq!(highlighted_edits.highlights.len(), 1);
22021            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22022            assert_eq!(
22023                highlighted_edits.highlights[0].1.background_color,
22024                Some(cx.theme().status().created_background)
22025            );
22026        },
22027    )
22028    .await;
22029
22030    // Replacement
22031    assert_highlighted_edits(
22032        "This is a test.",
22033        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22034        false,
22035        cx,
22036        |highlighted_edits, cx| {
22037            assert_eq!(highlighted_edits.text, "That is a test.");
22038            assert_eq!(highlighted_edits.highlights.len(), 1);
22039            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22040            assert_eq!(
22041                highlighted_edits.highlights[0].1.background_color,
22042                Some(cx.theme().status().created_background)
22043            );
22044        },
22045    )
22046    .await;
22047
22048    // Multiple edits
22049    assert_highlighted_edits(
22050        "Hello, world!",
22051        vec![
22052            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22053            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22054        ],
22055        false,
22056        cx,
22057        |highlighted_edits, cx| {
22058            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22059            assert_eq!(highlighted_edits.highlights.len(), 2);
22060            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22061            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22062            assert_eq!(
22063                highlighted_edits.highlights[0].1.background_color,
22064                Some(cx.theme().status().created_background)
22065            );
22066            assert_eq!(
22067                highlighted_edits.highlights[1].1.background_color,
22068                Some(cx.theme().status().created_background)
22069            );
22070        },
22071    )
22072    .await;
22073
22074    // Multiple lines with edits
22075    assert_highlighted_edits(
22076        "First line\nSecond line\nThird line\nFourth line",
22077        vec![
22078            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22079            (
22080                Point::new(2, 0)..Point::new(2, 10),
22081                "New third line".to_string(),
22082            ),
22083            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22084        ],
22085        false,
22086        cx,
22087        |highlighted_edits, cx| {
22088            assert_eq!(
22089                highlighted_edits.text,
22090                "Second modified\nNew third line\nFourth updated line"
22091            );
22092            assert_eq!(highlighted_edits.highlights.len(), 3);
22093            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22094            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22095            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22096            for highlight in &highlighted_edits.highlights {
22097                assert_eq!(
22098                    highlight.1.background_color,
22099                    Some(cx.theme().status().created_background)
22100                );
22101            }
22102        },
22103    )
22104    .await;
22105}
22106
22107#[gpui::test]
22108async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22109    init_test(cx, |_| {});
22110
22111    // Deletion
22112    assert_highlighted_edits(
22113        "Hello, world!",
22114        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22115        true,
22116        cx,
22117        |highlighted_edits, cx| {
22118            assert_eq!(highlighted_edits.text, "Hello, world!");
22119            assert_eq!(highlighted_edits.highlights.len(), 1);
22120            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22121            assert_eq!(
22122                highlighted_edits.highlights[0].1.background_color,
22123                Some(cx.theme().status().deleted_background)
22124            );
22125        },
22126    )
22127    .await;
22128
22129    // Insertion
22130    assert_highlighted_edits(
22131        "Hello, world!",
22132        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22133        true,
22134        cx,
22135        |highlighted_edits, cx| {
22136            assert_eq!(highlighted_edits.highlights.len(), 1);
22137            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22138            assert_eq!(
22139                highlighted_edits.highlights[0].1.background_color,
22140                Some(cx.theme().status().created_background)
22141            );
22142        },
22143    )
22144    .await;
22145}
22146
22147async fn assert_highlighted_edits(
22148    text: &str,
22149    edits: Vec<(Range<Point>, String)>,
22150    include_deletions: bool,
22151    cx: &mut TestAppContext,
22152    assertion_fn: impl Fn(HighlightedText, &App),
22153) {
22154    let window = cx.add_window(|window, cx| {
22155        let buffer = MultiBuffer::build_simple(text, cx);
22156        Editor::new(EditorMode::full(), buffer, None, window, cx)
22157    });
22158    let cx = &mut VisualTestContext::from_window(*window, cx);
22159
22160    let (buffer, snapshot) = window
22161        .update(cx, |editor, _window, cx| {
22162            (
22163                editor.buffer().clone(),
22164                editor.buffer().read(cx).snapshot(cx),
22165            )
22166        })
22167        .unwrap();
22168
22169    let edits = edits
22170        .into_iter()
22171        .map(|(range, edit)| {
22172            (
22173                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22174                edit,
22175            )
22176        })
22177        .collect::<Vec<_>>();
22178
22179    let text_anchor_edits = edits
22180        .clone()
22181        .into_iter()
22182        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22183        .collect::<Vec<_>>();
22184
22185    let edit_preview = window
22186        .update(cx, |_, _window, cx| {
22187            buffer
22188                .read(cx)
22189                .as_singleton()
22190                .unwrap()
22191                .read(cx)
22192                .preview_edits(text_anchor_edits.into(), cx)
22193        })
22194        .unwrap()
22195        .await;
22196
22197    cx.update(|_window, cx| {
22198        let highlighted_edits = edit_prediction_edit_text(
22199            snapshot.as_singleton().unwrap().2,
22200            &edits,
22201            &edit_preview,
22202            include_deletions,
22203            cx,
22204        );
22205        assertion_fn(highlighted_edits, cx)
22206    });
22207}
22208
22209#[track_caller]
22210fn assert_breakpoint(
22211    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22212    path: &Arc<Path>,
22213    expected: Vec<(u32, Breakpoint)>,
22214) {
22215    if expected.is_empty() {
22216        assert!(!breakpoints.contains_key(path), "{}", path.display());
22217    } else {
22218        let mut breakpoint = breakpoints
22219            .get(path)
22220            .unwrap()
22221            .iter()
22222            .map(|breakpoint| {
22223                (
22224                    breakpoint.row,
22225                    Breakpoint {
22226                        message: breakpoint.message.clone(),
22227                        state: breakpoint.state,
22228                        condition: breakpoint.condition.clone(),
22229                        hit_condition: breakpoint.hit_condition.clone(),
22230                    },
22231                )
22232            })
22233            .collect::<Vec<_>>();
22234
22235        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22236
22237        assert_eq!(expected, breakpoint);
22238    }
22239}
22240
22241fn add_log_breakpoint_at_cursor(
22242    editor: &mut Editor,
22243    log_message: &str,
22244    window: &mut Window,
22245    cx: &mut Context<Editor>,
22246) {
22247    let (anchor, bp) = editor
22248        .breakpoints_at_cursors(window, cx)
22249        .first()
22250        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22251        .unwrap_or_else(|| {
22252            let cursor_position: Point = editor.selections.newest(cx).head();
22253
22254            let breakpoint_position = editor
22255                .snapshot(window, cx)
22256                .display_snapshot
22257                .buffer_snapshot
22258                .anchor_before(Point::new(cursor_position.row, 0));
22259
22260            (breakpoint_position, Breakpoint::new_log(log_message))
22261        });
22262
22263    editor.edit_breakpoint_at_anchor(
22264        anchor,
22265        bp,
22266        BreakpointEditAction::EditLogMessage(log_message.into()),
22267        cx,
22268    );
22269}
22270
22271#[gpui::test]
22272async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22273    init_test(cx, |_| {});
22274
22275    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22276    let fs = FakeFs::new(cx.executor());
22277    fs.insert_tree(
22278        path!("/a"),
22279        json!({
22280            "main.rs": sample_text,
22281        }),
22282    )
22283    .await;
22284    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22285    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22286    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22287
22288    let fs = FakeFs::new(cx.executor());
22289    fs.insert_tree(
22290        path!("/a"),
22291        json!({
22292            "main.rs": sample_text,
22293        }),
22294    )
22295    .await;
22296    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22297    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22298    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22299    let worktree_id = workspace
22300        .update(cx, |workspace, _window, cx| {
22301            workspace.project().update(cx, |project, cx| {
22302                project.worktrees(cx).next().unwrap().read(cx).id()
22303            })
22304        })
22305        .unwrap();
22306
22307    let buffer = project
22308        .update(cx, |project, cx| {
22309            project.open_buffer((worktree_id, "main.rs"), cx)
22310        })
22311        .await
22312        .unwrap();
22313
22314    let (editor, cx) = cx.add_window_view(|window, cx| {
22315        Editor::new(
22316            EditorMode::full(),
22317            MultiBuffer::build_from_buffer(buffer, cx),
22318            Some(project.clone()),
22319            window,
22320            cx,
22321        )
22322    });
22323
22324    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22325    let abs_path = project.read_with(cx, |project, cx| {
22326        project
22327            .absolute_path(&project_path, cx)
22328            .map(Arc::from)
22329            .unwrap()
22330    });
22331
22332    // assert we can add breakpoint on the first line
22333    editor.update_in(cx, |editor, window, cx| {
22334        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22335        editor.move_to_end(&MoveToEnd, window, cx);
22336        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22337    });
22338
22339    let breakpoints = editor.update(cx, |editor, cx| {
22340        editor
22341            .breakpoint_store()
22342            .as_ref()
22343            .unwrap()
22344            .read(cx)
22345            .all_source_breakpoints(cx)
22346    });
22347
22348    assert_eq!(1, breakpoints.len());
22349    assert_breakpoint(
22350        &breakpoints,
22351        &abs_path,
22352        vec![
22353            (0, Breakpoint::new_standard()),
22354            (3, Breakpoint::new_standard()),
22355        ],
22356    );
22357
22358    editor.update_in(cx, |editor, window, cx| {
22359        editor.move_to_beginning(&MoveToBeginning, window, cx);
22360        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22361    });
22362
22363    let breakpoints = editor.update(cx, |editor, cx| {
22364        editor
22365            .breakpoint_store()
22366            .as_ref()
22367            .unwrap()
22368            .read(cx)
22369            .all_source_breakpoints(cx)
22370    });
22371
22372    assert_eq!(1, breakpoints.len());
22373    assert_breakpoint(
22374        &breakpoints,
22375        &abs_path,
22376        vec![(3, Breakpoint::new_standard())],
22377    );
22378
22379    editor.update_in(cx, |editor, window, cx| {
22380        editor.move_to_end(&MoveToEnd, window, cx);
22381        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22382    });
22383
22384    let breakpoints = editor.update(cx, |editor, cx| {
22385        editor
22386            .breakpoint_store()
22387            .as_ref()
22388            .unwrap()
22389            .read(cx)
22390            .all_source_breakpoints(cx)
22391    });
22392
22393    assert_eq!(0, breakpoints.len());
22394    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22395}
22396
22397#[gpui::test]
22398async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22399    init_test(cx, |_| {});
22400
22401    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22402
22403    let fs = FakeFs::new(cx.executor());
22404    fs.insert_tree(
22405        path!("/a"),
22406        json!({
22407            "main.rs": sample_text,
22408        }),
22409    )
22410    .await;
22411    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22412    let (workspace, cx) =
22413        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22414
22415    let worktree_id = workspace.update(cx, |workspace, cx| {
22416        workspace.project().update(cx, |project, cx| {
22417            project.worktrees(cx).next().unwrap().read(cx).id()
22418        })
22419    });
22420
22421    let buffer = project
22422        .update(cx, |project, cx| {
22423            project.open_buffer((worktree_id, "main.rs"), cx)
22424        })
22425        .await
22426        .unwrap();
22427
22428    let (editor, cx) = cx.add_window_view(|window, cx| {
22429        Editor::new(
22430            EditorMode::full(),
22431            MultiBuffer::build_from_buffer(buffer, cx),
22432            Some(project.clone()),
22433            window,
22434            cx,
22435        )
22436    });
22437
22438    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22439    let abs_path = project.read_with(cx, |project, cx| {
22440        project
22441            .absolute_path(&project_path, cx)
22442            .map(Arc::from)
22443            .unwrap()
22444    });
22445
22446    editor.update_in(cx, |editor, window, cx| {
22447        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22448    });
22449
22450    let breakpoints = editor.update(cx, |editor, cx| {
22451        editor
22452            .breakpoint_store()
22453            .as_ref()
22454            .unwrap()
22455            .read(cx)
22456            .all_source_breakpoints(cx)
22457    });
22458
22459    assert_breakpoint(
22460        &breakpoints,
22461        &abs_path,
22462        vec![(0, Breakpoint::new_log("hello world"))],
22463    );
22464
22465    // Removing a log message from a log breakpoint should remove it
22466    editor.update_in(cx, |editor, window, cx| {
22467        add_log_breakpoint_at_cursor(editor, "", window, cx);
22468    });
22469
22470    let breakpoints = editor.update(cx, |editor, cx| {
22471        editor
22472            .breakpoint_store()
22473            .as_ref()
22474            .unwrap()
22475            .read(cx)
22476            .all_source_breakpoints(cx)
22477    });
22478
22479    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22480
22481    editor.update_in(cx, |editor, window, cx| {
22482        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22483        editor.move_to_end(&MoveToEnd, window, cx);
22484        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22485        // Not adding a log message to a standard breakpoint shouldn't remove it
22486        add_log_breakpoint_at_cursor(editor, "", window, cx);
22487    });
22488
22489    let breakpoints = editor.update(cx, |editor, cx| {
22490        editor
22491            .breakpoint_store()
22492            .as_ref()
22493            .unwrap()
22494            .read(cx)
22495            .all_source_breakpoints(cx)
22496    });
22497
22498    assert_breakpoint(
22499        &breakpoints,
22500        &abs_path,
22501        vec![
22502            (0, Breakpoint::new_standard()),
22503            (3, Breakpoint::new_standard()),
22504        ],
22505    );
22506
22507    editor.update_in(cx, |editor, window, cx| {
22508        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22509    });
22510
22511    let breakpoints = editor.update(cx, |editor, cx| {
22512        editor
22513            .breakpoint_store()
22514            .as_ref()
22515            .unwrap()
22516            .read(cx)
22517            .all_source_breakpoints(cx)
22518    });
22519
22520    assert_breakpoint(
22521        &breakpoints,
22522        &abs_path,
22523        vec![
22524            (0, Breakpoint::new_standard()),
22525            (3, Breakpoint::new_log("hello world")),
22526        ],
22527    );
22528
22529    editor.update_in(cx, |editor, window, cx| {
22530        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22531    });
22532
22533    let breakpoints = editor.update(cx, |editor, cx| {
22534        editor
22535            .breakpoint_store()
22536            .as_ref()
22537            .unwrap()
22538            .read(cx)
22539            .all_source_breakpoints(cx)
22540    });
22541
22542    assert_breakpoint(
22543        &breakpoints,
22544        &abs_path,
22545        vec![
22546            (0, Breakpoint::new_standard()),
22547            (3, Breakpoint::new_log("hello Earth!!")),
22548        ],
22549    );
22550}
22551
22552/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22553/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22554/// or when breakpoints were placed out of order. This tests for a regression too
22555#[gpui::test]
22556async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22557    init_test(cx, |_| {});
22558
22559    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22560    let fs = FakeFs::new(cx.executor());
22561    fs.insert_tree(
22562        path!("/a"),
22563        json!({
22564            "main.rs": sample_text,
22565        }),
22566    )
22567    .await;
22568    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22569    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22570    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22571
22572    let fs = FakeFs::new(cx.executor());
22573    fs.insert_tree(
22574        path!("/a"),
22575        json!({
22576            "main.rs": sample_text,
22577        }),
22578    )
22579    .await;
22580    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22581    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22582    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22583    let worktree_id = workspace
22584        .update(cx, |workspace, _window, cx| {
22585            workspace.project().update(cx, |project, cx| {
22586                project.worktrees(cx).next().unwrap().read(cx).id()
22587            })
22588        })
22589        .unwrap();
22590
22591    let buffer = project
22592        .update(cx, |project, cx| {
22593            project.open_buffer((worktree_id, "main.rs"), cx)
22594        })
22595        .await
22596        .unwrap();
22597
22598    let (editor, cx) = cx.add_window_view(|window, cx| {
22599        Editor::new(
22600            EditorMode::full(),
22601            MultiBuffer::build_from_buffer(buffer, cx),
22602            Some(project.clone()),
22603            window,
22604            cx,
22605        )
22606    });
22607
22608    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22609    let abs_path = project.read_with(cx, |project, cx| {
22610        project
22611            .absolute_path(&project_path, cx)
22612            .map(Arc::from)
22613            .unwrap()
22614    });
22615
22616    // assert we can add breakpoint on the first line
22617    editor.update_in(cx, |editor, window, cx| {
22618        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22619        editor.move_to_end(&MoveToEnd, window, cx);
22620        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22621        editor.move_up(&MoveUp, window, cx);
22622        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22623    });
22624
22625    let breakpoints = editor.update(cx, |editor, cx| {
22626        editor
22627            .breakpoint_store()
22628            .as_ref()
22629            .unwrap()
22630            .read(cx)
22631            .all_source_breakpoints(cx)
22632    });
22633
22634    assert_eq!(1, breakpoints.len());
22635    assert_breakpoint(
22636        &breakpoints,
22637        &abs_path,
22638        vec![
22639            (0, Breakpoint::new_standard()),
22640            (2, Breakpoint::new_standard()),
22641            (3, Breakpoint::new_standard()),
22642        ],
22643    );
22644
22645    editor.update_in(cx, |editor, window, cx| {
22646        editor.move_to_beginning(&MoveToBeginning, window, cx);
22647        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22648        editor.move_to_end(&MoveToEnd, window, cx);
22649        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22650        // Disabling a breakpoint that doesn't exist should do nothing
22651        editor.move_up(&MoveUp, window, cx);
22652        editor.move_up(&MoveUp, window, cx);
22653        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22654    });
22655
22656    let breakpoints = editor.update(cx, |editor, cx| {
22657        editor
22658            .breakpoint_store()
22659            .as_ref()
22660            .unwrap()
22661            .read(cx)
22662            .all_source_breakpoints(cx)
22663    });
22664
22665    let disable_breakpoint = {
22666        let mut bp = Breakpoint::new_standard();
22667        bp.state = BreakpointState::Disabled;
22668        bp
22669    };
22670
22671    assert_eq!(1, breakpoints.len());
22672    assert_breakpoint(
22673        &breakpoints,
22674        &abs_path,
22675        vec![
22676            (0, disable_breakpoint.clone()),
22677            (2, Breakpoint::new_standard()),
22678            (3, disable_breakpoint.clone()),
22679        ],
22680    );
22681
22682    editor.update_in(cx, |editor, window, cx| {
22683        editor.move_to_beginning(&MoveToBeginning, window, cx);
22684        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22685        editor.move_to_end(&MoveToEnd, window, cx);
22686        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22687        editor.move_up(&MoveUp, window, cx);
22688        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22689    });
22690
22691    let breakpoints = editor.update(cx, |editor, cx| {
22692        editor
22693            .breakpoint_store()
22694            .as_ref()
22695            .unwrap()
22696            .read(cx)
22697            .all_source_breakpoints(cx)
22698    });
22699
22700    assert_eq!(1, breakpoints.len());
22701    assert_breakpoint(
22702        &breakpoints,
22703        &abs_path,
22704        vec![
22705            (0, Breakpoint::new_standard()),
22706            (2, disable_breakpoint),
22707            (3, Breakpoint::new_standard()),
22708        ],
22709    );
22710}
22711
22712#[gpui::test]
22713async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22714    init_test(cx, |_| {});
22715    let capabilities = lsp::ServerCapabilities {
22716        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22717            prepare_provider: Some(true),
22718            work_done_progress_options: Default::default(),
22719        })),
22720        ..Default::default()
22721    };
22722    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22723
22724    cx.set_state(indoc! {"
22725        struct Fˇoo {}
22726    "});
22727
22728    cx.update_editor(|editor, _, cx| {
22729        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22730        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22731        editor.highlight_background::<DocumentHighlightRead>(
22732            &[highlight_range],
22733            |theme| theme.colors().editor_document_highlight_read_background,
22734            cx,
22735        );
22736    });
22737
22738    let mut prepare_rename_handler = cx
22739        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22740            move |_, _, _| async move {
22741                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22742                    start: lsp::Position {
22743                        line: 0,
22744                        character: 7,
22745                    },
22746                    end: lsp::Position {
22747                        line: 0,
22748                        character: 10,
22749                    },
22750                })))
22751            },
22752        );
22753    let prepare_rename_task = cx
22754        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22755        .expect("Prepare rename was not started");
22756    prepare_rename_handler.next().await.unwrap();
22757    prepare_rename_task.await.expect("Prepare rename failed");
22758
22759    let mut rename_handler =
22760        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22761            let edit = lsp::TextEdit {
22762                range: lsp::Range {
22763                    start: lsp::Position {
22764                        line: 0,
22765                        character: 7,
22766                    },
22767                    end: lsp::Position {
22768                        line: 0,
22769                        character: 10,
22770                    },
22771                },
22772                new_text: "FooRenamed".to_string(),
22773            };
22774            Ok(Some(lsp::WorkspaceEdit::new(
22775                // Specify the same edit twice
22776                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
22777            )))
22778        });
22779    let rename_task = cx
22780        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22781        .expect("Confirm rename was not started");
22782    rename_handler.next().await.unwrap();
22783    rename_task.await.expect("Confirm rename failed");
22784    cx.run_until_parked();
22785
22786    // Despite two edits, only one is actually applied as those are identical
22787    cx.assert_editor_state(indoc! {"
22788        struct FooRenamedˇ {}
22789    "});
22790}
22791
22792#[gpui::test]
22793async fn test_rename_without_prepare(cx: &mut TestAppContext) {
22794    init_test(cx, |_| {});
22795    // These capabilities indicate that the server does not support prepare rename.
22796    let capabilities = lsp::ServerCapabilities {
22797        rename_provider: Some(lsp::OneOf::Left(true)),
22798        ..Default::default()
22799    };
22800    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22801
22802    cx.set_state(indoc! {"
22803        struct Fˇoo {}
22804    "});
22805
22806    cx.update_editor(|editor, _window, cx| {
22807        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22808        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22809        editor.highlight_background::<DocumentHighlightRead>(
22810            &[highlight_range],
22811            |theme| theme.colors().editor_document_highlight_read_background,
22812            cx,
22813        );
22814    });
22815
22816    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22817        .expect("Prepare rename was not started")
22818        .await
22819        .expect("Prepare rename failed");
22820
22821    let mut rename_handler =
22822        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22823            let edit = lsp::TextEdit {
22824                range: lsp::Range {
22825                    start: lsp::Position {
22826                        line: 0,
22827                        character: 7,
22828                    },
22829                    end: lsp::Position {
22830                        line: 0,
22831                        character: 10,
22832                    },
22833                },
22834                new_text: "FooRenamed".to_string(),
22835            };
22836            Ok(Some(lsp::WorkspaceEdit::new(
22837                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
22838            )))
22839        });
22840    let rename_task = cx
22841        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22842        .expect("Confirm rename was not started");
22843    rename_handler.next().await.unwrap();
22844    rename_task.await.expect("Confirm rename failed");
22845    cx.run_until_parked();
22846
22847    // Correct range is renamed, as `surrounding_word` is used to find it.
22848    cx.assert_editor_state(indoc! {"
22849        struct FooRenamedˇ {}
22850    "});
22851}
22852
22853#[gpui::test]
22854async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
22855    init_test(cx, |_| {});
22856    let mut cx = EditorTestContext::new(cx).await;
22857
22858    let language = Arc::new(
22859        Language::new(
22860            LanguageConfig::default(),
22861            Some(tree_sitter_html::LANGUAGE.into()),
22862        )
22863        .with_brackets_query(
22864            r#"
22865            ("<" @open "/>" @close)
22866            ("</" @open ">" @close)
22867            ("<" @open ">" @close)
22868            ("\"" @open "\"" @close)
22869            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
22870        "#,
22871        )
22872        .unwrap(),
22873    );
22874    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22875
22876    cx.set_state(indoc! {"
22877        <span>ˇ</span>
22878    "});
22879    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22880    cx.assert_editor_state(indoc! {"
22881        <span>
22882        ˇ
22883        </span>
22884    "});
22885
22886    cx.set_state(indoc! {"
22887        <span><span></span>ˇ</span>
22888    "});
22889    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22890    cx.assert_editor_state(indoc! {"
22891        <span><span></span>
22892        ˇ</span>
22893    "});
22894
22895    cx.set_state(indoc! {"
22896        <span>ˇ
22897        </span>
22898    "});
22899    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22900    cx.assert_editor_state(indoc! {"
22901        <span>
22902        ˇ
22903        </span>
22904    "});
22905}
22906
22907#[gpui::test(iterations = 10)]
22908async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
22909    init_test(cx, |_| {});
22910
22911    let fs = FakeFs::new(cx.executor());
22912    fs.insert_tree(
22913        path!("/dir"),
22914        json!({
22915            "a.ts": "a",
22916        }),
22917    )
22918    .await;
22919
22920    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
22921    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22922    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22923
22924    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22925    language_registry.add(Arc::new(Language::new(
22926        LanguageConfig {
22927            name: "TypeScript".into(),
22928            matcher: LanguageMatcher {
22929                path_suffixes: vec!["ts".to_string()],
22930                ..Default::default()
22931            },
22932            ..Default::default()
22933        },
22934        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
22935    )));
22936    let mut fake_language_servers = language_registry.register_fake_lsp(
22937        "TypeScript",
22938        FakeLspAdapter {
22939            capabilities: lsp::ServerCapabilities {
22940                code_lens_provider: Some(lsp::CodeLensOptions {
22941                    resolve_provider: Some(true),
22942                }),
22943                execute_command_provider: Some(lsp::ExecuteCommandOptions {
22944                    commands: vec!["_the/command".to_string()],
22945                    ..lsp::ExecuteCommandOptions::default()
22946                }),
22947                ..lsp::ServerCapabilities::default()
22948            },
22949            ..FakeLspAdapter::default()
22950        },
22951    );
22952
22953    let editor = workspace
22954        .update(cx, |workspace, window, cx| {
22955            workspace.open_abs_path(
22956                PathBuf::from(path!("/dir/a.ts")),
22957                OpenOptions::default(),
22958                window,
22959                cx,
22960            )
22961        })
22962        .unwrap()
22963        .await
22964        .unwrap()
22965        .downcast::<Editor>()
22966        .unwrap();
22967    cx.executor().run_until_parked();
22968
22969    let fake_server = fake_language_servers.next().await.unwrap();
22970
22971    let buffer = editor.update(cx, |editor, cx| {
22972        editor
22973            .buffer()
22974            .read(cx)
22975            .as_singleton()
22976            .expect("have opened a single file by path")
22977    });
22978
22979    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
22980    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
22981    drop(buffer_snapshot);
22982    let actions = cx
22983        .update_window(*workspace, |_, window, cx| {
22984            project.code_actions(&buffer, anchor..anchor, window, cx)
22985        })
22986        .unwrap();
22987
22988    fake_server
22989        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
22990            Ok(Some(vec![
22991                lsp::CodeLens {
22992                    range: lsp::Range::default(),
22993                    command: Some(lsp::Command {
22994                        title: "Code lens command".to_owned(),
22995                        command: "_the/command".to_owned(),
22996                        arguments: None,
22997                    }),
22998                    data: None,
22999                },
23000                lsp::CodeLens {
23001                    range: lsp::Range::default(),
23002                    command: Some(lsp::Command {
23003                        title: "Command not in capabilities".to_owned(),
23004                        command: "not in capabilities".to_owned(),
23005                        arguments: None,
23006                    }),
23007                    data: None,
23008                },
23009                lsp::CodeLens {
23010                    range: lsp::Range {
23011                        start: lsp::Position {
23012                            line: 1,
23013                            character: 1,
23014                        },
23015                        end: lsp::Position {
23016                            line: 1,
23017                            character: 1,
23018                        },
23019                    },
23020                    command: Some(lsp::Command {
23021                        title: "Command not in range".to_owned(),
23022                        command: "_the/command".to_owned(),
23023                        arguments: None,
23024                    }),
23025                    data: None,
23026                },
23027            ]))
23028        })
23029        .next()
23030        .await;
23031
23032    let actions = actions.await.unwrap();
23033    assert_eq!(
23034        actions.len(),
23035        1,
23036        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23037    );
23038    let action = actions[0].clone();
23039    let apply = project.update(cx, |project, cx| {
23040        project.apply_code_action(buffer.clone(), action, true, cx)
23041    });
23042
23043    // Resolving the code action does not populate its edits. In absence of
23044    // edits, we must execute the given command.
23045    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23046        |mut lens, _| async move {
23047            let lens_command = lens.command.as_mut().expect("should have a command");
23048            assert_eq!(lens_command.title, "Code lens command");
23049            lens_command.arguments = Some(vec![json!("the-argument")]);
23050            Ok(lens)
23051        },
23052    );
23053
23054    // While executing the command, the language server sends the editor
23055    // a `workspaceEdit` request.
23056    fake_server
23057        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23058            let fake = fake_server.clone();
23059            move |params, _| {
23060                assert_eq!(params.command, "_the/command");
23061                let fake = fake.clone();
23062                async move {
23063                    fake.server
23064                        .request::<lsp::request::ApplyWorkspaceEdit>(
23065                            lsp::ApplyWorkspaceEditParams {
23066                                label: None,
23067                                edit: lsp::WorkspaceEdit {
23068                                    changes: Some(
23069                                        [(
23070                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23071                                            vec![lsp::TextEdit {
23072                                                range: lsp::Range::new(
23073                                                    lsp::Position::new(0, 0),
23074                                                    lsp::Position::new(0, 0),
23075                                                ),
23076                                                new_text: "X".into(),
23077                                            }],
23078                                        )]
23079                                        .into_iter()
23080                                        .collect(),
23081                                    ),
23082                                    ..lsp::WorkspaceEdit::default()
23083                                },
23084                            },
23085                        )
23086                        .await
23087                        .into_response()
23088                        .unwrap();
23089                    Ok(Some(json!(null)))
23090                }
23091            }
23092        })
23093        .next()
23094        .await;
23095
23096    // Applying the code lens command returns a project transaction containing the edits
23097    // sent by the language server in its `workspaceEdit` request.
23098    let transaction = apply.await.unwrap();
23099    assert!(transaction.0.contains_key(&buffer));
23100    buffer.update(cx, |buffer, cx| {
23101        assert_eq!(buffer.text(), "Xa");
23102        buffer.undo(cx);
23103        assert_eq!(buffer.text(), "a");
23104    });
23105
23106    let actions_after_edits = cx
23107        .update_window(*workspace, |_, window, cx| {
23108            project.code_actions(&buffer, anchor..anchor, window, cx)
23109        })
23110        .unwrap()
23111        .await
23112        .unwrap();
23113    assert_eq!(
23114        actions, actions_after_edits,
23115        "For the same selection, same code lens actions should be returned"
23116    );
23117
23118    let _responses =
23119        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23120            panic!("No more code lens requests are expected");
23121        });
23122    editor.update_in(cx, |editor, window, cx| {
23123        editor.select_all(&SelectAll, window, cx);
23124    });
23125    cx.executor().run_until_parked();
23126    let new_actions = cx
23127        .update_window(*workspace, |_, window, cx| {
23128            project.code_actions(&buffer, anchor..anchor, window, cx)
23129        })
23130        .unwrap()
23131        .await
23132        .unwrap();
23133    assert_eq!(
23134        actions, new_actions,
23135        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23136    );
23137}
23138
23139#[gpui::test]
23140async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23141    init_test(cx, |_| {});
23142
23143    let fs = FakeFs::new(cx.executor());
23144    let main_text = r#"fn main() {
23145println!("1");
23146println!("2");
23147println!("3");
23148println!("4");
23149println!("5");
23150}"#;
23151    let lib_text = "mod foo {}";
23152    fs.insert_tree(
23153        path!("/a"),
23154        json!({
23155            "lib.rs": lib_text,
23156            "main.rs": main_text,
23157        }),
23158    )
23159    .await;
23160
23161    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23162    let (workspace, cx) =
23163        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23164    let worktree_id = workspace.update(cx, |workspace, cx| {
23165        workspace.project().update(cx, |project, cx| {
23166            project.worktrees(cx).next().unwrap().read(cx).id()
23167        })
23168    });
23169
23170    let expected_ranges = vec![
23171        Point::new(0, 0)..Point::new(0, 0),
23172        Point::new(1, 0)..Point::new(1, 1),
23173        Point::new(2, 0)..Point::new(2, 2),
23174        Point::new(3, 0)..Point::new(3, 3),
23175    ];
23176
23177    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23178    let editor_1 = workspace
23179        .update_in(cx, |workspace, window, cx| {
23180            workspace.open_path(
23181                (worktree_id, "main.rs"),
23182                Some(pane_1.downgrade()),
23183                true,
23184                window,
23185                cx,
23186            )
23187        })
23188        .unwrap()
23189        .await
23190        .downcast::<Editor>()
23191        .unwrap();
23192    pane_1.update(cx, |pane, cx| {
23193        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23194        open_editor.update(cx, |editor, cx| {
23195            assert_eq!(
23196                editor.display_text(cx),
23197                main_text,
23198                "Original main.rs text on initial open",
23199            );
23200            assert_eq!(
23201                editor
23202                    .selections
23203                    .all::<Point>(cx)
23204                    .into_iter()
23205                    .map(|s| s.range())
23206                    .collect::<Vec<_>>(),
23207                vec![Point::zero()..Point::zero()],
23208                "Default selections on initial open",
23209            );
23210        })
23211    });
23212    editor_1.update_in(cx, |editor, window, cx| {
23213        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23214            s.select_ranges(expected_ranges.clone());
23215        });
23216    });
23217
23218    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23219        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23220    });
23221    let editor_2 = workspace
23222        .update_in(cx, |workspace, window, cx| {
23223            workspace.open_path(
23224                (worktree_id, "main.rs"),
23225                Some(pane_2.downgrade()),
23226                true,
23227                window,
23228                cx,
23229            )
23230        })
23231        .unwrap()
23232        .await
23233        .downcast::<Editor>()
23234        .unwrap();
23235    pane_2.update(cx, |pane, cx| {
23236        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23237        open_editor.update(cx, |editor, cx| {
23238            assert_eq!(
23239                editor.display_text(cx),
23240                main_text,
23241                "Original main.rs text on initial open in another panel",
23242            );
23243            assert_eq!(
23244                editor
23245                    .selections
23246                    .all::<Point>(cx)
23247                    .into_iter()
23248                    .map(|s| s.range())
23249                    .collect::<Vec<_>>(),
23250                vec![Point::zero()..Point::zero()],
23251                "Default selections on initial open in another panel",
23252            );
23253        })
23254    });
23255
23256    editor_2.update_in(cx, |editor, window, cx| {
23257        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23258    });
23259
23260    let _other_editor_1 = workspace
23261        .update_in(cx, |workspace, window, cx| {
23262            workspace.open_path(
23263                (worktree_id, "lib.rs"),
23264                Some(pane_1.downgrade()),
23265                true,
23266                window,
23267                cx,
23268            )
23269        })
23270        .unwrap()
23271        .await
23272        .downcast::<Editor>()
23273        .unwrap();
23274    pane_1
23275        .update_in(cx, |pane, window, cx| {
23276            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23277        })
23278        .await
23279        .unwrap();
23280    drop(editor_1);
23281    pane_1.update(cx, |pane, cx| {
23282        pane.active_item()
23283            .unwrap()
23284            .downcast::<Editor>()
23285            .unwrap()
23286            .update(cx, |editor, cx| {
23287                assert_eq!(
23288                    editor.display_text(cx),
23289                    lib_text,
23290                    "Other file should be open and active",
23291                );
23292            });
23293        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23294    });
23295
23296    let _other_editor_2 = workspace
23297        .update_in(cx, |workspace, window, cx| {
23298            workspace.open_path(
23299                (worktree_id, "lib.rs"),
23300                Some(pane_2.downgrade()),
23301                true,
23302                window,
23303                cx,
23304            )
23305        })
23306        .unwrap()
23307        .await
23308        .downcast::<Editor>()
23309        .unwrap();
23310    pane_2
23311        .update_in(cx, |pane, window, cx| {
23312            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23313        })
23314        .await
23315        .unwrap();
23316    drop(editor_2);
23317    pane_2.update(cx, |pane, cx| {
23318        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23319        open_editor.update(cx, |editor, cx| {
23320            assert_eq!(
23321                editor.display_text(cx),
23322                lib_text,
23323                "Other file should be open and active in another panel too",
23324            );
23325        });
23326        assert_eq!(
23327            pane.items().count(),
23328            1,
23329            "No other editors should be open in another pane",
23330        );
23331    });
23332
23333    let _editor_1_reopened = workspace
23334        .update_in(cx, |workspace, window, cx| {
23335            workspace.open_path(
23336                (worktree_id, "main.rs"),
23337                Some(pane_1.downgrade()),
23338                true,
23339                window,
23340                cx,
23341            )
23342        })
23343        .unwrap()
23344        .await
23345        .downcast::<Editor>()
23346        .unwrap();
23347    let _editor_2_reopened = workspace
23348        .update_in(cx, |workspace, window, cx| {
23349            workspace.open_path(
23350                (worktree_id, "main.rs"),
23351                Some(pane_2.downgrade()),
23352                true,
23353                window,
23354                cx,
23355            )
23356        })
23357        .unwrap()
23358        .await
23359        .downcast::<Editor>()
23360        .unwrap();
23361    pane_1.update(cx, |pane, cx| {
23362        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23363        open_editor.update(cx, |editor, cx| {
23364            assert_eq!(
23365                editor.display_text(cx),
23366                main_text,
23367                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23368            );
23369            assert_eq!(
23370                editor
23371                    .selections
23372                    .all::<Point>(cx)
23373                    .into_iter()
23374                    .map(|s| s.range())
23375                    .collect::<Vec<_>>(),
23376                expected_ranges,
23377                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23378            );
23379        })
23380    });
23381    pane_2.update(cx, |pane, cx| {
23382        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23383        open_editor.update(cx, |editor, cx| {
23384            assert_eq!(
23385                editor.display_text(cx),
23386                r#"fn main() {
23387⋯rintln!("1");
23388⋯intln!("2");
23389⋯ntln!("3");
23390println!("4");
23391println!("5");
23392}"#,
23393                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23394            );
23395            assert_eq!(
23396                editor
23397                    .selections
23398                    .all::<Point>(cx)
23399                    .into_iter()
23400                    .map(|s| s.range())
23401                    .collect::<Vec<_>>(),
23402                vec![Point::zero()..Point::zero()],
23403                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23404            );
23405        })
23406    });
23407}
23408
23409#[gpui::test]
23410async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23411    init_test(cx, |_| {});
23412
23413    let fs = FakeFs::new(cx.executor());
23414    let main_text = r#"fn main() {
23415println!("1");
23416println!("2");
23417println!("3");
23418println!("4");
23419println!("5");
23420}"#;
23421    let lib_text = "mod foo {}";
23422    fs.insert_tree(
23423        path!("/a"),
23424        json!({
23425            "lib.rs": lib_text,
23426            "main.rs": main_text,
23427        }),
23428    )
23429    .await;
23430
23431    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23432    let (workspace, cx) =
23433        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23434    let worktree_id = workspace.update(cx, |workspace, cx| {
23435        workspace.project().update(cx, |project, cx| {
23436            project.worktrees(cx).next().unwrap().read(cx).id()
23437        })
23438    });
23439
23440    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23441    let editor = workspace
23442        .update_in(cx, |workspace, window, cx| {
23443            workspace.open_path(
23444                (worktree_id, "main.rs"),
23445                Some(pane.downgrade()),
23446                true,
23447                window,
23448                cx,
23449            )
23450        })
23451        .unwrap()
23452        .await
23453        .downcast::<Editor>()
23454        .unwrap();
23455    pane.update(cx, |pane, cx| {
23456        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23457        open_editor.update(cx, |editor, cx| {
23458            assert_eq!(
23459                editor.display_text(cx),
23460                main_text,
23461                "Original main.rs text on initial open",
23462            );
23463        })
23464    });
23465    editor.update_in(cx, |editor, window, cx| {
23466        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23467    });
23468
23469    cx.update_global(|store: &mut SettingsStore, cx| {
23470        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23471            s.restore_on_file_reopen = Some(false);
23472        });
23473    });
23474    editor.update_in(cx, |editor, window, cx| {
23475        editor.fold_ranges(
23476            vec![
23477                Point::new(1, 0)..Point::new(1, 1),
23478                Point::new(2, 0)..Point::new(2, 2),
23479                Point::new(3, 0)..Point::new(3, 3),
23480            ],
23481            false,
23482            window,
23483            cx,
23484        );
23485    });
23486    pane.update_in(cx, |pane, window, cx| {
23487        pane.close_all_items(&CloseAllItems::default(), window, cx)
23488    })
23489    .await
23490    .unwrap();
23491    pane.update(cx, |pane, _| {
23492        assert!(pane.active_item().is_none());
23493    });
23494    cx.update_global(|store: &mut SettingsStore, cx| {
23495        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23496            s.restore_on_file_reopen = Some(true);
23497        });
23498    });
23499
23500    let _editor_reopened = workspace
23501        .update_in(cx, |workspace, window, cx| {
23502            workspace.open_path(
23503                (worktree_id, "main.rs"),
23504                Some(pane.downgrade()),
23505                true,
23506                window,
23507                cx,
23508            )
23509        })
23510        .unwrap()
23511        .await
23512        .downcast::<Editor>()
23513        .unwrap();
23514    pane.update(cx, |pane, cx| {
23515        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23516        open_editor.update(cx, |editor, cx| {
23517            assert_eq!(
23518                editor.display_text(cx),
23519                main_text,
23520                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23521            );
23522        })
23523    });
23524}
23525
23526#[gpui::test]
23527async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23528    struct EmptyModalView {
23529        focus_handle: gpui::FocusHandle,
23530    }
23531    impl EventEmitter<DismissEvent> for EmptyModalView {}
23532    impl Render for EmptyModalView {
23533        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23534            div()
23535        }
23536    }
23537    impl Focusable for EmptyModalView {
23538        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23539            self.focus_handle.clone()
23540        }
23541    }
23542    impl workspace::ModalView for EmptyModalView {}
23543    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23544        EmptyModalView {
23545            focus_handle: cx.focus_handle(),
23546        }
23547    }
23548
23549    init_test(cx, |_| {});
23550
23551    let fs = FakeFs::new(cx.executor());
23552    let project = Project::test(fs, [], cx).await;
23553    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23554    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23555    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23556    let editor = cx.new_window_entity(|window, cx| {
23557        Editor::new(
23558            EditorMode::full(),
23559            buffer,
23560            Some(project.clone()),
23561            window,
23562            cx,
23563        )
23564    });
23565    workspace
23566        .update(cx, |workspace, window, cx| {
23567            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23568        })
23569        .unwrap();
23570    editor.update_in(cx, |editor, window, cx| {
23571        editor.open_context_menu(&OpenContextMenu, window, cx);
23572        assert!(editor.mouse_context_menu.is_some());
23573    });
23574    workspace
23575        .update(cx, |workspace, window, cx| {
23576            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23577        })
23578        .unwrap();
23579    cx.read(|cx| {
23580        assert!(editor.read(cx).mouse_context_menu.is_none());
23581    });
23582}
23583
23584#[gpui::test]
23585async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23586    init_test(cx, |_| {});
23587
23588    let fs = FakeFs::new(cx.executor());
23589    fs.insert_file(path!("/file.html"), Default::default())
23590        .await;
23591
23592    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23593
23594    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23595    let html_language = Arc::new(Language::new(
23596        LanguageConfig {
23597            name: "HTML".into(),
23598            matcher: LanguageMatcher {
23599                path_suffixes: vec!["html".to_string()],
23600                ..LanguageMatcher::default()
23601            },
23602            brackets: BracketPairConfig {
23603                pairs: vec![BracketPair {
23604                    start: "<".into(),
23605                    end: ">".into(),
23606                    close: true,
23607                    ..Default::default()
23608                }],
23609                ..Default::default()
23610            },
23611            ..Default::default()
23612        },
23613        Some(tree_sitter_html::LANGUAGE.into()),
23614    ));
23615    language_registry.add(html_language);
23616    let mut fake_servers = language_registry.register_fake_lsp(
23617        "HTML",
23618        FakeLspAdapter {
23619            capabilities: lsp::ServerCapabilities {
23620                completion_provider: Some(lsp::CompletionOptions {
23621                    resolve_provider: Some(true),
23622                    ..Default::default()
23623                }),
23624                ..Default::default()
23625            },
23626            ..Default::default()
23627        },
23628    );
23629
23630    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23631    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23632
23633    let worktree_id = workspace
23634        .update(cx, |workspace, _window, cx| {
23635            workspace.project().update(cx, |project, cx| {
23636                project.worktrees(cx).next().unwrap().read(cx).id()
23637            })
23638        })
23639        .unwrap();
23640    project
23641        .update(cx, |project, cx| {
23642            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23643        })
23644        .await
23645        .unwrap();
23646    let editor = workspace
23647        .update(cx, |workspace, window, cx| {
23648            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23649        })
23650        .unwrap()
23651        .await
23652        .unwrap()
23653        .downcast::<Editor>()
23654        .unwrap();
23655
23656    let fake_server = fake_servers.next().await.unwrap();
23657    editor.update_in(cx, |editor, window, cx| {
23658        editor.set_text("<ad></ad>", window, cx);
23659        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23660            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23661        });
23662        let Some((buffer, _)) = editor
23663            .buffer
23664            .read(cx)
23665            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23666        else {
23667            panic!("Failed to get buffer for selection position");
23668        };
23669        let buffer = buffer.read(cx);
23670        let buffer_id = buffer.remote_id();
23671        let opening_range =
23672            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
23673        let closing_range =
23674            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
23675        let mut linked_ranges = HashMap::default();
23676        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23677        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23678    });
23679    let mut completion_handle =
23680        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23681            Ok(Some(lsp::CompletionResponse::Array(vec![
23682                lsp::CompletionItem {
23683                    label: "head".to_string(),
23684                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23685                        lsp::InsertReplaceEdit {
23686                            new_text: "head".to_string(),
23687                            insert: lsp::Range::new(
23688                                lsp::Position::new(0, 1),
23689                                lsp::Position::new(0, 3),
23690                            ),
23691                            replace: lsp::Range::new(
23692                                lsp::Position::new(0, 1),
23693                                lsp::Position::new(0, 3),
23694                            ),
23695                        },
23696                    )),
23697                    ..Default::default()
23698                },
23699            ])))
23700        });
23701    editor.update_in(cx, |editor, window, cx| {
23702        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23703    });
23704    cx.run_until_parked();
23705    completion_handle.next().await.unwrap();
23706    editor.update(cx, |editor, _| {
23707        assert!(
23708            editor.context_menu_visible(),
23709            "Completion menu should be visible"
23710        );
23711    });
23712    editor.update_in(cx, |editor, window, cx| {
23713        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23714    });
23715    cx.executor().run_until_parked();
23716    editor.update(cx, |editor, cx| {
23717        assert_eq!(editor.text(cx), "<head></head>");
23718    });
23719}
23720
23721#[gpui::test]
23722async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
23723    init_test(cx, |_| {});
23724
23725    let fs = FakeFs::new(cx.executor());
23726    fs.insert_tree(
23727        path!("/root"),
23728        json!({
23729            "a": {
23730                "main.rs": "fn main() {}",
23731            },
23732            "foo": {
23733                "bar": {
23734                    "external_file.rs": "pub mod external {}",
23735                }
23736            }
23737        }),
23738    )
23739    .await;
23740
23741    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
23742    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23743    language_registry.add(rust_lang());
23744    let _fake_servers = language_registry.register_fake_lsp(
23745        "Rust",
23746        FakeLspAdapter {
23747            ..FakeLspAdapter::default()
23748        },
23749    );
23750    let (workspace, cx) =
23751        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23752    let worktree_id = workspace.update(cx, |workspace, cx| {
23753        workspace.project().update(cx, |project, cx| {
23754            project.worktrees(cx).next().unwrap().read(cx).id()
23755        })
23756    });
23757
23758    let assert_language_servers_count =
23759        |expected: usize, context: &str, cx: &mut VisualTestContext| {
23760            project.update(cx, |project, cx| {
23761                let current = project
23762                    .lsp_store()
23763                    .read(cx)
23764                    .as_local()
23765                    .unwrap()
23766                    .language_servers
23767                    .len();
23768                assert_eq!(expected, current, "{context}");
23769            });
23770        };
23771
23772    assert_language_servers_count(
23773        0,
23774        "No servers should be running before any file is open",
23775        cx,
23776    );
23777    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23778    let main_editor = workspace
23779        .update_in(cx, |workspace, window, cx| {
23780            workspace.open_path(
23781                (worktree_id, "main.rs"),
23782                Some(pane.downgrade()),
23783                true,
23784                window,
23785                cx,
23786            )
23787        })
23788        .unwrap()
23789        .await
23790        .downcast::<Editor>()
23791        .unwrap();
23792    pane.update(cx, |pane, cx| {
23793        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23794        open_editor.update(cx, |editor, cx| {
23795            assert_eq!(
23796                editor.display_text(cx),
23797                "fn main() {}",
23798                "Original main.rs text on initial open",
23799            );
23800        });
23801        assert_eq!(open_editor, main_editor);
23802    });
23803    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
23804
23805    let external_editor = workspace
23806        .update_in(cx, |workspace, window, cx| {
23807            workspace.open_abs_path(
23808                PathBuf::from("/root/foo/bar/external_file.rs"),
23809                OpenOptions::default(),
23810                window,
23811                cx,
23812            )
23813        })
23814        .await
23815        .expect("opening external file")
23816        .downcast::<Editor>()
23817        .expect("downcasted external file's open element to editor");
23818    pane.update(cx, |pane, cx| {
23819        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23820        open_editor.update(cx, |editor, cx| {
23821            assert_eq!(
23822                editor.display_text(cx),
23823                "pub mod external {}",
23824                "External file is open now",
23825            );
23826        });
23827        assert_eq!(open_editor, external_editor);
23828    });
23829    assert_language_servers_count(
23830        1,
23831        "Second, external, *.rs file should join the existing server",
23832        cx,
23833    );
23834
23835    pane.update_in(cx, |pane, window, cx| {
23836        pane.close_active_item(&CloseActiveItem::default(), window, cx)
23837    })
23838    .await
23839    .unwrap();
23840    pane.update_in(cx, |pane, window, cx| {
23841        pane.navigate_backward(&Default::default(), window, cx);
23842    });
23843    cx.run_until_parked();
23844    pane.update(cx, |pane, cx| {
23845        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23846        open_editor.update(cx, |editor, cx| {
23847            assert_eq!(
23848                editor.display_text(cx),
23849                "pub mod external {}",
23850                "External file is open now",
23851            );
23852        });
23853    });
23854    assert_language_servers_count(
23855        1,
23856        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
23857        cx,
23858    );
23859
23860    cx.update(|_, cx| {
23861        workspace::reload(cx);
23862    });
23863    assert_language_servers_count(
23864        1,
23865        "After reloading the worktree with local and external files opened, only one project should be started",
23866        cx,
23867    );
23868}
23869
23870#[gpui::test]
23871async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
23872    init_test(cx, |_| {});
23873
23874    let mut cx = EditorTestContext::new(cx).await;
23875    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23876    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23877
23878    // test cursor move to start of each line on tab
23879    // for `if`, `elif`, `else`, `while`, `with` and `for`
23880    cx.set_state(indoc! {"
23881        def main():
23882        ˇ    for item in items:
23883        ˇ        while item.active:
23884        ˇ            if item.value > 10:
23885        ˇ                continue
23886        ˇ            elif item.value < 0:
23887        ˇ                break
23888        ˇ            else:
23889        ˇ                with item.context() as ctx:
23890        ˇ                    yield count
23891        ˇ        else:
23892        ˇ            log('while else')
23893        ˇ    else:
23894        ˇ        log('for else')
23895    "});
23896    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23897    cx.assert_editor_state(indoc! {"
23898        def main():
23899            ˇfor item in items:
23900                ˇwhile item.active:
23901                    ˇif item.value > 10:
23902                        ˇcontinue
23903                    ˇelif item.value < 0:
23904                        ˇbreak
23905                    ˇelse:
23906                        ˇwith item.context() as ctx:
23907                            ˇyield count
23908                ˇelse:
23909                    ˇlog('while else')
23910            ˇelse:
23911                ˇlog('for else')
23912    "});
23913    // test relative indent is preserved when tab
23914    // for `if`, `elif`, `else`, `while`, `with` and `for`
23915    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23916    cx.assert_editor_state(indoc! {"
23917        def main():
23918                ˇfor item in items:
23919                    ˇwhile item.active:
23920                        ˇif item.value > 10:
23921                            ˇcontinue
23922                        ˇelif item.value < 0:
23923                            ˇbreak
23924                        ˇelse:
23925                            ˇwith item.context() as ctx:
23926                                ˇyield count
23927                    ˇelse:
23928                        ˇlog('while else')
23929                ˇelse:
23930                    ˇlog('for else')
23931    "});
23932
23933    // test cursor move to start of each line on tab
23934    // for `try`, `except`, `else`, `finally`, `match` and `def`
23935    cx.set_state(indoc! {"
23936        def main():
23937        ˇ    try:
23938        ˇ        fetch()
23939        ˇ    except ValueError:
23940        ˇ        handle_error()
23941        ˇ    else:
23942        ˇ        match value:
23943        ˇ            case _:
23944        ˇ    finally:
23945        ˇ        def status():
23946        ˇ            return 0
23947    "});
23948    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23949    cx.assert_editor_state(indoc! {"
23950        def main():
23951            ˇtry:
23952                ˇfetch()
23953            ˇexcept ValueError:
23954                ˇhandle_error()
23955            ˇelse:
23956                ˇmatch value:
23957                    ˇcase _:
23958            ˇfinally:
23959                ˇdef status():
23960                    ˇreturn 0
23961    "});
23962    // test relative indent is preserved when tab
23963    // for `try`, `except`, `else`, `finally`, `match` and `def`
23964    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23965    cx.assert_editor_state(indoc! {"
23966        def main():
23967                ˇtry:
23968                    ˇfetch()
23969                ˇexcept ValueError:
23970                    ˇhandle_error()
23971                ˇelse:
23972                    ˇmatch value:
23973                        ˇcase _:
23974                ˇfinally:
23975                    ˇdef status():
23976                        ˇreturn 0
23977    "});
23978}
23979
23980#[gpui::test]
23981async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
23982    init_test(cx, |_| {});
23983
23984    let mut cx = EditorTestContext::new(cx).await;
23985    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23986    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23987
23988    // test `else` auto outdents when typed inside `if` block
23989    cx.set_state(indoc! {"
23990        def main():
23991            if i == 2:
23992                return
23993                ˇ
23994    "});
23995    cx.update_editor(|editor, window, cx| {
23996        editor.handle_input("else:", window, cx);
23997    });
23998    cx.assert_editor_state(indoc! {"
23999        def main():
24000            if i == 2:
24001                return
24002            else:ˇ
24003    "});
24004
24005    // test `except` auto outdents when typed inside `try` block
24006    cx.set_state(indoc! {"
24007        def main():
24008            try:
24009                i = 2
24010                ˇ
24011    "});
24012    cx.update_editor(|editor, window, cx| {
24013        editor.handle_input("except:", window, cx);
24014    });
24015    cx.assert_editor_state(indoc! {"
24016        def main():
24017            try:
24018                i = 2
24019            except:ˇ
24020    "});
24021
24022    // test `else` auto outdents when typed inside `except` block
24023    cx.set_state(indoc! {"
24024        def main():
24025            try:
24026                i = 2
24027            except:
24028                j = 2
24029                ˇ
24030    "});
24031    cx.update_editor(|editor, window, cx| {
24032        editor.handle_input("else:", window, cx);
24033    });
24034    cx.assert_editor_state(indoc! {"
24035        def main():
24036            try:
24037                i = 2
24038            except:
24039                j = 2
24040            else:ˇ
24041    "});
24042
24043    // test `finally` auto outdents when typed inside `else` block
24044    cx.set_state(indoc! {"
24045        def main():
24046            try:
24047                i = 2
24048            except:
24049                j = 2
24050            else:
24051                k = 2
24052                ˇ
24053    "});
24054    cx.update_editor(|editor, window, cx| {
24055        editor.handle_input("finally:", window, cx);
24056    });
24057    cx.assert_editor_state(indoc! {"
24058        def main():
24059            try:
24060                i = 2
24061            except:
24062                j = 2
24063            else:
24064                k = 2
24065            finally:ˇ
24066    "});
24067
24068    // test `else` does not outdents when typed inside `except` block right after for block
24069    cx.set_state(indoc! {"
24070        def main():
24071            try:
24072                i = 2
24073            except:
24074                for i in range(n):
24075                    pass
24076                ˇ
24077    "});
24078    cx.update_editor(|editor, window, cx| {
24079        editor.handle_input("else:", window, cx);
24080    });
24081    cx.assert_editor_state(indoc! {"
24082        def main():
24083            try:
24084                i = 2
24085            except:
24086                for i in range(n):
24087                    pass
24088                else:ˇ
24089    "});
24090
24091    // test `finally` auto outdents when typed inside `else` block right after for block
24092    cx.set_state(indoc! {"
24093        def main():
24094            try:
24095                i = 2
24096            except:
24097                j = 2
24098            else:
24099                for i in range(n):
24100                    pass
24101                ˇ
24102    "});
24103    cx.update_editor(|editor, window, cx| {
24104        editor.handle_input("finally:", window, cx);
24105    });
24106    cx.assert_editor_state(indoc! {"
24107        def main():
24108            try:
24109                i = 2
24110            except:
24111                j = 2
24112            else:
24113                for i in range(n):
24114                    pass
24115            finally:ˇ
24116    "});
24117
24118    // test `except` outdents to inner "try" block
24119    cx.set_state(indoc! {"
24120        def main():
24121            try:
24122                i = 2
24123                if i == 2:
24124                    try:
24125                        i = 3
24126                        ˇ
24127    "});
24128    cx.update_editor(|editor, window, cx| {
24129        editor.handle_input("except:", window, cx);
24130    });
24131    cx.assert_editor_state(indoc! {"
24132        def main():
24133            try:
24134                i = 2
24135                if i == 2:
24136                    try:
24137                        i = 3
24138                    except:ˇ
24139    "});
24140
24141    // test `except` outdents to outer "try" block
24142    cx.set_state(indoc! {"
24143        def main():
24144            try:
24145                i = 2
24146                if i == 2:
24147                    try:
24148                        i = 3
24149                ˇ
24150    "});
24151    cx.update_editor(|editor, window, cx| {
24152        editor.handle_input("except:", window, cx);
24153    });
24154    cx.assert_editor_state(indoc! {"
24155        def main():
24156            try:
24157                i = 2
24158                if i == 2:
24159                    try:
24160                        i = 3
24161            except:ˇ
24162    "});
24163
24164    // test `else` stays at correct indent when typed after `for` block
24165    cx.set_state(indoc! {"
24166        def main():
24167            for i in range(10):
24168                if i == 3:
24169                    break
24170            ˇ
24171    "});
24172    cx.update_editor(|editor, window, cx| {
24173        editor.handle_input("else:", window, cx);
24174    });
24175    cx.assert_editor_state(indoc! {"
24176        def main():
24177            for i in range(10):
24178                if i == 3:
24179                    break
24180            else:ˇ
24181    "});
24182
24183    // test does not outdent on typing after line with square brackets
24184    cx.set_state(indoc! {"
24185        def f() -> list[str]:
24186            ˇ
24187    "});
24188    cx.update_editor(|editor, window, cx| {
24189        editor.handle_input("a", window, cx);
24190    });
24191    cx.assert_editor_state(indoc! {"
24192        def f() -> list[str]:
2419324194    "});
24195
24196    // test does not outdent on typing : after case keyword
24197    cx.set_state(indoc! {"
24198        match 1:
24199            caseˇ
24200    "});
24201    cx.update_editor(|editor, window, cx| {
24202        editor.handle_input(":", window, cx);
24203    });
24204    cx.assert_editor_state(indoc! {"
24205        match 1:
24206            case:ˇ
24207    "});
24208}
24209
24210#[gpui::test]
24211async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24212    init_test(cx, |_| {});
24213    update_test_language_settings(cx, |settings| {
24214        settings.defaults.extend_comment_on_newline = Some(false);
24215    });
24216    let mut cx = EditorTestContext::new(cx).await;
24217    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24218    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24219
24220    // test correct indent after newline on comment
24221    cx.set_state(indoc! {"
24222        # COMMENT:ˇ
24223    "});
24224    cx.update_editor(|editor, window, cx| {
24225        editor.newline(&Newline, window, cx);
24226    });
24227    cx.assert_editor_state(indoc! {"
24228        # COMMENT:
24229        ˇ
24230    "});
24231
24232    // test correct indent after newline in brackets
24233    cx.set_state(indoc! {"
24234        {ˇ}
24235    "});
24236    cx.update_editor(|editor, window, cx| {
24237        editor.newline(&Newline, window, cx);
24238    });
24239    cx.run_until_parked();
24240    cx.assert_editor_state(indoc! {"
24241        {
24242            ˇ
24243        }
24244    "});
24245
24246    cx.set_state(indoc! {"
24247        (ˇ)
24248    "});
24249    cx.update_editor(|editor, window, cx| {
24250        editor.newline(&Newline, window, cx);
24251    });
24252    cx.run_until_parked();
24253    cx.assert_editor_state(indoc! {"
24254        (
24255            ˇ
24256        )
24257    "});
24258
24259    // do not indent after empty lists or dictionaries
24260    cx.set_state(indoc! {"
24261        a = []ˇ
24262    "});
24263    cx.update_editor(|editor, window, cx| {
24264        editor.newline(&Newline, window, cx);
24265    });
24266    cx.run_until_parked();
24267    cx.assert_editor_state(indoc! {"
24268        a = []
24269        ˇ
24270    "});
24271}
24272
24273#[gpui::test]
24274async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24275    init_test(cx, |_| {});
24276
24277    let mut cx = EditorTestContext::new(cx).await;
24278    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24279    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24280
24281    // test cursor move to start of each line on tab
24282    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24283    cx.set_state(indoc! {"
24284        function main() {
24285        ˇ    for item in $items; do
24286        ˇ        while [ -n \"$item\" ]; do
24287        ˇ            if [ \"$value\" -gt 10 ]; then
24288        ˇ                continue
24289        ˇ            elif [ \"$value\" -lt 0 ]; then
24290        ˇ                break
24291        ˇ            else
24292        ˇ                echo \"$item\"
24293        ˇ            fi
24294        ˇ        done
24295        ˇ    done
24296        ˇ}
24297    "});
24298    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24299    cx.assert_editor_state(indoc! {"
24300        function main() {
24301            ˇfor item in $items; do
24302                ˇwhile [ -n \"$item\" ]; do
24303                    ˇif [ \"$value\" -gt 10 ]; then
24304                        ˇcontinue
24305                    ˇelif [ \"$value\" -lt 0 ]; then
24306                        ˇbreak
24307                    ˇelse
24308                        ˇecho \"$item\"
24309                    ˇfi
24310                ˇdone
24311            ˇdone
24312        ˇ}
24313    "});
24314    // test relative indent is preserved when tab
24315    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24316    cx.assert_editor_state(indoc! {"
24317        function main() {
24318                ˇfor item in $items; do
24319                    ˇwhile [ -n \"$item\" ]; do
24320                        ˇif [ \"$value\" -gt 10 ]; then
24321                            ˇcontinue
24322                        ˇelif [ \"$value\" -lt 0 ]; then
24323                            ˇbreak
24324                        ˇelse
24325                            ˇecho \"$item\"
24326                        ˇfi
24327                    ˇdone
24328                ˇdone
24329            ˇ}
24330    "});
24331
24332    // test cursor move to start of each line on tab
24333    // for `case` statement with patterns
24334    cx.set_state(indoc! {"
24335        function handle() {
24336        ˇ    case \"$1\" in
24337        ˇ        start)
24338        ˇ            echo \"a\"
24339        ˇ            ;;
24340        ˇ        stop)
24341        ˇ            echo \"b\"
24342        ˇ            ;;
24343        ˇ        *)
24344        ˇ            echo \"c\"
24345        ˇ            ;;
24346        ˇ    esac
24347        ˇ}
24348    "});
24349    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24350    cx.assert_editor_state(indoc! {"
24351        function handle() {
24352            ˇcase \"$1\" in
24353                ˇstart)
24354                    ˇecho \"a\"
24355                    ˇ;;
24356                ˇstop)
24357                    ˇecho \"b\"
24358                    ˇ;;
24359                ˇ*)
24360                    ˇecho \"c\"
24361                    ˇ;;
24362            ˇesac
24363        ˇ}
24364    "});
24365}
24366
24367#[gpui::test]
24368async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24369    init_test(cx, |_| {});
24370
24371    let mut cx = EditorTestContext::new(cx).await;
24372    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24373    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24374
24375    // test indents on comment insert
24376    cx.set_state(indoc! {"
24377        function main() {
24378        ˇ    for item in $items; do
24379        ˇ        while [ -n \"$item\" ]; do
24380        ˇ            if [ \"$value\" -gt 10 ]; then
24381        ˇ                continue
24382        ˇ            elif [ \"$value\" -lt 0 ]; then
24383        ˇ                break
24384        ˇ            else
24385        ˇ                echo \"$item\"
24386        ˇ            fi
24387        ˇ        done
24388        ˇ    done
24389        ˇ}
24390    "});
24391    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24392    cx.assert_editor_state(indoc! {"
24393        function main() {
24394        #ˇ    for item in $items; do
24395        #ˇ        while [ -n \"$item\" ]; do
24396        #ˇ            if [ \"$value\" -gt 10 ]; then
24397        #ˇ                continue
24398        #ˇ            elif [ \"$value\" -lt 0 ]; then
24399        #ˇ                break
24400        #ˇ            else
24401        #ˇ                echo \"$item\"
24402        #ˇ            fi
24403        #ˇ        done
24404        #ˇ    done
24405        #ˇ}
24406    "});
24407}
24408
24409#[gpui::test]
24410async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24411    init_test(cx, |_| {});
24412
24413    let mut cx = EditorTestContext::new(cx).await;
24414    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24415    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24416
24417    // test `else` auto outdents when typed inside `if` block
24418    cx.set_state(indoc! {"
24419        if [ \"$1\" = \"test\" ]; then
24420            echo \"foo bar\"
24421            ˇ
24422    "});
24423    cx.update_editor(|editor, window, cx| {
24424        editor.handle_input("else", window, cx);
24425    });
24426    cx.assert_editor_state(indoc! {"
24427        if [ \"$1\" = \"test\" ]; then
24428            echo \"foo bar\"
24429        elseˇ
24430    "});
24431
24432    // test `elif` auto outdents when typed inside `if` block
24433    cx.set_state(indoc! {"
24434        if [ \"$1\" = \"test\" ]; then
24435            echo \"foo bar\"
24436            ˇ
24437    "});
24438    cx.update_editor(|editor, window, cx| {
24439        editor.handle_input("elif", window, cx);
24440    });
24441    cx.assert_editor_state(indoc! {"
24442        if [ \"$1\" = \"test\" ]; then
24443            echo \"foo bar\"
24444        elifˇ
24445    "});
24446
24447    // test `fi` auto outdents when typed inside `else` block
24448    cx.set_state(indoc! {"
24449        if [ \"$1\" = \"test\" ]; then
24450            echo \"foo bar\"
24451        else
24452            echo \"bar baz\"
24453            ˇ
24454    "});
24455    cx.update_editor(|editor, window, cx| {
24456        editor.handle_input("fi", window, cx);
24457    });
24458    cx.assert_editor_state(indoc! {"
24459        if [ \"$1\" = \"test\" ]; then
24460            echo \"foo bar\"
24461        else
24462            echo \"bar baz\"
24463        fiˇ
24464    "});
24465
24466    // test `done` auto outdents when typed inside `while` block
24467    cx.set_state(indoc! {"
24468        while read line; do
24469            echo \"$line\"
24470            ˇ
24471    "});
24472    cx.update_editor(|editor, window, cx| {
24473        editor.handle_input("done", window, cx);
24474    });
24475    cx.assert_editor_state(indoc! {"
24476        while read line; do
24477            echo \"$line\"
24478        doneˇ
24479    "});
24480
24481    // test `done` auto outdents when typed inside `for` block
24482    cx.set_state(indoc! {"
24483        for file in *.txt; do
24484            cat \"$file\"
24485            ˇ
24486    "});
24487    cx.update_editor(|editor, window, cx| {
24488        editor.handle_input("done", window, cx);
24489    });
24490    cx.assert_editor_state(indoc! {"
24491        for file in *.txt; do
24492            cat \"$file\"
24493        doneˇ
24494    "});
24495
24496    // test `esac` auto outdents when typed inside `case` block
24497    cx.set_state(indoc! {"
24498        case \"$1\" in
24499            start)
24500                echo \"foo bar\"
24501                ;;
24502            stop)
24503                echo \"bar baz\"
24504                ;;
24505            ˇ
24506    "});
24507    cx.update_editor(|editor, window, cx| {
24508        editor.handle_input("esac", window, cx);
24509    });
24510    cx.assert_editor_state(indoc! {"
24511        case \"$1\" in
24512            start)
24513                echo \"foo bar\"
24514                ;;
24515            stop)
24516                echo \"bar baz\"
24517                ;;
24518        esacˇ
24519    "});
24520
24521    // test `*)` auto outdents when typed inside `case` block
24522    cx.set_state(indoc! {"
24523        case \"$1\" in
24524            start)
24525                echo \"foo bar\"
24526                ;;
24527                ˇ
24528    "});
24529    cx.update_editor(|editor, window, cx| {
24530        editor.handle_input("*)", window, cx);
24531    });
24532    cx.assert_editor_state(indoc! {"
24533        case \"$1\" in
24534            start)
24535                echo \"foo bar\"
24536                ;;
24537            *)ˇ
24538    "});
24539
24540    // test `fi` outdents to correct level with nested if blocks
24541    cx.set_state(indoc! {"
24542        if [ \"$1\" = \"test\" ]; then
24543            echo \"outer if\"
24544            if [ \"$2\" = \"debug\" ]; then
24545                echo \"inner if\"
24546                ˇ
24547    "});
24548    cx.update_editor(|editor, window, cx| {
24549        editor.handle_input("fi", window, cx);
24550    });
24551    cx.assert_editor_state(indoc! {"
24552        if [ \"$1\" = \"test\" ]; then
24553            echo \"outer if\"
24554            if [ \"$2\" = \"debug\" ]; then
24555                echo \"inner if\"
24556            fiˇ
24557    "});
24558}
24559
24560#[gpui::test]
24561async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24562    init_test(cx, |_| {});
24563    update_test_language_settings(cx, |settings| {
24564        settings.defaults.extend_comment_on_newline = Some(false);
24565    });
24566    let mut cx = EditorTestContext::new(cx).await;
24567    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24568    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24569
24570    // test correct indent after newline on comment
24571    cx.set_state(indoc! {"
24572        # COMMENT:ˇ
24573    "});
24574    cx.update_editor(|editor, window, cx| {
24575        editor.newline(&Newline, window, cx);
24576    });
24577    cx.assert_editor_state(indoc! {"
24578        # COMMENT:
24579        ˇ
24580    "});
24581
24582    // test correct indent after newline after `then`
24583    cx.set_state(indoc! {"
24584
24585        if [ \"$1\" = \"test\" ]; thenˇ
24586    "});
24587    cx.update_editor(|editor, window, cx| {
24588        editor.newline(&Newline, window, cx);
24589    });
24590    cx.run_until_parked();
24591    cx.assert_editor_state(indoc! {"
24592
24593        if [ \"$1\" = \"test\" ]; then
24594            ˇ
24595    "});
24596
24597    // test correct indent after newline after `else`
24598    cx.set_state(indoc! {"
24599        if [ \"$1\" = \"test\" ]; then
24600        elseˇ
24601    "});
24602    cx.update_editor(|editor, window, cx| {
24603        editor.newline(&Newline, window, cx);
24604    });
24605    cx.run_until_parked();
24606    cx.assert_editor_state(indoc! {"
24607        if [ \"$1\" = \"test\" ]; then
24608        else
24609            ˇ
24610    "});
24611
24612    // test correct indent after newline after `elif`
24613    cx.set_state(indoc! {"
24614        if [ \"$1\" = \"test\" ]; then
24615        elifˇ
24616    "});
24617    cx.update_editor(|editor, window, cx| {
24618        editor.newline(&Newline, window, cx);
24619    });
24620    cx.run_until_parked();
24621    cx.assert_editor_state(indoc! {"
24622        if [ \"$1\" = \"test\" ]; then
24623        elif
24624            ˇ
24625    "});
24626
24627    // test correct indent after newline after `do`
24628    cx.set_state(indoc! {"
24629        for file in *.txt; doˇ
24630    "});
24631    cx.update_editor(|editor, window, cx| {
24632        editor.newline(&Newline, window, cx);
24633    });
24634    cx.run_until_parked();
24635    cx.assert_editor_state(indoc! {"
24636        for file in *.txt; do
24637            ˇ
24638    "});
24639
24640    // test correct indent after newline after case pattern
24641    cx.set_state(indoc! {"
24642        case \"$1\" in
24643            start)ˇ
24644    "});
24645    cx.update_editor(|editor, window, cx| {
24646        editor.newline(&Newline, window, cx);
24647    });
24648    cx.run_until_parked();
24649    cx.assert_editor_state(indoc! {"
24650        case \"$1\" in
24651            start)
24652                ˇ
24653    "});
24654
24655    // test correct indent after newline after case pattern
24656    cx.set_state(indoc! {"
24657        case \"$1\" in
24658            start)
24659                ;;
24660            *)ˇ
24661    "});
24662    cx.update_editor(|editor, window, cx| {
24663        editor.newline(&Newline, window, cx);
24664    });
24665    cx.run_until_parked();
24666    cx.assert_editor_state(indoc! {"
24667        case \"$1\" in
24668            start)
24669                ;;
24670            *)
24671                ˇ
24672    "});
24673
24674    // test correct indent after newline after function opening brace
24675    cx.set_state(indoc! {"
24676        function test() {ˇ}
24677    "});
24678    cx.update_editor(|editor, window, cx| {
24679        editor.newline(&Newline, window, cx);
24680    });
24681    cx.run_until_parked();
24682    cx.assert_editor_state(indoc! {"
24683        function test() {
24684            ˇ
24685        }
24686    "});
24687
24688    // test no extra indent after semicolon on same line
24689    cx.set_state(indoc! {"
24690        echo \"test\"24691    "});
24692    cx.update_editor(|editor, window, cx| {
24693        editor.newline(&Newline, window, cx);
24694    });
24695    cx.run_until_parked();
24696    cx.assert_editor_state(indoc! {"
24697        echo \"test\";
24698        ˇ
24699    "});
24700}
24701
24702fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24703    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24704    point..point
24705}
24706
24707#[track_caller]
24708fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24709    let (text, ranges) = marked_text_ranges(marked_text, true);
24710    assert_eq!(editor.text(cx), text);
24711    assert_eq!(
24712        editor.selections.ranges(cx),
24713        ranges,
24714        "Assert selections are {}",
24715        marked_text
24716    );
24717}
24718
24719pub fn handle_signature_help_request(
24720    cx: &mut EditorLspTestContext,
24721    mocked_response: lsp::SignatureHelp,
24722) -> impl Future<Output = ()> + use<> {
24723    let mut request =
24724        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
24725            let mocked_response = mocked_response.clone();
24726            async move { Ok(Some(mocked_response)) }
24727        });
24728
24729    async move {
24730        request.next().await;
24731    }
24732}
24733
24734#[track_caller]
24735pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
24736    cx.update_editor(|editor, _, _| {
24737        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
24738            let entries = menu.entries.borrow();
24739            let entries = entries
24740                .iter()
24741                .map(|entry| entry.string.as_str())
24742                .collect::<Vec<_>>();
24743            assert_eq!(entries, expected);
24744        } else {
24745            panic!("Expected completions menu");
24746        }
24747    });
24748}
24749
24750/// Handle completion request passing a marked string specifying where the completion
24751/// should be triggered from using '|' character, what range should be replaced, and what completions
24752/// should be returned using '<' and '>' to delimit the range.
24753///
24754/// Also see `handle_completion_request_with_insert_and_replace`.
24755#[track_caller]
24756pub fn handle_completion_request(
24757    marked_string: &str,
24758    completions: Vec<&'static str>,
24759    is_incomplete: bool,
24760    counter: Arc<AtomicUsize>,
24761    cx: &mut EditorLspTestContext,
24762) -> impl Future<Output = ()> {
24763    let complete_from_marker: TextRangeMarker = '|'.into();
24764    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24765    let (_, mut marked_ranges) = marked_text_ranges_by(
24766        marked_string,
24767        vec![complete_from_marker.clone(), replace_range_marker.clone()],
24768    );
24769
24770    let complete_from_position =
24771        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24772    let replace_range =
24773        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24774
24775    let mut request =
24776        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24777            let completions = completions.clone();
24778            counter.fetch_add(1, atomic::Ordering::Release);
24779            async move {
24780                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24781                assert_eq!(
24782                    params.text_document_position.position,
24783                    complete_from_position
24784                );
24785                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
24786                    is_incomplete,
24787                    item_defaults: None,
24788                    items: completions
24789                        .iter()
24790                        .map(|completion_text| lsp::CompletionItem {
24791                            label: completion_text.to_string(),
24792                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
24793                                range: replace_range,
24794                                new_text: completion_text.to_string(),
24795                            })),
24796                            ..Default::default()
24797                        })
24798                        .collect(),
24799                })))
24800            }
24801        });
24802
24803    async move {
24804        request.next().await;
24805    }
24806}
24807
24808/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
24809/// given instead, which also contains an `insert` range.
24810///
24811/// This function uses markers to define ranges:
24812/// - `|` marks the cursor position
24813/// - `<>` marks the replace range
24814/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
24815pub fn handle_completion_request_with_insert_and_replace(
24816    cx: &mut EditorLspTestContext,
24817    marked_string: &str,
24818    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
24819    counter: Arc<AtomicUsize>,
24820) -> impl Future<Output = ()> {
24821    let complete_from_marker: TextRangeMarker = '|'.into();
24822    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24823    let insert_range_marker: TextRangeMarker = ('{', '}').into();
24824
24825    let (_, mut marked_ranges) = marked_text_ranges_by(
24826        marked_string,
24827        vec![
24828            complete_from_marker.clone(),
24829            replace_range_marker.clone(),
24830            insert_range_marker.clone(),
24831        ],
24832    );
24833
24834    let complete_from_position =
24835        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24836    let replace_range =
24837        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24838
24839    let insert_range = match marked_ranges.remove(&insert_range_marker) {
24840        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
24841        _ => lsp::Range {
24842            start: replace_range.start,
24843            end: complete_from_position,
24844        },
24845    };
24846
24847    let mut request =
24848        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24849            let completions = completions.clone();
24850            counter.fetch_add(1, atomic::Ordering::Release);
24851            async move {
24852                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24853                assert_eq!(
24854                    params.text_document_position.position, complete_from_position,
24855                    "marker `|` position doesn't match",
24856                );
24857                Ok(Some(lsp::CompletionResponse::Array(
24858                    completions
24859                        .iter()
24860                        .map(|(label, new_text)| lsp::CompletionItem {
24861                            label: label.to_string(),
24862                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24863                                lsp::InsertReplaceEdit {
24864                                    insert: insert_range,
24865                                    replace: replace_range,
24866                                    new_text: new_text.to_string(),
24867                                },
24868                            )),
24869                            ..Default::default()
24870                        })
24871                        .collect(),
24872                )))
24873            }
24874        });
24875
24876    async move {
24877        request.next().await;
24878    }
24879}
24880
24881fn handle_resolve_completion_request(
24882    cx: &mut EditorLspTestContext,
24883    edits: Option<Vec<(&'static str, &'static str)>>,
24884) -> impl Future<Output = ()> {
24885    let edits = edits.map(|edits| {
24886        edits
24887            .iter()
24888            .map(|(marked_string, new_text)| {
24889                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
24890                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
24891                lsp::TextEdit::new(replace_range, new_text.to_string())
24892            })
24893            .collect::<Vec<_>>()
24894    });
24895
24896    let mut request =
24897        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
24898            let edits = edits.clone();
24899            async move {
24900                Ok(lsp::CompletionItem {
24901                    additional_text_edits: edits,
24902                    ..Default::default()
24903                })
24904            }
24905        });
24906
24907    async move {
24908        request.next().await;
24909    }
24910}
24911
24912pub(crate) fn update_test_language_settings(
24913    cx: &mut TestAppContext,
24914    f: impl Fn(&mut AllLanguageSettingsContent),
24915) {
24916    cx.update(|cx| {
24917        SettingsStore::update_global(cx, |store, cx| {
24918            store.update_user_settings::<AllLanguageSettings>(cx, f);
24919        });
24920    });
24921}
24922
24923pub(crate) fn update_test_project_settings(
24924    cx: &mut TestAppContext,
24925    f: impl Fn(&mut ProjectSettings),
24926) {
24927    cx.update(|cx| {
24928        SettingsStore::update_global(cx, |store, cx| {
24929            store.update_user_settings::<ProjectSettings>(cx, f);
24930        });
24931    });
24932}
24933
24934pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
24935    cx.update(|cx| {
24936        assets::Assets.load_test_fonts(cx);
24937        let store = SettingsStore::test(cx);
24938        cx.set_global(store);
24939        theme::init(theme::LoadThemes::JustBase, cx);
24940        release_channel::init(SemanticVersion::default(), cx);
24941        client::init_settings(cx);
24942        language::init(cx);
24943        Project::init_settings(cx);
24944        workspace::init_settings(cx);
24945        crate::init(cx);
24946    });
24947    zlog::init_test();
24948    update_test_language_settings(cx, f);
24949}
24950
24951#[track_caller]
24952fn assert_hunk_revert(
24953    not_reverted_text_with_selections: &str,
24954    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
24955    expected_reverted_text_with_selections: &str,
24956    base_text: &str,
24957    cx: &mut EditorLspTestContext,
24958) {
24959    cx.set_state(not_reverted_text_with_selections);
24960    cx.set_head_text(base_text);
24961    cx.executor().run_until_parked();
24962
24963    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
24964        let snapshot = editor.snapshot(window, cx);
24965        let reverted_hunk_statuses = snapshot
24966            .buffer_snapshot
24967            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
24968            .map(|hunk| hunk.status().kind)
24969            .collect::<Vec<_>>();
24970
24971        editor.git_restore(&Default::default(), window, cx);
24972        reverted_hunk_statuses
24973    });
24974    cx.executor().run_until_parked();
24975    cx.assert_editor_state(expected_reverted_text_with_selections);
24976    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
24977}
24978
24979#[gpui::test(iterations = 10)]
24980async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
24981    init_test(cx, |_| {});
24982
24983    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
24984    let counter = diagnostic_requests.clone();
24985
24986    let fs = FakeFs::new(cx.executor());
24987    fs.insert_tree(
24988        path!("/a"),
24989        json!({
24990            "first.rs": "fn main() { let a = 5; }",
24991            "second.rs": "// Test file",
24992        }),
24993    )
24994    .await;
24995
24996    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24997    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24998    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24999
25000    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25001    language_registry.add(rust_lang());
25002    let mut fake_servers = language_registry.register_fake_lsp(
25003        "Rust",
25004        FakeLspAdapter {
25005            capabilities: lsp::ServerCapabilities {
25006                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25007                    lsp::DiagnosticOptions {
25008                        identifier: None,
25009                        inter_file_dependencies: true,
25010                        workspace_diagnostics: true,
25011                        work_done_progress_options: Default::default(),
25012                    },
25013                )),
25014                ..Default::default()
25015            },
25016            ..Default::default()
25017        },
25018    );
25019
25020    let editor = workspace
25021        .update(cx, |workspace, window, cx| {
25022            workspace.open_abs_path(
25023                PathBuf::from(path!("/a/first.rs")),
25024                OpenOptions::default(),
25025                window,
25026                cx,
25027            )
25028        })
25029        .unwrap()
25030        .await
25031        .unwrap()
25032        .downcast::<Editor>()
25033        .unwrap();
25034    let fake_server = fake_servers.next().await.unwrap();
25035    let server_id = fake_server.server.server_id();
25036    let mut first_request = fake_server
25037        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25038            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25039            let result_id = Some(new_result_id.to_string());
25040            assert_eq!(
25041                params.text_document.uri,
25042                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25043            );
25044            async move {
25045                Ok(lsp::DocumentDiagnosticReportResult::Report(
25046                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25047                        related_documents: None,
25048                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25049                            items: Vec::new(),
25050                            result_id,
25051                        },
25052                    }),
25053                ))
25054            }
25055        });
25056
25057    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25058        project.update(cx, |project, cx| {
25059            let buffer_id = editor
25060                .read(cx)
25061                .buffer()
25062                .read(cx)
25063                .as_singleton()
25064                .expect("created a singleton buffer")
25065                .read(cx)
25066                .remote_id();
25067            let buffer_result_id = project
25068                .lsp_store()
25069                .read(cx)
25070                .result_id(server_id, buffer_id, cx);
25071            assert_eq!(expected, buffer_result_id);
25072        });
25073    };
25074
25075    ensure_result_id(None, cx);
25076    cx.executor().advance_clock(Duration::from_millis(60));
25077    cx.executor().run_until_parked();
25078    assert_eq!(
25079        diagnostic_requests.load(atomic::Ordering::Acquire),
25080        1,
25081        "Opening file should trigger diagnostic request"
25082    );
25083    first_request
25084        .next()
25085        .await
25086        .expect("should have sent the first diagnostics pull request");
25087    ensure_result_id(Some("1".to_string()), cx);
25088
25089    // Editing should trigger diagnostics
25090    editor.update_in(cx, |editor, window, cx| {
25091        editor.handle_input("2", window, cx)
25092    });
25093    cx.executor().advance_clock(Duration::from_millis(60));
25094    cx.executor().run_until_parked();
25095    assert_eq!(
25096        diagnostic_requests.load(atomic::Ordering::Acquire),
25097        2,
25098        "Editing should trigger diagnostic request"
25099    );
25100    ensure_result_id(Some("2".to_string()), cx);
25101
25102    // Moving cursor should not trigger diagnostic request
25103    editor.update_in(cx, |editor, window, cx| {
25104        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25105            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25106        });
25107    });
25108    cx.executor().advance_clock(Duration::from_millis(60));
25109    cx.executor().run_until_parked();
25110    assert_eq!(
25111        diagnostic_requests.load(atomic::Ordering::Acquire),
25112        2,
25113        "Cursor movement should not trigger diagnostic request"
25114    );
25115    ensure_result_id(Some("2".to_string()), cx);
25116    // Multiple rapid edits should be debounced
25117    for _ in 0..5 {
25118        editor.update_in(cx, |editor, window, cx| {
25119            editor.handle_input("x", window, cx)
25120        });
25121    }
25122    cx.executor().advance_clock(Duration::from_millis(60));
25123    cx.executor().run_until_parked();
25124
25125    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25126    assert!(
25127        final_requests <= 4,
25128        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25129    );
25130    ensure_result_id(Some(final_requests.to_string()), cx);
25131}
25132
25133#[gpui::test]
25134async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25135    // Regression test for issue #11671
25136    // Previously, adding a cursor after moving multiple cursors would reset
25137    // the cursor count instead of adding to the existing cursors.
25138    init_test(cx, |_| {});
25139    let mut cx = EditorTestContext::new(cx).await;
25140
25141    // Create a simple buffer with cursor at start
25142    cx.set_state(indoc! {"
25143        ˇaaaa
25144        bbbb
25145        cccc
25146        dddd
25147        eeee
25148        ffff
25149        gggg
25150        hhhh"});
25151
25152    // Add 2 cursors below (so we have 3 total)
25153    cx.update_editor(|editor, window, cx| {
25154        editor.add_selection_below(&Default::default(), window, cx);
25155        editor.add_selection_below(&Default::default(), window, cx);
25156    });
25157
25158    // Verify we have 3 cursors
25159    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25160    assert_eq!(
25161        initial_count, 3,
25162        "Should have 3 cursors after adding 2 below"
25163    );
25164
25165    // Move down one line
25166    cx.update_editor(|editor, window, cx| {
25167        editor.move_down(&MoveDown, window, cx);
25168    });
25169
25170    // Add another cursor below
25171    cx.update_editor(|editor, window, cx| {
25172        editor.add_selection_below(&Default::default(), window, cx);
25173    });
25174
25175    // Should now have 4 cursors (3 original + 1 new)
25176    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25177    assert_eq!(
25178        final_count, 4,
25179        "Should have 4 cursors after moving and adding another"
25180    );
25181}
25182
25183#[gpui::test(iterations = 10)]
25184async fn test_document_colors(cx: &mut TestAppContext) {
25185    let expected_color = Rgba {
25186        r: 0.33,
25187        g: 0.33,
25188        b: 0.33,
25189        a: 0.33,
25190    };
25191
25192    init_test(cx, |_| {});
25193
25194    let fs = FakeFs::new(cx.executor());
25195    fs.insert_tree(
25196        path!("/a"),
25197        json!({
25198            "first.rs": "fn main() { let a = 5; }",
25199        }),
25200    )
25201    .await;
25202
25203    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25204    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25205    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25206
25207    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25208    language_registry.add(rust_lang());
25209    let mut fake_servers = language_registry.register_fake_lsp(
25210        "Rust",
25211        FakeLspAdapter {
25212            capabilities: lsp::ServerCapabilities {
25213                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25214                ..lsp::ServerCapabilities::default()
25215            },
25216            name: "rust-analyzer",
25217            ..FakeLspAdapter::default()
25218        },
25219    );
25220    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25221        "Rust",
25222        FakeLspAdapter {
25223            capabilities: lsp::ServerCapabilities {
25224                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25225                ..lsp::ServerCapabilities::default()
25226            },
25227            name: "not-rust-analyzer",
25228            ..FakeLspAdapter::default()
25229        },
25230    );
25231
25232    let editor = workspace
25233        .update(cx, |workspace, window, cx| {
25234            workspace.open_abs_path(
25235                PathBuf::from(path!("/a/first.rs")),
25236                OpenOptions::default(),
25237                window,
25238                cx,
25239            )
25240        })
25241        .unwrap()
25242        .await
25243        .unwrap()
25244        .downcast::<Editor>()
25245        .unwrap();
25246    let fake_language_server = fake_servers.next().await.unwrap();
25247    let fake_language_server_without_capabilities =
25248        fake_servers_without_capabilities.next().await.unwrap();
25249    let requests_made = Arc::new(AtomicUsize::new(0));
25250    let closure_requests_made = Arc::clone(&requests_made);
25251    let mut color_request_handle = fake_language_server
25252        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25253            let requests_made = Arc::clone(&closure_requests_made);
25254            async move {
25255                assert_eq!(
25256                    params.text_document.uri,
25257                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25258                );
25259                requests_made.fetch_add(1, atomic::Ordering::Release);
25260                Ok(vec![
25261                    lsp::ColorInformation {
25262                        range: lsp::Range {
25263                            start: lsp::Position {
25264                                line: 0,
25265                                character: 0,
25266                            },
25267                            end: lsp::Position {
25268                                line: 0,
25269                                character: 1,
25270                            },
25271                        },
25272                        color: lsp::Color {
25273                            red: 0.33,
25274                            green: 0.33,
25275                            blue: 0.33,
25276                            alpha: 0.33,
25277                        },
25278                    },
25279                    lsp::ColorInformation {
25280                        range: lsp::Range {
25281                            start: lsp::Position {
25282                                line: 0,
25283                                character: 0,
25284                            },
25285                            end: lsp::Position {
25286                                line: 0,
25287                                character: 1,
25288                            },
25289                        },
25290                        color: lsp::Color {
25291                            red: 0.33,
25292                            green: 0.33,
25293                            blue: 0.33,
25294                            alpha: 0.33,
25295                        },
25296                    },
25297                ])
25298            }
25299        });
25300
25301    let _handle = fake_language_server_without_capabilities
25302        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25303            panic!("Should not be called");
25304        });
25305    cx.executor().advance_clock(Duration::from_millis(100));
25306    color_request_handle.next().await.unwrap();
25307    cx.run_until_parked();
25308    assert_eq!(
25309        1,
25310        requests_made.load(atomic::Ordering::Acquire),
25311        "Should query for colors once per editor open"
25312    );
25313    editor.update_in(cx, |editor, _, cx| {
25314        assert_eq!(
25315            vec![expected_color],
25316            extract_color_inlays(editor, cx),
25317            "Should have an initial inlay"
25318        );
25319    });
25320
25321    // opening another file in a split should not influence the LSP query counter
25322    workspace
25323        .update(cx, |workspace, window, cx| {
25324            assert_eq!(
25325                workspace.panes().len(),
25326                1,
25327                "Should have one pane with one editor"
25328            );
25329            workspace.move_item_to_pane_in_direction(
25330                &MoveItemToPaneInDirection {
25331                    direction: SplitDirection::Right,
25332                    focus: false,
25333                    clone: true,
25334                },
25335                window,
25336                cx,
25337            );
25338        })
25339        .unwrap();
25340    cx.run_until_parked();
25341    workspace
25342        .update(cx, |workspace, _, cx| {
25343            let panes = workspace.panes();
25344            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25345            for pane in panes {
25346                let editor = pane
25347                    .read(cx)
25348                    .active_item()
25349                    .and_then(|item| item.downcast::<Editor>())
25350                    .expect("Should have opened an editor in each split");
25351                let editor_file = editor
25352                    .read(cx)
25353                    .buffer()
25354                    .read(cx)
25355                    .as_singleton()
25356                    .expect("test deals with singleton buffers")
25357                    .read(cx)
25358                    .file()
25359                    .expect("test buffese should have a file")
25360                    .path();
25361                assert_eq!(
25362                    editor_file.as_ref(),
25363                    Path::new("first.rs"),
25364                    "Both editors should be opened for the same file"
25365                )
25366            }
25367        })
25368        .unwrap();
25369
25370    cx.executor().advance_clock(Duration::from_millis(500));
25371    let save = editor.update_in(cx, |editor, window, cx| {
25372        editor.move_to_end(&MoveToEnd, window, cx);
25373        editor.handle_input("dirty", window, cx);
25374        editor.save(
25375            SaveOptions {
25376                format: true,
25377                autosave: true,
25378            },
25379            project.clone(),
25380            window,
25381            cx,
25382        )
25383    });
25384    save.await.unwrap();
25385
25386    color_request_handle.next().await.unwrap();
25387    cx.run_until_parked();
25388    assert_eq!(
25389        3,
25390        requests_made.load(atomic::Ordering::Acquire),
25391        "Should query for colors once per save and once per formatting after save"
25392    );
25393
25394    drop(editor);
25395    let close = workspace
25396        .update(cx, |workspace, window, cx| {
25397            workspace.active_pane().update(cx, |pane, cx| {
25398                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25399            })
25400        })
25401        .unwrap();
25402    close.await.unwrap();
25403    let close = workspace
25404        .update(cx, |workspace, window, cx| {
25405            workspace.active_pane().update(cx, |pane, cx| {
25406                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25407            })
25408        })
25409        .unwrap();
25410    close.await.unwrap();
25411    assert_eq!(
25412        3,
25413        requests_made.load(atomic::Ordering::Acquire),
25414        "After saving and closing all editors, no extra requests should be made"
25415    );
25416    workspace
25417        .update(cx, |workspace, _, cx| {
25418            assert!(
25419                workspace.active_item(cx).is_none(),
25420                "Should close all editors"
25421            )
25422        })
25423        .unwrap();
25424
25425    workspace
25426        .update(cx, |workspace, window, cx| {
25427            workspace.active_pane().update(cx, |pane, cx| {
25428                pane.navigate_backward(&Default::default(), window, cx);
25429            })
25430        })
25431        .unwrap();
25432    cx.executor().advance_clock(Duration::from_millis(100));
25433    cx.run_until_parked();
25434    let editor = workspace
25435        .update(cx, |workspace, _, cx| {
25436            workspace
25437                .active_item(cx)
25438                .expect("Should have reopened the editor again after navigating back")
25439                .downcast::<Editor>()
25440                .expect("Should be an editor")
25441        })
25442        .unwrap();
25443    color_request_handle.next().await.unwrap();
25444    assert_eq!(
25445        3,
25446        requests_made.load(atomic::Ordering::Acquire),
25447        "Cache should be reused on buffer close and reopen"
25448    );
25449    editor.update(cx, |editor, cx| {
25450        assert_eq!(
25451            vec![expected_color],
25452            extract_color_inlays(editor, cx),
25453            "Should have an initial inlay"
25454        );
25455    });
25456}
25457
25458#[gpui::test]
25459async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25460    init_test(cx, |_| {});
25461    let (editor, cx) = cx.add_window_view(Editor::single_line);
25462    editor.update_in(cx, |editor, window, cx| {
25463        editor.set_text("oops\n\nwow\n", window, cx)
25464    });
25465    cx.run_until_parked();
25466    editor.update(cx, |editor, cx| {
25467        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25468    });
25469    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25470    cx.run_until_parked();
25471    editor.update(cx, |editor, cx| {
25472        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25473    });
25474}
25475
25476#[gpui::test]
25477async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25478    init_test(cx, |_| {});
25479
25480    cx.update(|cx| {
25481        register_project_item::<Editor>(cx);
25482    });
25483
25484    let fs = FakeFs::new(cx.executor());
25485    fs.insert_tree("/root1", json!({})).await;
25486    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25487        .await;
25488
25489    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25490    let (workspace, cx) =
25491        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25492
25493    let worktree_id = project.update(cx, |project, cx| {
25494        project.worktrees(cx).next().unwrap().read(cx).id()
25495    });
25496
25497    let handle = workspace
25498        .update_in(cx, |workspace, window, cx| {
25499            let project_path = (worktree_id, "one.pdf");
25500            workspace.open_path(project_path, None, true, window, cx)
25501        })
25502        .await
25503        .unwrap();
25504
25505    assert_eq!(
25506        handle.to_any().entity_type(),
25507        TypeId::of::<InvalidBufferView>()
25508    );
25509}
25510
25511#[gpui::test]
25512async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25513    init_test(cx, |_| {});
25514
25515    let language = Arc::new(Language::new(
25516        LanguageConfig::default(),
25517        Some(tree_sitter_rust::LANGUAGE.into()),
25518    ));
25519
25520    // Test hierarchical sibling navigation
25521    let text = r#"
25522        fn outer() {
25523            if condition {
25524                let a = 1;
25525            }
25526            let b = 2;
25527        }
25528
25529        fn another() {
25530            let c = 3;
25531        }
25532    "#;
25533
25534    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25535    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25536    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25537
25538    // Wait for parsing to complete
25539    editor
25540        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25541        .await;
25542
25543    editor.update_in(cx, |editor, window, cx| {
25544        // Start by selecting "let a = 1;" inside the if block
25545        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25546            s.select_display_ranges([
25547                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25548            ]);
25549        });
25550
25551        let initial_selection = editor.selections.display_ranges(cx);
25552        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25553
25554        // Test select next sibling - should move up levels to find the next sibling
25555        // Since "let a = 1;" has no siblings in the if block, it should move up
25556        // to find "let b = 2;" which is a sibling of the if block
25557        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25558        let next_selection = editor.selections.display_ranges(cx);
25559
25560        // Should have a selection and it should be different from the initial
25561        assert_eq!(
25562            next_selection.len(),
25563            1,
25564            "Should have one selection after next"
25565        );
25566        assert_ne!(
25567            next_selection[0], initial_selection[0],
25568            "Next sibling selection should be different"
25569        );
25570
25571        // Test hierarchical navigation by going to the end of the current function
25572        // and trying to navigate to the next function
25573        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25574            s.select_display_ranges([
25575                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25576            ]);
25577        });
25578
25579        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25580        let function_next_selection = editor.selections.display_ranges(cx);
25581
25582        // Should move to the next function
25583        assert_eq!(
25584            function_next_selection.len(),
25585            1,
25586            "Should have one selection after function next"
25587        );
25588
25589        // Test select previous sibling navigation
25590        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25591        let prev_selection = editor.selections.display_ranges(cx);
25592
25593        // Should have a selection and it should be different
25594        assert_eq!(
25595            prev_selection.len(),
25596            1,
25597            "Should have one selection after prev"
25598        );
25599        assert_ne!(
25600            prev_selection[0], function_next_selection[0],
25601            "Previous sibling selection should be different from next"
25602        );
25603    });
25604}
25605
25606#[track_caller]
25607fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
25608    editor
25609        .all_inlays(cx)
25610        .into_iter()
25611        .filter_map(|inlay| inlay.get_color())
25612        .map(Rgba::from)
25613        .collect()
25614}