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_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8721    init_test(cx, |_| {});
 8722
 8723    let language = Arc::new(Language::new(
 8724        LanguageConfig::default(),
 8725        Some(tree_sitter_rust::LANGUAGE.into()),
 8726    ));
 8727
 8728    let text = r#"
 8729        use mod1::mod2::{mod3, mod4};
 8730
 8731        fn fn_1(param1: bool, param2: &str) {
 8732            let var1 = "hello world";
 8733        }
 8734    "#
 8735    .unindent();
 8736
 8737    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8738    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8739    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8740
 8741    editor
 8742        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8743        .await;
 8744
 8745    // Test 1: Cursor on a letter of a string word
 8746    editor.update_in(cx, |editor, window, cx| {
 8747        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8748            s.select_display_ranges([
 8749                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8750            ]);
 8751        });
 8752    });
 8753    editor.update_in(cx, |editor, window, cx| {
 8754        assert_text_with_selections(
 8755            editor,
 8756            indoc! {r#"
 8757                use mod1::mod2::{mod3, mod4};
 8758
 8759                fn fn_1(param1: bool, param2: &str) {
 8760                    let var1 = "hˇello world";
 8761                }
 8762            "#},
 8763            cx,
 8764        );
 8765        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8766        assert_text_with_selections(
 8767            editor,
 8768            indoc! {r#"
 8769                use mod1::mod2::{mod3, mod4};
 8770
 8771                fn fn_1(param1: bool, param2: &str) {
 8772                    let var1 = "«ˇhello» world";
 8773                }
 8774            "#},
 8775            cx,
 8776        );
 8777    });
 8778
 8779    // Test 2: Partial selection within a word
 8780    editor.update_in(cx, |editor, window, cx| {
 8781        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8782            s.select_display_ranges([
 8783                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 8784            ]);
 8785        });
 8786    });
 8787    editor.update_in(cx, |editor, window, cx| {
 8788        assert_text_with_selections(
 8789            editor,
 8790            indoc! {r#"
 8791                use mod1::mod2::{mod3, mod4};
 8792
 8793                fn fn_1(param1: bool, param2: &str) {
 8794                    let var1 = "h«elˇ»lo world";
 8795                }
 8796            "#},
 8797            cx,
 8798        );
 8799        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8800        assert_text_with_selections(
 8801            editor,
 8802            indoc! {r#"
 8803                use mod1::mod2::{mod3, mod4};
 8804
 8805                fn fn_1(param1: bool, param2: &str) {
 8806                    let var1 = "«ˇhello» world";
 8807                }
 8808            "#},
 8809            cx,
 8810        );
 8811    });
 8812
 8813    // Test 3: Complete word already selected
 8814    editor.update_in(cx, |editor, window, cx| {
 8815        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8816            s.select_display_ranges([
 8817                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 8818            ]);
 8819        });
 8820    });
 8821    editor.update_in(cx, |editor, window, cx| {
 8822        assert_text_with_selections(
 8823            editor,
 8824            indoc! {r#"
 8825                use mod1::mod2::{mod3, mod4};
 8826
 8827                fn fn_1(param1: bool, param2: &str) {
 8828                    let var1 = "«helloˇ» world";
 8829                }
 8830            "#},
 8831            cx,
 8832        );
 8833        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8834        assert_text_with_selections(
 8835            editor,
 8836            indoc! {r#"
 8837                use mod1::mod2::{mod3, mod4};
 8838
 8839                fn fn_1(param1: bool, param2: &str) {
 8840                    let var1 = "«hello worldˇ»";
 8841                }
 8842            "#},
 8843            cx,
 8844        );
 8845    });
 8846
 8847    // Test 4: Selection spanning across words
 8848    editor.update_in(cx, |editor, window, cx| {
 8849        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8850            s.select_display_ranges([
 8851                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 8852            ]);
 8853        });
 8854    });
 8855    editor.update_in(cx, |editor, window, cx| {
 8856        assert_text_with_selections(
 8857            editor,
 8858            indoc! {r#"
 8859                use mod1::mod2::{mod3, mod4};
 8860
 8861                fn fn_1(param1: bool, param2: &str) {
 8862                    let var1 = "hel«lo woˇ»rld";
 8863                }
 8864            "#},
 8865            cx,
 8866        );
 8867        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8868        assert_text_with_selections(
 8869            editor,
 8870            indoc! {r#"
 8871                use mod1::mod2::{mod3, mod4};
 8872
 8873                fn fn_1(param1: bool, param2: &str) {
 8874                    let var1 = "«ˇhello world»";
 8875                }
 8876            "#},
 8877            cx,
 8878        );
 8879    });
 8880
 8881    // Test 5: Expansion beyond string
 8882    editor.update_in(cx, |editor, window, cx| {
 8883        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8884        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8885        assert_text_with_selections(
 8886            editor,
 8887            indoc! {r#"
 8888                use mod1::mod2::{mod3, mod4};
 8889
 8890                fn fn_1(param1: bool, param2: &str) {
 8891                    «ˇlet var1 = "hello world";»
 8892                }
 8893            "#},
 8894            cx,
 8895        );
 8896    });
 8897}
 8898
 8899#[gpui::test]
 8900async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 8901    init_test(cx, |_| {});
 8902
 8903    let mut cx = EditorTestContext::new(cx).await;
 8904
 8905    let language = Arc::new(Language::new(
 8906        LanguageConfig::default(),
 8907        Some(tree_sitter_rust::LANGUAGE.into()),
 8908    ));
 8909
 8910    cx.update_buffer(|buffer, cx| {
 8911        buffer.set_language(Some(language), cx);
 8912    });
 8913
 8914    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 8915    cx.update_editor(|editor, window, cx| {
 8916        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 8917    });
 8918
 8919    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 8920}
 8921
 8922#[gpui::test]
 8923async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 8924    init_test(cx, |_| {});
 8925
 8926    let base_text = r#"
 8927        impl A {
 8928            // this is an uncommitted comment
 8929
 8930            fn b() {
 8931                c();
 8932            }
 8933
 8934            // this is another uncommitted comment
 8935
 8936            fn d() {
 8937                // e
 8938                // f
 8939            }
 8940        }
 8941
 8942        fn g() {
 8943            // h
 8944        }
 8945    "#
 8946    .unindent();
 8947
 8948    let text = r#"
 8949        ˇimpl A {
 8950
 8951            fn b() {
 8952                c();
 8953            }
 8954
 8955            fn d() {
 8956                // e
 8957                // f
 8958            }
 8959        }
 8960
 8961        fn g() {
 8962            // h
 8963        }
 8964    "#
 8965    .unindent();
 8966
 8967    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8968    cx.set_state(&text);
 8969    cx.set_head_text(&base_text);
 8970    cx.update_editor(|editor, window, cx| {
 8971        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8972    });
 8973
 8974    cx.assert_state_with_diff(
 8975        "
 8976        ˇimpl A {
 8977      -     // this is an uncommitted comment
 8978
 8979            fn b() {
 8980                c();
 8981            }
 8982
 8983      -     // this is another uncommitted comment
 8984      -
 8985            fn d() {
 8986                // e
 8987                // f
 8988            }
 8989        }
 8990
 8991        fn g() {
 8992            // h
 8993        }
 8994    "
 8995        .unindent(),
 8996    );
 8997
 8998    let expected_display_text = "
 8999        impl A {
 9000            // this is an uncommitted comment
 9001
 9002            fn b() {
 9003 9004            }
 9005
 9006            // this is another uncommitted comment
 9007
 9008            fn d() {
 9009 9010            }
 9011        }
 9012
 9013        fn g() {
 9014 9015        }
 9016        "
 9017    .unindent();
 9018
 9019    cx.update_editor(|editor, window, cx| {
 9020        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9021        assert_eq!(editor.display_text(cx), expected_display_text);
 9022    });
 9023}
 9024
 9025#[gpui::test]
 9026async fn test_autoindent(cx: &mut TestAppContext) {
 9027    init_test(cx, |_| {});
 9028
 9029    let language = Arc::new(
 9030        Language::new(
 9031            LanguageConfig {
 9032                brackets: BracketPairConfig {
 9033                    pairs: vec![
 9034                        BracketPair {
 9035                            start: "{".to_string(),
 9036                            end: "}".to_string(),
 9037                            close: false,
 9038                            surround: false,
 9039                            newline: true,
 9040                        },
 9041                        BracketPair {
 9042                            start: "(".to_string(),
 9043                            end: ")".to_string(),
 9044                            close: false,
 9045                            surround: false,
 9046                            newline: true,
 9047                        },
 9048                    ],
 9049                    ..Default::default()
 9050                },
 9051                ..Default::default()
 9052            },
 9053            Some(tree_sitter_rust::LANGUAGE.into()),
 9054        )
 9055        .with_indents_query(
 9056            r#"
 9057                (_ "(" ")" @end) @indent
 9058                (_ "{" "}" @end) @indent
 9059            "#,
 9060        )
 9061        .unwrap(),
 9062    );
 9063
 9064    let text = "fn a() {}";
 9065
 9066    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9067    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9068    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9069    editor
 9070        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9071        .await;
 9072
 9073    editor.update_in(cx, |editor, window, cx| {
 9074        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9075            s.select_ranges([5..5, 8..8, 9..9])
 9076        });
 9077        editor.newline(&Newline, window, cx);
 9078        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9079        assert_eq!(
 9080            editor.selections.ranges(cx),
 9081            &[
 9082                Point::new(1, 4)..Point::new(1, 4),
 9083                Point::new(3, 4)..Point::new(3, 4),
 9084                Point::new(5, 0)..Point::new(5, 0)
 9085            ]
 9086        );
 9087    });
 9088}
 9089
 9090#[gpui::test]
 9091async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9092    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9093
 9094    let language = Arc::new(
 9095        Language::new(
 9096            LanguageConfig {
 9097                brackets: BracketPairConfig {
 9098                    pairs: vec![
 9099                        BracketPair {
 9100                            start: "{".to_string(),
 9101                            end: "}".to_string(),
 9102                            close: false,
 9103                            surround: false,
 9104                            newline: true,
 9105                        },
 9106                        BracketPair {
 9107                            start: "(".to_string(),
 9108                            end: ")".to_string(),
 9109                            close: false,
 9110                            surround: false,
 9111                            newline: true,
 9112                        },
 9113                    ],
 9114                    ..Default::default()
 9115                },
 9116                ..Default::default()
 9117            },
 9118            Some(tree_sitter_rust::LANGUAGE.into()),
 9119        )
 9120        .with_indents_query(
 9121            r#"
 9122                (_ "(" ")" @end) @indent
 9123                (_ "{" "}" @end) @indent
 9124            "#,
 9125        )
 9126        .unwrap(),
 9127    );
 9128
 9129    let text = "fn a() {}";
 9130
 9131    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9132    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9133    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9134    editor
 9135        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9136        .await;
 9137
 9138    editor.update_in(cx, |editor, window, cx| {
 9139        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9140            s.select_ranges([5..5, 8..8, 9..9])
 9141        });
 9142        editor.newline(&Newline, window, cx);
 9143        assert_eq!(
 9144            editor.text(cx),
 9145            indoc!(
 9146                "
 9147                fn a(
 9148
 9149                ) {
 9150
 9151                }
 9152                "
 9153            )
 9154        );
 9155        assert_eq!(
 9156            editor.selections.ranges(cx),
 9157            &[
 9158                Point::new(1, 0)..Point::new(1, 0),
 9159                Point::new(3, 0)..Point::new(3, 0),
 9160                Point::new(5, 0)..Point::new(5, 0)
 9161            ]
 9162        );
 9163    });
 9164}
 9165
 9166#[gpui::test]
 9167async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9168    init_test(cx, |settings| {
 9169        settings.defaults.auto_indent = Some(true);
 9170        settings.languages.0.insert(
 9171            "python".into(),
 9172            LanguageSettingsContent {
 9173                auto_indent: Some(false),
 9174                ..Default::default()
 9175            },
 9176        );
 9177    });
 9178
 9179    let mut cx = EditorTestContext::new(cx).await;
 9180
 9181    let injected_language = Arc::new(
 9182        Language::new(
 9183            LanguageConfig {
 9184                brackets: BracketPairConfig {
 9185                    pairs: vec![
 9186                        BracketPair {
 9187                            start: "{".to_string(),
 9188                            end: "}".to_string(),
 9189                            close: false,
 9190                            surround: false,
 9191                            newline: true,
 9192                        },
 9193                        BracketPair {
 9194                            start: "(".to_string(),
 9195                            end: ")".to_string(),
 9196                            close: true,
 9197                            surround: false,
 9198                            newline: true,
 9199                        },
 9200                    ],
 9201                    ..Default::default()
 9202                },
 9203                name: "python".into(),
 9204                ..Default::default()
 9205            },
 9206            Some(tree_sitter_python::LANGUAGE.into()),
 9207        )
 9208        .with_indents_query(
 9209            r#"
 9210                (_ "(" ")" @end) @indent
 9211                (_ "{" "}" @end) @indent
 9212            "#,
 9213        )
 9214        .unwrap(),
 9215    );
 9216
 9217    let language = Arc::new(
 9218        Language::new(
 9219            LanguageConfig {
 9220                brackets: BracketPairConfig {
 9221                    pairs: vec![
 9222                        BracketPair {
 9223                            start: "{".to_string(),
 9224                            end: "}".to_string(),
 9225                            close: false,
 9226                            surround: false,
 9227                            newline: true,
 9228                        },
 9229                        BracketPair {
 9230                            start: "(".to_string(),
 9231                            end: ")".to_string(),
 9232                            close: true,
 9233                            surround: false,
 9234                            newline: true,
 9235                        },
 9236                    ],
 9237                    ..Default::default()
 9238                },
 9239                name: LanguageName::new("rust"),
 9240                ..Default::default()
 9241            },
 9242            Some(tree_sitter_rust::LANGUAGE.into()),
 9243        )
 9244        .with_indents_query(
 9245            r#"
 9246                (_ "(" ")" @end) @indent
 9247                (_ "{" "}" @end) @indent
 9248            "#,
 9249        )
 9250        .unwrap()
 9251        .with_injection_query(
 9252            r#"
 9253            (macro_invocation
 9254                macro: (identifier) @_macro_name
 9255                (token_tree) @injection.content
 9256                (#set! injection.language "python"))
 9257           "#,
 9258        )
 9259        .unwrap(),
 9260    );
 9261
 9262    cx.language_registry().add(injected_language);
 9263    cx.language_registry().add(language.clone());
 9264
 9265    cx.update_buffer(|buffer, cx| {
 9266        buffer.set_language(Some(language), cx);
 9267    });
 9268
 9269    cx.set_state(r#"struct A {ˇ}"#);
 9270
 9271    cx.update_editor(|editor, window, cx| {
 9272        editor.newline(&Default::default(), window, cx);
 9273    });
 9274
 9275    cx.assert_editor_state(indoc!(
 9276        "struct A {
 9277            ˇ
 9278        }"
 9279    ));
 9280
 9281    cx.set_state(r#"select_biased!(ˇ)"#);
 9282
 9283    cx.update_editor(|editor, window, cx| {
 9284        editor.newline(&Default::default(), window, cx);
 9285        editor.handle_input("def ", window, cx);
 9286        editor.handle_input("(", window, cx);
 9287        editor.newline(&Default::default(), window, cx);
 9288        editor.handle_input("a", window, cx);
 9289    });
 9290
 9291    cx.assert_editor_state(indoc!(
 9292        "select_biased!(
 9293        def (
 9294 9295        )
 9296        )"
 9297    ));
 9298}
 9299
 9300#[gpui::test]
 9301async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9302    init_test(cx, |_| {});
 9303
 9304    {
 9305        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9306        cx.set_state(indoc! {"
 9307            impl A {
 9308
 9309                fn b() {}
 9310
 9311            «fn c() {
 9312
 9313            }ˇ»
 9314            }
 9315        "});
 9316
 9317        cx.update_editor(|editor, window, cx| {
 9318            editor.autoindent(&Default::default(), window, cx);
 9319        });
 9320
 9321        cx.assert_editor_state(indoc! {"
 9322            impl A {
 9323
 9324                fn b() {}
 9325
 9326                «fn c() {
 9327
 9328                }ˇ»
 9329            }
 9330        "});
 9331    }
 9332
 9333    {
 9334        let mut cx = EditorTestContext::new_multibuffer(
 9335            cx,
 9336            [indoc! { "
 9337                impl A {
 9338                «
 9339                // a
 9340                fn b(){}
 9341                »
 9342                «
 9343                    }
 9344                    fn c(){}
 9345                »
 9346            "}],
 9347        );
 9348
 9349        let buffer = cx.update_editor(|editor, _, cx| {
 9350            let buffer = editor.buffer().update(cx, |buffer, _| {
 9351                buffer.all_buffers().iter().next().unwrap().clone()
 9352            });
 9353            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9354            buffer
 9355        });
 9356
 9357        cx.run_until_parked();
 9358        cx.update_editor(|editor, window, cx| {
 9359            editor.select_all(&Default::default(), window, cx);
 9360            editor.autoindent(&Default::default(), window, cx)
 9361        });
 9362        cx.run_until_parked();
 9363
 9364        cx.update(|_, cx| {
 9365            assert_eq!(
 9366                buffer.read(cx).text(),
 9367                indoc! { "
 9368                    impl A {
 9369
 9370                        // a
 9371                        fn b(){}
 9372
 9373
 9374                    }
 9375                    fn c(){}
 9376
 9377                " }
 9378            )
 9379        });
 9380    }
 9381}
 9382
 9383#[gpui::test]
 9384async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9385    init_test(cx, |_| {});
 9386
 9387    let mut cx = EditorTestContext::new(cx).await;
 9388
 9389    let language = Arc::new(Language::new(
 9390        LanguageConfig {
 9391            brackets: BracketPairConfig {
 9392                pairs: vec![
 9393                    BracketPair {
 9394                        start: "{".to_string(),
 9395                        end: "}".to_string(),
 9396                        close: true,
 9397                        surround: true,
 9398                        newline: true,
 9399                    },
 9400                    BracketPair {
 9401                        start: "(".to_string(),
 9402                        end: ")".to_string(),
 9403                        close: true,
 9404                        surround: true,
 9405                        newline: true,
 9406                    },
 9407                    BracketPair {
 9408                        start: "/*".to_string(),
 9409                        end: " */".to_string(),
 9410                        close: true,
 9411                        surround: true,
 9412                        newline: true,
 9413                    },
 9414                    BracketPair {
 9415                        start: "[".to_string(),
 9416                        end: "]".to_string(),
 9417                        close: false,
 9418                        surround: false,
 9419                        newline: true,
 9420                    },
 9421                    BracketPair {
 9422                        start: "\"".to_string(),
 9423                        end: "\"".to_string(),
 9424                        close: true,
 9425                        surround: true,
 9426                        newline: false,
 9427                    },
 9428                    BracketPair {
 9429                        start: "<".to_string(),
 9430                        end: ">".to_string(),
 9431                        close: false,
 9432                        surround: true,
 9433                        newline: true,
 9434                    },
 9435                ],
 9436                ..Default::default()
 9437            },
 9438            autoclose_before: "})]".to_string(),
 9439            ..Default::default()
 9440        },
 9441        Some(tree_sitter_rust::LANGUAGE.into()),
 9442    ));
 9443
 9444    cx.language_registry().add(language.clone());
 9445    cx.update_buffer(|buffer, cx| {
 9446        buffer.set_language(Some(language), cx);
 9447    });
 9448
 9449    cx.set_state(
 9450        &r#"
 9451            🏀ˇ
 9452            εˇ
 9453            ❤️ˇ
 9454        "#
 9455        .unindent(),
 9456    );
 9457
 9458    // autoclose multiple nested brackets at multiple cursors
 9459    cx.update_editor(|editor, window, cx| {
 9460        editor.handle_input("{", window, cx);
 9461        editor.handle_input("{", window, cx);
 9462        editor.handle_input("{", window, cx);
 9463    });
 9464    cx.assert_editor_state(
 9465        &"
 9466            🏀{{{ˇ}}}
 9467            ε{{{ˇ}}}
 9468            ❤️{{{ˇ}}}
 9469        "
 9470        .unindent(),
 9471    );
 9472
 9473    // insert a different closing bracket
 9474    cx.update_editor(|editor, window, cx| {
 9475        editor.handle_input(")", window, cx);
 9476    });
 9477    cx.assert_editor_state(
 9478        &"
 9479            🏀{{{)ˇ}}}
 9480            ε{{{)ˇ}}}
 9481            ❤️{{{)ˇ}}}
 9482        "
 9483        .unindent(),
 9484    );
 9485
 9486    // skip over the auto-closed brackets when typing a closing bracket
 9487    cx.update_editor(|editor, window, cx| {
 9488        editor.move_right(&MoveRight, window, cx);
 9489        editor.handle_input("}", window, cx);
 9490        editor.handle_input("}", window, cx);
 9491        editor.handle_input("}", window, cx);
 9492    });
 9493    cx.assert_editor_state(
 9494        &"
 9495            🏀{{{)}}}}ˇ
 9496            ε{{{)}}}}ˇ
 9497            ❤️{{{)}}}}ˇ
 9498        "
 9499        .unindent(),
 9500    );
 9501
 9502    // autoclose multi-character pairs
 9503    cx.set_state(
 9504        &"
 9505            ˇ
 9506            ˇ
 9507        "
 9508        .unindent(),
 9509    );
 9510    cx.update_editor(|editor, window, cx| {
 9511        editor.handle_input("/", window, cx);
 9512        editor.handle_input("*", window, cx);
 9513    });
 9514    cx.assert_editor_state(
 9515        &"
 9516            /*ˇ */
 9517            /*ˇ */
 9518        "
 9519        .unindent(),
 9520    );
 9521
 9522    // one cursor autocloses a multi-character pair, one cursor
 9523    // does not autoclose.
 9524    cx.set_state(
 9525        &"
 9526 9527            ˇ
 9528        "
 9529        .unindent(),
 9530    );
 9531    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9532    cx.assert_editor_state(
 9533        &"
 9534            /*ˇ */
 9535 9536        "
 9537        .unindent(),
 9538    );
 9539
 9540    // Don't autoclose if the next character isn't whitespace and isn't
 9541    // listed in the language's "autoclose_before" section.
 9542    cx.set_state("ˇa b");
 9543    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9544    cx.assert_editor_state("{ˇa b");
 9545
 9546    // Don't autoclose if `close` is false for the bracket pair
 9547    cx.set_state("ˇ");
 9548    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9549    cx.assert_editor_state("");
 9550
 9551    // Surround with brackets if text is selected
 9552    cx.set_state("«aˇ» b");
 9553    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9554    cx.assert_editor_state("{«aˇ»} b");
 9555
 9556    // Autoclose when not immediately after a word character
 9557    cx.set_state("a ˇ");
 9558    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9559    cx.assert_editor_state("a \"ˇ\"");
 9560
 9561    // Autoclose pair where the start and end characters are the same
 9562    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9563    cx.assert_editor_state("a \"\"ˇ");
 9564
 9565    // Don't autoclose when immediately after a word character
 9566    cx.set_state("");
 9567    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9568    cx.assert_editor_state("a\"ˇ");
 9569
 9570    // Do autoclose when after a non-word character
 9571    cx.set_state("");
 9572    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9573    cx.assert_editor_state("{\"ˇ\"");
 9574
 9575    // Non identical pairs autoclose regardless of preceding character
 9576    cx.set_state("");
 9577    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9578    cx.assert_editor_state("a{ˇ}");
 9579
 9580    // Don't autoclose pair if autoclose is disabled
 9581    cx.set_state("ˇ");
 9582    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9583    cx.assert_editor_state("");
 9584
 9585    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9586    cx.set_state("«aˇ» b");
 9587    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9588    cx.assert_editor_state("<«aˇ»> b");
 9589}
 9590
 9591#[gpui::test]
 9592async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9593    init_test(cx, |settings| {
 9594        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9595    });
 9596
 9597    let mut cx = EditorTestContext::new(cx).await;
 9598
 9599    let language = Arc::new(Language::new(
 9600        LanguageConfig {
 9601            brackets: BracketPairConfig {
 9602                pairs: vec![
 9603                    BracketPair {
 9604                        start: "{".to_string(),
 9605                        end: "}".to_string(),
 9606                        close: true,
 9607                        surround: true,
 9608                        newline: true,
 9609                    },
 9610                    BracketPair {
 9611                        start: "(".to_string(),
 9612                        end: ")".to_string(),
 9613                        close: true,
 9614                        surround: true,
 9615                        newline: true,
 9616                    },
 9617                    BracketPair {
 9618                        start: "[".to_string(),
 9619                        end: "]".to_string(),
 9620                        close: false,
 9621                        surround: false,
 9622                        newline: true,
 9623                    },
 9624                ],
 9625                ..Default::default()
 9626            },
 9627            autoclose_before: "})]".to_string(),
 9628            ..Default::default()
 9629        },
 9630        Some(tree_sitter_rust::LANGUAGE.into()),
 9631    ));
 9632
 9633    cx.language_registry().add(language.clone());
 9634    cx.update_buffer(|buffer, cx| {
 9635        buffer.set_language(Some(language), cx);
 9636    });
 9637
 9638    cx.set_state(
 9639        &"
 9640            ˇ
 9641            ˇ
 9642            ˇ
 9643        "
 9644        .unindent(),
 9645    );
 9646
 9647    // ensure only matching closing brackets are skipped over
 9648    cx.update_editor(|editor, window, cx| {
 9649        editor.handle_input("}", window, cx);
 9650        editor.move_left(&MoveLeft, window, cx);
 9651        editor.handle_input(")", window, cx);
 9652        editor.move_left(&MoveLeft, window, cx);
 9653    });
 9654    cx.assert_editor_state(
 9655        &"
 9656            ˇ)}
 9657            ˇ)}
 9658            ˇ)}
 9659        "
 9660        .unindent(),
 9661    );
 9662
 9663    // skip-over closing brackets at multiple cursors
 9664    cx.update_editor(|editor, window, cx| {
 9665        editor.handle_input(")", window, cx);
 9666        editor.handle_input("}", window, cx);
 9667    });
 9668    cx.assert_editor_state(
 9669        &"
 9670            )}ˇ
 9671            )}ˇ
 9672            )}ˇ
 9673        "
 9674        .unindent(),
 9675    );
 9676
 9677    // ignore non-close brackets
 9678    cx.update_editor(|editor, window, cx| {
 9679        editor.handle_input("]", window, cx);
 9680        editor.move_left(&MoveLeft, window, cx);
 9681        editor.handle_input("]", window, cx);
 9682    });
 9683    cx.assert_editor_state(
 9684        &"
 9685            )}]ˇ]
 9686            )}]ˇ]
 9687            )}]ˇ]
 9688        "
 9689        .unindent(),
 9690    );
 9691}
 9692
 9693#[gpui::test]
 9694async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9695    init_test(cx, |_| {});
 9696
 9697    let mut cx = EditorTestContext::new(cx).await;
 9698
 9699    let html_language = Arc::new(
 9700        Language::new(
 9701            LanguageConfig {
 9702                name: "HTML".into(),
 9703                brackets: BracketPairConfig {
 9704                    pairs: vec![
 9705                        BracketPair {
 9706                            start: "<".into(),
 9707                            end: ">".into(),
 9708                            close: true,
 9709                            ..Default::default()
 9710                        },
 9711                        BracketPair {
 9712                            start: "{".into(),
 9713                            end: "}".into(),
 9714                            close: true,
 9715                            ..Default::default()
 9716                        },
 9717                        BracketPair {
 9718                            start: "(".into(),
 9719                            end: ")".into(),
 9720                            close: true,
 9721                            ..Default::default()
 9722                        },
 9723                    ],
 9724                    ..Default::default()
 9725                },
 9726                autoclose_before: "})]>".into(),
 9727                ..Default::default()
 9728            },
 9729            Some(tree_sitter_html::LANGUAGE.into()),
 9730        )
 9731        .with_injection_query(
 9732            r#"
 9733            (script_element
 9734                (raw_text) @injection.content
 9735                (#set! injection.language "javascript"))
 9736            "#,
 9737        )
 9738        .unwrap(),
 9739    );
 9740
 9741    let javascript_language = Arc::new(Language::new(
 9742        LanguageConfig {
 9743            name: "JavaScript".into(),
 9744            brackets: BracketPairConfig {
 9745                pairs: vec![
 9746                    BracketPair {
 9747                        start: "/*".into(),
 9748                        end: " */".into(),
 9749                        close: true,
 9750                        ..Default::default()
 9751                    },
 9752                    BracketPair {
 9753                        start: "{".into(),
 9754                        end: "}".into(),
 9755                        close: true,
 9756                        ..Default::default()
 9757                    },
 9758                    BracketPair {
 9759                        start: "(".into(),
 9760                        end: ")".into(),
 9761                        close: true,
 9762                        ..Default::default()
 9763                    },
 9764                ],
 9765                ..Default::default()
 9766            },
 9767            autoclose_before: "})]>".into(),
 9768            ..Default::default()
 9769        },
 9770        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9771    ));
 9772
 9773    cx.language_registry().add(html_language.clone());
 9774    cx.language_registry().add(javascript_language);
 9775    cx.executor().run_until_parked();
 9776
 9777    cx.update_buffer(|buffer, cx| {
 9778        buffer.set_language(Some(html_language), cx);
 9779    });
 9780
 9781    cx.set_state(
 9782        &r#"
 9783            <body>ˇ
 9784                <script>
 9785                    var x = 1;ˇ
 9786                </script>
 9787            </body>ˇ
 9788        "#
 9789        .unindent(),
 9790    );
 9791
 9792    // Precondition: different languages are active at different locations.
 9793    cx.update_editor(|editor, window, cx| {
 9794        let snapshot = editor.snapshot(window, cx);
 9795        let cursors = editor.selections.ranges::<usize>(cx);
 9796        let languages = cursors
 9797            .iter()
 9798            .map(|c| snapshot.language_at(c.start).unwrap().name())
 9799            .collect::<Vec<_>>();
 9800        assert_eq!(
 9801            languages,
 9802            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 9803        );
 9804    });
 9805
 9806    // Angle brackets autoclose in HTML, but not JavaScript.
 9807    cx.update_editor(|editor, window, cx| {
 9808        editor.handle_input("<", window, cx);
 9809        editor.handle_input("a", window, cx);
 9810    });
 9811    cx.assert_editor_state(
 9812        &r#"
 9813            <body><aˇ>
 9814                <script>
 9815                    var x = 1;<aˇ
 9816                </script>
 9817            </body><aˇ>
 9818        "#
 9819        .unindent(),
 9820    );
 9821
 9822    // Curly braces and parens autoclose in both HTML and JavaScript.
 9823    cx.update_editor(|editor, window, cx| {
 9824        editor.handle_input(" b=", window, cx);
 9825        editor.handle_input("{", window, cx);
 9826        editor.handle_input("c", window, cx);
 9827        editor.handle_input("(", window, cx);
 9828    });
 9829    cx.assert_editor_state(
 9830        &r#"
 9831            <body><a b={c(ˇ)}>
 9832                <script>
 9833                    var x = 1;<a b={c(ˇ)}
 9834                </script>
 9835            </body><a b={c(ˇ)}>
 9836        "#
 9837        .unindent(),
 9838    );
 9839
 9840    // Brackets that were already autoclosed are skipped.
 9841    cx.update_editor(|editor, window, cx| {
 9842        editor.handle_input(")", window, cx);
 9843        editor.handle_input("d", window, cx);
 9844        editor.handle_input("}", window, cx);
 9845    });
 9846    cx.assert_editor_state(
 9847        &r#"
 9848            <body><a b={c()d}ˇ>
 9849                <script>
 9850                    var x = 1;<a b={c()d}ˇ
 9851                </script>
 9852            </body><a b={c()d}ˇ>
 9853        "#
 9854        .unindent(),
 9855    );
 9856    cx.update_editor(|editor, window, cx| {
 9857        editor.handle_input(">", window, cx);
 9858    });
 9859    cx.assert_editor_state(
 9860        &r#"
 9861            <body><a b={c()d}>ˇ
 9862                <script>
 9863                    var x = 1;<a b={c()d}>ˇ
 9864                </script>
 9865            </body><a b={c()d}>ˇ
 9866        "#
 9867        .unindent(),
 9868    );
 9869
 9870    // Reset
 9871    cx.set_state(
 9872        &r#"
 9873            <body>ˇ
 9874                <script>
 9875                    var x = 1;ˇ
 9876                </script>
 9877            </body>ˇ
 9878        "#
 9879        .unindent(),
 9880    );
 9881
 9882    cx.update_editor(|editor, window, cx| {
 9883        editor.handle_input("<", window, cx);
 9884    });
 9885    cx.assert_editor_state(
 9886        &r#"
 9887            <body><ˇ>
 9888                <script>
 9889                    var x = 1;<ˇ
 9890                </script>
 9891            </body><ˇ>
 9892        "#
 9893        .unindent(),
 9894    );
 9895
 9896    // When backspacing, the closing angle brackets are removed.
 9897    cx.update_editor(|editor, window, cx| {
 9898        editor.backspace(&Backspace, window, cx);
 9899    });
 9900    cx.assert_editor_state(
 9901        &r#"
 9902            <body>ˇ
 9903                <script>
 9904                    var x = 1;ˇ
 9905                </script>
 9906            </body>ˇ
 9907        "#
 9908        .unindent(),
 9909    );
 9910
 9911    // Block comments autoclose in JavaScript, but not HTML.
 9912    cx.update_editor(|editor, window, cx| {
 9913        editor.handle_input("/", window, cx);
 9914        editor.handle_input("*", window, cx);
 9915    });
 9916    cx.assert_editor_state(
 9917        &r#"
 9918            <body>/*ˇ
 9919                <script>
 9920                    var x = 1;/*ˇ */
 9921                </script>
 9922            </body>/*ˇ
 9923        "#
 9924        .unindent(),
 9925    );
 9926}
 9927
 9928#[gpui::test]
 9929async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 9930    init_test(cx, |_| {});
 9931
 9932    let mut cx = EditorTestContext::new(cx).await;
 9933
 9934    let rust_language = Arc::new(
 9935        Language::new(
 9936            LanguageConfig {
 9937                name: "Rust".into(),
 9938                brackets: serde_json::from_value(json!([
 9939                    { "start": "{", "end": "}", "close": true, "newline": true },
 9940                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 9941                ]))
 9942                .unwrap(),
 9943                autoclose_before: "})]>".into(),
 9944                ..Default::default()
 9945            },
 9946            Some(tree_sitter_rust::LANGUAGE.into()),
 9947        )
 9948        .with_override_query("(string_literal) @string")
 9949        .unwrap(),
 9950    );
 9951
 9952    cx.language_registry().add(rust_language.clone());
 9953    cx.update_buffer(|buffer, cx| {
 9954        buffer.set_language(Some(rust_language), cx);
 9955    });
 9956
 9957    cx.set_state(
 9958        &r#"
 9959            let x = ˇ
 9960        "#
 9961        .unindent(),
 9962    );
 9963
 9964    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 9965    cx.update_editor(|editor, window, cx| {
 9966        editor.handle_input("\"", window, cx);
 9967    });
 9968    cx.assert_editor_state(
 9969        &r#"
 9970            let x = "ˇ"
 9971        "#
 9972        .unindent(),
 9973    );
 9974
 9975    // Inserting another quotation mark. The cursor moves across the existing
 9976    // automatically-inserted quotation mark.
 9977    cx.update_editor(|editor, window, cx| {
 9978        editor.handle_input("\"", window, cx);
 9979    });
 9980    cx.assert_editor_state(
 9981        &r#"
 9982            let x = ""ˇ
 9983        "#
 9984        .unindent(),
 9985    );
 9986
 9987    // Reset
 9988    cx.set_state(
 9989        &r#"
 9990            let x = ˇ
 9991        "#
 9992        .unindent(),
 9993    );
 9994
 9995    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 9996    cx.update_editor(|editor, window, cx| {
 9997        editor.handle_input("\"", window, cx);
 9998        editor.handle_input(" ", window, cx);
 9999        editor.move_left(&Default::default(), window, cx);
10000        editor.handle_input("\\", window, cx);
10001        editor.handle_input("\"", window, cx);
10002    });
10003    cx.assert_editor_state(
10004        &r#"
10005            let x = "\"ˇ "
10006        "#
10007        .unindent(),
10008    );
10009
10010    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10011    // mark. Nothing is inserted.
10012    cx.update_editor(|editor, window, cx| {
10013        editor.move_right(&Default::default(), window, cx);
10014        editor.handle_input("\"", window, cx);
10015    });
10016    cx.assert_editor_state(
10017        &r#"
10018            let x = "\" "ˇ
10019        "#
10020        .unindent(),
10021    );
10022}
10023
10024#[gpui::test]
10025async fn test_surround_with_pair(cx: &mut TestAppContext) {
10026    init_test(cx, |_| {});
10027
10028    let language = Arc::new(Language::new(
10029        LanguageConfig {
10030            brackets: BracketPairConfig {
10031                pairs: vec![
10032                    BracketPair {
10033                        start: "{".to_string(),
10034                        end: "}".to_string(),
10035                        close: true,
10036                        surround: true,
10037                        newline: true,
10038                    },
10039                    BracketPair {
10040                        start: "/* ".to_string(),
10041                        end: "*/".to_string(),
10042                        close: true,
10043                        surround: true,
10044                        ..Default::default()
10045                    },
10046                ],
10047                ..Default::default()
10048            },
10049            ..Default::default()
10050        },
10051        Some(tree_sitter_rust::LANGUAGE.into()),
10052    ));
10053
10054    let text = r#"
10055        a
10056        b
10057        c
10058    "#
10059    .unindent();
10060
10061    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10062    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10063    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10064    editor
10065        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10066        .await;
10067
10068    editor.update_in(cx, |editor, window, cx| {
10069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10070            s.select_display_ranges([
10071                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10072                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10073                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10074            ])
10075        });
10076
10077        editor.handle_input("{", window, cx);
10078        editor.handle_input("{", window, cx);
10079        editor.handle_input("{", window, cx);
10080        assert_eq!(
10081            editor.text(cx),
10082            "
10083                {{{a}}}
10084                {{{b}}}
10085                {{{c}}}
10086            "
10087            .unindent()
10088        );
10089        assert_eq!(
10090            editor.selections.display_ranges(cx),
10091            [
10092                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10093                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10094                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10095            ]
10096        );
10097
10098        editor.undo(&Undo, window, cx);
10099        editor.undo(&Undo, window, cx);
10100        editor.undo(&Undo, window, cx);
10101        assert_eq!(
10102            editor.text(cx),
10103            "
10104                a
10105                b
10106                c
10107            "
10108            .unindent()
10109        );
10110        assert_eq!(
10111            editor.selections.display_ranges(cx),
10112            [
10113                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10114                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10115                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10116            ]
10117        );
10118
10119        // Ensure inserting the first character of a multi-byte bracket pair
10120        // doesn't surround the selections with the bracket.
10121        editor.handle_input("/", window, cx);
10122        assert_eq!(
10123            editor.text(cx),
10124            "
10125                /
10126                /
10127                /
10128            "
10129            .unindent()
10130        );
10131        assert_eq!(
10132            editor.selections.display_ranges(cx),
10133            [
10134                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10135                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10136                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10137            ]
10138        );
10139
10140        editor.undo(&Undo, window, cx);
10141        assert_eq!(
10142            editor.text(cx),
10143            "
10144                a
10145                b
10146                c
10147            "
10148            .unindent()
10149        );
10150        assert_eq!(
10151            editor.selections.display_ranges(cx),
10152            [
10153                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10154                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10155                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10156            ]
10157        );
10158
10159        // Ensure inserting the last character of a multi-byte bracket pair
10160        // doesn't surround the selections with the bracket.
10161        editor.handle_input("*", window, cx);
10162        assert_eq!(
10163            editor.text(cx),
10164            "
10165                *
10166                *
10167                *
10168            "
10169            .unindent()
10170        );
10171        assert_eq!(
10172            editor.selections.display_ranges(cx),
10173            [
10174                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10175                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10176                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10177            ]
10178        );
10179    });
10180}
10181
10182#[gpui::test]
10183async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10184    init_test(cx, |_| {});
10185
10186    let language = Arc::new(Language::new(
10187        LanguageConfig {
10188            brackets: BracketPairConfig {
10189                pairs: vec![BracketPair {
10190                    start: "{".to_string(),
10191                    end: "}".to_string(),
10192                    close: true,
10193                    surround: true,
10194                    newline: true,
10195                }],
10196                ..Default::default()
10197            },
10198            autoclose_before: "}".to_string(),
10199            ..Default::default()
10200        },
10201        Some(tree_sitter_rust::LANGUAGE.into()),
10202    ));
10203
10204    let text = r#"
10205        a
10206        b
10207        c
10208    "#
10209    .unindent();
10210
10211    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10212    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10213    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10214    editor
10215        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10216        .await;
10217
10218    editor.update_in(cx, |editor, window, cx| {
10219        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10220            s.select_ranges([
10221                Point::new(0, 1)..Point::new(0, 1),
10222                Point::new(1, 1)..Point::new(1, 1),
10223                Point::new(2, 1)..Point::new(2, 1),
10224            ])
10225        });
10226
10227        editor.handle_input("{", window, cx);
10228        editor.handle_input("{", window, cx);
10229        editor.handle_input("_", window, cx);
10230        assert_eq!(
10231            editor.text(cx),
10232            "
10233                a{{_}}
10234                b{{_}}
10235                c{{_}}
10236            "
10237            .unindent()
10238        );
10239        assert_eq!(
10240            editor.selections.ranges::<Point>(cx),
10241            [
10242                Point::new(0, 4)..Point::new(0, 4),
10243                Point::new(1, 4)..Point::new(1, 4),
10244                Point::new(2, 4)..Point::new(2, 4)
10245            ]
10246        );
10247
10248        editor.backspace(&Default::default(), window, cx);
10249        editor.backspace(&Default::default(), window, cx);
10250        assert_eq!(
10251            editor.text(cx),
10252            "
10253                a{}
10254                b{}
10255                c{}
10256            "
10257            .unindent()
10258        );
10259        assert_eq!(
10260            editor.selections.ranges::<Point>(cx),
10261            [
10262                Point::new(0, 2)..Point::new(0, 2),
10263                Point::new(1, 2)..Point::new(1, 2),
10264                Point::new(2, 2)..Point::new(2, 2)
10265            ]
10266        );
10267
10268        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10269        assert_eq!(
10270            editor.text(cx),
10271            "
10272                a
10273                b
10274                c
10275            "
10276            .unindent()
10277        );
10278        assert_eq!(
10279            editor.selections.ranges::<Point>(cx),
10280            [
10281                Point::new(0, 1)..Point::new(0, 1),
10282                Point::new(1, 1)..Point::new(1, 1),
10283                Point::new(2, 1)..Point::new(2, 1)
10284            ]
10285        );
10286    });
10287}
10288
10289#[gpui::test]
10290async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10291    init_test(cx, |settings| {
10292        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10293    });
10294
10295    let mut cx = EditorTestContext::new(cx).await;
10296
10297    let language = Arc::new(Language::new(
10298        LanguageConfig {
10299            brackets: BracketPairConfig {
10300                pairs: vec![
10301                    BracketPair {
10302                        start: "{".to_string(),
10303                        end: "}".to_string(),
10304                        close: true,
10305                        surround: true,
10306                        newline: true,
10307                    },
10308                    BracketPair {
10309                        start: "(".to_string(),
10310                        end: ")".to_string(),
10311                        close: true,
10312                        surround: true,
10313                        newline: true,
10314                    },
10315                    BracketPair {
10316                        start: "[".to_string(),
10317                        end: "]".to_string(),
10318                        close: false,
10319                        surround: true,
10320                        newline: true,
10321                    },
10322                ],
10323                ..Default::default()
10324            },
10325            autoclose_before: "})]".to_string(),
10326            ..Default::default()
10327        },
10328        Some(tree_sitter_rust::LANGUAGE.into()),
10329    ));
10330
10331    cx.language_registry().add(language.clone());
10332    cx.update_buffer(|buffer, cx| {
10333        buffer.set_language(Some(language), cx);
10334    });
10335
10336    cx.set_state(
10337        &"
10338            {(ˇ)}
10339            [[ˇ]]
10340            {(ˇ)}
10341        "
10342        .unindent(),
10343    );
10344
10345    cx.update_editor(|editor, window, cx| {
10346        editor.backspace(&Default::default(), window, cx);
10347        editor.backspace(&Default::default(), window, cx);
10348    });
10349
10350    cx.assert_editor_state(
10351        &"
10352            ˇ
10353            ˇ]]
10354            ˇ
10355        "
10356        .unindent(),
10357    );
10358
10359    cx.update_editor(|editor, window, cx| {
10360        editor.handle_input("{", window, cx);
10361        editor.handle_input("{", window, cx);
10362        editor.move_right(&MoveRight, window, cx);
10363        editor.move_right(&MoveRight, window, cx);
10364        editor.move_left(&MoveLeft, window, cx);
10365        editor.move_left(&MoveLeft, window, cx);
10366        editor.backspace(&Default::default(), window, cx);
10367    });
10368
10369    cx.assert_editor_state(
10370        &"
10371            {ˇ}
10372            {ˇ}]]
10373            {ˇ}
10374        "
10375        .unindent(),
10376    );
10377
10378    cx.update_editor(|editor, window, cx| {
10379        editor.backspace(&Default::default(), window, cx);
10380    });
10381
10382    cx.assert_editor_state(
10383        &"
10384            ˇ
10385            ˇ]]
10386            ˇ
10387        "
10388        .unindent(),
10389    );
10390}
10391
10392#[gpui::test]
10393async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10394    init_test(cx, |_| {});
10395
10396    let language = Arc::new(Language::new(
10397        LanguageConfig::default(),
10398        Some(tree_sitter_rust::LANGUAGE.into()),
10399    ));
10400
10401    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10402    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10403    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10404    editor
10405        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10406        .await;
10407
10408    editor.update_in(cx, |editor, window, cx| {
10409        editor.set_auto_replace_emoji_shortcode(true);
10410
10411        editor.handle_input("Hello ", window, cx);
10412        editor.handle_input(":wave", window, cx);
10413        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10414
10415        editor.handle_input(":", window, cx);
10416        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10417
10418        editor.handle_input(" :smile", window, cx);
10419        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10420
10421        editor.handle_input(":", window, cx);
10422        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10423
10424        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10425        editor.handle_input(":wave", window, cx);
10426        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10427
10428        editor.handle_input(":", window, cx);
10429        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10430
10431        editor.handle_input(":1", window, cx);
10432        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10433
10434        editor.handle_input(":", window, cx);
10435        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10436
10437        // Ensure shortcode does not get replaced when it is part of a word
10438        editor.handle_input(" Test:wave", window, cx);
10439        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10440
10441        editor.handle_input(":", window, cx);
10442        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10443
10444        editor.set_auto_replace_emoji_shortcode(false);
10445
10446        // Ensure shortcode does not get replaced when auto replace is off
10447        editor.handle_input(" :wave", window, cx);
10448        assert_eq!(
10449            editor.text(cx),
10450            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10451        );
10452
10453        editor.handle_input(":", window, cx);
10454        assert_eq!(
10455            editor.text(cx),
10456            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10457        );
10458    });
10459}
10460
10461#[gpui::test]
10462async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10463    init_test(cx, |_| {});
10464
10465    let (text, insertion_ranges) = marked_text_ranges(
10466        indoc! {"
10467            ˇ
10468        "},
10469        false,
10470    );
10471
10472    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10473    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10474
10475    _ = editor.update_in(cx, |editor, window, cx| {
10476        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10477
10478        editor
10479            .insert_snippet(&insertion_ranges, snippet, window, cx)
10480            .unwrap();
10481
10482        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10483            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10484            assert_eq!(editor.text(cx), expected_text);
10485            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10486        }
10487
10488        assert(
10489            editor,
10490            cx,
10491            indoc! {"
10492            type «» =•
10493            "},
10494        );
10495
10496        assert!(editor.context_menu_visible(), "There should be a matches");
10497    });
10498}
10499
10500#[gpui::test]
10501async fn test_snippets(cx: &mut TestAppContext) {
10502    init_test(cx, |_| {});
10503
10504    let mut cx = EditorTestContext::new(cx).await;
10505
10506    cx.set_state(indoc! {"
10507        a.ˇ b
10508        a.ˇ b
10509        a.ˇ b
10510    "});
10511
10512    cx.update_editor(|editor, window, cx| {
10513        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10514        let insertion_ranges = editor
10515            .selections
10516            .all(cx)
10517            .iter()
10518            .map(|s| s.range())
10519            .collect::<Vec<_>>();
10520        editor
10521            .insert_snippet(&insertion_ranges, snippet, window, cx)
10522            .unwrap();
10523    });
10524
10525    cx.assert_editor_state(indoc! {"
10526        a.f(«oneˇ», two, «threeˇ») b
10527        a.f(«oneˇ», two, «threeˇ») b
10528        a.f(«oneˇ», two, «threeˇ») b
10529    "});
10530
10531    // Can't move earlier than the first tab stop
10532    cx.update_editor(|editor, window, cx| {
10533        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10534    });
10535    cx.assert_editor_state(indoc! {"
10536        a.f(«oneˇ», two, «threeˇ») b
10537        a.f(«oneˇ», two, «threeˇ») b
10538        a.f(«oneˇ», two, «threeˇ») b
10539    "});
10540
10541    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10542    cx.assert_editor_state(indoc! {"
10543        a.f(one, «twoˇ», three) b
10544        a.f(one, «twoˇ», three) b
10545        a.f(one, «twoˇ», three) b
10546    "});
10547
10548    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10549    cx.assert_editor_state(indoc! {"
10550        a.f(«oneˇ», two, «threeˇ») b
10551        a.f(«oneˇ», two, «threeˇ») b
10552        a.f(«oneˇ», two, «threeˇ») b
10553    "});
10554
10555    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10556    cx.assert_editor_state(indoc! {"
10557        a.f(one, «twoˇ», three) b
10558        a.f(one, «twoˇ», three) b
10559        a.f(one, «twoˇ», three) b
10560    "});
10561    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10562    cx.assert_editor_state(indoc! {"
10563        a.f(one, two, three)ˇ b
10564        a.f(one, two, three)ˇ b
10565        a.f(one, two, three)ˇ b
10566    "});
10567
10568    // As soon as the last tab stop is reached, snippet state is gone
10569    cx.update_editor(|editor, window, cx| {
10570        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10571    });
10572    cx.assert_editor_state(indoc! {"
10573        a.f(one, two, three)ˇ b
10574        a.f(one, two, three)ˇ b
10575        a.f(one, two, three)ˇ b
10576    "});
10577}
10578
10579#[gpui::test]
10580async fn test_snippet_indentation(cx: &mut TestAppContext) {
10581    init_test(cx, |_| {});
10582
10583    let mut cx = EditorTestContext::new(cx).await;
10584
10585    cx.update_editor(|editor, window, cx| {
10586        let snippet = Snippet::parse(indoc! {"
10587            /*
10588             * Multiline comment with leading indentation
10589             *
10590             * $1
10591             */
10592            $0"})
10593        .unwrap();
10594        let insertion_ranges = editor
10595            .selections
10596            .all(cx)
10597            .iter()
10598            .map(|s| s.range())
10599            .collect::<Vec<_>>();
10600        editor
10601            .insert_snippet(&insertion_ranges, snippet, window, cx)
10602            .unwrap();
10603    });
10604
10605    cx.assert_editor_state(indoc! {"
10606        /*
10607         * Multiline comment with leading indentation
10608         *
10609         * ˇ
10610         */
10611    "});
10612
10613    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10614    cx.assert_editor_state(indoc! {"
10615        /*
10616         * Multiline comment with leading indentation
10617         *
10618         *•
10619         */
10620        ˇ"});
10621}
10622
10623#[gpui::test]
10624async fn test_document_format_during_save(cx: &mut TestAppContext) {
10625    init_test(cx, |_| {});
10626
10627    let fs = FakeFs::new(cx.executor());
10628    fs.insert_file(path!("/file.rs"), Default::default()).await;
10629
10630    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10631
10632    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10633    language_registry.add(rust_lang());
10634    let mut fake_servers = language_registry.register_fake_lsp(
10635        "Rust",
10636        FakeLspAdapter {
10637            capabilities: lsp::ServerCapabilities {
10638                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10639                ..Default::default()
10640            },
10641            ..Default::default()
10642        },
10643    );
10644
10645    let buffer = project
10646        .update(cx, |project, cx| {
10647            project.open_local_buffer(path!("/file.rs"), cx)
10648        })
10649        .await
10650        .unwrap();
10651
10652    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10653    let (editor, cx) = cx.add_window_view(|window, cx| {
10654        build_editor_with_project(project.clone(), buffer, window, cx)
10655    });
10656    editor.update_in(cx, |editor, window, cx| {
10657        editor.set_text("one\ntwo\nthree\n", window, cx)
10658    });
10659    assert!(cx.read(|cx| editor.is_dirty(cx)));
10660
10661    cx.executor().start_waiting();
10662    let fake_server = fake_servers.next().await.unwrap();
10663
10664    {
10665        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10666            move |params, _| async move {
10667                assert_eq!(
10668                    params.text_document.uri,
10669                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10670                );
10671                assert_eq!(params.options.tab_size, 4);
10672                Ok(Some(vec![lsp::TextEdit::new(
10673                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10674                    ", ".to_string(),
10675                )]))
10676            },
10677        );
10678        let save = editor
10679            .update_in(cx, |editor, window, cx| {
10680                editor.save(
10681                    SaveOptions {
10682                        format: true,
10683                        autosave: false,
10684                    },
10685                    project.clone(),
10686                    window,
10687                    cx,
10688                )
10689            })
10690            .unwrap();
10691        cx.executor().start_waiting();
10692        save.await;
10693
10694        assert_eq!(
10695            editor.update(cx, |editor, cx| editor.text(cx)),
10696            "one, two\nthree\n"
10697        );
10698        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10699    }
10700
10701    {
10702        editor.update_in(cx, |editor, window, cx| {
10703            editor.set_text("one\ntwo\nthree\n", window, cx)
10704        });
10705        assert!(cx.read(|cx| editor.is_dirty(cx)));
10706
10707        // Ensure we can still save even if formatting hangs.
10708        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10709            move |params, _| async move {
10710                assert_eq!(
10711                    params.text_document.uri,
10712                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10713                );
10714                futures::future::pending::<()>().await;
10715                unreachable!()
10716            },
10717        );
10718        let save = editor
10719            .update_in(cx, |editor, window, cx| {
10720                editor.save(
10721                    SaveOptions {
10722                        format: true,
10723                        autosave: false,
10724                    },
10725                    project.clone(),
10726                    window,
10727                    cx,
10728                )
10729            })
10730            .unwrap();
10731        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10732        cx.executor().start_waiting();
10733        save.await;
10734        assert_eq!(
10735            editor.update(cx, |editor, cx| editor.text(cx)),
10736            "one\ntwo\nthree\n"
10737        );
10738    }
10739
10740    // Set rust language override and assert overridden tabsize is sent to language server
10741    update_test_language_settings(cx, |settings| {
10742        settings.languages.0.insert(
10743            "Rust".into(),
10744            LanguageSettingsContent {
10745                tab_size: NonZeroU32::new(8),
10746                ..Default::default()
10747            },
10748        );
10749    });
10750
10751    {
10752        editor.update_in(cx, |editor, window, cx| {
10753            editor.set_text("somehting_new\n", window, cx)
10754        });
10755        assert!(cx.read(|cx| editor.is_dirty(cx)));
10756        let _formatting_request_signal = fake_server
10757            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10758                assert_eq!(
10759                    params.text_document.uri,
10760                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10761                );
10762                assert_eq!(params.options.tab_size, 8);
10763                Ok(Some(vec![]))
10764            });
10765        let save = editor
10766            .update_in(cx, |editor, window, cx| {
10767                editor.save(
10768                    SaveOptions {
10769                        format: true,
10770                        autosave: false,
10771                    },
10772                    project.clone(),
10773                    window,
10774                    cx,
10775                )
10776            })
10777            .unwrap();
10778        cx.executor().start_waiting();
10779        save.await;
10780    }
10781}
10782
10783#[gpui::test]
10784async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
10785    init_test(cx, |settings| {
10786        settings.defaults.ensure_final_newline_on_save = Some(false);
10787    });
10788
10789    let fs = FakeFs::new(cx.executor());
10790    fs.insert_file(path!("/file.txt"), "foo".into()).await;
10791
10792    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
10793
10794    let buffer = project
10795        .update(cx, |project, cx| {
10796            project.open_local_buffer(path!("/file.txt"), cx)
10797        })
10798        .await
10799        .unwrap();
10800
10801    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10802    let (editor, cx) = cx.add_window_view(|window, cx| {
10803        build_editor_with_project(project.clone(), buffer, window, cx)
10804    });
10805    editor.update_in(cx, |editor, window, cx| {
10806        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
10807            s.select_ranges([0..0])
10808        });
10809    });
10810    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10811
10812    editor.update_in(cx, |editor, window, cx| {
10813        editor.handle_input("\n", window, cx)
10814    });
10815    cx.run_until_parked();
10816    save(&editor, &project, cx).await;
10817    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10818
10819    editor.update_in(cx, |editor, window, cx| {
10820        editor.undo(&Default::default(), window, cx);
10821    });
10822    save(&editor, &project, cx).await;
10823    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10824
10825    editor.update_in(cx, |editor, window, cx| {
10826        editor.redo(&Default::default(), window, cx);
10827    });
10828    cx.run_until_parked();
10829    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10830
10831    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
10832        let save = editor
10833            .update_in(cx, |editor, window, cx| {
10834                editor.save(
10835                    SaveOptions {
10836                        format: true,
10837                        autosave: false,
10838                    },
10839                    project.clone(),
10840                    window,
10841                    cx,
10842                )
10843            })
10844            .unwrap();
10845        cx.executor().start_waiting();
10846        save.await;
10847        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10848    }
10849}
10850
10851#[gpui::test]
10852async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
10853    init_test(cx, |_| {});
10854
10855    let cols = 4;
10856    let rows = 10;
10857    let sample_text_1 = sample_text(rows, cols, 'a');
10858    assert_eq!(
10859        sample_text_1,
10860        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10861    );
10862    let sample_text_2 = sample_text(rows, cols, 'l');
10863    assert_eq!(
10864        sample_text_2,
10865        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10866    );
10867    let sample_text_3 = sample_text(rows, cols, 'v');
10868    assert_eq!(
10869        sample_text_3,
10870        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10871    );
10872
10873    let fs = FakeFs::new(cx.executor());
10874    fs.insert_tree(
10875        path!("/a"),
10876        json!({
10877            "main.rs": sample_text_1,
10878            "other.rs": sample_text_2,
10879            "lib.rs": sample_text_3,
10880        }),
10881    )
10882    .await;
10883
10884    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10885    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10886    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10887
10888    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10889    language_registry.add(rust_lang());
10890    let mut fake_servers = language_registry.register_fake_lsp(
10891        "Rust",
10892        FakeLspAdapter {
10893            capabilities: lsp::ServerCapabilities {
10894                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10895                ..Default::default()
10896            },
10897            ..Default::default()
10898        },
10899    );
10900
10901    let worktree = project.update(cx, |project, cx| {
10902        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
10903        assert_eq!(worktrees.len(), 1);
10904        worktrees.pop().unwrap()
10905    });
10906    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10907
10908    let buffer_1 = project
10909        .update(cx, |project, cx| {
10910            project.open_buffer((worktree_id, "main.rs"), cx)
10911        })
10912        .await
10913        .unwrap();
10914    let buffer_2 = project
10915        .update(cx, |project, cx| {
10916            project.open_buffer((worktree_id, "other.rs"), cx)
10917        })
10918        .await
10919        .unwrap();
10920    let buffer_3 = project
10921        .update(cx, |project, cx| {
10922            project.open_buffer((worktree_id, "lib.rs"), cx)
10923        })
10924        .await
10925        .unwrap();
10926
10927    let multi_buffer = cx.new(|cx| {
10928        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10929        multi_buffer.push_excerpts(
10930            buffer_1.clone(),
10931            [
10932                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10933                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10934                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10935            ],
10936            cx,
10937        );
10938        multi_buffer.push_excerpts(
10939            buffer_2.clone(),
10940            [
10941                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10942                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10943                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10944            ],
10945            cx,
10946        );
10947        multi_buffer.push_excerpts(
10948            buffer_3.clone(),
10949            [
10950                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10951                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10952                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10953            ],
10954            cx,
10955        );
10956        multi_buffer
10957    });
10958    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
10959        Editor::new(
10960            EditorMode::full(),
10961            multi_buffer,
10962            Some(project.clone()),
10963            window,
10964            cx,
10965        )
10966    });
10967
10968    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10969        editor.change_selections(
10970            SelectionEffects::scroll(Autoscroll::Next),
10971            window,
10972            cx,
10973            |s| s.select_ranges(Some(1..2)),
10974        );
10975        editor.insert("|one|two|three|", window, cx);
10976    });
10977    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10978    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10979        editor.change_selections(
10980            SelectionEffects::scroll(Autoscroll::Next),
10981            window,
10982            cx,
10983            |s| s.select_ranges(Some(60..70)),
10984        );
10985        editor.insert("|four|five|six|", window, cx);
10986    });
10987    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10988
10989    // First two buffers should be edited, but not the third one.
10990    assert_eq!(
10991        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10992        "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}",
10993    );
10994    buffer_1.update(cx, |buffer, _| {
10995        assert!(buffer.is_dirty());
10996        assert_eq!(
10997            buffer.text(),
10998            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
10999        )
11000    });
11001    buffer_2.update(cx, |buffer, _| {
11002        assert!(buffer.is_dirty());
11003        assert_eq!(
11004            buffer.text(),
11005            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11006        )
11007    });
11008    buffer_3.update(cx, |buffer, _| {
11009        assert!(!buffer.is_dirty());
11010        assert_eq!(buffer.text(), sample_text_3,)
11011    });
11012    cx.executor().run_until_parked();
11013
11014    cx.executor().start_waiting();
11015    let save = multi_buffer_editor
11016        .update_in(cx, |editor, window, cx| {
11017            editor.save(
11018                SaveOptions {
11019                    format: true,
11020                    autosave: false,
11021                },
11022                project.clone(),
11023                window,
11024                cx,
11025            )
11026        })
11027        .unwrap();
11028
11029    let fake_server = fake_servers.next().await.unwrap();
11030    fake_server
11031        .server
11032        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11033            Ok(Some(vec![lsp::TextEdit::new(
11034                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11035                format!("[{} formatted]", params.text_document.uri),
11036            )]))
11037        })
11038        .detach();
11039    save.await;
11040
11041    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11042    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11043    assert_eq!(
11044        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11045        uri!(
11046            "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}"
11047        ),
11048    );
11049    buffer_1.update(cx, |buffer, _| {
11050        assert!(!buffer.is_dirty());
11051        assert_eq!(
11052            buffer.text(),
11053            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11054        )
11055    });
11056    buffer_2.update(cx, |buffer, _| {
11057        assert!(!buffer.is_dirty());
11058        assert_eq!(
11059            buffer.text(),
11060            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11061        )
11062    });
11063    buffer_3.update(cx, |buffer, _| {
11064        assert!(!buffer.is_dirty());
11065        assert_eq!(buffer.text(), sample_text_3,)
11066    });
11067}
11068
11069#[gpui::test]
11070async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11071    init_test(cx, |_| {});
11072
11073    let fs = FakeFs::new(cx.executor());
11074    fs.insert_tree(
11075        path!("/dir"),
11076        json!({
11077            "file1.rs": "fn main() { println!(\"hello\"); }",
11078            "file2.rs": "fn test() { println!(\"test\"); }",
11079            "file3.rs": "fn other() { println!(\"other\"); }\n",
11080        }),
11081    )
11082    .await;
11083
11084    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11085    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11086    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11087
11088    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11089    language_registry.add(rust_lang());
11090
11091    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11092    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11093
11094    // Open three buffers
11095    let buffer_1 = project
11096        .update(cx, |project, cx| {
11097            project.open_buffer((worktree_id, "file1.rs"), cx)
11098        })
11099        .await
11100        .unwrap();
11101    let buffer_2 = project
11102        .update(cx, |project, cx| {
11103            project.open_buffer((worktree_id, "file2.rs"), cx)
11104        })
11105        .await
11106        .unwrap();
11107    let buffer_3 = project
11108        .update(cx, |project, cx| {
11109            project.open_buffer((worktree_id, "file3.rs"), cx)
11110        })
11111        .await
11112        .unwrap();
11113
11114    // Create a multi-buffer with all three buffers
11115    let multi_buffer = cx.new(|cx| {
11116        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11117        multi_buffer.push_excerpts(
11118            buffer_1.clone(),
11119            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11120            cx,
11121        );
11122        multi_buffer.push_excerpts(
11123            buffer_2.clone(),
11124            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11125            cx,
11126        );
11127        multi_buffer.push_excerpts(
11128            buffer_3.clone(),
11129            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11130            cx,
11131        );
11132        multi_buffer
11133    });
11134
11135    let editor = cx.new_window_entity(|window, cx| {
11136        Editor::new(
11137            EditorMode::full(),
11138            multi_buffer,
11139            Some(project.clone()),
11140            window,
11141            cx,
11142        )
11143    });
11144
11145    // Edit only the first buffer
11146    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(10..10)),
11152        );
11153        editor.insert("// edited", window, cx);
11154    });
11155
11156    // Verify that only buffer 1 is dirty
11157    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11158    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11159    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11160
11161    // Get write counts after file creation (files were created with initial content)
11162    // We expect each file to have been written once during creation
11163    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11164    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11165    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11166
11167    // Perform autosave
11168    let save_task = editor.update_in(cx, |editor, window, cx| {
11169        editor.save(
11170            SaveOptions {
11171                format: true,
11172                autosave: true,
11173            },
11174            project.clone(),
11175            window,
11176            cx,
11177        )
11178    });
11179    save_task.await.unwrap();
11180
11181    // Only the dirty buffer should have been saved
11182    assert_eq!(
11183        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11184        1,
11185        "Buffer 1 was dirty, so it should have been written once during autosave"
11186    );
11187    assert_eq!(
11188        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11189        0,
11190        "Buffer 2 was clean, so it should not have been written during autosave"
11191    );
11192    assert_eq!(
11193        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11194        0,
11195        "Buffer 3 was clean, so it should not have been written during autosave"
11196    );
11197
11198    // Verify buffer states after autosave
11199    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11200    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11201    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11202
11203    // Now perform a manual save (format = true)
11204    let save_task = editor.update_in(cx, |editor, window, cx| {
11205        editor.save(
11206            SaveOptions {
11207                format: true,
11208                autosave: false,
11209            },
11210            project.clone(),
11211            window,
11212            cx,
11213        )
11214    });
11215    save_task.await.unwrap();
11216
11217    // During manual save, clean buffers don't get written to disk
11218    // They just get did_save called for language server notifications
11219    assert_eq!(
11220        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11221        1,
11222        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11223    );
11224    assert_eq!(
11225        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11226        0,
11227        "Buffer 2 should not have been written at all"
11228    );
11229    assert_eq!(
11230        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11231        0,
11232        "Buffer 3 should not have been written at all"
11233    );
11234}
11235
11236async fn setup_range_format_test(
11237    cx: &mut TestAppContext,
11238) -> (
11239    Entity<Project>,
11240    Entity<Editor>,
11241    &mut gpui::VisualTestContext,
11242    lsp::FakeLanguageServer,
11243) {
11244    init_test(cx, |_| {});
11245
11246    let fs = FakeFs::new(cx.executor());
11247    fs.insert_file(path!("/file.rs"), Default::default()).await;
11248
11249    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11250
11251    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11252    language_registry.add(rust_lang());
11253    let mut fake_servers = language_registry.register_fake_lsp(
11254        "Rust",
11255        FakeLspAdapter {
11256            capabilities: lsp::ServerCapabilities {
11257                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11258                ..lsp::ServerCapabilities::default()
11259            },
11260            ..FakeLspAdapter::default()
11261        },
11262    );
11263
11264    let buffer = project
11265        .update(cx, |project, cx| {
11266            project.open_local_buffer(path!("/file.rs"), cx)
11267        })
11268        .await
11269        .unwrap();
11270
11271    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11272    let (editor, cx) = cx.add_window_view(|window, cx| {
11273        build_editor_with_project(project.clone(), buffer, window, cx)
11274    });
11275
11276    cx.executor().start_waiting();
11277    let fake_server = fake_servers.next().await.unwrap();
11278
11279    (project, editor, cx, fake_server)
11280}
11281
11282#[gpui::test]
11283async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11284    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11285
11286    editor.update_in(cx, |editor, window, cx| {
11287        editor.set_text("one\ntwo\nthree\n", window, cx)
11288    });
11289    assert!(cx.read(|cx| editor.is_dirty(cx)));
11290
11291    let save = editor
11292        .update_in(cx, |editor, window, cx| {
11293            editor.save(
11294                SaveOptions {
11295                    format: true,
11296                    autosave: false,
11297                },
11298                project.clone(),
11299                window,
11300                cx,
11301            )
11302        })
11303        .unwrap();
11304    fake_server
11305        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11306            assert_eq!(
11307                params.text_document.uri,
11308                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11309            );
11310            assert_eq!(params.options.tab_size, 4);
11311            Ok(Some(vec![lsp::TextEdit::new(
11312                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11313                ", ".to_string(),
11314            )]))
11315        })
11316        .next()
11317        .await;
11318    cx.executor().start_waiting();
11319    save.await;
11320    assert_eq!(
11321        editor.update(cx, |editor, cx| editor.text(cx)),
11322        "one, two\nthree\n"
11323    );
11324    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11325}
11326
11327#[gpui::test]
11328async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11329    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11330
11331    editor.update_in(cx, |editor, window, cx| {
11332        editor.set_text("one\ntwo\nthree\n", window, cx)
11333    });
11334    assert!(cx.read(|cx| editor.is_dirty(cx)));
11335
11336    // Test that save still works when formatting hangs
11337    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11338        move |params, _| async move {
11339            assert_eq!(
11340                params.text_document.uri,
11341                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11342            );
11343            futures::future::pending::<()>().await;
11344            unreachable!()
11345        },
11346    );
11347    let save = editor
11348        .update_in(cx, |editor, window, cx| {
11349            editor.save(
11350                SaveOptions {
11351                    format: true,
11352                    autosave: false,
11353                },
11354                project.clone(),
11355                window,
11356                cx,
11357            )
11358        })
11359        .unwrap();
11360    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11361    cx.executor().start_waiting();
11362    save.await;
11363    assert_eq!(
11364        editor.update(cx, |editor, cx| editor.text(cx)),
11365        "one\ntwo\nthree\n"
11366    );
11367    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11368}
11369
11370#[gpui::test]
11371async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11372    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11373
11374    // Buffer starts clean, no formatting should be requested
11375    let save = editor
11376        .update_in(cx, |editor, window, cx| {
11377            editor.save(
11378                SaveOptions {
11379                    format: false,
11380                    autosave: false,
11381                },
11382                project.clone(),
11383                window,
11384                cx,
11385            )
11386        })
11387        .unwrap();
11388    let _pending_format_request = fake_server
11389        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11390            panic!("Should not be invoked");
11391        })
11392        .next();
11393    cx.executor().start_waiting();
11394    save.await;
11395    cx.run_until_parked();
11396}
11397
11398#[gpui::test]
11399async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11400    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11401
11402    // Set Rust language override and assert overridden tabsize is sent to language server
11403    update_test_language_settings(cx, |settings| {
11404        settings.languages.0.insert(
11405            "Rust".into(),
11406            LanguageSettingsContent {
11407                tab_size: NonZeroU32::new(8),
11408                ..Default::default()
11409            },
11410        );
11411    });
11412
11413    editor.update_in(cx, |editor, window, cx| {
11414        editor.set_text("something_new\n", window, cx)
11415    });
11416    assert!(cx.read(|cx| editor.is_dirty(cx)));
11417    let save = editor
11418        .update_in(cx, |editor, window, cx| {
11419            editor.save(
11420                SaveOptions {
11421                    format: true,
11422                    autosave: false,
11423                },
11424                project.clone(),
11425                window,
11426                cx,
11427            )
11428        })
11429        .unwrap();
11430    fake_server
11431        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11432            assert_eq!(
11433                params.text_document.uri,
11434                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11435            );
11436            assert_eq!(params.options.tab_size, 8);
11437            Ok(Some(Vec::new()))
11438        })
11439        .next()
11440        .await;
11441    save.await;
11442}
11443
11444#[gpui::test]
11445async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11446    init_test(cx, |settings| {
11447        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11448            Formatter::LanguageServer { name: None },
11449        )))
11450    });
11451
11452    let fs = FakeFs::new(cx.executor());
11453    fs.insert_file(path!("/file.rs"), Default::default()).await;
11454
11455    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11456
11457    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11458    language_registry.add(Arc::new(Language::new(
11459        LanguageConfig {
11460            name: "Rust".into(),
11461            matcher: LanguageMatcher {
11462                path_suffixes: vec!["rs".to_string()],
11463                ..Default::default()
11464            },
11465            ..LanguageConfig::default()
11466        },
11467        Some(tree_sitter_rust::LANGUAGE.into()),
11468    )));
11469    update_test_language_settings(cx, |settings| {
11470        // Enable Prettier formatting for the same buffer, and ensure
11471        // LSP is called instead of Prettier.
11472        settings.defaults.prettier = Some(PrettierSettings {
11473            allowed: true,
11474            ..PrettierSettings::default()
11475        });
11476    });
11477    let mut fake_servers = language_registry.register_fake_lsp(
11478        "Rust",
11479        FakeLspAdapter {
11480            capabilities: lsp::ServerCapabilities {
11481                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11482                ..Default::default()
11483            },
11484            ..Default::default()
11485        },
11486    );
11487
11488    let buffer = project
11489        .update(cx, |project, cx| {
11490            project.open_local_buffer(path!("/file.rs"), cx)
11491        })
11492        .await
11493        .unwrap();
11494
11495    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11496    let (editor, cx) = cx.add_window_view(|window, cx| {
11497        build_editor_with_project(project.clone(), buffer, window, cx)
11498    });
11499    editor.update_in(cx, |editor, window, cx| {
11500        editor.set_text("one\ntwo\nthree\n", window, cx)
11501    });
11502
11503    cx.executor().start_waiting();
11504    let fake_server = fake_servers.next().await.unwrap();
11505
11506    let format = editor
11507        .update_in(cx, |editor, window, cx| {
11508            editor.perform_format(
11509                project.clone(),
11510                FormatTrigger::Manual,
11511                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11512                window,
11513                cx,
11514            )
11515        })
11516        .unwrap();
11517    fake_server
11518        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11519            assert_eq!(
11520                params.text_document.uri,
11521                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11522            );
11523            assert_eq!(params.options.tab_size, 4);
11524            Ok(Some(vec![lsp::TextEdit::new(
11525                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11526                ", ".to_string(),
11527            )]))
11528        })
11529        .next()
11530        .await;
11531    cx.executor().start_waiting();
11532    format.await;
11533    assert_eq!(
11534        editor.update(cx, |editor, cx| editor.text(cx)),
11535        "one, two\nthree\n"
11536    );
11537
11538    editor.update_in(cx, |editor, window, cx| {
11539        editor.set_text("one\ntwo\nthree\n", window, cx)
11540    });
11541    // Ensure we don't lock if formatting hangs.
11542    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11543        move |params, _| async move {
11544            assert_eq!(
11545                params.text_document.uri,
11546                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11547            );
11548            futures::future::pending::<()>().await;
11549            unreachable!()
11550        },
11551    );
11552    let format = editor
11553        .update_in(cx, |editor, window, cx| {
11554            editor.perform_format(
11555                project,
11556                FormatTrigger::Manual,
11557                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11558                window,
11559                cx,
11560            )
11561        })
11562        .unwrap();
11563    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11564    cx.executor().start_waiting();
11565    format.await;
11566    assert_eq!(
11567        editor.update(cx, |editor, cx| editor.text(cx)),
11568        "one\ntwo\nthree\n"
11569    );
11570}
11571
11572#[gpui::test]
11573async fn test_multiple_formatters(cx: &mut TestAppContext) {
11574    init_test(cx, |settings| {
11575        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11576        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11577            Formatter::LanguageServer { name: None },
11578            Formatter::CodeActions(
11579                [
11580                    ("code-action-1".into(), true),
11581                    ("code-action-2".into(), true),
11582                ]
11583                .into_iter()
11584                .collect(),
11585            ),
11586        ])))
11587    });
11588
11589    let fs = FakeFs::new(cx.executor());
11590    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11591        .await;
11592
11593    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11594    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11595    language_registry.add(rust_lang());
11596
11597    let mut fake_servers = language_registry.register_fake_lsp(
11598        "Rust",
11599        FakeLspAdapter {
11600            capabilities: lsp::ServerCapabilities {
11601                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11602                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11603                    commands: vec!["the-command-for-code-action-1".into()],
11604                    ..Default::default()
11605                }),
11606                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11607                ..Default::default()
11608            },
11609            ..Default::default()
11610        },
11611    );
11612
11613    let buffer = project
11614        .update(cx, |project, cx| {
11615            project.open_local_buffer(path!("/file.rs"), cx)
11616        })
11617        .await
11618        .unwrap();
11619
11620    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11621    let (editor, cx) = cx.add_window_view(|window, cx| {
11622        build_editor_with_project(project.clone(), buffer, window, cx)
11623    });
11624
11625    cx.executor().start_waiting();
11626
11627    let fake_server = fake_servers.next().await.unwrap();
11628    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11629        move |_params, _| async move {
11630            Ok(Some(vec![lsp::TextEdit::new(
11631                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11632                "applied-formatting\n".to_string(),
11633            )]))
11634        },
11635    );
11636    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11637        move |params, _| async move {
11638            assert_eq!(
11639                params.context.only,
11640                Some(vec!["code-action-1".into(), "code-action-2".into()])
11641            );
11642            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11643            Ok(Some(vec![
11644                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11645                    kind: Some("code-action-1".into()),
11646                    edit: Some(lsp::WorkspaceEdit::new(
11647                        [(
11648                            uri.clone(),
11649                            vec![lsp::TextEdit::new(
11650                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11651                                "applied-code-action-1-edit\n".to_string(),
11652                            )],
11653                        )]
11654                        .into_iter()
11655                        .collect(),
11656                    )),
11657                    command: Some(lsp::Command {
11658                        command: "the-command-for-code-action-1".into(),
11659                        ..Default::default()
11660                    }),
11661                    ..Default::default()
11662                }),
11663                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11664                    kind: Some("code-action-2".into()),
11665                    edit: Some(lsp::WorkspaceEdit::new(
11666                        [(
11667                            uri,
11668                            vec![lsp::TextEdit::new(
11669                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11670                                "applied-code-action-2-edit\n".to_string(),
11671                            )],
11672                        )]
11673                        .into_iter()
11674                        .collect(),
11675                    )),
11676                    ..Default::default()
11677                }),
11678            ]))
11679        },
11680    );
11681
11682    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11683        move |params, _| async move { Ok(params) }
11684    });
11685
11686    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11687    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11688        let fake = fake_server.clone();
11689        let lock = command_lock.clone();
11690        move |params, _| {
11691            assert_eq!(params.command, "the-command-for-code-action-1");
11692            let fake = fake.clone();
11693            let lock = lock.clone();
11694            async move {
11695                lock.lock().await;
11696                fake.server
11697                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11698                        label: None,
11699                        edit: lsp::WorkspaceEdit {
11700                            changes: Some(
11701                                [(
11702                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11703                                    vec![lsp::TextEdit {
11704                                        range: lsp::Range::new(
11705                                            lsp::Position::new(0, 0),
11706                                            lsp::Position::new(0, 0),
11707                                        ),
11708                                        new_text: "applied-code-action-1-command\n".into(),
11709                                    }],
11710                                )]
11711                                .into_iter()
11712                                .collect(),
11713                            ),
11714                            ..Default::default()
11715                        },
11716                    })
11717                    .await
11718                    .into_response()
11719                    .unwrap();
11720                Ok(Some(json!(null)))
11721            }
11722        }
11723    });
11724
11725    cx.executor().start_waiting();
11726    editor
11727        .update_in(cx, |editor, window, cx| {
11728            editor.perform_format(
11729                project.clone(),
11730                FormatTrigger::Manual,
11731                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11732                window,
11733                cx,
11734            )
11735        })
11736        .unwrap()
11737        .await;
11738    editor.update(cx, |editor, cx| {
11739        assert_eq!(
11740            editor.text(cx),
11741            r#"
11742                applied-code-action-2-edit
11743                applied-code-action-1-command
11744                applied-code-action-1-edit
11745                applied-formatting
11746                one
11747                two
11748                three
11749            "#
11750            .unindent()
11751        );
11752    });
11753
11754    editor.update_in(cx, |editor, window, cx| {
11755        editor.undo(&Default::default(), window, cx);
11756        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11757    });
11758
11759    // Perform a manual edit while waiting for an LSP command
11760    // that's being run as part of a formatting code action.
11761    let lock_guard = command_lock.lock().await;
11762    let format = editor
11763        .update_in(cx, |editor, window, cx| {
11764            editor.perform_format(
11765                project.clone(),
11766                FormatTrigger::Manual,
11767                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11768                window,
11769                cx,
11770            )
11771        })
11772        .unwrap();
11773    cx.run_until_parked();
11774    editor.update(cx, |editor, cx| {
11775        assert_eq!(
11776            editor.text(cx),
11777            r#"
11778                applied-code-action-1-edit
11779                applied-formatting
11780                one
11781                two
11782                three
11783            "#
11784            .unindent()
11785        );
11786
11787        editor.buffer.update(cx, |buffer, cx| {
11788            let ix = buffer.len(cx);
11789            buffer.edit([(ix..ix, "edited\n")], None, cx);
11790        });
11791    });
11792
11793    // Allow the LSP command to proceed. Because the buffer was edited,
11794    // the second code action will not be run.
11795    drop(lock_guard);
11796    format.await;
11797    editor.update_in(cx, |editor, window, cx| {
11798        assert_eq!(
11799            editor.text(cx),
11800            r#"
11801                applied-code-action-1-command
11802                applied-code-action-1-edit
11803                applied-formatting
11804                one
11805                two
11806                three
11807                edited
11808            "#
11809            .unindent()
11810        );
11811
11812        // The manual edit is undone first, because it is the last thing the user did
11813        // (even though the command completed afterwards).
11814        editor.undo(&Default::default(), window, cx);
11815        assert_eq!(
11816            editor.text(cx),
11817            r#"
11818                applied-code-action-1-command
11819                applied-code-action-1-edit
11820                applied-formatting
11821                one
11822                two
11823                three
11824            "#
11825            .unindent()
11826        );
11827
11828        // All the formatting (including the command, which completed after the manual edit)
11829        // is undone together.
11830        editor.undo(&Default::default(), window, cx);
11831        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11832    });
11833}
11834
11835#[gpui::test]
11836async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
11837    init_test(cx, |settings| {
11838        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11839            Formatter::LanguageServer { name: None },
11840        ])))
11841    });
11842
11843    let fs = FakeFs::new(cx.executor());
11844    fs.insert_file(path!("/file.ts"), Default::default()).await;
11845
11846    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11847
11848    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11849    language_registry.add(Arc::new(Language::new(
11850        LanguageConfig {
11851            name: "TypeScript".into(),
11852            matcher: LanguageMatcher {
11853                path_suffixes: vec!["ts".to_string()],
11854                ..Default::default()
11855            },
11856            ..LanguageConfig::default()
11857        },
11858        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11859    )));
11860    update_test_language_settings(cx, |settings| {
11861        settings.defaults.prettier = Some(PrettierSettings {
11862            allowed: true,
11863            ..PrettierSettings::default()
11864        });
11865    });
11866    let mut fake_servers = language_registry.register_fake_lsp(
11867        "TypeScript",
11868        FakeLspAdapter {
11869            capabilities: lsp::ServerCapabilities {
11870                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11871                ..Default::default()
11872            },
11873            ..Default::default()
11874        },
11875    );
11876
11877    let buffer = project
11878        .update(cx, |project, cx| {
11879            project.open_local_buffer(path!("/file.ts"), cx)
11880        })
11881        .await
11882        .unwrap();
11883
11884    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11885    let (editor, cx) = cx.add_window_view(|window, cx| {
11886        build_editor_with_project(project.clone(), buffer, window, cx)
11887    });
11888    editor.update_in(cx, |editor, window, cx| {
11889        editor.set_text(
11890            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11891            window,
11892            cx,
11893        )
11894    });
11895
11896    cx.executor().start_waiting();
11897    let fake_server = fake_servers.next().await.unwrap();
11898
11899    let format = editor
11900        .update_in(cx, |editor, window, cx| {
11901            editor.perform_code_action_kind(
11902                project.clone(),
11903                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11904                window,
11905                cx,
11906            )
11907        })
11908        .unwrap();
11909    fake_server
11910        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
11911            assert_eq!(
11912                params.text_document.uri,
11913                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
11914            );
11915            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11916                lsp::CodeAction {
11917                    title: "Organize Imports".to_string(),
11918                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
11919                    edit: Some(lsp::WorkspaceEdit {
11920                        changes: Some(
11921                            [(
11922                                params.text_document.uri.clone(),
11923                                vec![lsp::TextEdit::new(
11924                                    lsp::Range::new(
11925                                        lsp::Position::new(1, 0),
11926                                        lsp::Position::new(2, 0),
11927                                    ),
11928                                    "".to_string(),
11929                                )],
11930                            )]
11931                            .into_iter()
11932                            .collect(),
11933                        ),
11934                        ..Default::default()
11935                    }),
11936                    ..Default::default()
11937                },
11938            )]))
11939        })
11940        .next()
11941        .await;
11942    cx.executor().start_waiting();
11943    format.await;
11944    assert_eq!(
11945        editor.update(cx, |editor, cx| editor.text(cx)),
11946        "import { a } from 'module';\n\nconst x = a;\n"
11947    );
11948
11949    editor.update_in(cx, |editor, window, cx| {
11950        editor.set_text(
11951            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11952            window,
11953            cx,
11954        )
11955    });
11956    // Ensure we don't lock if code action hangs.
11957    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11958        move |params, _| async move {
11959            assert_eq!(
11960                params.text_document.uri,
11961                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
11962            );
11963            futures::future::pending::<()>().await;
11964            unreachable!()
11965        },
11966    );
11967    let format = editor
11968        .update_in(cx, |editor, window, cx| {
11969            editor.perform_code_action_kind(
11970                project,
11971                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11972                window,
11973                cx,
11974            )
11975        })
11976        .unwrap();
11977    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
11978    cx.executor().start_waiting();
11979    format.await;
11980    assert_eq!(
11981        editor.update(cx, |editor, cx| editor.text(cx)),
11982        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
11983    );
11984}
11985
11986#[gpui::test]
11987async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
11988    init_test(cx, |_| {});
11989
11990    let mut cx = EditorLspTestContext::new_rust(
11991        lsp::ServerCapabilities {
11992            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11993            ..Default::default()
11994        },
11995        cx,
11996    )
11997    .await;
11998
11999    cx.set_state(indoc! {"
12000        one.twoˇ
12001    "});
12002
12003    // The format request takes a long time. When it completes, it inserts
12004    // a newline and an indent before the `.`
12005    cx.lsp
12006        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12007            let executor = cx.background_executor().clone();
12008            async move {
12009                executor.timer(Duration::from_millis(100)).await;
12010                Ok(Some(vec![lsp::TextEdit {
12011                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12012                    new_text: "\n    ".into(),
12013                }]))
12014            }
12015        });
12016
12017    // Submit a format request.
12018    let format_1 = cx
12019        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12020        .unwrap();
12021    cx.executor().run_until_parked();
12022
12023    // Submit a second format request.
12024    let format_2 = cx
12025        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12026        .unwrap();
12027    cx.executor().run_until_parked();
12028
12029    // Wait for both format requests to complete
12030    cx.executor().advance_clock(Duration::from_millis(200));
12031    cx.executor().start_waiting();
12032    format_1.await.unwrap();
12033    cx.executor().start_waiting();
12034    format_2.await.unwrap();
12035
12036    // The formatting edits only happens once.
12037    cx.assert_editor_state(indoc! {"
12038        one
12039            .twoˇ
12040    "});
12041}
12042
12043#[gpui::test]
12044async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12045    init_test(cx, |settings| {
12046        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12047    });
12048
12049    let mut cx = EditorLspTestContext::new_rust(
12050        lsp::ServerCapabilities {
12051            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12052            ..Default::default()
12053        },
12054        cx,
12055    )
12056    .await;
12057
12058    // Set up a buffer white some trailing whitespace and no trailing newline.
12059    cx.set_state(
12060        &[
12061            "one ",   //
12062            "twoˇ",   //
12063            "three ", //
12064            "four",   //
12065        ]
12066        .join("\n"),
12067    );
12068
12069    // Submit a format request.
12070    let format = cx
12071        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12072        .unwrap();
12073
12074    // Record which buffer changes have been sent to the language server
12075    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12076    cx.lsp
12077        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12078            let buffer_changes = buffer_changes.clone();
12079            move |params, _| {
12080                buffer_changes.lock().extend(
12081                    params
12082                        .content_changes
12083                        .into_iter()
12084                        .map(|e| (e.range.unwrap(), e.text)),
12085                );
12086            }
12087        });
12088
12089    // Handle formatting requests to the language server.
12090    cx.lsp
12091        .set_request_handler::<lsp::request::Formatting, _, _>({
12092            let buffer_changes = buffer_changes.clone();
12093            move |_, _| {
12094                // When formatting is requested, trailing whitespace has already been stripped,
12095                // and the trailing newline has already been added.
12096                assert_eq!(
12097                    &buffer_changes.lock()[1..],
12098                    &[
12099                        (
12100                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12101                            "".into()
12102                        ),
12103                        (
12104                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12105                            "".into()
12106                        ),
12107                        (
12108                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12109                            "\n".into()
12110                        ),
12111                    ]
12112                );
12113
12114                // Insert blank lines between each line of the buffer.
12115                async move {
12116                    Ok(Some(vec![
12117                        lsp::TextEdit {
12118                            range: lsp::Range::new(
12119                                lsp::Position::new(1, 0),
12120                                lsp::Position::new(1, 0),
12121                            ),
12122                            new_text: "\n".into(),
12123                        },
12124                        lsp::TextEdit {
12125                            range: lsp::Range::new(
12126                                lsp::Position::new(2, 0),
12127                                lsp::Position::new(2, 0),
12128                            ),
12129                            new_text: "\n".into(),
12130                        },
12131                    ]))
12132                }
12133            }
12134        });
12135
12136    // After formatting the buffer, the trailing whitespace is stripped,
12137    // a newline is appended, and the edits provided by the language server
12138    // have been applied.
12139    format.await.unwrap();
12140    cx.assert_editor_state(
12141        &[
12142            "one",   //
12143            "",      //
12144            "twoˇ",  //
12145            "",      //
12146            "three", //
12147            "four",  //
12148            "",      //
12149        ]
12150        .join("\n"),
12151    );
12152
12153    // Undoing the formatting undoes the trailing whitespace removal, the
12154    // trailing newline, and the LSP edits.
12155    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12156    cx.assert_editor_state(
12157        &[
12158            "one ",   //
12159            "twoˇ",   //
12160            "three ", //
12161            "four",   //
12162        ]
12163        .join("\n"),
12164    );
12165}
12166
12167#[gpui::test]
12168async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12169    cx: &mut TestAppContext,
12170) {
12171    init_test(cx, |_| {});
12172
12173    cx.update(|cx| {
12174        cx.update_global::<SettingsStore, _>(|settings, cx| {
12175            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12176                settings.auto_signature_help = Some(true);
12177            });
12178        });
12179    });
12180
12181    let mut cx = EditorLspTestContext::new_rust(
12182        lsp::ServerCapabilities {
12183            signature_help_provider: Some(lsp::SignatureHelpOptions {
12184                ..Default::default()
12185            }),
12186            ..Default::default()
12187        },
12188        cx,
12189    )
12190    .await;
12191
12192    let language = Language::new(
12193        LanguageConfig {
12194            name: "Rust".into(),
12195            brackets: BracketPairConfig {
12196                pairs: vec![
12197                    BracketPair {
12198                        start: "{".to_string(),
12199                        end: "}".to_string(),
12200                        close: true,
12201                        surround: true,
12202                        newline: true,
12203                    },
12204                    BracketPair {
12205                        start: "(".to_string(),
12206                        end: ")".to_string(),
12207                        close: true,
12208                        surround: true,
12209                        newline: true,
12210                    },
12211                    BracketPair {
12212                        start: "/*".to_string(),
12213                        end: " */".to_string(),
12214                        close: true,
12215                        surround: true,
12216                        newline: true,
12217                    },
12218                    BracketPair {
12219                        start: "[".to_string(),
12220                        end: "]".to_string(),
12221                        close: false,
12222                        surround: false,
12223                        newline: true,
12224                    },
12225                    BracketPair {
12226                        start: "\"".to_string(),
12227                        end: "\"".to_string(),
12228                        close: true,
12229                        surround: true,
12230                        newline: false,
12231                    },
12232                    BracketPair {
12233                        start: "<".to_string(),
12234                        end: ">".to_string(),
12235                        close: false,
12236                        surround: true,
12237                        newline: true,
12238                    },
12239                ],
12240                ..Default::default()
12241            },
12242            autoclose_before: "})]".to_string(),
12243            ..Default::default()
12244        },
12245        Some(tree_sitter_rust::LANGUAGE.into()),
12246    );
12247    let language = Arc::new(language);
12248
12249    cx.language_registry().add(language.clone());
12250    cx.update_buffer(|buffer, cx| {
12251        buffer.set_language(Some(language), cx);
12252    });
12253
12254    cx.set_state(
12255        &r#"
12256            fn main() {
12257                sampleˇ
12258            }
12259        "#
12260        .unindent(),
12261    );
12262
12263    cx.update_editor(|editor, window, cx| {
12264        editor.handle_input("(", window, cx);
12265    });
12266    cx.assert_editor_state(
12267        &"
12268            fn main() {
12269                sample(ˇ)
12270            }
12271        "
12272        .unindent(),
12273    );
12274
12275    let mocked_response = lsp::SignatureHelp {
12276        signatures: vec![lsp::SignatureInformation {
12277            label: "fn sample(param1: u8, param2: u8)".to_string(),
12278            documentation: None,
12279            parameters: Some(vec![
12280                lsp::ParameterInformation {
12281                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12282                    documentation: None,
12283                },
12284                lsp::ParameterInformation {
12285                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12286                    documentation: None,
12287                },
12288            ]),
12289            active_parameter: None,
12290        }],
12291        active_signature: Some(0),
12292        active_parameter: Some(0),
12293    };
12294    handle_signature_help_request(&mut cx, mocked_response).await;
12295
12296    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12297        .await;
12298
12299    cx.editor(|editor, _, _| {
12300        let signature_help_state = editor.signature_help_state.popover().cloned();
12301        let signature = signature_help_state.unwrap();
12302        assert_eq!(
12303            signature.signatures[signature.current_signature].label,
12304            "fn sample(param1: u8, param2: u8)"
12305        );
12306    });
12307}
12308
12309#[gpui::test]
12310async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12311    init_test(cx, |_| {});
12312
12313    cx.update(|cx| {
12314        cx.update_global::<SettingsStore, _>(|settings, cx| {
12315            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12316                settings.auto_signature_help = Some(false);
12317                settings.show_signature_help_after_edits = Some(false);
12318            });
12319        });
12320    });
12321
12322    let mut cx = EditorLspTestContext::new_rust(
12323        lsp::ServerCapabilities {
12324            signature_help_provider: Some(lsp::SignatureHelpOptions {
12325                ..Default::default()
12326            }),
12327            ..Default::default()
12328        },
12329        cx,
12330    )
12331    .await;
12332
12333    let language = Language::new(
12334        LanguageConfig {
12335            name: "Rust".into(),
12336            brackets: BracketPairConfig {
12337                pairs: vec![
12338                    BracketPair {
12339                        start: "{".to_string(),
12340                        end: "}".to_string(),
12341                        close: true,
12342                        surround: true,
12343                        newline: true,
12344                    },
12345                    BracketPair {
12346                        start: "(".to_string(),
12347                        end: ")".to_string(),
12348                        close: true,
12349                        surround: true,
12350                        newline: true,
12351                    },
12352                    BracketPair {
12353                        start: "/*".to_string(),
12354                        end: " */".to_string(),
12355                        close: true,
12356                        surround: true,
12357                        newline: true,
12358                    },
12359                    BracketPair {
12360                        start: "[".to_string(),
12361                        end: "]".to_string(),
12362                        close: false,
12363                        surround: false,
12364                        newline: true,
12365                    },
12366                    BracketPair {
12367                        start: "\"".to_string(),
12368                        end: "\"".to_string(),
12369                        close: true,
12370                        surround: true,
12371                        newline: false,
12372                    },
12373                    BracketPair {
12374                        start: "<".to_string(),
12375                        end: ">".to_string(),
12376                        close: false,
12377                        surround: true,
12378                        newline: true,
12379                    },
12380                ],
12381                ..Default::default()
12382            },
12383            autoclose_before: "})]".to_string(),
12384            ..Default::default()
12385        },
12386        Some(tree_sitter_rust::LANGUAGE.into()),
12387    );
12388    let language = Arc::new(language);
12389
12390    cx.language_registry().add(language.clone());
12391    cx.update_buffer(|buffer, cx| {
12392        buffer.set_language(Some(language), cx);
12393    });
12394
12395    // Ensure that signature_help is not called when no signature help is enabled.
12396    cx.set_state(
12397        &r#"
12398            fn main() {
12399                sampleˇ
12400            }
12401        "#
12402        .unindent(),
12403    );
12404    cx.update_editor(|editor, window, cx| {
12405        editor.handle_input("(", window, cx);
12406    });
12407    cx.assert_editor_state(
12408        &"
12409            fn main() {
12410                sample(ˇ)
12411            }
12412        "
12413        .unindent(),
12414    );
12415    cx.editor(|editor, _, _| {
12416        assert!(editor.signature_help_state.task().is_none());
12417    });
12418
12419    let mocked_response = lsp::SignatureHelp {
12420        signatures: vec![lsp::SignatureInformation {
12421            label: "fn sample(param1: u8, param2: u8)".to_string(),
12422            documentation: None,
12423            parameters: Some(vec![
12424                lsp::ParameterInformation {
12425                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12426                    documentation: None,
12427                },
12428                lsp::ParameterInformation {
12429                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12430                    documentation: None,
12431                },
12432            ]),
12433            active_parameter: None,
12434        }],
12435        active_signature: Some(0),
12436        active_parameter: Some(0),
12437    };
12438
12439    // Ensure that signature_help is called when enabled afte edits
12440    cx.update(|_, cx| {
12441        cx.update_global::<SettingsStore, _>(|settings, cx| {
12442            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12443                settings.auto_signature_help = Some(false);
12444                settings.show_signature_help_after_edits = Some(true);
12445            });
12446        });
12447    });
12448    cx.set_state(
12449        &r#"
12450            fn main() {
12451                sampleˇ
12452            }
12453        "#
12454        .unindent(),
12455    );
12456    cx.update_editor(|editor, window, cx| {
12457        editor.handle_input("(", window, cx);
12458    });
12459    cx.assert_editor_state(
12460        &"
12461            fn main() {
12462                sample(ˇ)
12463            }
12464        "
12465        .unindent(),
12466    );
12467    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12468    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12469        .await;
12470    cx.update_editor(|editor, _, _| {
12471        let signature_help_state = editor.signature_help_state.popover().cloned();
12472        assert!(signature_help_state.is_some());
12473        let signature = signature_help_state.unwrap();
12474        assert_eq!(
12475            signature.signatures[signature.current_signature].label,
12476            "fn sample(param1: u8, param2: u8)"
12477        );
12478        editor.signature_help_state = SignatureHelpState::default();
12479    });
12480
12481    // Ensure that signature_help is called when auto signature help override is enabled
12482    cx.update(|_, cx| {
12483        cx.update_global::<SettingsStore, _>(|settings, cx| {
12484            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12485                settings.auto_signature_help = Some(true);
12486                settings.show_signature_help_after_edits = Some(false);
12487            });
12488        });
12489    });
12490    cx.set_state(
12491        &r#"
12492            fn main() {
12493                sampleˇ
12494            }
12495        "#
12496        .unindent(),
12497    );
12498    cx.update_editor(|editor, window, cx| {
12499        editor.handle_input("(", window, cx);
12500    });
12501    cx.assert_editor_state(
12502        &"
12503            fn main() {
12504                sample(ˇ)
12505            }
12506        "
12507        .unindent(),
12508    );
12509    handle_signature_help_request(&mut cx, mocked_response).await;
12510    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12511        .await;
12512    cx.editor(|editor, _, _| {
12513        let signature_help_state = editor.signature_help_state.popover().cloned();
12514        assert!(signature_help_state.is_some());
12515        let signature = signature_help_state.unwrap();
12516        assert_eq!(
12517            signature.signatures[signature.current_signature].label,
12518            "fn sample(param1: u8, param2: u8)"
12519        );
12520    });
12521}
12522
12523#[gpui::test]
12524async fn test_signature_help(cx: &mut TestAppContext) {
12525    init_test(cx, |_| {});
12526    cx.update(|cx| {
12527        cx.update_global::<SettingsStore, _>(|settings, cx| {
12528            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12529                settings.auto_signature_help = Some(true);
12530            });
12531        });
12532    });
12533
12534    let mut cx = EditorLspTestContext::new_rust(
12535        lsp::ServerCapabilities {
12536            signature_help_provider: Some(lsp::SignatureHelpOptions {
12537                ..Default::default()
12538            }),
12539            ..Default::default()
12540        },
12541        cx,
12542    )
12543    .await;
12544
12545    // A test that directly calls `show_signature_help`
12546    cx.update_editor(|editor, window, cx| {
12547        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12548    });
12549
12550    let mocked_response = lsp::SignatureHelp {
12551        signatures: vec![lsp::SignatureInformation {
12552            label: "fn sample(param1: u8, param2: u8)".to_string(),
12553            documentation: None,
12554            parameters: Some(vec![
12555                lsp::ParameterInformation {
12556                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12557                    documentation: None,
12558                },
12559                lsp::ParameterInformation {
12560                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12561                    documentation: None,
12562                },
12563            ]),
12564            active_parameter: None,
12565        }],
12566        active_signature: Some(0),
12567        active_parameter: Some(0),
12568    };
12569    handle_signature_help_request(&mut cx, mocked_response).await;
12570
12571    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12572        .await;
12573
12574    cx.editor(|editor, _, _| {
12575        let signature_help_state = editor.signature_help_state.popover().cloned();
12576        assert!(signature_help_state.is_some());
12577        let signature = signature_help_state.unwrap();
12578        assert_eq!(
12579            signature.signatures[signature.current_signature].label,
12580            "fn sample(param1: u8, param2: u8)"
12581        );
12582    });
12583
12584    // When exiting outside from inside the brackets, `signature_help` is closed.
12585    cx.set_state(indoc! {"
12586        fn main() {
12587            sample(ˇ);
12588        }
12589
12590        fn sample(param1: u8, param2: u8) {}
12591    "});
12592
12593    cx.update_editor(|editor, window, cx| {
12594        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12595            s.select_ranges([0..0])
12596        });
12597    });
12598
12599    let mocked_response = lsp::SignatureHelp {
12600        signatures: Vec::new(),
12601        active_signature: None,
12602        active_parameter: None,
12603    };
12604    handle_signature_help_request(&mut cx, mocked_response).await;
12605
12606    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12607        .await;
12608
12609    cx.editor(|editor, _, _| {
12610        assert!(!editor.signature_help_state.is_shown());
12611    });
12612
12613    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12614    cx.set_state(indoc! {"
12615        fn main() {
12616            sample(ˇ);
12617        }
12618
12619        fn sample(param1: u8, param2: u8) {}
12620    "});
12621
12622    let mocked_response = lsp::SignatureHelp {
12623        signatures: vec![lsp::SignatureInformation {
12624            label: "fn sample(param1: u8, param2: u8)".to_string(),
12625            documentation: None,
12626            parameters: Some(vec![
12627                lsp::ParameterInformation {
12628                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12629                    documentation: None,
12630                },
12631                lsp::ParameterInformation {
12632                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12633                    documentation: None,
12634                },
12635            ]),
12636            active_parameter: None,
12637        }],
12638        active_signature: Some(0),
12639        active_parameter: Some(0),
12640    };
12641    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12642    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12643        .await;
12644    cx.editor(|editor, _, _| {
12645        assert!(editor.signature_help_state.is_shown());
12646    });
12647
12648    // Restore the popover with more parameter input
12649    cx.set_state(indoc! {"
12650        fn main() {
12651            sample(param1, param2ˇ);
12652        }
12653
12654        fn sample(param1: u8, param2: u8) {}
12655    "});
12656
12657    let mocked_response = lsp::SignatureHelp {
12658        signatures: vec![lsp::SignatureInformation {
12659            label: "fn sample(param1: u8, param2: u8)".to_string(),
12660            documentation: None,
12661            parameters: Some(vec![
12662                lsp::ParameterInformation {
12663                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12664                    documentation: None,
12665                },
12666                lsp::ParameterInformation {
12667                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12668                    documentation: None,
12669                },
12670            ]),
12671            active_parameter: None,
12672        }],
12673        active_signature: Some(0),
12674        active_parameter: Some(1),
12675    };
12676    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12677    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12678        .await;
12679
12680    // When selecting a range, the popover is gone.
12681    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12682    cx.update_editor(|editor, window, cx| {
12683        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12684            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12685        })
12686    });
12687    cx.assert_editor_state(indoc! {"
12688        fn main() {
12689            sample(param1, «ˇparam2»);
12690        }
12691
12692        fn sample(param1: u8, param2: u8) {}
12693    "});
12694    cx.editor(|editor, _, _| {
12695        assert!(!editor.signature_help_state.is_shown());
12696    });
12697
12698    // When unselecting again, the popover is back if within the brackets.
12699    cx.update_editor(|editor, window, cx| {
12700        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12701            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12702        })
12703    });
12704    cx.assert_editor_state(indoc! {"
12705        fn main() {
12706            sample(param1, ˇparam2);
12707        }
12708
12709        fn sample(param1: u8, param2: u8) {}
12710    "});
12711    handle_signature_help_request(&mut cx, mocked_response).await;
12712    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12713        .await;
12714    cx.editor(|editor, _, _| {
12715        assert!(editor.signature_help_state.is_shown());
12716    });
12717
12718    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
12719    cx.update_editor(|editor, window, cx| {
12720        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12721            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
12722            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12723        })
12724    });
12725    cx.assert_editor_state(indoc! {"
12726        fn main() {
12727            sample(param1, ˇparam2);
12728        }
12729
12730        fn sample(param1: u8, param2: u8) {}
12731    "});
12732
12733    let mocked_response = lsp::SignatureHelp {
12734        signatures: vec![lsp::SignatureInformation {
12735            label: "fn sample(param1: u8, param2: u8)".to_string(),
12736            documentation: None,
12737            parameters: Some(vec![
12738                lsp::ParameterInformation {
12739                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12740                    documentation: None,
12741                },
12742                lsp::ParameterInformation {
12743                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12744                    documentation: None,
12745                },
12746            ]),
12747            active_parameter: None,
12748        }],
12749        active_signature: Some(0),
12750        active_parameter: Some(1),
12751    };
12752    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12753    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12754        .await;
12755    cx.update_editor(|editor, _, cx| {
12756        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
12757    });
12758    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12759        .await;
12760    cx.update_editor(|editor, window, cx| {
12761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12762            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12763        })
12764    });
12765    cx.assert_editor_state(indoc! {"
12766        fn main() {
12767            sample(param1, «ˇparam2»);
12768        }
12769
12770        fn sample(param1: u8, param2: u8) {}
12771    "});
12772    cx.update_editor(|editor, window, cx| {
12773        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12774            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12775        })
12776    });
12777    cx.assert_editor_state(indoc! {"
12778        fn main() {
12779            sample(param1, ˇparam2);
12780        }
12781
12782        fn sample(param1: u8, param2: u8) {}
12783    "});
12784    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
12785        .await;
12786}
12787
12788#[gpui::test]
12789async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
12790    init_test(cx, |_| {});
12791
12792    let mut cx = EditorLspTestContext::new_rust(
12793        lsp::ServerCapabilities {
12794            signature_help_provider: Some(lsp::SignatureHelpOptions {
12795                ..Default::default()
12796            }),
12797            ..Default::default()
12798        },
12799        cx,
12800    )
12801    .await;
12802
12803    cx.set_state(indoc! {"
12804        fn main() {
12805            overloadedˇ
12806        }
12807    "});
12808
12809    cx.update_editor(|editor, window, cx| {
12810        editor.handle_input("(", window, cx);
12811        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12812    });
12813
12814    // Mock response with 3 signatures
12815    let mocked_response = lsp::SignatureHelp {
12816        signatures: vec![
12817            lsp::SignatureInformation {
12818                label: "fn overloaded(x: i32)".to_string(),
12819                documentation: None,
12820                parameters: Some(vec![lsp::ParameterInformation {
12821                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12822                    documentation: None,
12823                }]),
12824                active_parameter: None,
12825            },
12826            lsp::SignatureInformation {
12827                label: "fn overloaded(x: i32, y: i32)".to_string(),
12828                documentation: None,
12829                parameters: Some(vec![
12830                    lsp::ParameterInformation {
12831                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12832                        documentation: None,
12833                    },
12834                    lsp::ParameterInformation {
12835                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
12836                        documentation: None,
12837                    },
12838                ]),
12839                active_parameter: None,
12840            },
12841            lsp::SignatureInformation {
12842                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
12843                documentation: None,
12844                parameters: Some(vec![
12845                    lsp::ParameterInformation {
12846                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12847                        documentation: None,
12848                    },
12849                    lsp::ParameterInformation {
12850                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
12851                        documentation: None,
12852                    },
12853                    lsp::ParameterInformation {
12854                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
12855                        documentation: None,
12856                    },
12857                ]),
12858                active_parameter: None,
12859            },
12860        ],
12861        active_signature: Some(1),
12862        active_parameter: Some(0),
12863    };
12864    handle_signature_help_request(&mut cx, mocked_response).await;
12865
12866    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12867        .await;
12868
12869    // Verify we have multiple signatures and the right one is selected
12870    cx.editor(|editor, _, _| {
12871        let popover = editor.signature_help_state.popover().cloned().unwrap();
12872        assert_eq!(popover.signatures.len(), 3);
12873        // active_signature was 1, so that should be the current
12874        assert_eq!(popover.current_signature, 1);
12875        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
12876        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
12877        assert_eq!(
12878            popover.signatures[2].label,
12879            "fn overloaded(x: i32, y: i32, z: i32)"
12880        );
12881    });
12882
12883    // Test navigation functionality
12884    cx.update_editor(|editor, window, cx| {
12885        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12886    });
12887
12888    cx.editor(|editor, _, _| {
12889        let popover = editor.signature_help_state.popover().cloned().unwrap();
12890        assert_eq!(popover.current_signature, 2);
12891    });
12892
12893    // Test wrap around
12894    cx.update_editor(|editor, window, cx| {
12895        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12896    });
12897
12898    cx.editor(|editor, _, _| {
12899        let popover = editor.signature_help_state.popover().cloned().unwrap();
12900        assert_eq!(popover.current_signature, 0);
12901    });
12902
12903    // Test previous navigation
12904    cx.update_editor(|editor, window, cx| {
12905        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
12906    });
12907
12908    cx.editor(|editor, _, _| {
12909        let popover = editor.signature_help_state.popover().cloned().unwrap();
12910        assert_eq!(popover.current_signature, 2);
12911    });
12912}
12913
12914#[gpui::test]
12915async fn test_completion_mode(cx: &mut TestAppContext) {
12916    init_test(cx, |_| {});
12917    let mut cx = EditorLspTestContext::new_rust(
12918        lsp::ServerCapabilities {
12919            completion_provider: Some(lsp::CompletionOptions {
12920                resolve_provider: Some(true),
12921                ..Default::default()
12922            }),
12923            ..Default::default()
12924        },
12925        cx,
12926    )
12927    .await;
12928
12929    struct Run {
12930        run_description: &'static str,
12931        initial_state: String,
12932        buffer_marked_text: String,
12933        completion_label: &'static str,
12934        completion_text: &'static str,
12935        expected_with_insert_mode: String,
12936        expected_with_replace_mode: String,
12937        expected_with_replace_subsequence_mode: String,
12938        expected_with_replace_suffix_mode: String,
12939    }
12940
12941    let runs = [
12942        Run {
12943            run_description: "Start of word matches completion text",
12944            initial_state: "before ediˇ after".into(),
12945            buffer_marked_text: "before <edi|> after".into(),
12946            completion_label: "editor",
12947            completion_text: "editor",
12948            expected_with_insert_mode: "before editorˇ after".into(),
12949            expected_with_replace_mode: "before editorˇ after".into(),
12950            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12951            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12952        },
12953        Run {
12954            run_description: "Accept same text at the middle of the word",
12955            initial_state: "before ediˇtor after".into(),
12956            buffer_marked_text: "before <edi|tor> after".into(),
12957            completion_label: "editor",
12958            completion_text: "editor",
12959            expected_with_insert_mode: "before editorˇtor after".into(),
12960            expected_with_replace_mode: "before editorˇ after".into(),
12961            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12962            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12963        },
12964        Run {
12965            run_description: "End of word matches completion text -- cursor at end",
12966            initial_state: "before torˇ after".into(),
12967            buffer_marked_text: "before <tor|> after".into(),
12968            completion_label: "editor",
12969            completion_text: "editor",
12970            expected_with_insert_mode: "before editorˇ after".into(),
12971            expected_with_replace_mode: "before editorˇ after".into(),
12972            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12973            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12974        },
12975        Run {
12976            run_description: "End of word matches completion text -- cursor at start",
12977            initial_state: "before ˇtor after".into(),
12978            buffer_marked_text: "before <|tor> after".into(),
12979            completion_label: "editor",
12980            completion_text: "editor",
12981            expected_with_insert_mode: "before editorˇtor after".into(),
12982            expected_with_replace_mode: "before editorˇ after".into(),
12983            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12984            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12985        },
12986        Run {
12987            run_description: "Prepend text containing whitespace",
12988            initial_state: "pˇfield: bool".into(),
12989            buffer_marked_text: "<p|field>: bool".into(),
12990            completion_label: "pub ",
12991            completion_text: "pub ",
12992            expected_with_insert_mode: "pub ˇfield: bool".into(),
12993            expected_with_replace_mode: "pub ˇ: bool".into(),
12994            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
12995            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
12996        },
12997        Run {
12998            run_description: "Add element to start of list",
12999            initial_state: "[element_ˇelement_2]".into(),
13000            buffer_marked_text: "[<element_|element_2>]".into(),
13001            completion_label: "element_1",
13002            completion_text: "element_1",
13003            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13004            expected_with_replace_mode: "[element_1ˇ]".into(),
13005            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13006            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13007        },
13008        Run {
13009            run_description: "Add element to start of list -- first and second elements are equal",
13010            initial_state: "[elˇelement]".into(),
13011            buffer_marked_text: "[<el|element>]".into(),
13012            completion_label: "element",
13013            completion_text: "element",
13014            expected_with_insert_mode: "[elementˇelement]".into(),
13015            expected_with_replace_mode: "[elementˇ]".into(),
13016            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13017            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13018        },
13019        Run {
13020            run_description: "Ends with matching suffix",
13021            initial_state: "SubˇError".into(),
13022            buffer_marked_text: "<Sub|Error>".into(),
13023            completion_label: "SubscriptionError",
13024            completion_text: "SubscriptionError",
13025            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13026            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13027            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13028            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13029        },
13030        Run {
13031            run_description: "Suffix is a subsequence -- contiguous",
13032            initial_state: "SubˇErr".into(),
13033            buffer_marked_text: "<Sub|Err>".into(),
13034            completion_label: "SubscriptionError",
13035            completion_text: "SubscriptionError",
13036            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13037            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13038            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13039            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13040        },
13041        Run {
13042            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13043            initial_state: "Suˇscrirr".into(),
13044            buffer_marked_text: "<Su|scrirr>".into(),
13045            completion_label: "SubscriptionError",
13046            completion_text: "SubscriptionError",
13047            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13048            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13049            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13050            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13051        },
13052        Run {
13053            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13054            initial_state: "foo(indˇix)".into(),
13055            buffer_marked_text: "foo(<ind|ix>)".into(),
13056            completion_label: "node_index",
13057            completion_text: "node_index",
13058            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13059            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13060            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13061            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13062        },
13063        Run {
13064            run_description: "Replace range ends before cursor - should extend to cursor",
13065            initial_state: "before editˇo after".into(),
13066            buffer_marked_text: "before <{ed}>it|o after".into(),
13067            completion_label: "editor",
13068            completion_text: "editor",
13069            expected_with_insert_mode: "before editorˇo after".into(),
13070            expected_with_replace_mode: "before editorˇo after".into(),
13071            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13072            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13073        },
13074        Run {
13075            run_description: "Uses label for suffix matching",
13076            initial_state: "before ediˇtor after".into(),
13077            buffer_marked_text: "before <edi|tor> after".into(),
13078            completion_label: "editor",
13079            completion_text: "editor()",
13080            expected_with_insert_mode: "before editor()ˇtor after".into(),
13081            expected_with_replace_mode: "before editor()ˇ after".into(),
13082            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13083            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13084        },
13085        Run {
13086            run_description: "Case insensitive subsequence and suffix matching",
13087            initial_state: "before EDiˇtoR after".into(),
13088            buffer_marked_text: "before <EDi|toR> after".into(),
13089            completion_label: "editor",
13090            completion_text: "editor",
13091            expected_with_insert_mode: "before editorˇtoR after".into(),
13092            expected_with_replace_mode: "before editorˇ after".into(),
13093            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13094            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13095        },
13096    ];
13097
13098    for run in runs {
13099        let run_variations = [
13100            (LspInsertMode::Insert, run.expected_with_insert_mode),
13101            (LspInsertMode::Replace, run.expected_with_replace_mode),
13102            (
13103                LspInsertMode::ReplaceSubsequence,
13104                run.expected_with_replace_subsequence_mode,
13105            ),
13106            (
13107                LspInsertMode::ReplaceSuffix,
13108                run.expected_with_replace_suffix_mode,
13109            ),
13110        ];
13111
13112        for (lsp_insert_mode, expected_text) in run_variations {
13113            eprintln!(
13114                "run = {:?}, mode = {lsp_insert_mode:.?}",
13115                run.run_description,
13116            );
13117
13118            update_test_language_settings(&mut cx, |settings| {
13119                settings.defaults.completions = Some(CompletionSettings {
13120                    lsp_insert_mode,
13121                    words: WordsCompletionMode::Disabled,
13122                    words_min_length: 0,
13123                    lsp: true,
13124                    lsp_fetch_timeout_ms: 0,
13125                });
13126            });
13127
13128            cx.set_state(&run.initial_state);
13129            cx.update_editor(|editor, window, cx| {
13130                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13131            });
13132
13133            let counter = Arc::new(AtomicUsize::new(0));
13134            handle_completion_request_with_insert_and_replace(
13135                &mut cx,
13136                &run.buffer_marked_text,
13137                vec![(run.completion_label, run.completion_text)],
13138                counter.clone(),
13139            )
13140            .await;
13141            cx.condition(|editor, _| editor.context_menu_visible())
13142                .await;
13143            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13144
13145            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13146                editor
13147                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13148                    .unwrap()
13149            });
13150            cx.assert_editor_state(&expected_text);
13151            handle_resolve_completion_request(&mut cx, None).await;
13152            apply_additional_edits.await.unwrap();
13153        }
13154    }
13155}
13156
13157#[gpui::test]
13158async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13159    init_test(cx, |_| {});
13160    let mut cx = EditorLspTestContext::new_rust(
13161        lsp::ServerCapabilities {
13162            completion_provider: Some(lsp::CompletionOptions {
13163                resolve_provider: Some(true),
13164                ..Default::default()
13165            }),
13166            ..Default::default()
13167        },
13168        cx,
13169    )
13170    .await;
13171
13172    let initial_state = "SubˇError";
13173    let buffer_marked_text = "<Sub|Error>";
13174    let completion_text = "SubscriptionError";
13175    let expected_with_insert_mode = "SubscriptionErrorˇError";
13176    let expected_with_replace_mode = "SubscriptionErrorˇ";
13177
13178    update_test_language_settings(&mut cx, |settings| {
13179        settings.defaults.completions = Some(CompletionSettings {
13180            words: WordsCompletionMode::Disabled,
13181            words_min_length: 0,
13182            // set the opposite here to ensure that the action is overriding the default behavior
13183            lsp_insert_mode: LspInsertMode::Insert,
13184            lsp: true,
13185            lsp_fetch_timeout_ms: 0,
13186        });
13187    });
13188
13189    cx.set_state(initial_state);
13190    cx.update_editor(|editor, window, cx| {
13191        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13192    });
13193
13194    let counter = Arc::new(AtomicUsize::new(0));
13195    handle_completion_request_with_insert_and_replace(
13196        &mut cx,
13197        buffer_marked_text,
13198        vec![(completion_text, completion_text)],
13199        counter.clone(),
13200    )
13201    .await;
13202    cx.condition(|editor, _| editor.context_menu_visible())
13203        .await;
13204    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13205
13206    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13207        editor
13208            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13209            .unwrap()
13210    });
13211    cx.assert_editor_state(expected_with_replace_mode);
13212    handle_resolve_completion_request(&mut cx, None).await;
13213    apply_additional_edits.await.unwrap();
13214
13215    update_test_language_settings(&mut cx, |settings| {
13216        settings.defaults.completions = Some(CompletionSettings {
13217            words: WordsCompletionMode::Disabled,
13218            words_min_length: 0,
13219            // set the opposite here to ensure that the action is overriding the default behavior
13220            lsp_insert_mode: LspInsertMode::Replace,
13221            lsp: true,
13222            lsp_fetch_timeout_ms: 0,
13223        });
13224    });
13225
13226    cx.set_state(initial_state);
13227    cx.update_editor(|editor, window, cx| {
13228        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13229    });
13230    handle_completion_request_with_insert_and_replace(
13231        &mut cx,
13232        buffer_marked_text,
13233        vec![(completion_text, completion_text)],
13234        counter.clone(),
13235    )
13236    .await;
13237    cx.condition(|editor, _| editor.context_menu_visible())
13238        .await;
13239    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13240
13241    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13242        editor
13243            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13244            .unwrap()
13245    });
13246    cx.assert_editor_state(expected_with_insert_mode);
13247    handle_resolve_completion_request(&mut cx, None).await;
13248    apply_additional_edits.await.unwrap();
13249}
13250
13251#[gpui::test]
13252async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13253    init_test(cx, |_| {});
13254    let mut cx = EditorLspTestContext::new_rust(
13255        lsp::ServerCapabilities {
13256            completion_provider: Some(lsp::CompletionOptions {
13257                resolve_provider: Some(true),
13258                ..Default::default()
13259            }),
13260            ..Default::default()
13261        },
13262        cx,
13263    )
13264    .await;
13265
13266    // scenario: surrounding text matches completion text
13267    let completion_text = "to_offset";
13268    let initial_state = indoc! {"
13269        1. buf.to_offˇsuffix
13270        2. buf.to_offˇsuf
13271        3. buf.to_offˇfix
13272        4. buf.to_offˇ
13273        5. into_offˇensive
13274        6. ˇsuffix
13275        7. let ˇ //
13276        8. aaˇzz
13277        9. buf.to_off«zzzzzˇ»suffix
13278        10. buf.«ˇzzzzz»suffix
13279        11. to_off«ˇzzzzz»
13280
13281        buf.to_offˇsuffix  // newest cursor
13282    "};
13283    let completion_marked_buffer = indoc! {"
13284        1. buf.to_offsuffix
13285        2. buf.to_offsuf
13286        3. buf.to_offfix
13287        4. buf.to_off
13288        5. into_offensive
13289        6. suffix
13290        7. let  //
13291        8. aazz
13292        9. buf.to_offzzzzzsuffix
13293        10. buf.zzzzzsuffix
13294        11. to_offzzzzz
13295
13296        buf.<to_off|suffix>  // newest cursor
13297    "};
13298    let expected = indoc! {"
13299        1. buf.to_offsetˇ
13300        2. buf.to_offsetˇsuf
13301        3. buf.to_offsetˇfix
13302        4. buf.to_offsetˇ
13303        5. into_offsetˇensive
13304        6. to_offsetˇsuffix
13305        7. let to_offsetˇ //
13306        8. aato_offsetˇzz
13307        9. buf.to_offsetˇ
13308        10. buf.to_offsetˇsuffix
13309        11. to_offsetˇ
13310
13311        buf.to_offsetˇ  // newest cursor
13312    "};
13313    cx.set_state(initial_state);
13314    cx.update_editor(|editor, window, cx| {
13315        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13316    });
13317    handle_completion_request_with_insert_and_replace(
13318        &mut cx,
13319        completion_marked_buffer,
13320        vec![(completion_text, completion_text)],
13321        Arc::new(AtomicUsize::new(0)),
13322    )
13323    .await;
13324    cx.condition(|editor, _| editor.context_menu_visible())
13325        .await;
13326    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13327        editor
13328            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13329            .unwrap()
13330    });
13331    cx.assert_editor_state(expected);
13332    handle_resolve_completion_request(&mut cx, None).await;
13333    apply_additional_edits.await.unwrap();
13334
13335    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13336    let completion_text = "foo_and_bar";
13337    let initial_state = indoc! {"
13338        1. ooanbˇ
13339        2. zooanbˇ
13340        3. ooanbˇz
13341        4. zooanbˇz
13342        5. ooanˇ
13343        6. oanbˇ
13344
13345        ooanbˇ
13346    "};
13347    let completion_marked_buffer = indoc! {"
13348        1. ooanb
13349        2. zooanb
13350        3. ooanbz
13351        4. zooanbz
13352        5. ooan
13353        6. oanb
13354
13355        <ooanb|>
13356    "};
13357    let expected = indoc! {"
13358        1. foo_and_barˇ
13359        2. zfoo_and_barˇ
13360        3. foo_and_barˇz
13361        4. zfoo_and_barˇz
13362        5. ooanfoo_and_barˇ
13363        6. oanbfoo_and_barˇ
13364
13365        foo_and_barˇ
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    handle_completion_request_with_insert_and_replace(
13372        &mut cx,
13373        completion_marked_buffer,
13374        vec![(completion_text, completion_text)],
13375        Arc::new(AtomicUsize::new(0)),
13376    )
13377    .await;
13378    cx.condition(|editor, _| editor.context_menu_visible())
13379        .await;
13380    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13381        editor
13382            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13383            .unwrap()
13384    });
13385    cx.assert_editor_state(expected);
13386    handle_resolve_completion_request(&mut cx, None).await;
13387    apply_additional_edits.await.unwrap();
13388
13389    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13390    // (expects the same as if it was inserted at the end)
13391    let completion_text = "foo_and_bar";
13392    let initial_state = indoc! {"
13393        1. ooˇanb
13394        2. zooˇanb
13395        3. ooˇanbz
13396        4. zooˇanbz
13397
13398        ooˇanb
13399    "};
13400    let completion_marked_buffer = indoc! {"
13401        1. ooanb
13402        2. zooanb
13403        3. ooanbz
13404        4. zooanbz
13405
13406        <oo|anb>
13407    "};
13408    let expected = indoc! {"
13409        1. foo_and_barˇ
13410        2. zfoo_and_barˇ
13411        3. foo_and_barˇz
13412        4. zfoo_and_barˇz
13413
13414        foo_and_barˇ
13415    "};
13416    cx.set_state(initial_state);
13417    cx.update_editor(|editor, window, cx| {
13418        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13419    });
13420    handle_completion_request_with_insert_and_replace(
13421        &mut cx,
13422        completion_marked_buffer,
13423        vec![(completion_text, completion_text)],
13424        Arc::new(AtomicUsize::new(0)),
13425    )
13426    .await;
13427    cx.condition(|editor, _| editor.context_menu_visible())
13428        .await;
13429    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13430        editor
13431            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13432            .unwrap()
13433    });
13434    cx.assert_editor_state(expected);
13435    handle_resolve_completion_request(&mut cx, None).await;
13436    apply_additional_edits.await.unwrap();
13437}
13438
13439// This used to crash
13440#[gpui::test]
13441async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13442    init_test(cx, |_| {});
13443
13444    let buffer_text = indoc! {"
13445        fn main() {
13446            10.satu;
13447
13448            //
13449            // separate cursors so they open in different excerpts (manually reproducible)
13450            //
13451
13452            10.satu20;
13453        }
13454    "};
13455    let multibuffer_text_with_selections = indoc! {"
13456        fn main() {
13457            10.satuˇ;
13458
13459            //
13460
13461            //
13462
13463            10.satuˇ20;
13464        }
13465    "};
13466    let expected_multibuffer = indoc! {"
13467        fn main() {
13468            10.saturating_sub()ˇ;
13469
13470            //
13471
13472            //
13473
13474            10.saturating_sub()ˇ;
13475        }
13476    "};
13477
13478    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13479    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13480
13481    let fs = FakeFs::new(cx.executor());
13482    fs.insert_tree(
13483        path!("/a"),
13484        json!({
13485            "main.rs": buffer_text,
13486        }),
13487    )
13488    .await;
13489
13490    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13491    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13492    language_registry.add(rust_lang());
13493    let mut fake_servers = language_registry.register_fake_lsp(
13494        "Rust",
13495        FakeLspAdapter {
13496            capabilities: lsp::ServerCapabilities {
13497                completion_provider: Some(lsp::CompletionOptions {
13498                    resolve_provider: None,
13499                    ..lsp::CompletionOptions::default()
13500                }),
13501                ..lsp::ServerCapabilities::default()
13502            },
13503            ..FakeLspAdapter::default()
13504        },
13505    );
13506    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13507    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13508    let buffer = project
13509        .update(cx, |project, cx| {
13510            project.open_local_buffer(path!("/a/main.rs"), cx)
13511        })
13512        .await
13513        .unwrap();
13514
13515    let multi_buffer = cx.new(|cx| {
13516        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13517        multi_buffer.push_excerpts(
13518            buffer.clone(),
13519            [ExcerptRange::new(0..first_excerpt_end)],
13520            cx,
13521        );
13522        multi_buffer.push_excerpts(
13523            buffer.clone(),
13524            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13525            cx,
13526        );
13527        multi_buffer
13528    });
13529
13530    let editor = workspace
13531        .update(cx, |_, window, cx| {
13532            cx.new(|cx| {
13533                Editor::new(
13534                    EditorMode::Full {
13535                        scale_ui_elements_with_buffer_font_size: false,
13536                        show_active_line_background: false,
13537                        sized_by_content: false,
13538                    },
13539                    multi_buffer.clone(),
13540                    Some(project.clone()),
13541                    window,
13542                    cx,
13543                )
13544            })
13545        })
13546        .unwrap();
13547
13548    let pane = workspace
13549        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13550        .unwrap();
13551    pane.update_in(cx, |pane, window, cx| {
13552        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13553    });
13554
13555    let fake_server = fake_servers.next().await.unwrap();
13556
13557    editor.update_in(cx, |editor, window, cx| {
13558        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13559            s.select_ranges([
13560                Point::new(1, 11)..Point::new(1, 11),
13561                Point::new(7, 11)..Point::new(7, 11),
13562            ])
13563        });
13564
13565        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13566    });
13567
13568    editor.update_in(cx, |editor, window, cx| {
13569        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13570    });
13571
13572    fake_server
13573        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13574            let completion_item = lsp::CompletionItem {
13575                label: "saturating_sub()".into(),
13576                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13577                    lsp::InsertReplaceEdit {
13578                        new_text: "saturating_sub()".to_owned(),
13579                        insert: lsp::Range::new(
13580                            lsp::Position::new(7, 7),
13581                            lsp::Position::new(7, 11),
13582                        ),
13583                        replace: lsp::Range::new(
13584                            lsp::Position::new(7, 7),
13585                            lsp::Position::new(7, 13),
13586                        ),
13587                    },
13588                )),
13589                ..lsp::CompletionItem::default()
13590            };
13591
13592            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13593        })
13594        .next()
13595        .await
13596        .unwrap();
13597
13598    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13599        .await;
13600
13601    editor
13602        .update_in(cx, |editor, window, cx| {
13603            editor
13604                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13605                .unwrap()
13606        })
13607        .await
13608        .unwrap();
13609
13610    editor.update(cx, |editor, cx| {
13611        assert_text_with_selections(editor, expected_multibuffer, cx);
13612    })
13613}
13614
13615#[gpui::test]
13616async fn test_completion(cx: &mut TestAppContext) {
13617    init_test(cx, |_| {});
13618
13619    let mut cx = EditorLspTestContext::new_rust(
13620        lsp::ServerCapabilities {
13621            completion_provider: Some(lsp::CompletionOptions {
13622                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13623                resolve_provider: Some(true),
13624                ..Default::default()
13625            }),
13626            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13627            ..Default::default()
13628        },
13629        cx,
13630    )
13631    .await;
13632    let counter = Arc::new(AtomicUsize::new(0));
13633
13634    cx.set_state(indoc! {"
13635        oneˇ
13636        two
13637        three
13638    "});
13639    cx.simulate_keystroke(".");
13640    handle_completion_request(
13641        indoc! {"
13642            one.|<>
13643            two
13644            three
13645        "},
13646        vec!["first_completion", "second_completion"],
13647        true,
13648        counter.clone(),
13649        &mut cx,
13650    )
13651    .await;
13652    cx.condition(|editor, _| editor.context_menu_visible())
13653        .await;
13654    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13655
13656    let _handler = handle_signature_help_request(
13657        &mut cx,
13658        lsp::SignatureHelp {
13659            signatures: vec![lsp::SignatureInformation {
13660                label: "test signature".to_string(),
13661                documentation: None,
13662                parameters: Some(vec![lsp::ParameterInformation {
13663                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13664                    documentation: None,
13665                }]),
13666                active_parameter: None,
13667            }],
13668            active_signature: None,
13669            active_parameter: None,
13670        },
13671    );
13672    cx.update_editor(|editor, window, cx| {
13673        assert!(
13674            !editor.signature_help_state.is_shown(),
13675            "No signature help was called for"
13676        );
13677        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13678    });
13679    cx.run_until_parked();
13680    cx.update_editor(|editor, _, _| {
13681        assert!(
13682            !editor.signature_help_state.is_shown(),
13683            "No signature help should be shown when completions menu is open"
13684        );
13685    });
13686
13687    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13688        editor.context_menu_next(&Default::default(), window, cx);
13689        editor
13690            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13691            .unwrap()
13692    });
13693    cx.assert_editor_state(indoc! {"
13694        one.second_completionˇ
13695        two
13696        three
13697    "});
13698
13699    handle_resolve_completion_request(
13700        &mut cx,
13701        Some(vec![
13702            (
13703                //This overlaps with the primary completion edit which is
13704                //misbehavior from the LSP spec, test that we filter it out
13705                indoc! {"
13706                    one.second_ˇcompletion
13707                    two
13708                    threeˇ
13709                "},
13710                "overlapping additional edit",
13711            ),
13712            (
13713                indoc! {"
13714                    one.second_completion
13715                    two
13716                    threeˇ
13717                "},
13718                "\nadditional edit",
13719            ),
13720        ]),
13721    )
13722    .await;
13723    apply_additional_edits.await.unwrap();
13724    cx.assert_editor_state(indoc! {"
13725        one.second_completionˇ
13726        two
13727        three
13728        additional edit
13729    "});
13730
13731    cx.set_state(indoc! {"
13732        one.second_completion
13733        twoˇ
13734        threeˇ
13735        additional edit
13736    "});
13737    cx.simulate_keystroke(" ");
13738    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13739    cx.simulate_keystroke("s");
13740    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13741
13742    cx.assert_editor_state(indoc! {"
13743        one.second_completion
13744        two sˇ
13745        three sˇ
13746        additional edit
13747    "});
13748    handle_completion_request(
13749        indoc! {"
13750            one.second_completion
13751            two s
13752            three <s|>
13753            additional edit
13754        "},
13755        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13756        true,
13757        counter.clone(),
13758        &mut cx,
13759    )
13760    .await;
13761    cx.condition(|editor, _| editor.context_menu_visible())
13762        .await;
13763    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13764
13765    cx.simulate_keystroke("i");
13766
13767    handle_completion_request(
13768        indoc! {"
13769            one.second_completion
13770            two si
13771            three <si|>
13772            additional edit
13773        "},
13774        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13775        true,
13776        counter.clone(),
13777        &mut cx,
13778    )
13779    .await;
13780    cx.condition(|editor, _| editor.context_menu_visible())
13781        .await;
13782    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13783
13784    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13785        editor
13786            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13787            .unwrap()
13788    });
13789    cx.assert_editor_state(indoc! {"
13790        one.second_completion
13791        two sixth_completionˇ
13792        three sixth_completionˇ
13793        additional edit
13794    "});
13795
13796    apply_additional_edits.await.unwrap();
13797
13798    update_test_language_settings(&mut cx, |settings| {
13799        settings.defaults.show_completions_on_input = Some(false);
13800    });
13801    cx.set_state("editorˇ");
13802    cx.simulate_keystroke(".");
13803    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13804    cx.simulate_keystrokes("c l o");
13805    cx.assert_editor_state("editor.cloˇ");
13806    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13807    cx.update_editor(|editor, window, cx| {
13808        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13809    });
13810    handle_completion_request(
13811        "editor.<clo|>",
13812        vec!["close", "clobber"],
13813        true,
13814        counter.clone(),
13815        &mut cx,
13816    )
13817    .await;
13818    cx.condition(|editor, _| editor.context_menu_visible())
13819        .await;
13820    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
13821
13822    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13823        editor
13824            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13825            .unwrap()
13826    });
13827    cx.assert_editor_state("editor.clobberˇ");
13828    handle_resolve_completion_request(&mut cx, None).await;
13829    apply_additional_edits.await.unwrap();
13830}
13831
13832#[gpui::test]
13833async fn test_completion_reuse(cx: &mut TestAppContext) {
13834    init_test(cx, |_| {});
13835
13836    let mut cx = EditorLspTestContext::new_rust(
13837        lsp::ServerCapabilities {
13838            completion_provider: Some(lsp::CompletionOptions {
13839                trigger_characters: Some(vec![".".to_string()]),
13840                ..Default::default()
13841            }),
13842            ..Default::default()
13843        },
13844        cx,
13845    )
13846    .await;
13847
13848    let counter = Arc::new(AtomicUsize::new(0));
13849    cx.set_state("objˇ");
13850    cx.simulate_keystroke(".");
13851
13852    // Initial completion request returns complete results
13853    let is_incomplete = false;
13854    handle_completion_request(
13855        "obj.|<>",
13856        vec!["a", "ab", "abc"],
13857        is_incomplete,
13858        counter.clone(),
13859        &mut cx,
13860    )
13861    .await;
13862    cx.run_until_parked();
13863    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13864    cx.assert_editor_state("obj.ˇ");
13865    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13866
13867    // Type "a" - filters existing completions
13868    cx.simulate_keystroke("a");
13869    cx.run_until_parked();
13870    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13871    cx.assert_editor_state("obj.aˇ");
13872    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13873
13874    // Type "b" - filters existing completions
13875    cx.simulate_keystroke("b");
13876    cx.run_until_parked();
13877    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13878    cx.assert_editor_state("obj.abˇ");
13879    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13880
13881    // Type "c" - filters existing completions
13882    cx.simulate_keystroke("c");
13883    cx.run_until_parked();
13884    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13885    cx.assert_editor_state("obj.abcˇ");
13886    check_displayed_completions(vec!["abc"], &mut cx);
13887
13888    // Backspace to delete "c" - filters existing completions
13889    cx.update_editor(|editor, window, cx| {
13890        editor.backspace(&Backspace, window, cx);
13891    });
13892    cx.run_until_parked();
13893    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13894    cx.assert_editor_state("obj.abˇ");
13895    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13896
13897    // Moving cursor to the left dismisses menu.
13898    cx.update_editor(|editor, window, cx| {
13899        editor.move_left(&MoveLeft, window, cx);
13900    });
13901    cx.run_until_parked();
13902    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13903    cx.assert_editor_state("obj.aˇb");
13904    cx.update_editor(|editor, _, _| {
13905        assert_eq!(editor.context_menu_visible(), false);
13906    });
13907
13908    // Type "b" - new request
13909    cx.simulate_keystroke("b");
13910    let is_incomplete = false;
13911    handle_completion_request(
13912        "obj.<ab|>a",
13913        vec!["ab", "abc"],
13914        is_incomplete,
13915        counter.clone(),
13916        &mut cx,
13917    )
13918    .await;
13919    cx.run_until_parked();
13920    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13921    cx.assert_editor_state("obj.abˇb");
13922    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13923
13924    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
13925    cx.update_editor(|editor, window, cx| {
13926        editor.backspace(&Backspace, window, cx);
13927    });
13928    let is_incomplete = false;
13929    handle_completion_request(
13930        "obj.<a|>b",
13931        vec!["a", "ab", "abc"],
13932        is_incomplete,
13933        counter.clone(),
13934        &mut cx,
13935    )
13936    .await;
13937    cx.run_until_parked();
13938    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13939    cx.assert_editor_state("obj.aˇb");
13940    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13941
13942    // Backspace to delete "a" - dismisses menu.
13943    cx.update_editor(|editor, window, cx| {
13944        editor.backspace(&Backspace, window, cx);
13945    });
13946    cx.run_until_parked();
13947    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13948    cx.assert_editor_state("obj.ˇb");
13949    cx.update_editor(|editor, _, _| {
13950        assert_eq!(editor.context_menu_visible(), false);
13951    });
13952}
13953
13954#[gpui::test]
13955async fn test_word_completion(cx: &mut TestAppContext) {
13956    let lsp_fetch_timeout_ms = 10;
13957    init_test(cx, |language_settings| {
13958        language_settings.defaults.completions = Some(CompletionSettings {
13959            words: WordsCompletionMode::Fallback,
13960            words_min_length: 0,
13961            lsp: true,
13962            lsp_fetch_timeout_ms: 10,
13963            lsp_insert_mode: LspInsertMode::Insert,
13964        });
13965    });
13966
13967    let mut cx = EditorLspTestContext::new_rust(
13968        lsp::ServerCapabilities {
13969            completion_provider: Some(lsp::CompletionOptions {
13970                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13971                ..lsp::CompletionOptions::default()
13972            }),
13973            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13974            ..lsp::ServerCapabilities::default()
13975        },
13976        cx,
13977    )
13978    .await;
13979
13980    let throttle_completions = Arc::new(AtomicBool::new(false));
13981
13982    let lsp_throttle_completions = throttle_completions.clone();
13983    let _completion_requests_handler =
13984        cx.lsp
13985            .server
13986            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
13987                let lsp_throttle_completions = lsp_throttle_completions.clone();
13988                let cx = cx.clone();
13989                async move {
13990                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
13991                        cx.background_executor()
13992                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
13993                            .await;
13994                    }
13995                    Ok(Some(lsp::CompletionResponse::Array(vec![
13996                        lsp::CompletionItem {
13997                            label: "first".into(),
13998                            ..lsp::CompletionItem::default()
13999                        },
14000                        lsp::CompletionItem {
14001                            label: "last".into(),
14002                            ..lsp::CompletionItem::default()
14003                        },
14004                    ])))
14005                }
14006            });
14007
14008    cx.set_state(indoc! {"
14009        oneˇ
14010        two
14011        three
14012    "});
14013    cx.simulate_keystroke(".");
14014    cx.executor().run_until_parked();
14015    cx.condition(|editor, _| editor.context_menu_visible())
14016        .await;
14017    cx.update_editor(|editor, window, cx| {
14018        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14019        {
14020            assert_eq!(
14021                completion_menu_entries(menu),
14022                &["first", "last"],
14023                "When LSP server is fast to reply, no fallback word completions are used"
14024            );
14025        } else {
14026            panic!("expected completion menu to be open");
14027        }
14028        editor.cancel(&Cancel, window, cx);
14029    });
14030    cx.executor().run_until_parked();
14031    cx.condition(|editor, _| !editor.context_menu_visible())
14032        .await;
14033
14034    throttle_completions.store(true, atomic::Ordering::Release);
14035    cx.simulate_keystroke(".");
14036    cx.executor()
14037        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14038    cx.executor().run_until_parked();
14039    cx.condition(|editor, _| editor.context_menu_visible())
14040        .await;
14041    cx.update_editor(|editor, _, _| {
14042        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14043        {
14044            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14045                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14046        } else {
14047            panic!("expected completion menu to be open");
14048        }
14049    });
14050}
14051
14052#[gpui::test]
14053async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14054    init_test(cx, |language_settings| {
14055        language_settings.defaults.completions = Some(CompletionSettings {
14056            words: WordsCompletionMode::Enabled,
14057            words_min_length: 0,
14058            lsp: true,
14059            lsp_fetch_timeout_ms: 0,
14060            lsp_insert_mode: LspInsertMode::Insert,
14061        });
14062    });
14063
14064    let mut cx = EditorLspTestContext::new_rust(
14065        lsp::ServerCapabilities {
14066            completion_provider: Some(lsp::CompletionOptions {
14067                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14068                ..lsp::CompletionOptions::default()
14069            }),
14070            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14071            ..lsp::ServerCapabilities::default()
14072        },
14073        cx,
14074    )
14075    .await;
14076
14077    let _completion_requests_handler =
14078        cx.lsp
14079            .server
14080            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14081                Ok(Some(lsp::CompletionResponse::Array(vec![
14082                    lsp::CompletionItem {
14083                        label: "first".into(),
14084                        ..lsp::CompletionItem::default()
14085                    },
14086                    lsp::CompletionItem {
14087                        label: "last".into(),
14088                        ..lsp::CompletionItem::default()
14089                    },
14090                ])))
14091            });
14092
14093    cx.set_state(indoc! {"ˇ
14094        first
14095        last
14096        second
14097    "});
14098    cx.simulate_keystroke(".");
14099    cx.executor().run_until_parked();
14100    cx.condition(|editor, _| editor.context_menu_visible())
14101        .await;
14102    cx.update_editor(|editor, _, _| {
14103        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14104        {
14105            assert_eq!(
14106                completion_menu_entries(menu),
14107                &["first", "last", "second"],
14108                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14109            );
14110        } else {
14111            panic!("expected completion menu to be open");
14112        }
14113    });
14114}
14115
14116#[gpui::test]
14117async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14118    init_test(cx, |language_settings| {
14119        language_settings.defaults.completions = Some(CompletionSettings {
14120            words: WordsCompletionMode::Disabled,
14121            words_min_length: 0,
14122            lsp: true,
14123            lsp_fetch_timeout_ms: 0,
14124            lsp_insert_mode: LspInsertMode::Insert,
14125        });
14126    });
14127
14128    let mut cx = EditorLspTestContext::new_rust(
14129        lsp::ServerCapabilities {
14130            completion_provider: Some(lsp::CompletionOptions {
14131                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14132                ..lsp::CompletionOptions::default()
14133            }),
14134            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14135            ..lsp::ServerCapabilities::default()
14136        },
14137        cx,
14138    )
14139    .await;
14140
14141    let _completion_requests_handler =
14142        cx.lsp
14143            .server
14144            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14145                panic!("LSP completions should not be queried when dealing with word completions")
14146            });
14147
14148    cx.set_state(indoc! {"ˇ
14149        first
14150        last
14151        second
14152    "});
14153    cx.update_editor(|editor, window, cx| {
14154        editor.show_word_completions(&ShowWordCompletions, window, cx);
14155    });
14156    cx.executor().run_until_parked();
14157    cx.condition(|editor, _| editor.context_menu_visible())
14158        .await;
14159    cx.update_editor(|editor, _, _| {
14160        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14161        {
14162            assert_eq!(
14163                completion_menu_entries(menu),
14164                &["first", "last", "second"],
14165                "`ShowWordCompletions` action should show word completions"
14166            );
14167        } else {
14168            panic!("expected completion menu to be open");
14169        }
14170    });
14171
14172    cx.simulate_keystroke("l");
14173    cx.executor().run_until_parked();
14174    cx.condition(|editor, _| editor.context_menu_visible())
14175        .await;
14176    cx.update_editor(|editor, _, _| {
14177        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14178        {
14179            assert_eq!(
14180                completion_menu_entries(menu),
14181                &["last"],
14182                "After showing word completions, further editing should filter them and not query the LSP"
14183            );
14184        } else {
14185            panic!("expected completion menu to be open");
14186        }
14187    });
14188}
14189
14190#[gpui::test]
14191async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14192    init_test(cx, |language_settings| {
14193        language_settings.defaults.completions = Some(CompletionSettings {
14194            words: WordsCompletionMode::Fallback,
14195            words_min_length: 0,
14196            lsp: false,
14197            lsp_fetch_timeout_ms: 0,
14198            lsp_insert_mode: LspInsertMode::Insert,
14199        });
14200    });
14201
14202    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14203
14204    cx.set_state(indoc! {"ˇ
14205        0_usize
14206        let
14207        33
14208        4.5f32
14209    "});
14210    cx.update_editor(|editor, window, cx| {
14211        editor.show_completions(&ShowCompletions::default(), window, cx);
14212    });
14213    cx.executor().run_until_parked();
14214    cx.condition(|editor, _| editor.context_menu_visible())
14215        .await;
14216    cx.update_editor(|editor, window, cx| {
14217        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14218        {
14219            assert_eq!(
14220                completion_menu_entries(menu),
14221                &["let"],
14222                "With no digits in the completion query, no digits should be in the word completions"
14223            );
14224        } else {
14225            panic!("expected completion menu to be open");
14226        }
14227        editor.cancel(&Cancel, window, cx);
14228    });
14229
14230    cx.set_state(indoc! {"14231        0_usize
14232        let
14233        3
14234        33.35f32
14235    "});
14236    cx.update_editor(|editor, window, cx| {
14237        editor.show_completions(&ShowCompletions::default(), window, cx);
14238    });
14239    cx.executor().run_until_parked();
14240    cx.condition(|editor, _| editor.context_menu_visible())
14241        .await;
14242    cx.update_editor(|editor, _, _| {
14243        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14244        {
14245            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14246                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14247        } else {
14248            panic!("expected completion menu to be open");
14249        }
14250    });
14251}
14252
14253#[gpui::test]
14254async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14255    init_test(cx, |language_settings| {
14256        language_settings.defaults.completions = Some(CompletionSettings {
14257            words: WordsCompletionMode::Enabled,
14258            words_min_length: 3,
14259            lsp: true,
14260            lsp_fetch_timeout_ms: 0,
14261            lsp_insert_mode: LspInsertMode::Insert,
14262        });
14263    });
14264
14265    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14266    cx.set_state(indoc! {"ˇ
14267        wow
14268        wowen
14269        wowser
14270    "});
14271    cx.simulate_keystroke("w");
14272    cx.executor().run_until_parked();
14273    cx.update_editor(|editor, _, _| {
14274        if editor.context_menu.borrow_mut().is_some() {
14275            panic!(
14276                "expected completion menu to be hidden, as words completion threshold is not met"
14277            );
14278        }
14279    });
14280
14281    cx.simulate_keystroke("o");
14282    cx.executor().run_until_parked();
14283    cx.update_editor(|editor, _, _| {
14284        if editor.context_menu.borrow_mut().is_some() {
14285            panic!(
14286                "expected completion menu to be hidden, as words completion threshold is not met still"
14287            );
14288        }
14289    });
14290
14291    cx.simulate_keystroke("w");
14292    cx.executor().run_until_parked();
14293    cx.update_editor(|editor, _, _| {
14294        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14295        {
14296            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14297        } else {
14298            panic!("expected completion menu to be open after the word completions threshold is met");
14299        }
14300    });
14301}
14302
14303fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14304    let position = || lsp::Position {
14305        line: params.text_document_position.position.line,
14306        character: params.text_document_position.position.character,
14307    };
14308    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14309        range: lsp::Range {
14310            start: position(),
14311            end: position(),
14312        },
14313        new_text: text.to_string(),
14314    }))
14315}
14316
14317#[gpui::test]
14318async fn test_multiline_completion(cx: &mut TestAppContext) {
14319    init_test(cx, |_| {});
14320
14321    let fs = FakeFs::new(cx.executor());
14322    fs.insert_tree(
14323        path!("/a"),
14324        json!({
14325            "main.ts": "a",
14326        }),
14327    )
14328    .await;
14329
14330    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14331    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14332    let typescript_language = Arc::new(Language::new(
14333        LanguageConfig {
14334            name: "TypeScript".into(),
14335            matcher: LanguageMatcher {
14336                path_suffixes: vec!["ts".to_string()],
14337                ..LanguageMatcher::default()
14338            },
14339            line_comments: vec!["// ".into()],
14340            ..LanguageConfig::default()
14341        },
14342        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14343    ));
14344    language_registry.add(typescript_language.clone());
14345    let mut fake_servers = language_registry.register_fake_lsp(
14346        "TypeScript",
14347        FakeLspAdapter {
14348            capabilities: lsp::ServerCapabilities {
14349                completion_provider: Some(lsp::CompletionOptions {
14350                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14351                    ..lsp::CompletionOptions::default()
14352                }),
14353                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14354                ..lsp::ServerCapabilities::default()
14355            },
14356            // Emulate vtsls label generation
14357            label_for_completion: Some(Box::new(|item, _| {
14358                let text = if let Some(description) = item
14359                    .label_details
14360                    .as_ref()
14361                    .and_then(|label_details| label_details.description.as_ref())
14362                {
14363                    format!("{} {}", item.label, description)
14364                } else if let Some(detail) = &item.detail {
14365                    format!("{} {}", item.label, detail)
14366                } else {
14367                    item.label.clone()
14368                };
14369                let len = text.len();
14370                Some(language::CodeLabel {
14371                    text,
14372                    runs: Vec::new(),
14373                    filter_range: 0..len,
14374                })
14375            })),
14376            ..FakeLspAdapter::default()
14377        },
14378    );
14379    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14380    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14381    let worktree_id = workspace
14382        .update(cx, |workspace, _window, cx| {
14383            workspace.project().update(cx, |project, cx| {
14384                project.worktrees(cx).next().unwrap().read(cx).id()
14385            })
14386        })
14387        .unwrap();
14388    let _buffer = project
14389        .update(cx, |project, cx| {
14390            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14391        })
14392        .await
14393        .unwrap();
14394    let editor = workspace
14395        .update(cx, |workspace, window, cx| {
14396            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14397        })
14398        .unwrap()
14399        .await
14400        .unwrap()
14401        .downcast::<Editor>()
14402        .unwrap();
14403    let fake_server = fake_servers.next().await.unwrap();
14404
14405    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14406    let multiline_label_2 = "a\nb\nc\n";
14407    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14408    let multiline_description = "d\ne\nf\n";
14409    let multiline_detail_2 = "g\nh\ni\n";
14410
14411    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14412        move |params, _| async move {
14413            Ok(Some(lsp::CompletionResponse::Array(vec![
14414                lsp::CompletionItem {
14415                    label: multiline_label.to_string(),
14416                    text_edit: gen_text_edit(&params, "new_text_1"),
14417                    ..lsp::CompletionItem::default()
14418                },
14419                lsp::CompletionItem {
14420                    label: "single line label 1".to_string(),
14421                    detail: Some(multiline_detail.to_string()),
14422                    text_edit: gen_text_edit(&params, "new_text_2"),
14423                    ..lsp::CompletionItem::default()
14424                },
14425                lsp::CompletionItem {
14426                    label: "single line label 2".to_string(),
14427                    label_details: Some(lsp::CompletionItemLabelDetails {
14428                        description: Some(multiline_description.to_string()),
14429                        detail: None,
14430                    }),
14431                    text_edit: gen_text_edit(&params, "new_text_2"),
14432                    ..lsp::CompletionItem::default()
14433                },
14434                lsp::CompletionItem {
14435                    label: multiline_label_2.to_string(),
14436                    detail: Some(multiline_detail_2.to_string()),
14437                    text_edit: gen_text_edit(&params, "new_text_3"),
14438                    ..lsp::CompletionItem::default()
14439                },
14440                lsp::CompletionItem {
14441                    label: "Label with many     spaces and \t but without newlines".to_string(),
14442                    detail: Some(
14443                        "Details with many     spaces and \t but without newlines".to_string(),
14444                    ),
14445                    text_edit: gen_text_edit(&params, "new_text_4"),
14446                    ..lsp::CompletionItem::default()
14447                },
14448            ])))
14449        },
14450    );
14451
14452    editor.update_in(cx, |editor, window, cx| {
14453        cx.focus_self(window);
14454        editor.move_to_end(&MoveToEnd, window, cx);
14455        editor.handle_input(".", window, cx);
14456    });
14457    cx.run_until_parked();
14458    completion_handle.next().await.unwrap();
14459
14460    editor.update(cx, |editor, _| {
14461        assert!(editor.context_menu_visible());
14462        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14463        {
14464            let completion_labels = menu
14465                .completions
14466                .borrow()
14467                .iter()
14468                .map(|c| c.label.text.clone())
14469                .collect::<Vec<_>>();
14470            assert_eq!(
14471                completion_labels,
14472                &[
14473                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14474                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14475                    "single line label 2 d e f ",
14476                    "a b c g h i ",
14477                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14478                ],
14479                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14480            );
14481
14482            for completion in menu
14483                .completions
14484                .borrow()
14485                .iter() {
14486                    assert_eq!(
14487                        completion.label.filter_range,
14488                        0..completion.label.text.len(),
14489                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14490                    );
14491                }
14492        } else {
14493            panic!("expected completion menu to be open");
14494        }
14495    });
14496}
14497
14498#[gpui::test]
14499async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14500    init_test(cx, |_| {});
14501    let mut cx = EditorLspTestContext::new_rust(
14502        lsp::ServerCapabilities {
14503            completion_provider: Some(lsp::CompletionOptions {
14504                trigger_characters: Some(vec![".".to_string()]),
14505                ..Default::default()
14506            }),
14507            ..Default::default()
14508        },
14509        cx,
14510    )
14511    .await;
14512    cx.lsp
14513        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14514            Ok(Some(lsp::CompletionResponse::Array(vec![
14515                lsp::CompletionItem {
14516                    label: "first".into(),
14517                    ..Default::default()
14518                },
14519                lsp::CompletionItem {
14520                    label: "last".into(),
14521                    ..Default::default()
14522                },
14523            ])))
14524        });
14525    cx.set_state("variableˇ");
14526    cx.simulate_keystroke(".");
14527    cx.executor().run_until_parked();
14528
14529    cx.update_editor(|editor, _, _| {
14530        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14531        {
14532            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14533        } else {
14534            panic!("expected completion menu to be open");
14535        }
14536    });
14537
14538    cx.update_editor(|editor, window, cx| {
14539        editor.move_page_down(&MovePageDown::default(), window, cx);
14540        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14541        {
14542            assert!(
14543                menu.selected_item == 1,
14544                "expected PageDown to select the last item from the context menu"
14545            );
14546        } else {
14547            panic!("expected completion menu to stay open after PageDown");
14548        }
14549    });
14550
14551    cx.update_editor(|editor, window, cx| {
14552        editor.move_page_up(&MovePageUp::default(), window, cx);
14553        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14554        {
14555            assert!(
14556                menu.selected_item == 0,
14557                "expected PageUp to select the first item from the context menu"
14558            );
14559        } else {
14560            panic!("expected completion menu to stay open after PageUp");
14561        }
14562    });
14563}
14564
14565#[gpui::test]
14566async fn test_as_is_completions(cx: &mut TestAppContext) {
14567    init_test(cx, |_| {});
14568    let mut cx = EditorLspTestContext::new_rust(
14569        lsp::ServerCapabilities {
14570            completion_provider: Some(lsp::CompletionOptions {
14571                ..Default::default()
14572            }),
14573            ..Default::default()
14574        },
14575        cx,
14576    )
14577    .await;
14578    cx.lsp
14579        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14580            Ok(Some(lsp::CompletionResponse::Array(vec![
14581                lsp::CompletionItem {
14582                    label: "unsafe".into(),
14583                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14584                        range: lsp::Range {
14585                            start: lsp::Position {
14586                                line: 1,
14587                                character: 2,
14588                            },
14589                            end: lsp::Position {
14590                                line: 1,
14591                                character: 3,
14592                            },
14593                        },
14594                        new_text: "unsafe".to_string(),
14595                    })),
14596                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14597                    ..Default::default()
14598                },
14599            ])))
14600        });
14601    cx.set_state("fn a() {}\n");
14602    cx.executor().run_until_parked();
14603    cx.update_editor(|editor, window, cx| {
14604        editor.show_completions(
14605            &ShowCompletions {
14606                trigger: Some("\n".into()),
14607            },
14608            window,
14609            cx,
14610        );
14611    });
14612    cx.executor().run_until_parked();
14613
14614    cx.update_editor(|editor, window, cx| {
14615        editor.confirm_completion(&Default::default(), window, cx)
14616    });
14617    cx.executor().run_until_parked();
14618    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14619}
14620
14621#[gpui::test]
14622async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14623    init_test(cx, |_| {});
14624    let language =
14625        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14626    let mut cx = EditorLspTestContext::new(
14627        language,
14628        lsp::ServerCapabilities {
14629            completion_provider: Some(lsp::CompletionOptions {
14630                ..lsp::CompletionOptions::default()
14631            }),
14632            ..lsp::ServerCapabilities::default()
14633        },
14634        cx,
14635    )
14636    .await;
14637
14638    cx.set_state(
14639        "#ifndef BAR_H
14640#define BAR_H
14641
14642#include <stdbool.h>
14643
14644int fn_branch(bool do_branch1, bool do_branch2);
14645
14646#endif // BAR_H
14647ˇ",
14648    );
14649    cx.executor().run_until_parked();
14650    cx.update_editor(|editor, window, cx| {
14651        editor.handle_input("#", window, cx);
14652    });
14653    cx.executor().run_until_parked();
14654    cx.update_editor(|editor, window, cx| {
14655        editor.handle_input("i", window, cx);
14656    });
14657    cx.executor().run_until_parked();
14658    cx.update_editor(|editor, window, cx| {
14659        editor.handle_input("n", window, cx);
14660    });
14661    cx.executor().run_until_parked();
14662    cx.assert_editor_state(
14663        "#ifndef BAR_H
14664#define BAR_H
14665
14666#include <stdbool.h>
14667
14668int fn_branch(bool do_branch1, bool do_branch2);
14669
14670#endif // BAR_H
14671#inˇ",
14672    );
14673
14674    cx.lsp
14675        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14676            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14677                is_incomplete: false,
14678                item_defaults: None,
14679                items: vec![lsp::CompletionItem {
14680                    kind: Some(lsp::CompletionItemKind::SNIPPET),
14681                    label_details: Some(lsp::CompletionItemLabelDetails {
14682                        detail: Some("header".to_string()),
14683                        description: None,
14684                    }),
14685                    label: " include".to_string(),
14686                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14687                        range: lsp::Range {
14688                            start: lsp::Position {
14689                                line: 8,
14690                                character: 1,
14691                            },
14692                            end: lsp::Position {
14693                                line: 8,
14694                                character: 1,
14695                            },
14696                        },
14697                        new_text: "include \"$0\"".to_string(),
14698                    })),
14699                    sort_text: Some("40b67681include".to_string()),
14700                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14701                    filter_text: Some("include".to_string()),
14702                    insert_text: Some("include \"$0\"".to_string()),
14703                    ..lsp::CompletionItem::default()
14704                }],
14705            })))
14706        });
14707    cx.update_editor(|editor, window, cx| {
14708        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14709    });
14710    cx.executor().run_until_parked();
14711    cx.update_editor(|editor, window, cx| {
14712        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14713    });
14714    cx.executor().run_until_parked();
14715    cx.assert_editor_state(
14716        "#ifndef BAR_H
14717#define BAR_H
14718
14719#include <stdbool.h>
14720
14721int fn_branch(bool do_branch1, bool do_branch2);
14722
14723#endif // BAR_H
14724#include \"ˇ\"",
14725    );
14726
14727    cx.lsp
14728        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14729            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14730                is_incomplete: true,
14731                item_defaults: None,
14732                items: vec![lsp::CompletionItem {
14733                    kind: Some(lsp::CompletionItemKind::FILE),
14734                    label: "AGL/".to_string(),
14735                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14736                        range: lsp::Range {
14737                            start: lsp::Position {
14738                                line: 8,
14739                                character: 10,
14740                            },
14741                            end: lsp::Position {
14742                                line: 8,
14743                                character: 11,
14744                            },
14745                        },
14746                        new_text: "AGL/".to_string(),
14747                    })),
14748                    sort_text: Some("40b67681AGL/".to_string()),
14749                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14750                    filter_text: Some("AGL/".to_string()),
14751                    insert_text: Some("AGL/".to_string()),
14752                    ..lsp::CompletionItem::default()
14753                }],
14754            })))
14755        });
14756    cx.update_editor(|editor, window, cx| {
14757        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14758    });
14759    cx.executor().run_until_parked();
14760    cx.update_editor(|editor, window, cx| {
14761        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14762    });
14763    cx.executor().run_until_parked();
14764    cx.assert_editor_state(
14765        r##"#ifndef BAR_H
14766#define BAR_H
14767
14768#include <stdbool.h>
14769
14770int fn_branch(bool do_branch1, bool do_branch2);
14771
14772#endif // BAR_H
14773#include "AGL/ˇ"##,
14774    );
14775
14776    cx.update_editor(|editor, window, cx| {
14777        editor.handle_input("\"", window, cx);
14778    });
14779    cx.executor().run_until_parked();
14780    cx.assert_editor_state(
14781        r##"#ifndef BAR_H
14782#define BAR_H
14783
14784#include <stdbool.h>
14785
14786int fn_branch(bool do_branch1, bool do_branch2);
14787
14788#endif // BAR_H
14789#include "AGL/"ˇ"##,
14790    );
14791}
14792
14793#[gpui::test]
14794async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
14795    init_test(cx, |_| {});
14796
14797    let mut cx = EditorLspTestContext::new_rust(
14798        lsp::ServerCapabilities {
14799            completion_provider: Some(lsp::CompletionOptions {
14800                trigger_characters: Some(vec![".".to_string()]),
14801                resolve_provider: Some(true),
14802                ..Default::default()
14803            }),
14804            ..Default::default()
14805        },
14806        cx,
14807    )
14808    .await;
14809
14810    cx.set_state("fn main() { let a = 2ˇ; }");
14811    cx.simulate_keystroke(".");
14812    let completion_item = lsp::CompletionItem {
14813        label: "Some".into(),
14814        kind: Some(lsp::CompletionItemKind::SNIPPET),
14815        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
14816        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
14817            kind: lsp::MarkupKind::Markdown,
14818            value: "```rust\nSome(2)\n```".to_string(),
14819        })),
14820        deprecated: Some(false),
14821        sort_text: Some("Some".to_string()),
14822        filter_text: Some("Some".to_string()),
14823        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14824        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14825            range: lsp::Range {
14826                start: lsp::Position {
14827                    line: 0,
14828                    character: 22,
14829                },
14830                end: lsp::Position {
14831                    line: 0,
14832                    character: 22,
14833                },
14834            },
14835            new_text: "Some(2)".to_string(),
14836        })),
14837        additional_text_edits: Some(vec![lsp::TextEdit {
14838            range: lsp::Range {
14839                start: lsp::Position {
14840                    line: 0,
14841                    character: 20,
14842                },
14843                end: lsp::Position {
14844                    line: 0,
14845                    character: 22,
14846                },
14847            },
14848            new_text: "".to_string(),
14849        }]),
14850        ..Default::default()
14851    };
14852
14853    let closure_completion_item = completion_item.clone();
14854    let counter = Arc::new(AtomicUsize::new(0));
14855    let counter_clone = counter.clone();
14856    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14857        let task_completion_item = closure_completion_item.clone();
14858        counter_clone.fetch_add(1, atomic::Ordering::Release);
14859        async move {
14860            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14861                is_incomplete: true,
14862                item_defaults: None,
14863                items: vec![task_completion_item],
14864            })))
14865        }
14866    });
14867
14868    cx.condition(|editor, _| editor.context_menu_visible())
14869        .await;
14870    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
14871    assert!(request.next().await.is_some());
14872    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14873
14874    cx.simulate_keystrokes("S o m");
14875    cx.condition(|editor, _| editor.context_menu_visible())
14876        .await;
14877    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
14878    assert!(request.next().await.is_some());
14879    assert!(request.next().await.is_some());
14880    assert!(request.next().await.is_some());
14881    request.close();
14882    assert!(request.next().await.is_none());
14883    assert_eq!(
14884        counter.load(atomic::Ordering::Acquire),
14885        4,
14886        "With the completions menu open, only one LSP request should happen per input"
14887    );
14888}
14889
14890#[gpui::test]
14891async fn test_toggle_comment(cx: &mut TestAppContext) {
14892    init_test(cx, |_| {});
14893    let mut cx = EditorTestContext::new(cx).await;
14894    let language = Arc::new(Language::new(
14895        LanguageConfig {
14896            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14897            ..Default::default()
14898        },
14899        Some(tree_sitter_rust::LANGUAGE.into()),
14900    ));
14901    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14902
14903    // If multiple selections intersect a line, the line is only toggled once.
14904    cx.set_state(indoc! {"
14905        fn a() {
14906            «//b();
14907            ˇ»// «c();
14908            //ˇ»  d();
14909        }
14910    "});
14911
14912    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14913
14914    cx.assert_editor_state(indoc! {"
14915        fn a() {
14916            «b();
14917            c();
14918            ˇ» d();
14919        }
14920    "});
14921
14922    // The comment prefix is inserted at the same column for every line in a
14923    // selection.
14924    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14925
14926    cx.assert_editor_state(indoc! {"
14927        fn a() {
14928            // «b();
14929            // c();
14930            ˇ»//  d();
14931        }
14932    "});
14933
14934    // If a selection ends at the beginning of a line, that line is not toggled.
14935    cx.set_selections_state(indoc! {"
14936        fn a() {
14937            // b();
14938            «// c();
14939        ˇ»    //  d();
14940        }
14941    "});
14942
14943    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14944
14945    cx.assert_editor_state(indoc! {"
14946        fn a() {
14947            // b();
14948            «c();
14949        ˇ»    //  d();
14950        }
14951    "});
14952
14953    // If a selection span a single line and is empty, the line is toggled.
14954    cx.set_state(indoc! {"
14955        fn a() {
14956            a();
14957            b();
14958        ˇ
14959        }
14960    "});
14961
14962    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14963
14964    cx.assert_editor_state(indoc! {"
14965        fn a() {
14966            a();
14967            b();
14968        //•ˇ
14969        }
14970    "});
14971
14972    // If a selection span multiple lines, empty lines are not toggled.
14973    cx.set_state(indoc! {"
14974        fn a() {
14975            «a();
14976
14977            c();ˇ»
14978        }
14979    "});
14980
14981    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14982
14983    cx.assert_editor_state(indoc! {"
14984        fn a() {
14985            // «a();
14986
14987            // c();ˇ»
14988        }
14989    "});
14990
14991    // If a selection includes multiple comment prefixes, all lines are uncommented.
14992    cx.set_state(indoc! {"
14993        fn a() {
14994            «// a();
14995            /// b();
14996            //! c();ˇ»
14997        }
14998    "});
14999
15000    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15001
15002    cx.assert_editor_state(indoc! {"
15003        fn a() {
15004            «a();
15005            b();
15006            c();ˇ»
15007        }
15008    "});
15009}
15010
15011#[gpui::test]
15012async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15013    init_test(cx, |_| {});
15014    let mut cx = EditorTestContext::new(cx).await;
15015    let language = Arc::new(Language::new(
15016        LanguageConfig {
15017            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15018            ..Default::default()
15019        },
15020        Some(tree_sitter_rust::LANGUAGE.into()),
15021    ));
15022    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15023
15024    let toggle_comments = &ToggleComments {
15025        advance_downwards: false,
15026        ignore_indent: true,
15027    };
15028
15029    // If multiple selections intersect a line, the line is only toggled once.
15030    cx.set_state(indoc! {"
15031        fn a() {
15032        //    «b();
15033        //    c();
15034        //    ˇ» d();
15035        }
15036    "});
15037
15038    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15039
15040    cx.assert_editor_state(indoc! {"
15041        fn a() {
15042            «b();
15043            c();
15044            ˇ» d();
15045        }
15046    "});
15047
15048    // The comment prefix is inserted at the beginning of each line
15049    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15050
15051    cx.assert_editor_state(indoc! {"
15052        fn a() {
15053        //    «b();
15054        //    c();
15055        //    ˇ» d();
15056        }
15057    "});
15058
15059    // If a selection ends at the beginning of a line, that line is not toggled.
15060    cx.set_selections_state(indoc! {"
15061        fn a() {
15062        //    b();
15063        //    «c();
15064        ˇ»//     d();
15065        }
15066    "});
15067
15068    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15069
15070    cx.assert_editor_state(indoc! {"
15071        fn a() {
15072        //    b();
15073            «c();
15074        ˇ»//     d();
15075        }
15076    "});
15077
15078    // If a selection span a single line and is empty, the line is toggled.
15079    cx.set_state(indoc! {"
15080        fn a() {
15081            a();
15082            b();
15083        ˇ
15084        }
15085    "});
15086
15087    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15088
15089    cx.assert_editor_state(indoc! {"
15090        fn a() {
15091            a();
15092            b();
15093        //ˇ
15094        }
15095    "});
15096
15097    // If a selection span multiple lines, empty lines are not toggled.
15098    cx.set_state(indoc! {"
15099        fn a() {
15100            «a();
15101
15102            c();ˇ»
15103        }
15104    "});
15105
15106    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15107
15108    cx.assert_editor_state(indoc! {"
15109        fn a() {
15110        //    «a();
15111
15112        //    c();ˇ»
15113        }
15114    "});
15115
15116    // If a selection includes multiple comment prefixes, all lines are uncommented.
15117    cx.set_state(indoc! {"
15118        fn a() {
15119        //    «a();
15120        ///    b();
15121        //!    c();ˇ»
15122        }
15123    "});
15124
15125    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15126
15127    cx.assert_editor_state(indoc! {"
15128        fn a() {
15129            «a();
15130            b();
15131            c();ˇ»
15132        }
15133    "});
15134}
15135
15136#[gpui::test]
15137async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15138    init_test(cx, |_| {});
15139
15140    let language = Arc::new(Language::new(
15141        LanguageConfig {
15142            line_comments: vec!["// ".into()],
15143            ..Default::default()
15144        },
15145        Some(tree_sitter_rust::LANGUAGE.into()),
15146    ));
15147
15148    let mut cx = EditorTestContext::new(cx).await;
15149
15150    cx.language_registry().add(language.clone());
15151    cx.update_buffer(|buffer, cx| {
15152        buffer.set_language(Some(language), cx);
15153    });
15154
15155    let toggle_comments = &ToggleComments {
15156        advance_downwards: true,
15157        ignore_indent: false,
15158    };
15159
15160    // Single cursor on one line -> advance
15161    // Cursor moves horizontally 3 characters as well on non-blank line
15162    cx.set_state(indoc!(
15163        "fn a() {
15164             ˇdog();
15165             cat();
15166        }"
15167    ));
15168    cx.update_editor(|editor, window, cx| {
15169        editor.toggle_comments(toggle_comments, window, cx);
15170    });
15171    cx.assert_editor_state(indoc!(
15172        "fn a() {
15173             // dog();
15174             catˇ();
15175        }"
15176    ));
15177
15178    // Single selection on one line -> don't advance
15179    cx.set_state(indoc!(
15180        "fn a() {
15181             «dog()ˇ»;
15182             cat();
15183        }"
15184    ));
15185    cx.update_editor(|editor, window, cx| {
15186        editor.toggle_comments(toggle_comments, window, cx);
15187    });
15188    cx.assert_editor_state(indoc!(
15189        "fn a() {
15190             // «dog()ˇ»;
15191             cat();
15192        }"
15193    ));
15194
15195    // Multiple cursors on one line -> advance
15196    cx.set_state(indoc!(
15197        "fn a() {
15198             ˇdˇog();
15199             cat();
15200        }"
15201    ));
15202    cx.update_editor(|editor, window, cx| {
15203        editor.toggle_comments(toggle_comments, window, cx);
15204    });
15205    cx.assert_editor_state(indoc!(
15206        "fn a() {
15207             // dog();
15208             catˇ(ˇ);
15209        }"
15210    ));
15211
15212    // Multiple cursors on one line, with selection -> don't advance
15213    cx.set_state(indoc!(
15214        "fn a() {
15215             ˇdˇog«()ˇ»;
15216             cat();
15217        }"
15218    ));
15219    cx.update_editor(|editor, window, cx| {
15220        editor.toggle_comments(toggle_comments, window, cx);
15221    });
15222    cx.assert_editor_state(indoc!(
15223        "fn a() {
15224             // ˇdˇog«()ˇ»;
15225             cat();
15226        }"
15227    ));
15228
15229    // Single cursor on one line -> advance
15230    // Cursor moves to column 0 on blank line
15231    cx.set_state(indoc!(
15232        "fn a() {
15233             ˇdog();
15234
15235             cat();
15236        }"
15237    ));
15238    cx.update_editor(|editor, window, cx| {
15239        editor.toggle_comments(toggle_comments, window, cx);
15240    });
15241    cx.assert_editor_state(indoc!(
15242        "fn a() {
15243             // dog();
15244        ˇ
15245             cat();
15246        }"
15247    ));
15248
15249    // Single cursor on one line -> advance
15250    // Cursor starts and ends at column 0
15251    cx.set_state(indoc!(
15252        "fn a() {
15253         ˇ    dog();
15254             cat();
15255        }"
15256    ));
15257    cx.update_editor(|editor, window, cx| {
15258        editor.toggle_comments(toggle_comments, window, cx);
15259    });
15260    cx.assert_editor_state(indoc!(
15261        "fn a() {
15262             // dog();
15263         ˇ    cat();
15264        }"
15265    ));
15266}
15267
15268#[gpui::test]
15269async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15270    init_test(cx, |_| {});
15271
15272    let mut cx = EditorTestContext::new(cx).await;
15273
15274    let html_language = Arc::new(
15275        Language::new(
15276            LanguageConfig {
15277                name: "HTML".into(),
15278                block_comment: Some(BlockCommentConfig {
15279                    start: "<!-- ".into(),
15280                    prefix: "".into(),
15281                    end: " -->".into(),
15282                    tab_size: 0,
15283                }),
15284                ..Default::default()
15285            },
15286            Some(tree_sitter_html::LANGUAGE.into()),
15287        )
15288        .with_injection_query(
15289            r#"
15290            (script_element
15291                (raw_text) @injection.content
15292                (#set! injection.language "javascript"))
15293            "#,
15294        )
15295        .unwrap(),
15296    );
15297
15298    let javascript_language = Arc::new(Language::new(
15299        LanguageConfig {
15300            name: "JavaScript".into(),
15301            line_comments: vec!["// ".into()],
15302            ..Default::default()
15303        },
15304        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15305    ));
15306
15307    cx.language_registry().add(html_language.clone());
15308    cx.language_registry().add(javascript_language);
15309    cx.update_buffer(|buffer, cx| {
15310        buffer.set_language(Some(html_language), cx);
15311    });
15312
15313    // Toggle comments for empty selections
15314    cx.set_state(
15315        &r#"
15316            <p>A</p>ˇ
15317            <p>B</p>ˇ
15318            <p>C</p>ˇ
15319        "#
15320        .unindent(),
15321    );
15322    cx.update_editor(|editor, window, cx| {
15323        editor.toggle_comments(&ToggleComments::default(), window, cx)
15324    });
15325    cx.assert_editor_state(
15326        &r#"
15327            <!-- <p>A</p>ˇ -->
15328            <!-- <p>B</p>ˇ -->
15329            <!-- <p>C</p>ˇ -->
15330        "#
15331        .unindent(),
15332    );
15333    cx.update_editor(|editor, window, cx| {
15334        editor.toggle_comments(&ToggleComments::default(), window, cx)
15335    });
15336    cx.assert_editor_state(
15337        &r#"
15338            <p>A</p>ˇ
15339            <p>B</p>ˇ
15340            <p>C</p>ˇ
15341        "#
15342        .unindent(),
15343    );
15344
15345    // Toggle comments for mixture of empty and non-empty selections, where
15346    // multiple selections occupy a given line.
15347    cx.set_state(
15348        &r#"
15349            <p>A«</p>
15350            <p>ˇ»B</p>ˇ
15351            <p>C«</p>
15352            <p>ˇ»D</p>ˇ
15353        "#
15354        .unindent(),
15355    );
15356
15357    cx.update_editor(|editor, window, cx| {
15358        editor.toggle_comments(&ToggleComments::default(), window, cx)
15359    });
15360    cx.assert_editor_state(
15361        &r#"
15362            <!-- <p>A«</p>
15363            <p>ˇ»B</p>ˇ -->
15364            <!-- <p>C«</p>
15365            <p>ˇ»D</p>ˇ -->
15366        "#
15367        .unindent(),
15368    );
15369    cx.update_editor(|editor, window, cx| {
15370        editor.toggle_comments(&ToggleComments::default(), window, cx)
15371    });
15372    cx.assert_editor_state(
15373        &r#"
15374            <p>A«</p>
15375            <p>ˇ»B</p>ˇ
15376            <p>C«</p>
15377            <p>ˇ»D</p>ˇ
15378        "#
15379        .unindent(),
15380    );
15381
15382    // Toggle comments when different languages are active for different
15383    // selections.
15384    cx.set_state(
15385        &r#"
15386            ˇ<script>
15387                ˇvar x = new Y();
15388            ˇ</script>
15389        "#
15390        .unindent(),
15391    );
15392    cx.executor().run_until_parked();
15393    cx.update_editor(|editor, window, cx| {
15394        editor.toggle_comments(&ToggleComments::default(), window, cx)
15395    });
15396    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15397    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15398    cx.assert_editor_state(
15399        &r#"
15400            <!-- ˇ<script> -->
15401                // ˇvar x = new Y();
15402            <!-- ˇ</script> -->
15403        "#
15404        .unindent(),
15405    );
15406}
15407
15408#[gpui::test]
15409fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15410    init_test(cx, |_| {});
15411
15412    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15413    let multibuffer = cx.new(|cx| {
15414        let mut multibuffer = MultiBuffer::new(ReadWrite);
15415        multibuffer.push_excerpts(
15416            buffer.clone(),
15417            [
15418                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15419                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15420            ],
15421            cx,
15422        );
15423        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15424        multibuffer
15425    });
15426
15427    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15428    editor.update_in(cx, |editor, window, cx| {
15429        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15430        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15431            s.select_ranges([
15432                Point::new(0, 0)..Point::new(0, 0),
15433                Point::new(1, 0)..Point::new(1, 0),
15434            ])
15435        });
15436
15437        editor.handle_input("X", window, cx);
15438        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15439        assert_eq!(
15440            editor.selections.ranges(cx),
15441            [
15442                Point::new(0, 1)..Point::new(0, 1),
15443                Point::new(1, 1)..Point::new(1, 1),
15444            ]
15445        );
15446
15447        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15448        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15449            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15450        });
15451        editor.backspace(&Default::default(), window, cx);
15452        assert_eq!(editor.text(cx), "Xa\nbbb");
15453        assert_eq!(
15454            editor.selections.ranges(cx),
15455            [Point::new(1, 0)..Point::new(1, 0)]
15456        );
15457
15458        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15459            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15460        });
15461        editor.backspace(&Default::default(), window, cx);
15462        assert_eq!(editor.text(cx), "X\nbb");
15463        assert_eq!(
15464            editor.selections.ranges(cx),
15465            [Point::new(0, 1)..Point::new(0, 1)]
15466        );
15467    });
15468}
15469
15470#[gpui::test]
15471fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15472    init_test(cx, |_| {});
15473
15474    let markers = vec![('[', ']').into(), ('(', ')').into()];
15475    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15476        indoc! {"
15477            [aaaa
15478            (bbbb]
15479            cccc)",
15480        },
15481        markers.clone(),
15482    );
15483    let excerpt_ranges = markers.into_iter().map(|marker| {
15484        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15485        ExcerptRange::new(context)
15486    });
15487    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15488    let multibuffer = cx.new(|cx| {
15489        let mut multibuffer = MultiBuffer::new(ReadWrite);
15490        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15491        multibuffer
15492    });
15493
15494    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15495    editor.update_in(cx, |editor, window, cx| {
15496        let (expected_text, selection_ranges) = marked_text_ranges(
15497            indoc! {"
15498                aaaa
15499                bˇbbb
15500                bˇbbˇb
15501                cccc"
15502            },
15503            true,
15504        );
15505        assert_eq!(editor.text(cx), expected_text);
15506        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15507            s.select_ranges(selection_ranges)
15508        });
15509
15510        editor.handle_input("X", window, cx);
15511
15512        let (expected_text, expected_selections) = marked_text_ranges(
15513            indoc! {"
15514                aaaa
15515                bXˇbbXb
15516                bXˇbbXˇb
15517                cccc"
15518            },
15519            false,
15520        );
15521        assert_eq!(editor.text(cx), expected_text);
15522        assert_eq!(editor.selections.ranges(cx), expected_selections);
15523
15524        editor.newline(&Newline, window, cx);
15525        let (expected_text, expected_selections) = marked_text_ranges(
15526            indoc! {"
15527                aaaa
15528                bX
15529                ˇbbX
15530                b
15531                bX
15532                ˇbbX
15533                ˇb
15534                cccc"
15535            },
15536            false,
15537        );
15538        assert_eq!(editor.text(cx), expected_text);
15539        assert_eq!(editor.selections.ranges(cx), expected_selections);
15540    });
15541}
15542
15543#[gpui::test]
15544fn test_refresh_selections(cx: &mut TestAppContext) {
15545    init_test(cx, |_| {});
15546
15547    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15548    let mut excerpt1_id = None;
15549    let multibuffer = cx.new(|cx| {
15550        let mut multibuffer = MultiBuffer::new(ReadWrite);
15551        excerpt1_id = multibuffer
15552            .push_excerpts(
15553                buffer.clone(),
15554                [
15555                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15556                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15557                ],
15558                cx,
15559            )
15560            .into_iter()
15561            .next();
15562        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15563        multibuffer
15564    });
15565
15566    let editor = cx.add_window(|window, cx| {
15567        let mut editor = build_editor(multibuffer.clone(), window, cx);
15568        let snapshot = editor.snapshot(window, cx);
15569        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15570            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15571        });
15572        editor.begin_selection(
15573            Point::new(2, 1).to_display_point(&snapshot),
15574            true,
15575            1,
15576            window,
15577            cx,
15578        );
15579        assert_eq!(
15580            editor.selections.ranges(cx),
15581            [
15582                Point::new(1, 3)..Point::new(1, 3),
15583                Point::new(2, 1)..Point::new(2, 1),
15584            ]
15585        );
15586        editor
15587    });
15588
15589    // Refreshing selections is a no-op when excerpts haven't changed.
15590    _ = editor.update(cx, |editor, window, cx| {
15591        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15592        assert_eq!(
15593            editor.selections.ranges(cx),
15594            [
15595                Point::new(1, 3)..Point::new(1, 3),
15596                Point::new(2, 1)..Point::new(2, 1),
15597            ]
15598        );
15599    });
15600
15601    multibuffer.update(cx, |multibuffer, cx| {
15602        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15603    });
15604    _ = editor.update(cx, |editor, window, cx| {
15605        // Removing an excerpt causes the first selection to become degenerate.
15606        assert_eq!(
15607            editor.selections.ranges(cx),
15608            [
15609                Point::new(0, 0)..Point::new(0, 0),
15610                Point::new(0, 1)..Point::new(0, 1)
15611            ]
15612        );
15613
15614        // Refreshing selections will relocate the first selection to the original buffer
15615        // location.
15616        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15617        assert_eq!(
15618            editor.selections.ranges(cx),
15619            [
15620                Point::new(0, 1)..Point::new(0, 1),
15621                Point::new(0, 3)..Point::new(0, 3)
15622            ]
15623        );
15624        assert!(editor.selections.pending_anchor().is_some());
15625    });
15626}
15627
15628#[gpui::test]
15629fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15630    init_test(cx, |_| {});
15631
15632    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15633    let mut excerpt1_id = None;
15634    let multibuffer = cx.new(|cx| {
15635        let mut multibuffer = MultiBuffer::new(ReadWrite);
15636        excerpt1_id = multibuffer
15637            .push_excerpts(
15638                buffer.clone(),
15639                [
15640                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15641                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15642                ],
15643                cx,
15644            )
15645            .into_iter()
15646            .next();
15647        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15648        multibuffer
15649    });
15650
15651    let editor = cx.add_window(|window, cx| {
15652        let mut editor = build_editor(multibuffer.clone(), window, cx);
15653        let snapshot = editor.snapshot(window, cx);
15654        editor.begin_selection(
15655            Point::new(1, 3).to_display_point(&snapshot),
15656            false,
15657            1,
15658            window,
15659            cx,
15660        );
15661        assert_eq!(
15662            editor.selections.ranges(cx),
15663            [Point::new(1, 3)..Point::new(1, 3)]
15664        );
15665        editor
15666    });
15667
15668    multibuffer.update(cx, |multibuffer, cx| {
15669        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15670    });
15671    _ = editor.update(cx, |editor, window, cx| {
15672        assert_eq!(
15673            editor.selections.ranges(cx),
15674            [Point::new(0, 0)..Point::new(0, 0)]
15675        );
15676
15677        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
15678        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15679        assert_eq!(
15680            editor.selections.ranges(cx),
15681            [Point::new(0, 3)..Point::new(0, 3)]
15682        );
15683        assert!(editor.selections.pending_anchor().is_some());
15684    });
15685}
15686
15687#[gpui::test]
15688async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
15689    init_test(cx, |_| {});
15690
15691    let language = Arc::new(
15692        Language::new(
15693            LanguageConfig {
15694                brackets: BracketPairConfig {
15695                    pairs: vec![
15696                        BracketPair {
15697                            start: "{".to_string(),
15698                            end: "}".to_string(),
15699                            close: true,
15700                            surround: true,
15701                            newline: true,
15702                        },
15703                        BracketPair {
15704                            start: "/* ".to_string(),
15705                            end: " */".to_string(),
15706                            close: true,
15707                            surround: true,
15708                            newline: true,
15709                        },
15710                    ],
15711                    ..Default::default()
15712                },
15713                ..Default::default()
15714            },
15715            Some(tree_sitter_rust::LANGUAGE.into()),
15716        )
15717        .with_indents_query("")
15718        .unwrap(),
15719    );
15720
15721    let text = concat!(
15722        "{   }\n",     //
15723        "  x\n",       //
15724        "  /*   */\n", //
15725        "x\n",         //
15726        "{{} }\n",     //
15727    );
15728
15729    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15730    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15731    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15732    editor
15733        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
15734        .await;
15735
15736    editor.update_in(cx, |editor, window, cx| {
15737        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15738            s.select_display_ranges([
15739                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
15740                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
15741                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
15742            ])
15743        });
15744        editor.newline(&Newline, window, cx);
15745
15746        assert_eq!(
15747            editor.buffer().read(cx).read(cx).text(),
15748            concat!(
15749                "{ \n",    // Suppress rustfmt
15750                "\n",      //
15751                "}\n",     //
15752                "  x\n",   //
15753                "  /* \n", //
15754                "  \n",    //
15755                "  */\n",  //
15756                "x\n",     //
15757                "{{} \n",  //
15758                "}\n",     //
15759            )
15760        );
15761    });
15762}
15763
15764#[gpui::test]
15765fn test_highlighted_ranges(cx: &mut TestAppContext) {
15766    init_test(cx, |_| {});
15767
15768    let editor = cx.add_window(|window, cx| {
15769        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
15770        build_editor(buffer, window, cx)
15771    });
15772
15773    _ = editor.update(cx, |editor, window, cx| {
15774        struct Type1;
15775        struct Type2;
15776
15777        let buffer = editor.buffer.read(cx).snapshot(cx);
15778
15779        let anchor_range =
15780            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
15781
15782        editor.highlight_background::<Type1>(
15783            &[
15784                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
15785                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
15786                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
15787                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
15788            ],
15789            |_| Hsla::red(),
15790            cx,
15791        );
15792        editor.highlight_background::<Type2>(
15793            &[
15794                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
15795                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
15796                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
15797                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
15798            ],
15799            |_| Hsla::green(),
15800            cx,
15801        );
15802
15803        let snapshot = editor.snapshot(window, cx);
15804        let highlighted_ranges = editor.sorted_background_highlights_in_range(
15805            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
15806            &snapshot,
15807            cx.theme(),
15808        );
15809        assert_eq!(
15810            highlighted_ranges,
15811            &[
15812                (
15813                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
15814                    Hsla::green(),
15815                ),
15816                (
15817                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
15818                    Hsla::red(),
15819                ),
15820                (
15821                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
15822                    Hsla::green(),
15823                ),
15824                (
15825                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
15826                    Hsla::red(),
15827                ),
15828            ]
15829        );
15830        assert_eq!(
15831            editor.sorted_background_highlights_in_range(
15832                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
15833                &snapshot,
15834                cx.theme(),
15835            ),
15836            &[(
15837                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
15838                Hsla::red(),
15839            )]
15840        );
15841    });
15842}
15843
15844#[gpui::test]
15845async fn test_following(cx: &mut TestAppContext) {
15846    init_test(cx, |_| {});
15847
15848    let fs = FakeFs::new(cx.executor());
15849    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
15850
15851    let buffer = project.update(cx, |project, cx| {
15852        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
15853        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
15854    });
15855    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
15856    let follower = cx.update(|cx| {
15857        cx.open_window(
15858            WindowOptions {
15859                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
15860                    gpui::Point::new(px(0.), px(0.)),
15861                    gpui::Point::new(px(10.), px(80.)),
15862                ))),
15863                ..Default::default()
15864            },
15865            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
15866        )
15867        .unwrap()
15868    });
15869
15870    let is_still_following = Rc::new(RefCell::new(true));
15871    let follower_edit_event_count = Rc::new(RefCell::new(0));
15872    let pending_update = Rc::new(RefCell::new(None));
15873    let leader_entity = leader.root(cx).unwrap();
15874    let follower_entity = follower.root(cx).unwrap();
15875    _ = follower.update(cx, {
15876        let update = pending_update.clone();
15877        let is_still_following = is_still_following.clone();
15878        let follower_edit_event_count = follower_edit_event_count.clone();
15879        |_, window, cx| {
15880            cx.subscribe_in(
15881                &leader_entity,
15882                window,
15883                move |_, leader, event, window, cx| {
15884                    leader.read(cx).add_event_to_update_proto(
15885                        event,
15886                        &mut update.borrow_mut(),
15887                        window,
15888                        cx,
15889                    );
15890                },
15891            )
15892            .detach();
15893
15894            cx.subscribe_in(
15895                &follower_entity,
15896                window,
15897                move |_, _, event: &EditorEvent, _window, _cx| {
15898                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
15899                        *is_still_following.borrow_mut() = false;
15900                    }
15901
15902                    if let EditorEvent::BufferEdited = event {
15903                        *follower_edit_event_count.borrow_mut() += 1;
15904                    }
15905                },
15906            )
15907            .detach();
15908        }
15909    });
15910
15911    // Update the selections only
15912    _ = leader.update(cx, |leader, window, cx| {
15913        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15914            s.select_ranges([1..1])
15915        });
15916    });
15917    follower
15918        .update(cx, |follower, window, cx| {
15919            follower.apply_update_proto(
15920                &project,
15921                pending_update.borrow_mut().take().unwrap(),
15922                window,
15923                cx,
15924            )
15925        })
15926        .unwrap()
15927        .await
15928        .unwrap();
15929    _ = follower.update(cx, |follower, _, cx| {
15930        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
15931    });
15932    assert!(*is_still_following.borrow());
15933    assert_eq!(*follower_edit_event_count.borrow(), 0);
15934
15935    // Update the scroll position only
15936    _ = leader.update(cx, |leader, window, cx| {
15937        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15938    });
15939    follower
15940        .update(cx, |follower, window, cx| {
15941            follower.apply_update_proto(
15942                &project,
15943                pending_update.borrow_mut().take().unwrap(),
15944                window,
15945                cx,
15946            )
15947        })
15948        .unwrap()
15949        .await
15950        .unwrap();
15951    assert_eq!(
15952        follower
15953            .update(cx, |follower, _, cx| follower.scroll_position(cx))
15954            .unwrap(),
15955        gpui::Point::new(1.5, 3.5)
15956    );
15957    assert!(*is_still_following.borrow());
15958    assert_eq!(*follower_edit_event_count.borrow(), 0);
15959
15960    // Update the selections and scroll position. The follower's scroll position is updated
15961    // via autoscroll, not via the leader's exact scroll position.
15962    _ = leader.update(cx, |leader, window, cx| {
15963        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15964            s.select_ranges([0..0])
15965        });
15966        leader.request_autoscroll(Autoscroll::newest(), cx);
15967        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15968    });
15969    follower
15970        .update(cx, |follower, window, cx| {
15971            follower.apply_update_proto(
15972                &project,
15973                pending_update.borrow_mut().take().unwrap(),
15974                window,
15975                cx,
15976            )
15977        })
15978        .unwrap()
15979        .await
15980        .unwrap();
15981    _ = follower.update(cx, |follower, _, cx| {
15982        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
15983        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
15984    });
15985    assert!(*is_still_following.borrow());
15986
15987    // Creating a pending selection that precedes another selection
15988    _ = leader.update(cx, |leader, window, cx| {
15989        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15990            s.select_ranges([1..1])
15991        });
15992        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
15993    });
15994    follower
15995        .update(cx, |follower, window, cx| {
15996            follower.apply_update_proto(
15997                &project,
15998                pending_update.borrow_mut().take().unwrap(),
15999                window,
16000                cx,
16001            )
16002        })
16003        .unwrap()
16004        .await
16005        .unwrap();
16006    _ = follower.update(cx, |follower, _, cx| {
16007        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16008    });
16009    assert!(*is_still_following.borrow());
16010
16011    // Extend the pending selection so that it surrounds another selection
16012    _ = leader.update(cx, |leader, window, cx| {
16013        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16014    });
16015    follower
16016        .update(cx, |follower, window, cx| {
16017            follower.apply_update_proto(
16018                &project,
16019                pending_update.borrow_mut().take().unwrap(),
16020                window,
16021                cx,
16022            )
16023        })
16024        .unwrap()
16025        .await
16026        .unwrap();
16027    _ = follower.update(cx, |follower, _, cx| {
16028        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16029    });
16030
16031    // Scrolling locally breaks the follow
16032    _ = follower.update(cx, |follower, window, cx| {
16033        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16034        follower.set_scroll_anchor(
16035            ScrollAnchor {
16036                anchor: top_anchor,
16037                offset: gpui::Point::new(0.0, 0.5),
16038            },
16039            window,
16040            cx,
16041        );
16042    });
16043    assert!(!(*is_still_following.borrow()));
16044}
16045
16046#[gpui::test]
16047async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16048    init_test(cx, |_| {});
16049
16050    let fs = FakeFs::new(cx.executor());
16051    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16052    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16053    let pane = workspace
16054        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16055        .unwrap();
16056
16057    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16058
16059    let leader = pane.update_in(cx, |_, window, cx| {
16060        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16061        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16062    });
16063
16064    // Start following the editor when it has no excerpts.
16065    let mut state_message =
16066        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16067    let workspace_entity = workspace.root(cx).unwrap();
16068    let follower_1 = cx
16069        .update_window(*workspace.deref(), |_, window, cx| {
16070            Editor::from_state_proto(
16071                workspace_entity,
16072                ViewId {
16073                    creator: CollaboratorId::PeerId(PeerId::default()),
16074                    id: 0,
16075                },
16076                &mut state_message,
16077                window,
16078                cx,
16079            )
16080        })
16081        .unwrap()
16082        .unwrap()
16083        .await
16084        .unwrap();
16085
16086    let update_message = Rc::new(RefCell::new(None));
16087    follower_1.update_in(cx, {
16088        let update = update_message.clone();
16089        |_, window, cx| {
16090            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16091                leader.read(cx).add_event_to_update_proto(
16092                    event,
16093                    &mut update.borrow_mut(),
16094                    window,
16095                    cx,
16096                );
16097            })
16098            .detach();
16099        }
16100    });
16101
16102    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16103        (
16104            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
16105            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
16106        )
16107    });
16108
16109    // Insert some excerpts.
16110    leader.update(cx, |leader, cx| {
16111        leader.buffer.update(cx, |multibuffer, cx| {
16112            multibuffer.set_excerpts_for_path(
16113                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
16114                buffer_1.clone(),
16115                vec![
16116                    Point::row_range(0..3),
16117                    Point::row_range(1..6),
16118                    Point::row_range(12..15),
16119                ],
16120                0,
16121                cx,
16122            );
16123            multibuffer.set_excerpts_for_path(
16124                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
16125                buffer_2.clone(),
16126                vec![Point::row_range(0..6), Point::row_range(8..12)],
16127                0,
16128                cx,
16129            );
16130        });
16131    });
16132
16133    // Apply the update of adding the excerpts.
16134    follower_1
16135        .update_in(cx, |follower, window, cx| {
16136            follower.apply_update_proto(
16137                &project,
16138                update_message.borrow().clone().unwrap(),
16139                window,
16140                cx,
16141            )
16142        })
16143        .await
16144        .unwrap();
16145    assert_eq!(
16146        follower_1.update(cx, |editor, cx| editor.text(cx)),
16147        leader.update(cx, |editor, cx| editor.text(cx))
16148    );
16149    update_message.borrow_mut().take();
16150
16151    // Start following separately after it already has excerpts.
16152    let mut state_message =
16153        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16154    let workspace_entity = workspace.root(cx).unwrap();
16155    let follower_2 = cx
16156        .update_window(*workspace.deref(), |_, window, cx| {
16157            Editor::from_state_proto(
16158                workspace_entity,
16159                ViewId {
16160                    creator: CollaboratorId::PeerId(PeerId::default()),
16161                    id: 0,
16162                },
16163                &mut state_message,
16164                window,
16165                cx,
16166            )
16167        })
16168        .unwrap()
16169        .unwrap()
16170        .await
16171        .unwrap();
16172    assert_eq!(
16173        follower_2.update(cx, |editor, cx| editor.text(cx)),
16174        leader.update(cx, |editor, cx| editor.text(cx))
16175    );
16176
16177    // Remove some excerpts.
16178    leader.update(cx, |leader, cx| {
16179        leader.buffer.update(cx, |multibuffer, cx| {
16180            let excerpt_ids = multibuffer.excerpt_ids();
16181            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16182            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16183        });
16184    });
16185
16186    // Apply the update of removing the excerpts.
16187    follower_1
16188        .update_in(cx, |follower, window, cx| {
16189            follower.apply_update_proto(
16190                &project,
16191                update_message.borrow().clone().unwrap(),
16192                window,
16193                cx,
16194            )
16195        })
16196        .await
16197        .unwrap();
16198    follower_2
16199        .update_in(cx, |follower, window, cx| {
16200            follower.apply_update_proto(
16201                &project,
16202                update_message.borrow().clone().unwrap(),
16203                window,
16204                cx,
16205            )
16206        })
16207        .await
16208        .unwrap();
16209    update_message.borrow_mut().take();
16210    assert_eq!(
16211        follower_1.update(cx, |editor, cx| editor.text(cx)),
16212        leader.update(cx, |editor, cx| editor.text(cx))
16213    );
16214}
16215
16216#[gpui::test]
16217async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16218    init_test(cx, |_| {});
16219
16220    let mut cx = EditorTestContext::new(cx).await;
16221    let lsp_store =
16222        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16223
16224    cx.set_state(indoc! {"
16225        ˇfn func(abc def: i32) -> u32 {
16226        }
16227    "});
16228
16229    cx.update(|_, cx| {
16230        lsp_store.update(cx, |lsp_store, cx| {
16231            lsp_store
16232                .update_diagnostics(
16233                    LanguageServerId(0),
16234                    lsp::PublishDiagnosticsParams {
16235                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16236                        version: None,
16237                        diagnostics: vec![
16238                            lsp::Diagnostic {
16239                                range: lsp::Range::new(
16240                                    lsp::Position::new(0, 11),
16241                                    lsp::Position::new(0, 12),
16242                                ),
16243                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16244                                ..Default::default()
16245                            },
16246                            lsp::Diagnostic {
16247                                range: lsp::Range::new(
16248                                    lsp::Position::new(0, 12),
16249                                    lsp::Position::new(0, 15),
16250                                ),
16251                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16252                                ..Default::default()
16253                            },
16254                            lsp::Diagnostic {
16255                                range: lsp::Range::new(
16256                                    lsp::Position::new(0, 25),
16257                                    lsp::Position::new(0, 28),
16258                                ),
16259                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16260                                ..Default::default()
16261                            },
16262                        ],
16263                    },
16264                    None,
16265                    DiagnosticSourceKind::Pushed,
16266                    &[],
16267                    cx,
16268                )
16269                .unwrap()
16270        });
16271    });
16272
16273    executor.run_until_parked();
16274
16275    cx.update_editor(|editor, window, cx| {
16276        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16277    });
16278
16279    cx.assert_editor_state(indoc! {"
16280        fn func(abc def: i32) -> ˇu32 {
16281        }
16282    "});
16283
16284    cx.update_editor(|editor, window, cx| {
16285        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16286    });
16287
16288    cx.assert_editor_state(indoc! {"
16289        fn func(abc ˇdef: i32) -> u32 {
16290        }
16291    "});
16292
16293    cx.update_editor(|editor, window, cx| {
16294        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16295    });
16296
16297    cx.assert_editor_state(indoc! {"
16298        fn func(abcˇ def: i32) -> u32 {
16299        }
16300    "});
16301
16302    cx.update_editor(|editor, window, cx| {
16303        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16304    });
16305
16306    cx.assert_editor_state(indoc! {"
16307        fn func(abc def: i32) -> ˇu32 {
16308        }
16309    "});
16310}
16311
16312#[gpui::test]
16313async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16314    init_test(cx, |_| {});
16315
16316    let mut cx = EditorTestContext::new(cx).await;
16317
16318    let diff_base = r#"
16319        use some::mod;
16320
16321        const A: u32 = 42;
16322
16323        fn main() {
16324            println!("hello");
16325
16326            println!("world");
16327        }
16328        "#
16329    .unindent();
16330
16331    // Edits are modified, removed, modified, added
16332    cx.set_state(
16333        &r#"
16334        use some::modified;
16335
16336        ˇ
16337        fn main() {
16338            println!("hello there");
16339
16340            println!("around the");
16341            println!("world");
16342        }
16343        "#
16344        .unindent(),
16345    );
16346
16347    cx.set_head_text(&diff_base);
16348    executor.run_until_parked();
16349
16350    cx.update_editor(|editor, window, cx| {
16351        //Wrap around the bottom of the buffer
16352        for _ in 0..3 {
16353            editor.go_to_next_hunk(&GoToHunk, window, cx);
16354        }
16355    });
16356
16357    cx.assert_editor_state(
16358        &r#"
16359        ˇuse some::modified;
16360
16361
16362        fn main() {
16363            println!("hello there");
16364
16365            println!("around the");
16366            println!("world");
16367        }
16368        "#
16369        .unindent(),
16370    );
16371
16372    cx.update_editor(|editor, window, cx| {
16373        //Wrap around the top of the buffer
16374        for _ in 0..2 {
16375            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16376        }
16377    });
16378
16379    cx.assert_editor_state(
16380        &r#"
16381        use some::modified;
16382
16383
16384        fn main() {
16385        ˇ    println!("hello there");
16386
16387            println!("around the");
16388            println!("world");
16389        }
16390        "#
16391        .unindent(),
16392    );
16393
16394    cx.update_editor(|editor, window, cx| {
16395        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16396    });
16397
16398    cx.assert_editor_state(
16399        &r#"
16400        use some::modified;
16401
16402        ˇ
16403        fn main() {
16404            println!("hello there");
16405
16406            println!("around the");
16407            println!("world");
16408        }
16409        "#
16410        .unindent(),
16411    );
16412
16413    cx.update_editor(|editor, window, cx| {
16414        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16415    });
16416
16417    cx.assert_editor_state(
16418        &r#"
16419        ˇuse some::modified;
16420
16421
16422        fn main() {
16423            println!("hello there");
16424
16425            println!("around the");
16426            println!("world");
16427        }
16428        "#
16429        .unindent(),
16430    );
16431
16432    cx.update_editor(|editor, window, cx| {
16433        for _ in 0..2 {
16434            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16435        }
16436    });
16437
16438    cx.assert_editor_state(
16439        &r#"
16440        use some::modified;
16441
16442
16443        fn main() {
16444        ˇ    println!("hello there");
16445
16446            println!("around the");
16447            println!("world");
16448        }
16449        "#
16450        .unindent(),
16451    );
16452
16453    cx.update_editor(|editor, window, cx| {
16454        editor.fold(&Fold, window, cx);
16455    });
16456
16457    cx.update_editor(|editor, window, cx| {
16458        editor.go_to_next_hunk(&GoToHunk, window, cx);
16459    });
16460
16461    cx.assert_editor_state(
16462        &r#"
16463        ˇuse some::modified;
16464
16465
16466        fn main() {
16467            println!("hello there");
16468
16469            println!("around the");
16470            println!("world");
16471        }
16472        "#
16473        .unindent(),
16474    );
16475}
16476
16477#[test]
16478fn test_split_words() {
16479    fn split(text: &str) -> Vec<&str> {
16480        split_words(text).collect()
16481    }
16482
16483    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16484    assert_eq!(split("hello_world"), &["hello_", "world"]);
16485    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16486    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16487    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16488    assert_eq!(split("helloworld"), &["helloworld"]);
16489
16490    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16491}
16492
16493#[gpui::test]
16494async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16495    init_test(cx, |_| {});
16496
16497    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16498    let mut assert = |before, after| {
16499        let _state_context = cx.set_state(before);
16500        cx.run_until_parked();
16501        cx.update_editor(|editor, window, cx| {
16502            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16503        });
16504        cx.run_until_parked();
16505        cx.assert_editor_state(after);
16506    };
16507
16508    // Outside bracket jumps to outside of matching bracket
16509    assert("console.logˇ(var);", "console.log(var)ˇ;");
16510    assert("console.log(var)ˇ;", "console.logˇ(var);");
16511
16512    // Inside bracket jumps to inside of matching bracket
16513    assert("console.log(ˇvar);", "console.log(varˇ);");
16514    assert("console.log(varˇ);", "console.log(ˇvar);");
16515
16516    // When outside a bracket and inside, favor jumping to the inside bracket
16517    assert(
16518        "console.log('foo', [1, 2, 3]ˇ);",
16519        "console.log(ˇ'foo', [1, 2, 3]);",
16520    );
16521    assert(
16522        "console.log(ˇ'foo', [1, 2, 3]);",
16523        "console.log('foo', [1, 2, 3]ˇ);",
16524    );
16525
16526    // Bias forward if two options are equally likely
16527    assert(
16528        "let result = curried_fun()ˇ();",
16529        "let result = curried_fun()()ˇ;",
16530    );
16531
16532    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16533    assert(
16534        indoc! {"
16535            function test() {
16536                console.log('test')ˇ
16537            }"},
16538        indoc! {"
16539            function test() {
16540                console.logˇ('test')
16541            }"},
16542    );
16543}
16544
16545#[gpui::test]
16546async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16547    init_test(cx, |_| {});
16548
16549    let fs = FakeFs::new(cx.executor());
16550    fs.insert_tree(
16551        path!("/a"),
16552        json!({
16553            "main.rs": "fn main() { let a = 5; }",
16554            "other.rs": "// Test file",
16555        }),
16556    )
16557    .await;
16558    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16559
16560    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16561    language_registry.add(Arc::new(Language::new(
16562        LanguageConfig {
16563            name: "Rust".into(),
16564            matcher: LanguageMatcher {
16565                path_suffixes: vec!["rs".to_string()],
16566                ..Default::default()
16567            },
16568            brackets: BracketPairConfig {
16569                pairs: vec![BracketPair {
16570                    start: "{".to_string(),
16571                    end: "}".to_string(),
16572                    close: true,
16573                    surround: true,
16574                    newline: true,
16575                }],
16576                disabled_scopes_by_bracket_ix: Vec::new(),
16577            },
16578            ..Default::default()
16579        },
16580        Some(tree_sitter_rust::LANGUAGE.into()),
16581    )));
16582    let mut fake_servers = language_registry.register_fake_lsp(
16583        "Rust",
16584        FakeLspAdapter {
16585            capabilities: lsp::ServerCapabilities {
16586                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16587                    first_trigger_character: "{".to_string(),
16588                    more_trigger_character: None,
16589                }),
16590                ..Default::default()
16591            },
16592            ..Default::default()
16593        },
16594    );
16595
16596    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16597
16598    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16599
16600    let worktree_id = workspace
16601        .update(cx, |workspace, _, cx| {
16602            workspace.project().update(cx, |project, cx| {
16603                project.worktrees(cx).next().unwrap().read(cx).id()
16604            })
16605        })
16606        .unwrap();
16607
16608    let buffer = project
16609        .update(cx, |project, cx| {
16610            project.open_local_buffer(path!("/a/main.rs"), cx)
16611        })
16612        .await
16613        .unwrap();
16614    let editor_handle = workspace
16615        .update(cx, |workspace, window, cx| {
16616            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16617        })
16618        .unwrap()
16619        .await
16620        .unwrap()
16621        .downcast::<Editor>()
16622        .unwrap();
16623
16624    cx.executor().start_waiting();
16625    let fake_server = fake_servers.next().await.unwrap();
16626
16627    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16628        |params, _| async move {
16629            assert_eq!(
16630                params.text_document_position.text_document.uri,
16631                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16632            );
16633            assert_eq!(
16634                params.text_document_position.position,
16635                lsp::Position::new(0, 21),
16636            );
16637
16638            Ok(Some(vec![lsp::TextEdit {
16639                new_text: "]".to_string(),
16640                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16641            }]))
16642        },
16643    );
16644
16645    editor_handle.update_in(cx, |editor, window, cx| {
16646        window.focus(&editor.focus_handle(cx));
16647        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16648            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16649        });
16650        editor.handle_input("{", window, cx);
16651    });
16652
16653    cx.executor().run_until_parked();
16654
16655    buffer.update(cx, |buffer, _| {
16656        assert_eq!(
16657            buffer.text(),
16658            "fn main() { let a = {5}; }",
16659            "No extra braces from on type formatting should appear in the buffer"
16660        )
16661    });
16662}
16663
16664#[gpui::test(iterations = 20, seeds(31))]
16665async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
16666    init_test(cx, |_| {});
16667
16668    let mut cx = EditorLspTestContext::new_rust(
16669        lsp::ServerCapabilities {
16670            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16671                first_trigger_character: ".".to_string(),
16672                more_trigger_character: None,
16673            }),
16674            ..Default::default()
16675        },
16676        cx,
16677    )
16678    .await;
16679
16680    cx.update_buffer(|buffer, _| {
16681        // This causes autoindent to be async.
16682        buffer.set_sync_parse_timeout(Duration::ZERO)
16683    });
16684
16685    cx.set_state("fn c() {\n    d()ˇ\n}\n");
16686    cx.simulate_keystroke("\n");
16687    cx.run_until_parked();
16688
16689    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
16690    let mut request =
16691        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
16692            let buffer_cloned = buffer_cloned.clone();
16693            async move {
16694                buffer_cloned.update(&mut cx, |buffer, _| {
16695                    assert_eq!(
16696                        buffer.text(),
16697                        "fn c() {\n    d()\n        .\n}\n",
16698                        "OnTypeFormatting should triggered after autoindent applied"
16699                    )
16700                })?;
16701
16702                Ok(Some(vec![]))
16703            }
16704        });
16705
16706    cx.simulate_keystroke(".");
16707    cx.run_until_parked();
16708
16709    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
16710    assert!(request.next().await.is_some());
16711    request.close();
16712    assert!(request.next().await.is_none());
16713}
16714
16715#[gpui::test]
16716async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
16717    init_test(cx, |_| {});
16718
16719    let fs = FakeFs::new(cx.executor());
16720    fs.insert_tree(
16721        path!("/a"),
16722        json!({
16723            "main.rs": "fn main() { let a = 5; }",
16724            "other.rs": "// Test file",
16725        }),
16726    )
16727    .await;
16728
16729    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16730
16731    let server_restarts = Arc::new(AtomicUsize::new(0));
16732    let closure_restarts = Arc::clone(&server_restarts);
16733    let language_server_name = "test language server";
16734    let language_name: LanguageName = "Rust".into();
16735
16736    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16737    language_registry.add(Arc::new(Language::new(
16738        LanguageConfig {
16739            name: language_name.clone(),
16740            matcher: LanguageMatcher {
16741                path_suffixes: vec!["rs".to_string()],
16742                ..Default::default()
16743            },
16744            ..Default::default()
16745        },
16746        Some(tree_sitter_rust::LANGUAGE.into()),
16747    )));
16748    let mut fake_servers = language_registry.register_fake_lsp(
16749        "Rust",
16750        FakeLspAdapter {
16751            name: language_server_name,
16752            initialization_options: Some(json!({
16753                "testOptionValue": true
16754            })),
16755            initializer: Some(Box::new(move |fake_server| {
16756                let task_restarts = Arc::clone(&closure_restarts);
16757                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
16758                    task_restarts.fetch_add(1, atomic::Ordering::Release);
16759                    futures::future::ready(Ok(()))
16760                });
16761            })),
16762            ..Default::default()
16763        },
16764    );
16765
16766    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16767    let _buffer = project
16768        .update(cx, |project, cx| {
16769            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
16770        })
16771        .await
16772        .unwrap();
16773    let _fake_server = fake_servers.next().await.unwrap();
16774    update_test_language_settings(cx, |language_settings| {
16775        language_settings.languages.0.insert(
16776            language_name.clone(),
16777            LanguageSettingsContent {
16778                tab_size: NonZeroU32::new(8),
16779                ..Default::default()
16780            },
16781        );
16782    });
16783    cx.executor().run_until_parked();
16784    assert_eq!(
16785        server_restarts.load(atomic::Ordering::Acquire),
16786        0,
16787        "Should not restart LSP server on an unrelated change"
16788    );
16789
16790    update_test_project_settings(cx, |project_settings| {
16791        project_settings.lsp.insert(
16792            "Some other server name".into(),
16793            LspSettings {
16794                binary: None,
16795                settings: None,
16796                initialization_options: Some(json!({
16797                    "some other init value": false
16798                })),
16799                enable_lsp_tasks: false,
16800                fetch: None,
16801            },
16802        );
16803    });
16804    cx.executor().run_until_parked();
16805    assert_eq!(
16806        server_restarts.load(atomic::Ordering::Acquire),
16807        0,
16808        "Should not restart LSP server on an unrelated LSP settings change"
16809    );
16810
16811    update_test_project_settings(cx, |project_settings| {
16812        project_settings.lsp.insert(
16813            language_server_name.into(),
16814            LspSettings {
16815                binary: None,
16816                settings: None,
16817                initialization_options: Some(json!({
16818                    "anotherInitValue": false
16819                })),
16820                enable_lsp_tasks: false,
16821                fetch: None,
16822            },
16823        );
16824    });
16825    cx.executor().run_until_parked();
16826    assert_eq!(
16827        server_restarts.load(atomic::Ordering::Acquire),
16828        1,
16829        "Should restart LSP server on a related LSP settings change"
16830    );
16831
16832    update_test_project_settings(cx, |project_settings| {
16833        project_settings.lsp.insert(
16834            language_server_name.into(),
16835            LspSettings {
16836                binary: None,
16837                settings: None,
16838                initialization_options: Some(json!({
16839                    "anotherInitValue": false
16840                })),
16841                enable_lsp_tasks: false,
16842                fetch: None,
16843            },
16844        );
16845    });
16846    cx.executor().run_until_parked();
16847    assert_eq!(
16848        server_restarts.load(atomic::Ordering::Acquire),
16849        1,
16850        "Should not restart LSP server on a related LSP settings change that is the same"
16851    );
16852
16853    update_test_project_settings(cx, |project_settings| {
16854        project_settings.lsp.insert(
16855            language_server_name.into(),
16856            LspSettings {
16857                binary: None,
16858                settings: None,
16859                initialization_options: None,
16860                enable_lsp_tasks: false,
16861                fetch: None,
16862            },
16863        );
16864    });
16865    cx.executor().run_until_parked();
16866    assert_eq!(
16867        server_restarts.load(atomic::Ordering::Acquire),
16868        2,
16869        "Should restart LSP server on another related LSP settings change"
16870    );
16871}
16872
16873#[gpui::test]
16874async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
16875    init_test(cx, |_| {});
16876
16877    let mut cx = EditorLspTestContext::new_rust(
16878        lsp::ServerCapabilities {
16879            completion_provider: Some(lsp::CompletionOptions {
16880                trigger_characters: Some(vec![".".to_string()]),
16881                resolve_provider: Some(true),
16882                ..Default::default()
16883            }),
16884            ..Default::default()
16885        },
16886        cx,
16887    )
16888    .await;
16889
16890    cx.set_state("fn main() { let a = 2ˇ; }");
16891    cx.simulate_keystroke(".");
16892    let completion_item = lsp::CompletionItem {
16893        label: "some".into(),
16894        kind: Some(lsp::CompletionItemKind::SNIPPET),
16895        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16896        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16897            kind: lsp::MarkupKind::Markdown,
16898            value: "```rust\nSome(2)\n```".to_string(),
16899        })),
16900        deprecated: Some(false),
16901        sort_text: Some("fffffff2".to_string()),
16902        filter_text: Some("some".to_string()),
16903        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16904        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16905            range: lsp::Range {
16906                start: lsp::Position {
16907                    line: 0,
16908                    character: 22,
16909                },
16910                end: lsp::Position {
16911                    line: 0,
16912                    character: 22,
16913                },
16914            },
16915            new_text: "Some(2)".to_string(),
16916        })),
16917        additional_text_edits: Some(vec![lsp::TextEdit {
16918            range: lsp::Range {
16919                start: lsp::Position {
16920                    line: 0,
16921                    character: 20,
16922                },
16923                end: lsp::Position {
16924                    line: 0,
16925                    character: 22,
16926                },
16927            },
16928            new_text: "".to_string(),
16929        }]),
16930        ..Default::default()
16931    };
16932
16933    let closure_completion_item = completion_item.clone();
16934    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16935        let task_completion_item = closure_completion_item.clone();
16936        async move {
16937            Ok(Some(lsp::CompletionResponse::Array(vec![
16938                task_completion_item,
16939            ])))
16940        }
16941    });
16942
16943    request.next().await;
16944
16945    cx.condition(|editor, _| editor.context_menu_visible())
16946        .await;
16947    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16948        editor
16949            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16950            .unwrap()
16951    });
16952    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
16953
16954    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16955        let task_completion_item = completion_item.clone();
16956        async move { Ok(task_completion_item) }
16957    })
16958    .next()
16959    .await
16960    .unwrap();
16961    apply_additional_edits.await.unwrap();
16962    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
16963}
16964
16965#[gpui::test]
16966async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
16967    init_test(cx, |_| {});
16968
16969    let mut cx = EditorLspTestContext::new_rust(
16970        lsp::ServerCapabilities {
16971            completion_provider: Some(lsp::CompletionOptions {
16972                trigger_characters: Some(vec![".".to_string()]),
16973                resolve_provider: Some(true),
16974                ..Default::default()
16975            }),
16976            ..Default::default()
16977        },
16978        cx,
16979    )
16980    .await;
16981
16982    cx.set_state("fn main() { let a = 2ˇ; }");
16983    cx.simulate_keystroke(".");
16984
16985    let item1 = lsp::CompletionItem {
16986        label: "method id()".to_string(),
16987        filter_text: Some("id".to_string()),
16988        detail: None,
16989        documentation: None,
16990        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16991            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16992            new_text: ".id".to_string(),
16993        })),
16994        ..lsp::CompletionItem::default()
16995    };
16996
16997    let item2 = lsp::CompletionItem {
16998        label: "other".to_string(),
16999        filter_text: Some("other".to_string()),
17000        detail: None,
17001        documentation: None,
17002        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17003            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17004            new_text: ".other".to_string(),
17005        })),
17006        ..lsp::CompletionItem::default()
17007    };
17008
17009    let item1 = item1.clone();
17010    cx.set_request_handler::<lsp::request::Completion, _, _>({
17011        let item1 = item1.clone();
17012        move |_, _, _| {
17013            let item1 = item1.clone();
17014            let item2 = item2.clone();
17015            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17016        }
17017    })
17018    .next()
17019    .await;
17020
17021    cx.condition(|editor, _| editor.context_menu_visible())
17022        .await;
17023    cx.update_editor(|editor, _, _| {
17024        let context_menu = editor.context_menu.borrow_mut();
17025        let context_menu = context_menu
17026            .as_ref()
17027            .expect("Should have the context menu deployed");
17028        match context_menu {
17029            CodeContextMenu::Completions(completions_menu) => {
17030                let completions = completions_menu.completions.borrow_mut();
17031                assert_eq!(
17032                    completions
17033                        .iter()
17034                        .map(|completion| &completion.label.text)
17035                        .collect::<Vec<_>>(),
17036                    vec!["method id()", "other"]
17037                )
17038            }
17039            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17040        }
17041    });
17042
17043    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17044        let item1 = item1.clone();
17045        move |_, item_to_resolve, _| {
17046            let item1 = item1.clone();
17047            async move {
17048                if item1 == item_to_resolve {
17049                    Ok(lsp::CompletionItem {
17050                        label: "method id()".to_string(),
17051                        filter_text: Some("id".to_string()),
17052                        detail: Some("Now resolved!".to_string()),
17053                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17054                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17055                            range: lsp::Range::new(
17056                                lsp::Position::new(0, 22),
17057                                lsp::Position::new(0, 22),
17058                            ),
17059                            new_text: ".id".to_string(),
17060                        })),
17061                        ..lsp::CompletionItem::default()
17062                    })
17063                } else {
17064                    Ok(item_to_resolve)
17065                }
17066            }
17067        }
17068    })
17069    .next()
17070    .await
17071    .unwrap();
17072    cx.run_until_parked();
17073
17074    cx.update_editor(|editor, window, cx| {
17075        editor.context_menu_next(&Default::default(), window, cx);
17076    });
17077
17078    cx.update_editor(|editor, _, _| {
17079        let context_menu = editor.context_menu.borrow_mut();
17080        let context_menu = context_menu
17081            .as_ref()
17082            .expect("Should have the context menu deployed");
17083        match context_menu {
17084            CodeContextMenu::Completions(completions_menu) => {
17085                let completions = completions_menu.completions.borrow_mut();
17086                assert_eq!(
17087                    completions
17088                        .iter()
17089                        .map(|completion| &completion.label.text)
17090                        .collect::<Vec<_>>(),
17091                    vec!["method id() Now resolved!", "other"],
17092                    "Should update first completion label, but not second as the filter text did not match."
17093                );
17094            }
17095            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17096        }
17097    });
17098}
17099
17100#[gpui::test]
17101async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17102    init_test(cx, |_| {});
17103    let mut cx = EditorLspTestContext::new_rust(
17104        lsp::ServerCapabilities {
17105            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17106            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17107            completion_provider: Some(lsp::CompletionOptions {
17108                resolve_provider: Some(true),
17109                ..Default::default()
17110            }),
17111            ..Default::default()
17112        },
17113        cx,
17114    )
17115    .await;
17116    cx.set_state(indoc! {"
17117        struct TestStruct {
17118            field: i32
17119        }
17120
17121        fn mainˇ() {
17122            let unused_var = 42;
17123            let test_struct = TestStruct { field: 42 };
17124        }
17125    "});
17126    let symbol_range = cx.lsp_range(indoc! {"
17127        struct TestStruct {
17128            field: i32
17129        }
17130
17131        «fn main»() {
17132            let unused_var = 42;
17133            let test_struct = TestStruct { field: 42 };
17134        }
17135    "});
17136    let mut hover_requests =
17137        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17138            Ok(Some(lsp::Hover {
17139                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17140                    kind: lsp::MarkupKind::Markdown,
17141                    value: "Function documentation".to_string(),
17142                }),
17143                range: Some(symbol_range),
17144            }))
17145        });
17146
17147    // Case 1: Test that code action menu hide hover popover
17148    cx.dispatch_action(Hover);
17149    hover_requests.next().await;
17150    cx.condition(|editor, _| editor.hover_state.visible()).await;
17151    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17152        move |_, _, _| async move {
17153            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17154                lsp::CodeAction {
17155                    title: "Remove unused variable".to_string(),
17156                    kind: Some(CodeActionKind::QUICKFIX),
17157                    edit: Some(lsp::WorkspaceEdit {
17158                        changes: Some(
17159                            [(
17160                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17161                                vec![lsp::TextEdit {
17162                                    range: lsp::Range::new(
17163                                        lsp::Position::new(5, 4),
17164                                        lsp::Position::new(5, 27),
17165                                    ),
17166                                    new_text: "".to_string(),
17167                                }],
17168                            )]
17169                            .into_iter()
17170                            .collect(),
17171                        ),
17172                        ..Default::default()
17173                    }),
17174                    ..Default::default()
17175                },
17176            )]))
17177        },
17178    );
17179    cx.update_editor(|editor, window, cx| {
17180        editor.toggle_code_actions(
17181            &ToggleCodeActions {
17182                deployed_from: None,
17183                quick_launch: false,
17184            },
17185            window,
17186            cx,
17187        );
17188    });
17189    code_action_requests.next().await;
17190    cx.run_until_parked();
17191    cx.condition(|editor, _| editor.context_menu_visible())
17192        .await;
17193    cx.update_editor(|editor, _, _| {
17194        assert!(
17195            !editor.hover_state.visible(),
17196            "Hover popover should be hidden when code action menu is shown"
17197        );
17198        // Hide code actions
17199        editor.context_menu.take();
17200    });
17201
17202    // Case 2: Test that code completions hide hover popover
17203    cx.dispatch_action(Hover);
17204    hover_requests.next().await;
17205    cx.condition(|editor, _| editor.hover_state.visible()).await;
17206    let counter = Arc::new(AtomicUsize::new(0));
17207    let mut completion_requests =
17208        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17209            let counter = counter.clone();
17210            async move {
17211                counter.fetch_add(1, atomic::Ordering::Release);
17212                Ok(Some(lsp::CompletionResponse::Array(vec![
17213                    lsp::CompletionItem {
17214                        label: "main".into(),
17215                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17216                        detail: Some("() -> ()".to_string()),
17217                        ..Default::default()
17218                    },
17219                    lsp::CompletionItem {
17220                        label: "TestStruct".into(),
17221                        kind: Some(lsp::CompletionItemKind::STRUCT),
17222                        detail: Some("struct TestStruct".to_string()),
17223                        ..Default::default()
17224                    },
17225                ])))
17226            }
17227        });
17228    cx.update_editor(|editor, window, cx| {
17229        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17230    });
17231    completion_requests.next().await;
17232    cx.condition(|editor, _| editor.context_menu_visible())
17233        .await;
17234    cx.update_editor(|editor, _, _| {
17235        assert!(
17236            !editor.hover_state.visible(),
17237            "Hover popover should be hidden when completion menu is shown"
17238        );
17239    });
17240}
17241
17242#[gpui::test]
17243async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17244    init_test(cx, |_| {});
17245
17246    let mut cx = EditorLspTestContext::new_rust(
17247        lsp::ServerCapabilities {
17248            completion_provider: Some(lsp::CompletionOptions {
17249                trigger_characters: Some(vec![".".to_string()]),
17250                resolve_provider: Some(true),
17251                ..Default::default()
17252            }),
17253            ..Default::default()
17254        },
17255        cx,
17256    )
17257    .await;
17258
17259    cx.set_state("fn main() { let a = 2ˇ; }");
17260    cx.simulate_keystroke(".");
17261
17262    let unresolved_item_1 = lsp::CompletionItem {
17263        label: "id".to_string(),
17264        filter_text: Some("id".to_string()),
17265        detail: None,
17266        documentation: None,
17267        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17268            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17269            new_text: ".id".to_string(),
17270        })),
17271        ..lsp::CompletionItem::default()
17272    };
17273    let resolved_item_1 = lsp::CompletionItem {
17274        additional_text_edits: Some(vec![lsp::TextEdit {
17275            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17276            new_text: "!!".to_string(),
17277        }]),
17278        ..unresolved_item_1.clone()
17279    };
17280    let unresolved_item_2 = lsp::CompletionItem {
17281        label: "other".to_string(),
17282        filter_text: Some("other".to_string()),
17283        detail: None,
17284        documentation: None,
17285        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17286            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17287            new_text: ".other".to_string(),
17288        })),
17289        ..lsp::CompletionItem::default()
17290    };
17291    let resolved_item_2 = lsp::CompletionItem {
17292        additional_text_edits: Some(vec![lsp::TextEdit {
17293            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17294            new_text: "??".to_string(),
17295        }]),
17296        ..unresolved_item_2.clone()
17297    };
17298
17299    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17300    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17301    cx.lsp
17302        .server
17303        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17304            let unresolved_item_1 = unresolved_item_1.clone();
17305            let resolved_item_1 = resolved_item_1.clone();
17306            let unresolved_item_2 = unresolved_item_2.clone();
17307            let resolved_item_2 = resolved_item_2.clone();
17308            let resolve_requests_1 = resolve_requests_1.clone();
17309            let resolve_requests_2 = resolve_requests_2.clone();
17310            move |unresolved_request, _| {
17311                let unresolved_item_1 = unresolved_item_1.clone();
17312                let resolved_item_1 = resolved_item_1.clone();
17313                let unresolved_item_2 = unresolved_item_2.clone();
17314                let resolved_item_2 = resolved_item_2.clone();
17315                let resolve_requests_1 = resolve_requests_1.clone();
17316                let resolve_requests_2 = resolve_requests_2.clone();
17317                async move {
17318                    if unresolved_request == unresolved_item_1 {
17319                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17320                        Ok(resolved_item_1.clone())
17321                    } else if unresolved_request == unresolved_item_2 {
17322                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17323                        Ok(resolved_item_2.clone())
17324                    } else {
17325                        panic!("Unexpected completion item {unresolved_request:?}")
17326                    }
17327                }
17328            }
17329        })
17330        .detach();
17331
17332    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17333        let unresolved_item_1 = unresolved_item_1.clone();
17334        let unresolved_item_2 = unresolved_item_2.clone();
17335        async move {
17336            Ok(Some(lsp::CompletionResponse::Array(vec![
17337                unresolved_item_1,
17338                unresolved_item_2,
17339            ])))
17340        }
17341    })
17342    .next()
17343    .await;
17344
17345    cx.condition(|editor, _| editor.context_menu_visible())
17346        .await;
17347    cx.update_editor(|editor, _, _| {
17348        let context_menu = editor.context_menu.borrow_mut();
17349        let context_menu = context_menu
17350            .as_ref()
17351            .expect("Should have the context menu deployed");
17352        match context_menu {
17353            CodeContextMenu::Completions(completions_menu) => {
17354                let completions = completions_menu.completions.borrow_mut();
17355                assert_eq!(
17356                    completions
17357                        .iter()
17358                        .map(|completion| &completion.label.text)
17359                        .collect::<Vec<_>>(),
17360                    vec!["id", "other"]
17361                )
17362            }
17363            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17364        }
17365    });
17366    cx.run_until_parked();
17367
17368    cx.update_editor(|editor, window, cx| {
17369        editor.context_menu_next(&ContextMenuNext, window, cx);
17370    });
17371    cx.run_until_parked();
17372    cx.update_editor(|editor, window, cx| {
17373        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17374    });
17375    cx.run_until_parked();
17376    cx.update_editor(|editor, window, cx| {
17377        editor.context_menu_next(&ContextMenuNext, window, cx);
17378    });
17379    cx.run_until_parked();
17380    cx.update_editor(|editor, window, cx| {
17381        editor
17382            .compose_completion(&ComposeCompletion::default(), window, cx)
17383            .expect("No task returned")
17384    })
17385    .await
17386    .expect("Completion failed");
17387    cx.run_until_parked();
17388
17389    cx.update_editor(|editor, _, cx| {
17390        assert_eq!(
17391            resolve_requests_1.load(atomic::Ordering::Acquire),
17392            1,
17393            "Should always resolve once despite multiple selections"
17394        );
17395        assert_eq!(
17396            resolve_requests_2.load(atomic::Ordering::Acquire),
17397            1,
17398            "Should always resolve once after multiple selections and applying the completion"
17399        );
17400        assert_eq!(
17401            editor.text(cx),
17402            "fn main() { let a = ??.other; }",
17403            "Should use resolved data when applying the completion"
17404        );
17405    });
17406}
17407
17408#[gpui::test]
17409async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17410    init_test(cx, |_| {});
17411
17412    let item_0 = lsp::CompletionItem {
17413        label: "abs".into(),
17414        insert_text: Some("abs".into()),
17415        data: Some(json!({ "very": "special"})),
17416        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17417        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17418            lsp::InsertReplaceEdit {
17419                new_text: "abs".to_string(),
17420                insert: lsp::Range::default(),
17421                replace: lsp::Range::default(),
17422            },
17423        )),
17424        ..lsp::CompletionItem::default()
17425    };
17426    let items = iter::once(item_0.clone())
17427        .chain((11..51).map(|i| lsp::CompletionItem {
17428            label: format!("item_{}", i),
17429            insert_text: Some(format!("item_{}", i)),
17430            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17431            ..lsp::CompletionItem::default()
17432        }))
17433        .collect::<Vec<_>>();
17434
17435    let default_commit_characters = vec!["?".to_string()];
17436    let default_data = json!({ "default": "data"});
17437    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17438    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17439    let default_edit_range = lsp::Range {
17440        start: lsp::Position {
17441            line: 0,
17442            character: 5,
17443        },
17444        end: lsp::Position {
17445            line: 0,
17446            character: 5,
17447        },
17448    };
17449
17450    let mut cx = EditorLspTestContext::new_rust(
17451        lsp::ServerCapabilities {
17452            completion_provider: Some(lsp::CompletionOptions {
17453                trigger_characters: Some(vec![".".to_string()]),
17454                resolve_provider: Some(true),
17455                ..Default::default()
17456            }),
17457            ..Default::default()
17458        },
17459        cx,
17460    )
17461    .await;
17462
17463    cx.set_state("fn main() { let a = 2ˇ; }");
17464    cx.simulate_keystroke(".");
17465
17466    let completion_data = default_data.clone();
17467    let completion_characters = default_commit_characters.clone();
17468    let completion_items = items.clone();
17469    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17470        let default_data = completion_data.clone();
17471        let default_commit_characters = completion_characters.clone();
17472        let items = completion_items.clone();
17473        async move {
17474            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17475                items,
17476                item_defaults: Some(lsp::CompletionListItemDefaults {
17477                    data: Some(default_data.clone()),
17478                    commit_characters: Some(default_commit_characters.clone()),
17479                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17480                        default_edit_range,
17481                    )),
17482                    insert_text_format: Some(default_insert_text_format),
17483                    insert_text_mode: Some(default_insert_text_mode),
17484                }),
17485                ..lsp::CompletionList::default()
17486            })))
17487        }
17488    })
17489    .next()
17490    .await;
17491
17492    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17493    cx.lsp
17494        .server
17495        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17496            let closure_resolved_items = resolved_items.clone();
17497            move |item_to_resolve, _| {
17498                let closure_resolved_items = closure_resolved_items.clone();
17499                async move {
17500                    closure_resolved_items.lock().push(item_to_resolve.clone());
17501                    Ok(item_to_resolve)
17502                }
17503            }
17504        })
17505        .detach();
17506
17507    cx.condition(|editor, _| editor.context_menu_visible())
17508        .await;
17509    cx.run_until_parked();
17510    cx.update_editor(|editor, _, _| {
17511        let menu = editor.context_menu.borrow_mut();
17512        match menu.as_ref().expect("should have the completions menu") {
17513            CodeContextMenu::Completions(completions_menu) => {
17514                assert_eq!(
17515                    completions_menu
17516                        .entries
17517                        .borrow()
17518                        .iter()
17519                        .map(|mat| mat.string.clone())
17520                        .collect::<Vec<String>>(),
17521                    items
17522                        .iter()
17523                        .map(|completion| completion.label.clone())
17524                        .collect::<Vec<String>>()
17525                );
17526            }
17527            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17528        }
17529    });
17530    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17531    // with 4 from the end.
17532    assert_eq!(
17533        *resolved_items.lock(),
17534        [&items[0..16], &items[items.len() - 4..items.len()]]
17535            .concat()
17536            .iter()
17537            .cloned()
17538            .map(|mut item| {
17539                if item.data.is_none() {
17540                    item.data = Some(default_data.clone());
17541                }
17542                item
17543            })
17544            .collect::<Vec<lsp::CompletionItem>>(),
17545        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17546    );
17547    resolved_items.lock().clear();
17548
17549    cx.update_editor(|editor, window, cx| {
17550        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17551    });
17552    cx.run_until_parked();
17553    // Completions that have already been resolved are skipped.
17554    assert_eq!(
17555        *resolved_items.lock(),
17556        items[items.len() - 17..items.len() - 4]
17557            .iter()
17558            .cloned()
17559            .map(|mut item| {
17560                if item.data.is_none() {
17561                    item.data = Some(default_data.clone());
17562                }
17563                item
17564            })
17565            .collect::<Vec<lsp::CompletionItem>>()
17566    );
17567    resolved_items.lock().clear();
17568}
17569
17570#[gpui::test]
17571async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17572    init_test(cx, |_| {});
17573
17574    let mut cx = EditorLspTestContext::new(
17575        Language::new(
17576            LanguageConfig {
17577                matcher: LanguageMatcher {
17578                    path_suffixes: vec!["jsx".into()],
17579                    ..Default::default()
17580                },
17581                overrides: [(
17582                    "element".into(),
17583                    LanguageConfigOverride {
17584                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17585                        ..Default::default()
17586                    },
17587                )]
17588                .into_iter()
17589                .collect(),
17590                ..Default::default()
17591            },
17592            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17593        )
17594        .with_override_query("(jsx_self_closing_element) @element")
17595        .unwrap(),
17596        lsp::ServerCapabilities {
17597            completion_provider: Some(lsp::CompletionOptions {
17598                trigger_characters: Some(vec![":".to_string()]),
17599                ..Default::default()
17600            }),
17601            ..Default::default()
17602        },
17603        cx,
17604    )
17605    .await;
17606
17607    cx.lsp
17608        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17609            Ok(Some(lsp::CompletionResponse::Array(vec![
17610                lsp::CompletionItem {
17611                    label: "bg-blue".into(),
17612                    ..Default::default()
17613                },
17614                lsp::CompletionItem {
17615                    label: "bg-red".into(),
17616                    ..Default::default()
17617                },
17618                lsp::CompletionItem {
17619                    label: "bg-yellow".into(),
17620                    ..Default::default()
17621                },
17622            ])))
17623        });
17624
17625    cx.set_state(r#"<p class="bgˇ" />"#);
17626
17627    // Trigger completion when typing a dash, because the dash is an extra
17628    // word character in the 'element' scope, which contains the cursor.
17629    cx.simulate_keystroke("-");
17630    cx.executor().run_until_parked();
17631    cx.update_editor(|editor, _, _| {
17632        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17633        {
17634            assert_eq!(
17635                completion_menu_entries(menu),
17636                &["bg-blue", "bg-red", "bg-yellow"]
17637            );
17638        } else {
17639            panic!("expected completion menu to be open");
17640        }
17641    });
17642
17643    cx.simulate_keystroke("l");
17644    cx.executor().run_until_parked();
17645    cx.update_editor(|editor, _, _| {
17646        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17647        {
17648            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17649        } else {
17650            panic!("expected completion menu to be open");
17651        }
17652    });
17653
17654    // When filtering completions, consider the character after the '-' to
17655    // be the start of a subword.
17656    cx.set_state(r#"<p class="yelˇ" />"#);
17657    cx.simulate_keystroke("l");
17658    cx.executor().run_until_parked();
17659    cx.update_editor(|editor, _, _| {
17660        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17661        {
17662            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
17663        } else {
17664            panic!("expected completion menu to be open");
17665        }
17666    });
17667}
17668
17669fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
17670    let entries = menu.entries.borrow();
17671    entries.iter().map(|mat| mat.string.clone()).collect()
17672}
17673
17674#[gpui::test]
17675async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
17676    init_test(cx, |settings| {
17677        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
17678            Formatter::Prettier,
17679        )))
17680    });
17681
17682    let fs = FakeFs::new(cx.executor());
17683    fs.insert_file(path!("/file.ts"), Default::default()).await;
17684
17685    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
17686    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17687
17688    language_registry.add(Arc::new(Language::new(
17689        LanguageConfig {
17690            name: "TypeScript".into(),
17691            matcher: LanguageMatcher {
17692                path_suffixes: vec!["ts".to_string()],
17693                ..Default::default()
17694            },
17695            ..Default::default()
17696        },
17697        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17698    )));
17699    update_test_language_settings(cx, |settings| {
17700        settings.defaults.prettier = Some(PrettierSettings {
17701            allowed: true,
17702            ..PrettierSettings::default()
17703        });
17704    });
17705
17706    let test_plugin = "test_plugin";
17707    let _ = language_registry.register_fake_lsp(
17708        "TypeScript",
17709        FakeLspAdapter {
17710            prettier_plugins: vec![test_plugin],
17711            ..Default::default()
17712        },
17713    );
17714
17715    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
17716    let buffer = project
17717        .update(cx, |project, cx| {
17718            project.open_local_buffer(path!("/file.ts"), cx)
17719        })
17720        .await
17721        .unwrap();
17722
17723    let buffer_text = "one\ntwo\nthree\n";
17724    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17725    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17726    editor.update_in(cx, |editor, window, cx| {
17727        editor.set_text(buffer_text, window, cx)
17728    });
17729
17730    editor
17731        .update_in(cx, |editor, window, cx| {
17732            editor.perform_format(
17733                project.clone(),
17734                FormatTrigger::Manual,
17735                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
17736                window,
17737                cx,
17738            )
17739        })
17740        .unwrap()
17741        .await;
17742    assert_eq!(
17743        editor.update(cx, |editor, cx| editor.text(cx)),
17744        buffer_text.to_string() + prettier_format_suffix,
17745        "Test prettier formatting was not applied to the original buffer text",
17746    );
17747
17748    update_test_language_settings(cx, |settings| {
17749        settings.defaults.formatter = Some(SelectedFormatter::Auto)
17750    });
17751    let format = editor.update_in(cx, |editor, window, cx| {
17752        editor.perform_format(
17753            project.clone(),
17754            FormatTrigger::Manual,
17755            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
17756            window,
17757            cx,
17758        )
17759    });
17760    format.await.unwrap();
17761    assert_eq!(
17762        editor.update(cx, |editor, cx| editor.text(cx)),
17763        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
17764        "Autoformatting (via test prettier) was not applied to the original buffer text",
17765    );
17766}
17767
17768#[gpui::test]
17769async fn test_addition_reverts(cx: &mut TestAppContext) {
17770    init_test(cx, |_| {});
17771    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17772    let base_text = indoc! {r#"
17773        struct Row;
17774        struct Row1;
17775        struct Row2;
17776
17777        struct Row4;
17778        struct Row5;
17779        struct Row6;
17780
17781        struct Row8;
17782        struct Row9;
17783        struct Row10;"#};
17784
17785    // When addition hunks are not adjacent to carets, no hunk revert is performed
17786    assert_hunk_revert(
17787        indoc! {r#"struct Row;
17788                   struct Row1;
17789                   struct Row1.1;
17790                   struct Row1.2;
17791                   struct Row2;ˇ
17792
17793                   struct Row4;
17794                   struct Row5;
17795                   struct Row6;
17796
17797                   struct Row8;
17798                   ˇstruct Row9;
17799                   struct Row9.1;
17800                   struct Row9.2;
17801                   struct Row9.3;
17802                   struct Row10;"#},
17803        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
17804        indoc! {r#"struct Row;
17805                   struct Row1;
17806                   struct Row1.1;
17807                   struct Row1.2;
17808                   struct Row2;ˇ
17809
17810                   struct Row4;
17811                   struct Row5;
17812                   struct Row6;
17813
17814                   struct Row8;
17815                   ˇstruct Row9;
17816                   struct Row9.1;
17817                   struct Row9.2;
17818                   struct Row9.3;
17819                   struct Row10;"#},
17820        base_text,
17821        &mut cx,
17822    );
17823    // Same for selections
17824    assert_hunk_revert(
17825        indoc! {r#"struct Row;
17826                   struct Row1;
17827                   struct Row2;
17828                   struct Row2.1;
17829                   struct Row2.2;
17830                   «ˇ
17831                   struct Row4;
17832                   struct» Row5;
17833                   «struct Row6;
17834                   ˇ»
17835                   struct Row9.1;
17836                   struct Row9.2;
17837                   struct Row9.3;
17838                   struct Row8;
17839                   struct Row9;
17840                   struct Row10;"#},
17841        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
17842        indoc! {r#"struct Row;
17843                   struct Row1;
17844                   struct Row2;
17845                   struct Row2.1;
17846                   struct Row2.2;
17847                   «ˇ
17848                   struct Row4;
17849                   struct» Row5;
17850                   «struct Row6;
17851                   ˇ»
17852                   struct Row9.1;
17853                   struct Row9.2;
17854                   struct Row9.3;
17855                   struct Row8;
17856                   struct Row9;
17857                   struct Row10;"#},
17858        base_text,
17859        &mut cx,
17860    );
17861
17862    // When carets and selections intersect the addition hunks, those are reverted.
17863    // Adjacent carets got merged.
17864    assert_hunk_revert(
17865        indoc! {r#"struct Row;
17866                   ˇ// something on the top
17867                   struct Row1;
17868                   struct Row2;
17869                   struct Roˇw3.1;
17870                   struct Row2.2;
17871                   struct Row2.3;ˇ
17872
17873                   struct Row4;
17874                   struct ˇRow5.1;
17875                   struct Row5.2;
17876                   struct «Rowˇ»5.3;
17877                   struct Row5;
17878                   struct Row6;
17879                   ˇ
17880                   struct Row9.1;
17881                   struct «Rowˇ»9.2;
17882                   struct «ˇRow»9.3;
17883                   struct Row8;
17884                   struct Row9;
17885                   «ˇ// something on bottom»
17886                   struct Row10;"#},
17887        vec![
17888            DiffHunkStatusKind::Added,
17889            DiffHunkStatusKind::Added,
17890            DiffHunkStatusKind::Added,
17891            DiffHunkStatusKind::Added,
17892            DiffHunkStatusKind::Added,
17893        ],
17894        indoc! {r#"struct Row;
17895                   ˇstruct Row1;
17896                   struct Row2;
17897                   ˇ
17898                   struct Row4;
17899                   ˇstruct Row5;
17900                   struct Row6;
17901                   ˇ
17902                   ˇstruct Row8;
17903                   struct Row9;
17904                   ˇstruct Row10;"#},
17905        base_text,
17906        &mut cx,
17907    );
17908}
17909
17910#[gpui::test]
17911async fn test_modification_reverts(cx: &mut TestAppContext) {
17912    init_test(cx, |_| {});
17913    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17914    let base_text = indoc! {r#"
17915        struct Row;
17916        struct Row1;
17917        struct Row2;
17918
17919        struct Row4;
17920        struct Row5;
17921        struct Row6;
17922
17923        struct Row8;
17924        struct Row9;
17925        struct Row10;"#};
17926
17927    // Modification hunks behave the same as the addition ones.
17928    assert_hunk_revert(
17929        indoc! {r#"struct Row;
17930                   struct Row1;
17931                   struct Row33;
17932                   ˇ
17933                   struct Row4;
17934                   struct Row5;
17935                   struct Row6;
17936                   ˇ
17937                   struct Row99;
17938                   struct Row9;
17939                   struct Row10;"#},
17940        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17941        indoc! {r#"struct Row;
17942                   struct Row1;
17943                   struct Row33;
17944                   ˇ
17945                   struct Row4;
17946                   struct Row5;
17947                   struct Row6;
17948                   ˇ
17949                   struct Row99;
17950                   struct Row9;
17951                   struct Row10;"#},
17952        base_text,
17953        &mut cx,
17954    );
17955    assert_hunk_revert(
17956        indoc! {r#"struct Row;
17957                   struct Row1;
17958                   struct Row33;
17959                   «ˇ
17960                   struct Row4;
17961                   struct» Row5;
17962                   «struct Row6;
17963                   ˇ»
17964                   struct Row99;
17965                   struct Row9;
17966                   struct Row10;"#},
17967        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17968        indoc! {r#"struct Row;
17969                   struct Row1;
17970                   struct Row33;
17971                   «ˇ
17972                   struct Row4;
17973                   struct» Row5;
17974                   «struct Row6;
17975                   ˇ»
17976                   struct Row99;
17977                   struct Row9;
17978                   struct Row10;"#},
17979        base_text,
17980        &mut cx,
17981    );
17982
17983    assert_hunk_revert(
17984        indoc! {r#"ˇstruct Row1.1;
17985                   struct Row1;
17986                   «ˇstr»uct Row22;
17987
17988                   struct ˇRow44;
17989                   struct Row5;
17990                   struct «Rˇ»ow66;ˇ
17991
17992                   «struˇ»ct Row88;
17993                   struct Row9;
17994                   struct Row1011;ˇ"#},
17995        vec![
17996            DiffHunkStatusKind::Modified,
17997            DiffHunkStatusKind::Modified,
17998            DiffHunkStatusKind::Modified,
17999            DiffHunkStatusKind::Modified,
18000            DiffHunkStatusKind::Modified,
18001            DiffHunkStatusKind::Modified,
18002        ],
18003        indoc! {r#"struct Row;
18004                   ˇstruct Row1;
18005                   struct Row2;
18006                   ˇ
18007                   struct Row4;
18008                   ˇstruct Row5;
18009                   struct Row6;
18010                   ˇ
18011                   struct Row8;
18012                   ˇstruct Row9;
18013                   struct Row10;ˇ"#},
18014        base_text,
18015        &mut cx,
18016    );
18017}
18018
18019#[gpui::test]
18020async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18021    init_test(cx, |_| {});
18022    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18023    let base_text = indoc! {r#"
18024        one
18025
18026        two
18027        three
18028        "#};
18029
18030    cx.set_head_text(base_text);
18031    cx.set_state("\nˇ\n");
18032    cx.executor().run_until_parked();
18033    cx.update_editor(|editor, _window, cx| {
18034        editor.expand_selected_diff_hunks(cx);
18035    });
18036    cx.executor().run_until_parked();
18037    cx.update_editor(|editor, window, cx| {
18038        editor.backspace(&Default::default(), window, cx);
18039    });
18040    cx.run_until_parked();
18041    cx.assert_state_with_diff(
18042        indoc! {r#"
18043
18044        - two
18045        - threeˇ
18046        +
18047        "#}
18048        .to_string(),
18049    );
18050}
18051
18052#[gpui::test]
18053async fn test_deletion_reverts(cx: &mut TestAppContext) {
18054    init_test(cx, |_| {});
18055    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18056    let base_text = indoc! {r#"struct Row;
18057struct Row1;
18058struct Row2;
18059
18060struct Row4;
18061struct Row5;
18062struct Row6;
18063
18064struct Row8;
18065struct Row9;
18066struct Row10;"#};
18067
18068    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18069    assert_hunk_revert(
18070        indoc! {r#"struct Row;
18071                   struct Row2;
18072
18073                   ˇstruct Row4;
18074                   struct Row5;
18075                   struct Row6;
18076                   ˇ
18077                   struct Row8;
18078                   struct Row10;"#},
18079        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18080        indoc! {r#"struct Row;
18081                   struct Row2;
18082
18083                   ˇstruct Row4;
18084                   struct Row5;
18085                   struct Row6;
18086                   ˇ
18087                   struct Row8;
18088                   struct Row10;"#},
18089        base_text,
18090        &mut cx,
18091    );
18092    assert_hunk_revert(
18093        indoc! {r#"struct Row;
18094                   struct Row2;
18095
18096                   «ˇstruct Row4;
18097                   struct» Row5;
18098                   «struct Row6;
18099                   ˇ»
18100                   struct Row8;
18101                   struct Row10;"#},
18102        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18103        indoc! {r#"struct Row;
18104                   struct Row2;
18105
18106                   «ˇstruct Row4;
18107                   struct» Row5;
18108                   «struct Row6;
18109                   ˇ»
18110                   struct Row8;
18111                   struct Row10;"#},
18112        base_text,
18113        &mut cx,
18114    );
18115
18116    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18117    assert_hunk_revert(
18118        indoc! {r#"struct Row;
18119                   ˇstruct Row2;
18120
18121                   struct Row4;
18122                   struct Row5;
18123                   struct Row6;
18124
18125                   struct Row8;ˇ
18126                   struct Row10;"#},
18127        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18128        indoc! {r#"struct Row;
18129                   struct Row1;
18130                   ˇstruct Row2;
18131
18132                   struct Row4;
18133                   struct Row5;
18134                   struct Row6;
18135
18136                   struct Row8;ˇ
18137                   struct Row9;
18138                   struct Row10;"#},
18139        base_text,
18140        &mut cx,
18141    );
18142    assert_hunk_revert(
18143        indoc! {r#"struct Row;
18144                   struct Row2«ˇ;
18145                   struct Row4;
18146                   struct» Row5;
18147                   «struct Row6;
18148
18149                   struct Row8;ˇ»
18150                   struct Row10;"#},
18151        vec![
18152            DiffHunkStatusKind::Deleted,
18153            DiffHunkStatusKind::Deleted,
18154            DiffHunkStatusKind::Deleted,
18155        ],
18156        indoc! {r#"struct Row;
18157                   struct Row1;
18158                   struct Row2«ˇ;
18159
18160                   struct Row4;
18161                   struct» Row5;
18162                   «struct Row6;
18163
18164                   struct Row8;ˇ»
18165                   struct Row9;
18166                   struct Row10;"#},
18167        base_text,
18168        &mut cx,
18169    );
18170}
18171
18172#[gpui::test]
18173async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18174    init_test(cx, |_| {});
18175
18176    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18177    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18178    let base_text_3 =
18179        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18180
18181    let text_1 = edit_first_char_of_every_line(base_text_1);
18182    let text_2 = edit_first_char_of_every_line(base_text_2);
18183    let text_3 = edit_first_char_of_every_line(base_text_3);
18184
18185    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18186    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18187    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18188
18189    let multibuffer = cx.new(|cx| {
18190        let mut multibuffer = MultiBuffer::new(ReadWrite);
18191        multibuffer.push_excerpts(
18192            buffer_1.clone(),
18193            [
18194                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18195                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18196                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18197            ],
18198            cx,
18199        );
18200        multibuffer.push_excerpts(
18201            buffer_2.clone(),
18202            [
18203                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18204                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18205                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18206            ],
18207            cx,
18208        );
18209        multibuffer.push_excerpts(
18210            buffer_3.clone(),
18211            [
18212                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18213                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18214                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18215            ],
18216            cx,
18217        );
18218        multibuffer
18219    });
18220
18221    let fs = FakeFs::new(cx.executor());
18222    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18223    let (editor, cx) = cx
18224        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18225    editor.update_in(cx, |editor, _window, cx| {
18226        for (buffer, diff_base) in [
18227            (buffer_1.clone(), base_text_1),
18228            (buffer_2.clone(), base_text_2),
18229            (buffer_3.clone(), base_text_3),
18230        ] {
18231            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18232            editor
18233                .buffer
18234                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18235        }
18236    });
18237    cx.executor().run_until_parked();
18238
18239    editor.update_in(cx, |editor, window, cx| {
18240        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}");
18241        editor.select_all(&SelectAll, window, cx);
18242        editor.git_restore(&Default::default(), window, cx);
18243    });
18244    cx.executor().run_until_parked();
18245
18246    // When all ranges are selected, all buffer hunks are reverted.
18247    editor.update(cx, |editor, cx| {
18248        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");
18249    });
18250    buffer_1.update(cx, |buffer, _| {
18251        assert_eq!(buffer.text(), base_text_1);
18252    });
18253    buffer_2.update(cx, |buffer, _| {
18254        assert_eq!(buffer.text(), base_text_2);
18255    });
18256    buffer_3.update(cx, |buffer, _| {
18257        assert_eq!(buffer.text(), base_text_3);
18258    });
18259
18260    editor.update_in(cx, |editor, window, cx| {
18261        editor.undo(&Default::default(), window, cx);
18262    });
18263
18264    editor.update_in(cx, |editor, window, cx| {
18265        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18266            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18267        });
18268        editor.git_restore(&Default::default(), window, cx);
18269    });
18270
18271    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18272    // but not affect buffer_2 and its related excerpts.
18273    editor.update(cx, |editor, cx| {
18274        assert_eq!(
18275            editor.text(cx),
18276            "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}"
18277        );
18278    });
18279    buffer_1.update(cx, |buffer, _| {
18280        assert_eq!(buffer.text(), base_text_1);
18281    });
18282    buffer_2.update(cx, |buffer, _| {
18283        assert_eq!(
18284            buffer.text(),
18285            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18286        );
18287    });
18288    buffer_3.update(cx, |buffer, _| {
18289        assert_eq!(
18290            buffer.text(),
18291            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18292        );
18293    });
18294
18295    fn edit_first_char_of_every_line(text: &str) -> String {
18296        text.split('\n')
18297            .map(|line| format!("X{}", &line[1..]))
18298            .collect::<Vec<_>>()
18299            .join("\n")
18300    }
18301}
18302
18303#[gpui::test]
18304async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18305    init_test(cx, |_| {});
18306
18307    let cols = 4;
18308    let rows = 10;
18309    let sample_text_1 = sample_text(rows, cols, 'a');
18310    assert_eq!(
18311        sample_text_1,
18312        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18313    );
18314    let sample_text_2 = sample_text(rows, cols, 'l');
18315    assert_eq!(
18316        sample_text_2,
18317        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18318    );
18319    let sample_text_3 = sample_text(rows, cols, 'v');
18320    assert_eq!(
18321        sample_text_3,
18322        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18323    );
18324
18325    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18326    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18327    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18328
18329    let multi_buffer = cx.new(|cx| {
18330        let mut multibuffer = MultiBuffer::new(ReadWrite);
18331        multibuffer.push_excerpts(
18332            buffer_1.clone(),
18333            [
18334                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18335                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18336                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18337            ],
18338            cx,
18339        );
18340        multibuffer.push_excerpts(
18341            buffer_2.clone(),
18342            [
18343                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18344                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18345                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18346            ],
18347            cx,
18348        );
18349        multibuffer.push_excerpts(
18350            buffer_3.clone(),
18351            [
18352                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18353                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18354                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18355            ],
18356            cx,
18357        );
18358        multibuffer
18359    });
18360
18361    let fs = FakeFs::new(cx.executor());
18362    fs.insert_tree(
18363        "/a",
18364        json!({
18365            "main.rs": sample_text_1,
18366            "other.rs": sample_text_2,
18367            "lib.rs": sample_text_3,
18368        }),
18369    )
18370    .await;
18371    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18372    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18373    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18374    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18375        Editor::new(
18376            EditorMode::full(),
18377            multi_buffer,
18378            Some(project.clone()),
18379            window,
18380            cx,
18381        )
18382    });
18383    let multibuffer_item_id = workspace
18384        .update(cx, |workspace, window, cx| {
18385            assert!(
18386                workspace.active_item(cx).is_none(),
18387                "active item should be None before the first item is added"
18388            );
18389            workspace.add_item_to_active_pane(
18390                Box::new(multi_buffer_editor.clone()),
18391                None,
18392                true,
18393                window,
18394                cx,
18395            );
18396            let active_item = workspace
18397                .active_item(cx)
18398                .expect("should have an active item after adding the multi buffer");
18399            assert!(
18400                !active_item.is_singleton(cx),
18401                "A multi buffer was expected to active after adding"
18402            );
18403            active_item.item_id()
18404        })
18405        .unwrap();
18406    cx.executor().run_until_parked();
18407
18408    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18409        editor.change_selections(
18410            SelectionEffects::scroll(Autoscroll::Next),
18411            window,
18412            cx,
18413            |s| s.select_ranges(Some(1..2)),
18414        );
18415        editor.open_excerpts(&OpenExcerpts, window, cx);
18416    });
18417    cx.executor().run_until_parked();
18418    let first_item_id = workspace
18419        .update(cx, |workspace, window, cx| {
18420            let active_item = workspace
18421                .active_item(cx)
18422                .expect("should have an active item after navigating into the 1st buffer");
18423            let first_item_id = active_item.item_id();
18424            assert_ne!(
18425                first_item_id, multibuffer_item_id,
18426                "Should navigate into the 1st buffer and activate it"
18427            );
18428            assert!(
18429                active_item.is_singleton(cx),
18430                "New active item should be a singleton buffer"
18431            );
18432            assert_eq!(
18433                active_item
18434                    .act_as::<Editor>(cx)
18435                    .expect("should have navigated into an editor for the 1st buffer")
18436                    .read(cx)
18437                    .text(cx),
18438                sample_text_1
18439            );
18440
18441            workspace
18442                .go_back(workspace.active_pane().downgrade(), window, cx)
18443                .detach_and_log_err(cx);
18444
18445            first_item_id
18446        })
18447        .unwrap();
18448    cx.executor().run_until_parked();
18449    workspace
18450        .update(cx, |workspace, _, cx| {
18451            let active_item = workspace
18452                .active_item(cx)
18453                .expect("should have an active item after navigating back");
18454            assert_eq!(
18455                active_item.item_id(),
18456                multibuffer_item_id,
18457                "Should navigate back to the multi buffer"
18458            );
18459            assert!(!active_item.is_singleton(cx));
18460        })
18461        .unwrap();
18462
18463    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18464        editor.change_selections(
18465            SelectionEffects::scroll(Autoscroll::Next),
18466            window,
18467            cx,
18468            |s| s.select_ranges(Some(39..40)),
18469        );
18470        editor.open_excerpts(&OpenExcerpts, window, cx);
18471    });
18472    cx.executor().run_until_parked();
18473    let second_item_id = workspace
18474        .update(cx, |workspace, window, cx| {
18475            let active_item = workspace
18476                .active_item(cx)
18477                .expect("should have an active item after navigating into the 2nd buffer");
18478            let second_item_id = active_item.item_id();
18479            assert_ne!(
18480                second_item_id, multibuffer_item_id,
18481                "Should navigate away from the multibuffer"
18482            );
18483            assert_ne!(
18484                second_item_id, first_item_id,
18485                "Should navigate into the 2nd buffer and activate it"
18486            );
18487            assert!(
18488                active_item.is_singleton(cx),
18489                "New active item should be a singleton buffer"
18490            );
18491            assert_eq!(
18492                active_item
18493                    .act_as::<Editor>(cx)
18494                    .expect("should have navigated into an editor")
18495                    .read(cx)
18496                    .text(cx),
18497                sample_text_2
18498            );
18499
18500            workspace
18501                .go_back(workspace.active_pane().downgrade(), window, cx)
18502                .detach_and_log_err(cx);
18503
18504            second_item_id
18505        })
18506        .unwrap();
18507    cx.executor().run_until_parked();
18508    workspace
18509        .update(cx, |workspace, _, cx| {
18510            let active_item = workspace
18511                .active_item(cx)
18512                .expect("should have an active item after navigating back from the 2nd buffer");
18513            assert_eq!(
18514                active_item.item_id(),
18515                multibuffer_item_id,
18516                "Should navigate back from the 2nd buffer to the multi buffer"
18517            );
18518            assert!(!active_item.is_singleton(cx));
18519        })
18520        .unwrap();
18521
18522    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18523        editor.change_selections(
18524            SelectionEffects::scroll(Autoscroll::Next),
18525            window,
18526            cx,
18527            |s| s.select_ranges(Some(70..70)),
18528        );
18529        editor.open_excerpts(&OpenExcerpts, window, cx);
18530    });
18531    cx.executor().run_until_parked();
18532    workspace
18533        .update(cx, |workspace, window, cx| {
18534            let active_item = workspace
18535                .active_item(cx)
18536                .expect("should have an active item after navigating into the 3rd buffer");
18537            let third_item_id = active_item.item_id();
18538            assert_ne!(
18539                third_item_id, multibuffer_item_id,
18540                "Should navigate into the 3rd buffer and activate it"
18541            );
18542            assert_ne!(third_item_id, first_item_id);
18543            assert_ne!(third_item_id, second_item_id);
18544            assert!(
18545                active_item.is_singleton(cx),
18546                "New active item should be a singleton buffer"
18547            );
18548            assert_eq!(
18549                active_item
18550                    .act_as::<Editor>(cx)
18551                    .expect("should have navigated into an editor")
18552                    .read(cx)
18553                    .text(cx),
18554                sample_text_3
18555            );
18556
18557            workspace
18558                .go_back(workspace.active_pane().downgrade(), window, cx)
18559                .detach_and_log_err(cx);
18560        })
18561        .unwrap();
18562    cx.executor().run_until_parked();
18563    workspace
18564        .update(cx, |workspace, _, cx| {
18565            let active_item = workspace
18566                .active_item(cx)
18567                .expect("should have an active item after navigating back from the 3rd buffer");
18568            assert_eq!(
18569                active_item.item_id(),
18570                multibuffer_item_id,
18571                "Should navigate back from the 3rd buffer to the multi buffer"
18572            );
18573            assert!(!active_item.is_singleton(cx));
18574        })
18575        .unwrap();
18576}
18577
18578#[gpui::test]
18579async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18580    init_test(cx, |_| {});
18581
18582    let mut cx = EditorTestContext::new(cx).await;
18583
18584    let diff_base = r#"
18585        use some::mod;
18586
18587        const A: u32 = 42;
18588
18589        fn main() {
18590            println!("hello");
18591
18592            println!("world");
18593        }
18594        "#
18595    .unindent();
18596
18597    cx.set_state(
18598        &r#"
18599        use some::modified;
18600
18601        ˇ
18602        fn main() {
18603            println!("hello there");
18604
18605            println!("around the");
18606            println!("world");
18607        }
18608        "#
18609        .unindent(),
18610    );
18611
18612    cx.set_head_text(&diff_base);
18613    executor.run_until_parked();
18614
18615    cx.update_editor(|editor, window, cx| {
18616        editor.go_to_next_hunk(&GoToHunk, window, cx);
18617        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18618    });
18619    executor.run_until_parked();
18620    cx.assert_state_with_diff(
18621        r#"
18622          use some::modified;
18623
18624
18625          fn main() {
18626        -     println!("hello");
18627        + ˇ    println!("hello there");
18628
18629              println!("around the");
18630              println!("world");
18631          }
18632        "#
18633        .unindent(),
18634    );
18635
18636    cx.update_editor(|editor, window, cx| {
18637        for _ in 0..2 {
18638            editor.go_to_next_hunk(&GoToHunk, window, cx);
18639            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18640        }
18641    });
18642    executor.run_until_parked();
18643    cx.assert_state_with_diff(
18644        r#"
18645        - use some::mod;
18646        + ˇuse some::modified;
18647
18648
18649          fn main() {
18650        -     println!("hello");
18651        +     println!("hello there");
18652
18653        +     println!("around the");
18654              println!("world");
18655          }
18656        "#
18657        .unindent(),
18658    );
18659
18660    cx.update_editor(|editor, window, cx| {
18661        editor.go_to_next_hunk(&GoToHunk, window, cx);
18662        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18663    });
18664    executor.run_until_parked();
18665    cx.assert_state_with_diff(
18666        r#"
18667        - use some::mod;
18668        + use some::modified;
18669
18670        - const A: u32 = 42;
18671          ˇ
18672          fn main() {
18673        -     println!("hello");
18674        +     println!("hello there");
18675
18676        +     println!("around the");
18677              println!("world");
18678          }
18679        "#
18680        .unindent(),
18681    );
18682
18683    cx.update_editor(|editor, window, cx| {
18684        editor.cancel(&Cancel, window, cx);
18685    });
18686
18687    cx.assert_state_with_diff(
18688        r#"
18689          use some::modified;
18690
18691          ˇ
18692          fn main() {
18693              println!("hello there");
18694
18695              println!("around the");
18696              println!("world");
18697          }
18698        "#
18699        .unindent(),
18700    );
18701}
18702
18703#[gpui::test]
18704async fn test_diff_base_change_with_expanded_diff_hunks(
18705    executor: BackgroundExecutor,
18706    cx: &mut TestAppContext,
18707) {
18708    init_test(cx, |_| {});
18709
18710    let mut cx = EditorTestContext::new(cx).await;
18711
18712    let diff_base = r#"
18713        use some::mod1;
18714        use some::mod2;
18715
18716        const A: u32 = 42;
18717        const B: u32 = 42;
18718        const C: u32 = 42;
18719
18720        fn main() {
18721            println!("hello");
18722
18723            println!("world");
18724        }
18725        "#
18726    .unindent();
18727
18728    cx.set_state(
18729        &r#"
18730        use some::mod2;
18731
18732        const A: u32 = 42;
18733        const C: u32 = 42;
18734
18735        fn main(ˇ) {
18736            //println!("hello");
18737
18738            println!("world");
18739            //
18740            //
18741        }
18742        "#
18743        .unindent(),
18744    );
18745
18746    cx.set_head_text(&diff_base);
18747    executor.run_until_parked();
18748
18749    cx.update_editor(|editor, window, cx| {
18750        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18751    });
18752    executor.run_until_parked();
18753    cx.assert_state_with_diff(
18754        r#"
18755        - use some::mod1;
18756          use some::mod2;
18757
18758          const A: u32 = 42;
18759        - const B: u32 = 42;
18760          const C: u32 = 42;
18761
18762          fn main(ˇ) {
18763        -     println!("hello");
18764        +     //println!("hello");
18765
18766              println!("world");
18767        +     //
18768        +     //
18769          }
18770        "#
18771        .unindent(),
18772    );
18773
18774    cx.set_head_text("new diff base!");
18775    executor.run_until_parked();
18776    cx.assert_state_with_diff(
18777        r#"
18778        - new diff base!
18779        + use some::mod2;
18780        +
18781        + const A: u32 = 42;
18782        + const C: u32 = 42;
18783        +
18784        + fn main(ˇ) {
18785        +     //println!("hello");
18786        +
18787        +     println!("world");
18788        +     //
18789        +     //
18790        + }
18791        "#
18792        .unindent(),
18793    );
18794}
18795
18796#[gpui::test]
18797async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
18798    init_test(cx, |_| {});
18799
18800    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
18801    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
18802    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
18803    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
18804    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
18805    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
18806
18807    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
18808    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
18809    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
18810
18811    let multi_buffer = cx.new(|cx| {
18812        let mut multibuffer = MultiBuffer::new(ReadWrite);
18813        multibuffer.push_excerpts(
18814            buffer_1.clone(),
18815            [
18816                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18817                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18818                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18819            ],
18820            cx,
18821        );
18822        multibuffer.push_excerpts(
18823            buffer_2.clone(),
18824            [
18825                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18826                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18827                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18828            ],
18829            cx,
18830        );
18831        multibuffer.push_excerpts(
18832            buffer_3.clone(),
18833            [
18834                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18835                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18836                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18837            ],
18838            cx,
18839        );
18840        multibuffer
18841    });
18842
18843    let editor =
18844        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18845    editor
18846        .update(cx, |editor, _window, cx| {
18847            for (buffer, diff_base) in [
18848                (buffer_1.clone(), file_1_old),
18849                (buffer_2.clone(), file_2_old),
18850                (buffer_3.clone(), file_3_old),
18851            ] {
18852                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18853                editor
18854                    .buffer
18855                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18856            }
18857        })
18858        .unwrap();
18859
18860    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18861    cx.run_until_parked();
18862
18863    cx.assert_editor_state(
18864        &"
18865            ˇaaa
18866            ccc
18867            ddd
18868
18869            ggg
18870            hhh
18871
18872
18873            lll
18874            mmm
18875            NNN
18876
18877            qqq
18878            rrr
18879
18880            uuu
18881            111
18882            222
18883            333
18884
18885            666
18886            777
18887
18888            000
18889            !!!"
18890        .unindent(),
18891    );
18892
18893    cx.update_editor(|editor, window, cx| {
18894        editor.select_all(&SelectAll, window, cx);
18895        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18896    });
18897    cx.executor().run_until_parked();
18898
18899    cx.assert_state_with_diff(
18900        "
18901            «aaa
18902          - bbb
18903            ccc
18904            ddd
18905
18906            ggg
18907            hhh
18908
18909
18910            lll
18911            mmm
18912          - nnn
18913          + NNN
18914
18915            qqq
18916            rrr
18917
18918            uuu
18919            111
18920            222
18921            333
18922
18923          + 666
18924            777
18925
18926            000
18927            !!!ˇ»"
18928            .unindent(),
18929    );
18930}
18931
18932#[gpui::test]
18933async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
18934    init_test(cx, |_| {});
18935
18936    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
18937    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
18938
18939    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
18940    let multi_buffer = cx.new(|cx| {
18941        let mut multibuffer = MultiBuffer::new(ReadWrite);
18942        multibuffer.push_excerpts(
18943            buffer.clone(),
18944            [
18945                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
18946                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
18947                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
18948            ],
18949            cx,
18950        );
18951        multibuffer
18952    });
18953
18954    let editor =
18955        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18956    editor
18957        .update(cx, |editor, _window, cx| {
18958            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
18959            editor
18960                .buffer
18961                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
18962        })
18963        .unwrap();
18964
18965    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18966    cx.run_until_parked();
18967
18968    cx.update_editor(|editor, window, cx| {
18969        editor.expand_all_diff_hunks(&Default::default(), window, cx)
18970    });
18971    cx.executor().run_until_parked();
18972
18973    // When the start of a hunk coincides with the start of its excerpt,
18974    // the hunk is expanded. When the start of a a hunk is earlier than
18975    // the start of its excerpt, the hunk is not expanded.
18976    cx.assert_state_with_diff(
18977        "
18978            ˇaaa
18979          - bbb
18980          + BBB
18981
18982          - ddd
18983          - eee
18984          + DDD
18985          + EEE
18986            fff
18987
18988            iii
18989        "
18990        .unindent(),
18991    );
18992}
18993
18994#[gpui::test]
18995async fn test_edits_around_expanded_insertion_hunks(
18996    executor: BackgroundExecutor,
18997    cx: &mut TestAppContext,
18998) {
18999    init_test(cx, |_| {});
19000
19001    let mut cx = EditorTestContext::new(cx).await;
19002
19003    let diff_base = r#"
19004        use some::mod1;
19005        use some::mod2;
19006
19007        const A: u32 = 42;
19008
19009        fn main() {
19010            println!("hello");
19011
19012            println!("world");
19013        }
19014        "#
19015    .unindent();
19016    executor.run_until_parked();
19017    cx.set_state(
19018        &r#"
19019        use some::mod1;
19020        use some::mod2;
19021
19022        const A: u32 = 42;
19023        const B: u32 = 42;
19024        const C: u32 = 42;
19025        ˇ
19026
19027        fn main() {
19028            println!("hello");
19029
19030            println!("world");
19031        }
19032        "#
19033        .unindent(),
19034    );
19035
19036    cx.set_head_text(&diff_base);
19037    executor.run_until_parked();
19038
19039    cx.update_editor(|editor, window, cx| {
19040        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19041    });
19042    executor.run_until_parked();
19043
19044    cx.assert_state_with_diff(
19045        r#"
19046        use some::mod1;
19047        use some::mod2;
19048
19049        const A: u32 = 42;
19050      + const B: u32 = 42;
19051      + const C: u32 = 42;
19052      + ˇ
19053
19054        fn main() {
19055            println!("hello");
19056
19057            println!("world");
19058        }
19059      "#
19060        .unindent(),
19061    );
19062
19063    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19064    executor.run_until_parked();
19065
19066    cx.assert_state_with_diff(
19067        r#"
19068        use some::mod1;
19069        use some::mod2;
19070
19071        const A: u32 = 42;
19072      + const B: u32 = 42;
19073      + const C: u32 = 42;
19074      + const D: u32 = 42;
19075      + ˇ
19076
19077        fn main() {
19078            println!("hello");
19079
19080            println!("world");
19081        }
19082      "#
19083        .unindent(),
19084    );
19085
19086    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19087    executor.run_until_parked();
19088
19089    cx.assert_state_with_diff(
19090        r#"
19091        use some::mod1;
19092        use some::mod2;
19093
19094        const A: u32 = 42;
19095      + const B: u32 = 42;
19096      + const C: u32 = 42;
19097      + const D: u32 = 42;
19098      + const E: u32 = 42;
19099      + ˇ
19100
19101        fn main() {
19102            println!("hello");
19103
19104            println!("world");
19105        }
19106      "#
19107        .unindent(),
19108    );
19109
19110    cx.update_editor(|editor, window, cx| {
19111        editor.delete_line(&DeleteLine, window, cx);
19112    });
19113    executor.run_until_parked();
19114
19115    cx.assert_state_with_diff(
19116        r#"
19117        use some::mod1;
19118        use some::mod2;
19119
19120        const A: u32 = 42;
19121      + const B: u32 = 42;
19122      + const C: u32 = 42;
19123      + const D: u32 = 42;
19124      + const E: u32 = 42;
19125        ˇ
19126        fn main() {
19127            println!("hello");
19128
19129            println!("world");
19130        }
19131      "#
19132        .unindent(),
19133    );
19134
19135    cx.update_editor(|editor, window, cx| {
19136        editor.move_up(&MoveUp, window, cx);
19137        editor.delete_line(&DeleteLine, window, cx);
19138        editor.move_up(&MoveUp, window, cx);
19139        editor.delete_line(&DeleteLine, window, cx);
19140        editor.move_up(&MoveUp, window, cx);
19141        editor.delete_line(&DeleteLine, window, cx);
19142    });
19143    executor.run_until_parked();
19144    cx.assert_state_with_diff(
19145        r#"
19146        use some::mod1;
19147        use some::mod2;
19148
19149        const A: u32 = 42;
19150      + const B: u32 = 42;
19151        ˇ
19152        fn main() {
19153            println!("hello");
19154
19155            println!("world");
19156        }
19157      "#
19158        .unindent(),
19159    );
19160
19161    cx.update_editor(|editor, window, cx| {
19162        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19163        editor.delete_line(&DeleteLine, window, cx);
19164    });
19165    executor.run_until_parked();
19166    cx.assert_state_with_diff(
19167        r#"
19168        ˇ
19169        fn main() {
19170            println!("hello");
19171
19172            println!("world");
19173        }
19174      "#
19175        .unindent(),
19176    );
19177}
19178
19179#[gpui::test]
19180async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19181    init_test(cx, |_| {});
19182
19183    let mut cx = EditorTestContext::new(cx).await;
19184    cx.set_head_text(indoc! { "
19185        one
19186        two
19187        three
19188        four
19189        five
19190        "
19191    });
19192    cx.set_state(indoc! { "
19193        one
19194        ˇthree
19195        five
19196    "});
19197    cx.run_until_parked();
19198    cx.update_editor(|editor, window, cx| {
19199        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19200    });
19201    cx.assert_state_with_diff(
19202        indoc! { "
19203        one
19204      - two
19205        ˇthree
19206      - four
19207        five
19208    "}
19209        .to_string(),
19210    );
19211    cx.update_editor(|editor, window, cx| {
19212        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19213    });
19214
19215    cx.assert_state_with_diff(
19216        indoc! { "
19217        one
19218        ˇthree
19219        five
19220    "}
19221        .to_string(),
19222    );
19223
19224    cx.set_state(indoc! { "
19225        one
19226        ˇTWO
19227        three
19228        four
19229        five
19230    "});
19231    cx.run_until_parked();
19232    cx.update_editor(|editor, window, cx| {
19233        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19234    });
19235
19236    cx.assert_state_with_diff(
19237        indoc! { "
19238            one
19239          - two
19240          + ˇTWO
19241            three
19242            four
19243            five
19244        "}
19245        .to_string(),
19246    );
19247    cx.update_editor(|editor, window, cx| {
19248        editor.move_up(&Default::default(), window, cx);
19249        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19250    });
19251    cx.assert_state_with_diff(
19252        indoc! { "
19253            one
19254            ˇTWO
19255            three
19256            four
19257            five
19258        "}
19259        .to_string(),
19260    );
19261}
19262
19263#[gpui::test]
19264async fn test_edits_around_expanded_deletion_hunks(
19265    executor: BackgroundExecutor,
19266    cx: &mut TestAppContext,
19267) {
19268    init_test(cx, |_| {});
19269
19270    let mut cx = EditorTestContext::new(cx).await;
19271
19272    let diff_base = r#"
19273        use some::mod1;
19274        use some::mod2;
19275
19276        const A: u32 = 42;
19277        const B: u32 = 42;
19278        const C: u32 = 42;
19279
19280
19281        fn main() {
19282            println!("hello");
19283
19284            println!("world");
19285        }
19286    "#
19287    .unindent();
19288    executor.run_until_parked();
19289    cx.set_state(
19290        &r#"
19291        use some::mod1;
19292        use some::mod2;
19293
19294        ˇconst B: u32 = 42;
19295        const C: u32 = 42;
19296
19297
19298        fn main() {
19299            println!("hello");
19300
19301            println!("world");
19302        }
19303        "#
19304        .unindent(),
19305    );
19306
19307    cx.set_head_text(&diff_base);
19308    executor.run_until_parked();
19309
19310    cx.update_editor(|editor, window, cx| {
19311        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19312    });
19313    executor.run_until_parked();
19314
19315    cx.assert_state_with_diff(
19316        r#"
19317        use some::mod1;
19318        use some::mod2;
19319
19320      - const A: u32 = 42;
19321        ˇconst B: u32 = 42;
19322        const C: u32 = 42;
19323
19324
19325        fn main() {
19326            println!("hello");
19327
19328            println!("world");
19329        }
19330      "#
19331        .unindent(),
19332    );
19333
19334    cx.update_editor(|editor, window, cx| {
19335        editor.delete_line(&DeleteLine, window, cx);
19336    });
19337    executor.run_until_parked();
19338    cx.assert_state_with_diff(
19339        r#"
19340        use some::mod1;
19341        use some::mod2;
19342
19343      - const A: u32 = 42;
19344      - const B: u32 = 42;
19345        ˇconst C: u32 = 42;
19346
19347
19348        fn main() {
19349            println!("hello");
19350
19351            println!("world");
19352        }
19353      "#
19354        .unindent(),
19355    );
19356
19357    cx.update_editor(|editor, window, cx| {
19358        editor.delete_line(&DeleteLine, window, cx);
19359    });
19360    executor.run_until_parked();
19361    cx.assert_state_with_diff(
19362        r#"
19363        use some::mod1;
19364        use some::mod2;
19365
19366      - const A: u32 = 42;
19367      - const B: u32 = 42;
19368      - const C: u32 = 42;
19369        ˇ
19370
19371        fn main() {
19372            println!("hello");
19373
19374            println!("world");
19375        }
19376      "#
19377        .unindent(),
19378    );
19379
19380    cx.update_editor(|editor, window, cx| {
19381        editor.handle_input("replacement", window, cx);
19382    });
19383    executor.run_until_parked();
19384    cx.assert_state_with_diff(
19385        r#"
19386        use some::mod1;
19387        use some::mod2;
19388
19389      - const A: u32 = 42;
19390      - const B: u32 = 42;
19391      - const C: u32 = 42;
19392      -
19393      + replacementˇ
19394
19395        fn main() {
19396            println!("hello");
19397
19398            println!("world");
19399        }
19400      "#
19401        .unindent(),
19402    );
19403}
19404
19405#[gpui::test]
19406async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19407    init_test(cx, |_| {});
19408
19409    let mut cx = EditorTestContext::new(cx).await;
19410
19411    let base_text = r#"
19412        one
19413        two
19414        three
19415        four
19416        five
19417    "#
19418    .unindent();
19419    executor.run_until_parked();
19420    cx.set_state(
19421        &r#"
19422        one
19423        two
19424        fˇour
19425        five
19426        "#
19427        .unindent(),
19428    );
19429
19430    cx.set_head_text(&base_text);
19431    executor.run_until_parked();
19432
19433    cx.update_editor(|editor, window, cx| {
19434        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19435    });
19436    executor.run_until_parked();
19437
19438    cx.assert_state_with_diff(
19439        r#"
19440          one
19441          two
19442        - three
19443          fˇour
19444          five
19445        "#
19446        .unindent(),
19447    );
19448
19449    cx.update_editor(|editor, window, cx| {
19450        editor.backspace(&Backspace, window, cx);
19451        editor.backspace(&Backspace, window, cx);
19452    });
19453    executor.run_until_parked();
19454    cx.assert_state_with_diff(
19455        r#"
19456          one
19457          two
19458        - threeˇ
19459        - four
19460        + our
19461          five
19462        "#
19463        .unindent(),
19464    );
19465}
19466
19467#[gpui::test]
19468async fn test_edit_after_expanded_modification_hunk(
19469    executor: BackgroundExecutor,
19470    cx: &mut TestAppContext,
19471) {
19472    init_test(cx, |_| {});
19473
19474    let mut cx = EditorTestContext::new(cx).await;
19475
19476    let diff_base = r#"
19477        use some::mod1;
19478        use some::mod2;
19479
19480        const A: u32 = 42;
19481        const B: u32 = 42;
19482        const C: u32 = 42;
19483        const D: u32 = 42;
19484
19485
19486        fn main() {
19487            println!("hello");
19488
19489            println!("world");
19490        }"#
19491    .unindent();
19492
19493    cx.set_state(
19494        &r#"
19495        use some::mod1;
19496        use some::mod2;
19497
19498        const A: u32 = 42;
19499        const B: u32 = 42;
19500        const C: u32 = 43ˇ
19501        const D: u32 = 42;
19502
19503
19504        fn main() {
19505            println!("hello");
19506
19507            println!("world");
19508        }"#
19509        .unindent(),
19510    );
19511
19512    cx.set_head_text(&diff_base);
19513    executor.run_until_parked();
19514    cx.update_editor(|editor, window, cx| {
19515        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19516    });
19517    executor.run_until_parked();
19518
19519    cx.assert_state_with_diff(
19520        r#"
19521        use some::mod1;
19522        use some::mod2;
19523
19524        const A: u32 = 42;
19525        const B: u32 = 42;
19526      - const C: u32 = 42;
19527      + const C: u32 = 43ˇ
19528        const D: u32 = 42;
19529
19530
19531        fn main() {
19532            println!("hello");
19533
19534            println!("world");
19535        }"#
19536        .unindent(),
19537    );
19538
19539    cx.update_editor(|editor, window, cx| {
19540        editor.handle_input("\nnew_line\n", window, cx);
19541    });
19542    executor.run_until_parked();
19543
19544    cx.assert_state_with_diff(
19545        r#"
19546        use some::mod1;
19547        use some::mod2;
19548
19549        const A: u32 = 42;
19550        const B: u32 = 42;
19551      - const C: u32 = 42;
19552      + const C: u32 = 43
19553      + new_line
19554      + ˇ
19555        const D: u32 = 42;
19556
19557
19558        fn main() {
19559            println!("hello");
19560
19561            println!("world");
19562        }"#
19563        .unindent(),
19564    );
19565}
19566
19567#[gpui::test]
19568async fn test_stage_and_unstage_added_file_hunk(
19569    executor: BackgroundExecutor,
19570    cx: &mut TestAppContext,
19571) {
19572    init_test(cx, |_| {});
19573
19574    let mut cx = EditorTestContext::new(cx).await;
19575    cx.update_editor(|editor, _, cx| {
19576        editor.set_expand_all_diff_hunks(cx);
19577    });
19578
19579    let working_copy = r#"
19580            ˇfn main() {
19581                println!("hello, world!");
19582            }
19583        "#
19584    .unindent();
19585
19586    cx.set_state(&working_copy);
19587    executor.run_until_parked();
19588
19589    cx.assert_state_with_diff(
19590        r#"
19591            + ˇfn main() {
19592            +     println!("hello, world!");
19593            + }
19594        "#
19595        .unindent(),
19596    );
19597    cx.assert_index_text(None);
19598
19599    cx.update_editor(|editor, window, cx| {
19600        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19601    });
19602    executor.run_until_parked();
19603    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19604    cx.assert_state_with_diff(
19605        r#"
19606            + ˇfn main() {
19607            +     println!("hello, world!");
19608            + }
19609        "#
19610        .unindent(),
19611    );
19612
19613    cx.update_editor(|editor, window, cx| {
19614        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19615    });
19616    executor.run_until_parked();
19617    cx.assert_index_text(None);
19618}
19619
19620async fn setup_indent_guides_editor(
19621    text: &str,
19622    cx: &mut TestAppContext,
19623) -> (BufferId, EditorTestContext) {
19624    init_test(cx, |_| {});
19625
19626    let mut cx = EditorTestContext::new(cx).await;
19627
19628    let buffer_id = cx.update_editor(|editor, window, cx| {
19629        editor.set_text(text, window, cx);
19630        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19631
19632        buffer_ids[0]
19633    });
19634
19635    (buffer_id, cx)
19636}
19637
19638fn assert_indent_guides(
19639    range: Range<u32>,
19640    expected: Vec<IndentGuide>,
19641    active_indices: Option<Vec<usize>>,
19642    cx: &mut EditorTestContext,
19643) {
19644    let indent_guides = cx.update_editor(|editor, window, cx| {
19645        let snapshot = editor.snapshot(window, cx).display_snapshot;
19646        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19647            editor,
19648            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19649            true,
19650            &snapshot,
19651            cx,
19652        );
19653
19654        indent_guides.sort_by(|a, b| {
19655            a.depth.cmp(&b.depth).then(
19656                a.start_row
19657                    .cmp(&b.start_row)
19658                    .then(a.end_row.cmp(&b.end_row)),
19659            )
19660        });
19661        indent_guides
19662    });
19663
19664    if let Some(expected) = active_indices {
19665        let active_indices = cx.update_editor(|editor, window, cx| {
19666            let snapshot = editor.snapshot(window, cx).display_snapshot;
19667            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
19668        });
19669
19670        assert_eq!(
19671            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
19672            expected,
19673            "Active indent guide indices do not match"
19674        );
19675    }
19676
19677    assert_eq!(indent_guides, expected, "Indent guides do not match");
19678}
19679
19680fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
19681    IndentGuide {
19682        buffer_id,
19683        start_row: MultiBufferRow(start_row),
19684        end_row: MultiBufferRow(end_row),
19685        depth,
19686        tab_size: 4,
19687        settings: IndentGuideSettings {
19688            enabled: true,
19689            line_width: 1,
19690            active_line_width: 1,
19691            ..Default::default()
19692        },
19693    }
19694}
19695
19696#[gpui::test]
19697async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
19698    let (buffer_id, mut cx) = setup_indent_guides_editor(
19699        &"
19700        fn main() {
19701            let a = 1;
19702        }"
19703        .unindent(),
19704        cx,
19705    )
19706    .await;
19707
19708    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19709}
19710
19711#[gpui::test]
19712async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
19713    let (buffer_id, mut cx) = setup_indent_guides_editor(
19714        &"
19715        fn main() {
19716            let a = 1;
19717            let b = 2;
19718        }"
19719        .unindent(),
19720        cx,
19721    )
19722    .await;
19723
19724    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
19725}
19726
19727#[gpui::test]
19728async fn test_indent_guide_nested(cx: &mut TestAppContext) {
19729    let (buffer_id, mut cx) = setup_indent_guides_editor(
19730        &"
19731        fn main() {
19732            let a = 1;
19733            if a == 3 {
19734                let b = 2;
19735            } else {
19736                let c = 3;
19737            }
19738        }"
19739        .unindent(),
19740        cx,
19741    )
19742    .await;
19743
19744    assert_indent_guides(
19745        0..8,
19746        vec![
19747            indent_guide(buffer_id, 1, 6, 0),
19748            indent_guide(buffer_id, 3, 3, 1),
19749            indent_guide(buffer_id, 5, 5, 1),
19750        ],
19751        None,
19752        &mut cx,
19753    );
19754}
19755
19756#[gpui::test]
19757async fn test_indent_guide_tab(cx: &mut TestAppContext) {
19758    let (buffer_id, mut cx) = setup_indent_guides_editor(
19759        &"
19760        fn main() {
19761            let a = 1;
19762                let b = 2;
19763            let c = 3;
19764        }"
19765        .unindent(),
19766        cx,
19767    )
19768    .await;
19769
19770    assert_indent_guides(
19771        0..5,
19772        vec![
19773            indent_guide(buffer_id, 1, 3, 0),
19774            indent_guide(buffer_id, 2, 2, 1),
19775        ],
19776        None,
19777        &mut cx,
19778    );
19779}
19780
19781#[gpui::test]
19782async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
19783    let (buffer_id, mut cx) = setup_indent_guides_editor(
19784        &"
19785        fn main() {
19786            let a = 1;
19787
19788            let c = 3;
19789        }"
19790        .unindent(),
19791        cx,
19792    )
19793    .await;
19794
19795    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
19796}
19797
19798#[gpui::test]
19799async fn test_indent_guide_complex(cx: &mut TestAppContext) {
19800    let (buffer_id, mut cx) = setup_indent_guides_editor(
19801        &"
19802        fn main() {
19803            let a = 1;
19804
19805            let c = 3;
19806
19807            if a == 3 {
19808                let b = 2;
19809            } else {
19810                let c = 3;
19811            }
19812        }"
19813        .unindent(),
19814        cx,
19815    )
19816    .await;
19817
19818    assert_indent_guides(
19819        0..11,
19820        vec![
19821            indent_guide(buffer_id, 1, 9, 0),
19822            indent_guide(buffer_id, 6, 6, 1),
19823            indent_guide(buffer_id, 8, 8, 1),
19824        ],
19825        None,
19826        &mut cx,
19827    );
19828}
19829
19830#[gpui::test]
19831async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
19832    let (buffer_id, mut cx) = setup_indent_guides_editor(
19833        &"
19834        fn main() {
19835            let a = 1;
19836
19837            let c = 3;
19838
19839            if a == 3 {
19840                let b = 2;
19841            } else {
19842                let c = 3;
19843            }
19844        }"
19845        .unindent(),
19846        cx,
19847    )
19848    .await;
19849
19850    assert_indent_guides(
19851        1..11,
19852        vec![
19853            indent_guide(buffer_id, 1, 9, 0),
19854            indent_guide(buffer_id, 6, 6, 1),
19855            indent_guide(buffer_id, 8, 8, 1),
19856        ],
19857        None,
19858        &mut cx,
19859    );
19860}
19861
19862#[gpui::test]
19863async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
19864    let (buffer_id, mut cx) = setup_indent_guides_editor(
19865        &"
19866        fn main() {
19867            let a = 1;
19868
19869            let c = 3;
19870
19871            if a == 3 {
19872                let b = 2;
19873            } else {
19874                let c = 3;
19875            }
19876        }"
19877        .unindent(),
19878        cx,
19879    )
19880    .await;
19881
19882    assert_indent_guides(
19883        1..10,
19884        vec![
19885            indent_guide(buffer_id, 1, 9, 0),
19886            indent_guide(buffer_id, 6, 6, 1),
19887            indent_guide(buffer_id, 8, 8, 1),
19888        ],
19889        None,
19890        &mut cx,
19891    );
19892}
19893
19894#[gpui::test]
19895async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
19896    let (buffer_id, mut cx) = setup_indent_guides_editor(
19897        &"
19898        fn main() {
19899            if a {
19900                b(
19901                    c,
19902                    d,
19903                )
19904            } else {
19905                e(
19906                    f
19907                )
19908            }
19909        }"
19910        .unindent(),
19911        cx,
19912    )
19913    .await;
19914
19915    assert_indent_guides(
19916        0..11,
19917        vec![
19918            indent_guide(buffer_id, 1, 10, 0),
19919            indent_guide(buffer_id, 2, 5, 1),
19920            indent_guide(buffer_id, 7, 9, 1),
19921            indent_guide(buffer_id, 3, 4, 2),
19922            indent_guide(buffer_id, 8, 8, 2),
19923        ],
19924        None,
19925        &mut cx,
19926    );
19927
19928    cx.update_editor(|editor, window, cx| {
19929        editor.fold_at(MultiBufferRow(2), window, cx);
19930        assert_eq!(
19931            editor.display_text(cx),
19932            "
19933            fn main() {
19934                if a {
19935                    b(⋯
19936                    )
19937                } else {
19938                    e(
19939                        f
19940                    )
19941                }
19942            }"
19943            .unindent()
19944        );
19945    });
19946
19947    assert_indent_guides(
19948        0..11,
19949        vec![
19950            indent_guide(buffer_id, 1, 10, 0),
19951            indent_guide(buffer_id, 2, 5, 1),
19952            indent_guide(buffer_id, 7, 9, 1),
19953            indent_guide(buffer_id, 8, 8, 2),
19954        ],
19955        None,
19956        &mut cx,
19957    );
19958}
19959
19960#[gpui::test]
19961async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
19962    let (buffer_id, mut cx) = setup_indent_guides_editor(
19963        &"
19964        block1
19965            block2
19966                block3
19967                    block4
19968            block2
19969        block1
19970        block1"
19971            .unindent(),
19972        cx,
19973    )
19974    .await;
19975
19976    assert_indent_guides(
19977        1..10,
19978        vec![
19979            indent_guide(buffer_id, 1, 4, 0),
19980            indent_guide(buffer_id, 2, 3, 1),
19981            indent_guide(buffer_id, 3, 3, 2),
19982        ],
19983        None,
19984        &mut cx,
19985    );
19986}
19987
19988#[gpui::test]
19989async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
19990    let (buffer_id, mut cx) = setup_indent_guides_editor(
19991        &"
19992        block1
19993            block2
19994                block3
19995
19996        block1
19997        block1"
19998            .unindent(),
19999        cx,
20000    )
20001    .await;
20002
20003    assert_indent_guides(
20004        0..6,
20005        vec![
20006            indent_guide(buffer_id, 1, 2, 0),
20007            indent_guide(buffer_id, 2, 2, 1),
20008        ],
20009        None,
20010        &mut cx,
20011    );
20012}
20013
20014#[gpui::test]
20015async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20016    let (buffer_id, mut cx) = setup_indent_guides_editor(
20017        &"
20018        function component() {
20019        \treturn (
20020        \t\t\t
20021        \t\t<div>
20022        \t\t\t<abc></abc>
20023        \t\t</div>
20024        \t)
20025        }"
20026        .unindent(),
20027        cx,
20028    )
20029    .await;
20030
20031    assert_indent_guides(
20032        0..8,
20033        vec![
20034            indent_guide(buffer_id, 1, 6, 0),
20035            indent_guide(buffer_id, 2, 5, 1),
20036            indent_guide(buffer_id, 4, 4, 2),
20037        ],
20038        None,
20039        &mut cx,
20040    );
20041}
20042
20043#[gpui::test]
20044async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20045    let (buffer_id, mut cx) = setup_indent_guides_editor(
20046        &"
20047        function component() {
20048        \treturn (
20049        \t
20050        \t\t<div>
20051        \t\t\t<abc></abc>
20052        \t\t</div>
20053        \t)
20054        }"
20055        .unindent(),
20056        cx,
20057    )
20058    .await;
20059
20060    assert_indent_guides(
20061        0..8,
20062        vec![
20063            indent_guide(buffer_id, 1, 6, 0),
20064            indent_guide(buffer_id, 2, 5, 1),
20065            indent_guide(buffer_id, 4, 4, 2),
20066        ],
20067        None,
20068        &mut cx,
20069    );
20070}
20071
20072#[gpui::test]
20073async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20074    let (buffer_id, mut cx) = setup_indent_guides_editor(
20075        &"
20076        block1
20077
20078
20079
20080            block2
20081        "
20082        .unindent(),
20083        cx,
20084    )
20085    .await;
20086
20087    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20088}
20089
20090#[gpui::test]
20091async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20092    let (buffer_id, mut cx) = setup_indent_guides_editor(
20093        &"
20094        def a:
20095        \tb = 3
20096        \tif True:
20097        \t\tc = 4
20098        \t\td = 5
20099        \tprint(b)
20100        "
20101        .unindent(),
20102        cx,
20103    )
20104    .await;
20105
20106    assert_indent_guides(
20107        0..6,
20108        vec![
20109            indent_guide(buffer_id, 1, 5, 0),
20110            indent_guide(buffer_id, 3, 4, 1),
20111        ],
20112        None,
20113        &mut cx,
20114    );
20115}
20116
20117#[gpui::test]
20118async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20119    let (buffer_id, mut cx) = setup_indent_guides_editor(
20120        &"
20121    fn main() {
20122        let a = 1;
20123    }"
20124        .unindent(),
20125        cx,
20126    )
20127    .await;
20128
20129    cx.update_editor(|editor, window, cx| {
20130        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20131            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20132        });
20133    });
20134
20135    assert_indent_guides(
20136        0..3,
20137        vec![indent_guide(buffer_id, 1, 1, 0)],
20138        Some(vec![0]),
20139        &mut cx,
20140    );
20141}
20142
20143#[gpui::test]
20144async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20145    let (buffer_id, mut cx) = setup_indent_guides_editor(
20146        &"
20147    fn main() {
20148        if 1 == 2 {
20149            let a = 1;
20150        }
20151    }"
20152        .unindent(),
20153        cx,
20154    )
20155    .await;
20156
20157    cx.update_editor(|editor, window, cx| {
20158        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20159            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20160        });
20161    });
20162
20163    assert_indent_guides(
20164        0..4,
20165        vec![
20166            indent_guide(buffer_id, 1, 3, 0),
20167            indent_guide(buffer_id, 2, 2, 1),
20168        ],
20169        Some(vec![1]),
20170        &mut cx,
20171    );
20172
20173    cx.update_editor(|editor, window, cx| {
20174        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20175            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20176        });
20177    });
20178
20179    assert_indent_guides(
20180        0..4,
20181        vec![
20182            indent_guide(buffer_id, 1, 3, 0),
20183            indent_guide(buffer_id, 2, 2, 1),
20184        ],
20185        Some(vec![1]),
20186        &mut cx,
20187    );
20188
20189    cx.update_editor(|editor, window, cx| {
20190        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20191            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20192        });
20193    });
20194
20195    assert_indent_guides(
20196        0..4,
20197        vec![
20198            indent_guide(buffer_id, 1, 3, 0),
20199            indent_guide(buffer_id, 2, 2, 1),
20200        ],
20201        Some(vec![0]),
20202        &mut cx,
20203    );
20204}
20205
20206#[gpui::test]
20207async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20208    let (buffer_id, mut cx) = setup_indent_guides_editor(
20209        &"
20210    fn main() {
20211        let a = 1;
20212
20213        let b = 2;
20214    }"
20215        .unindent(),
20216        cx,
20217    )
20218    .await;
20219
20220    cx.update_editor(|editor, window, cx| {
20221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20222            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20223        });
20224    });
20225
20226    assert_indent_guides(
20227        0..5,
20228        vec![indent_guide(buffer_id, 1, 3, 0)],
20229        Some(vec![0]),
20230        &mut cx,
20231    );
20232}
20233
20234#[gpui::test]
20235async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20236    let (buffer_id, mut cx) = setup_indent_guides_editor(
20237        &"
20238    def m:
20239        a = 1
20240        pass"
20241            .unindent(),
20242        cx,
20243    )
20244    .await;
20245
20246    cx.update_editor(|editor, window, cx| {
20247        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20248            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20249        });
20250    });
20251
20252    assert_indent_guides(
20253        0..3,
20254        vec![indent_guide(buffer_id, 1, 2, 0)],
20255        Some(vec![0]),
20256        &mut cx,
20257    );
20258}
20259
20260#[gpui::test]
20261async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20262    init_test(cx, |_| {});
20263    let mut cx = EditorTestContext::new(cx).await;
20264    let text = indoc! {
20265        "
20266        impl A {
20267            fn b() {
20268                0;
20269                3;
20270                5;
20271                6;
20272                7;
20273            }
20274        }
20275        "
20276    };
20277    let base_text = indoc! {
20278        "
20279        impl A {
20280            fn b() {
20281                0;
20282                1;
20283                2;
20284                3;
20285                4;
20286            }
20287            fn c() {
20288                5;
20289                6;
20290                7;
20291            }
20292        }
20293        "
20294    };
20295
20296    cx.update_editor(|editor, window, cx| {
20297        editor.set_text(text, window, cx);
20298
20299        editor.buffer().update(cx, |multibuffer, cx| {
20300            let buffer = multibuffer.as_singleton().unwrap();
20301            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20302
20303            multibuffer.set_all_diff_hunks_expanded(cx);
20304            multibuffer.add_diff(diff, cx);
20305
20306            buffer.read(cx).remote_id()
20307        })
20308    });
20309    cx.run_until_parked();
20310
20311    cx.assert_state_with_diff(
20312        indoc! { "
20313          impl A {
20314              fn b() {
20315                  0;
20316        -         1;
20317        -         2;
20318                  3;
20319        -         4;
20320        -     }
20321        -     fn c() {
20322                  5;
20323                  6;
20324                  7;
20325              }
20326          }
20327          ˇ"
20328        }
20329        .to_string(),
20330    );
20331
20332    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20333        editor
20334            .snapshot(window, cx)
20335            .buffer_snapshot
20336            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20337            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20338            .collect::<Vec<_>>()
20339    });
20340    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20341    assert_eq!(
20342        actual_guides,
20343        vec![
20344            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20345            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20346            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20347        ]
20348    );
20349}
20350
20351#[gpui::test]
20352async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20353    init_test(cx, |_| {});
20354    let mut cx = EditorTestContext::new(cx).await;
20355
20356    let diff_base = r#"
20357        a
20358        b
20359        c
20360        "#
20361    .unindent();
20362
20363    cx.set_state(
20364        &r#"
20365        ˇA
20366        b
20367        C
20368        "#
20369        .unindent(),
20370    );
20371    cx.set_head_text(&diff_base);
20372    cx.update_editor(|editor, window, cx| {
20373        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20374    });
20375    executor.run_until_parked();
20376
20377    let both_hunks_expanded = r#"
20378        - a
20379        + ˇA
20380          b
20381        - c
20382        + C
20383        "#
20384    .unindent();
20385
20386    cx.assert_state_with_diff(both_hunks_expanded.clone());
20387
20388    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20389        let snapshot = editor.snapshot(window, cx);
20390        let hunks = editor
20391            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20392            .collect::<Vec<_>>();
20393        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20394        let buffer_id = hunks[0].buffer_id;
20395        hunks
20396            .into_iter()
20397            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20398            .collect::<Vec<_>>()
20399    });
20400    assert_eq!(hunk_ranges.len(), 2);
20401
20402    cx.update_editor(|editor, _, cx| {
20403        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20404    });
20405    executor.run_until_parked();
20406
20407    let second_hunk_expanded = r#"
20408          ˇA
20409          b
20410        - c
20411        + C
20412        "#
20413    .unindent();
20414
20415    cx.assert_state_with_diff(second_hunk_expanded);
20416
20417    cx.update_editor(|editor, _, cx| {
20418        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20419    });
20420    executor.run_until_parked();
20421
20422    cx.assert_state_with_diff(both_hunks_expanded.clone());
20423
20424    cx.update_editor(|editor, _, cx| {
20425        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20426    });
20427    executor.run_until_parked();
20428
20429    let first_hunk_expanded = r#"
20430        - a
20431        + ˇA
20432          b
20433          C
20434        "#
20435    .unindent();
20436
20437    cx.assert_state_with_diff(first_hunk_expanded);
20438
20439    cx.update_editor(|editor, _, cx| {
20440        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20441    });
20442    executor.run_until_parked();
20443
20444    cx.assert_state_with_diff(both_hunks_expanded);
20445
20446    cx.set_state(
20447        &r#"
20448        ˇA
20449        b
20450        "#
20451        .unindent(),
20452    );
20453    cx.run_until_parked();
20454
20455    // TODO this cursor position seems bad
20456    cx.assert_state_with_diff(
20457        r#"
20458        - ˇa
20459        + A
20460          b
20461        "#
20462        .unindent(),
20463    );
20464
20465    cx.update_editor(|editor, window, cx| {
20466        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20467    });
20468
20469    cx.assert_state_with_diff(
20470        r#"
20471            - ˇa
20472            + A
20473              b
20474            - c
20475            "#
20476        .unindent(),
20477    );
20478
20479    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20480        let snapshot = editor.snapshot(window, cx);
20481        let hunks = editor
20482            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20483            .collect::<Vec<_>>();
20484        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20485        let buffer_id = hunks[0].buffer_id;
20486        hunks
20487            .into_iter()
20488            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20489            .collect::<Vec<_>>()
20490    });
20491    assert_eq!(hunk_ranges.len(), 2);
20492
20493    cx.update_editor(|editor, _, cx| {
20494        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20495    });
20496    executor.run_until_parked();
20497
20498    cx.assert_state_with_diff(
20499        r#"
20500        - ˇa
20501        + A
20502          b
20503        "#
20504        .unindent(),
20505    );
20506}
20507
20508#[gpui::test]
20509async fn test_toggle_deletion_hunk_at_start_of_file(
20510    executor: BackgroundExecutor,
20511    cx: &mut TestAppContext,
20512) {
20513    init_test(cx, |_| {});
20514    let mut cx = EditorTestContext::new(cx).await;
20515
20516    let diff_base = r#"
20517        a
20518        b
20519        c
20520        "#
20521    .unindent();
20522
20523    cx.set_state(
20524        &r#"
20525        ˇb
20526        c
20527        "#
20528        .unindent(),
20529    );
20530    cx.set_head_text(&diff_base);
20531    cx.update_editor(|editor, window, cx| {
20532        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20533    });
20534    executor.run_until_parked();
20535
20536    let hunk_expanded = r#"
20537        - a
20538          ˇb
20539          c
20540        "#
20541    .unindent();
20542
20543    cx.assert_state_with_diff(hunk_expanded.clone());
20544
20545    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20546        let snapshot = editor.snapshot(window, cx);
20547        let hunks = editor
20548            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20549            .collect::<Vec<_>>();
20550        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20551        let buffer_id = hunks[0].buffer_id;
20552        hunks
20553            .into_iter()
20554            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20555            .collect::<Vec<_>>()
20556    });
20557    assert_eq!(hunk_ranges.len(), 1);
20558
20559    cx.update_editor(|editor, _, cx| {
20560        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20561    });
20562    executor.run_until_parked();
20563
20564    let hunk_collapsed = r#"
20565          ˇb
20566          c
20567        "#
20568    .unindent();
20569
20570    cx.assert_state_with_diff(hunk_collapsed);
20571
20572    cx.update_editor(|editor, _, cx| {
20573        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20574    });
20575    executor.run_until_parked();
20576
20577    cx.assert_state_with_diff(hunk_expanded);
20578}
20579
20580#[gpui::test]
20581async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20582    init_test(cx, |_| {});
20583
20584    let fs = FakeFs::new(cx.executor());
20585    fs.insert_tree(
20586        path!("/test"),
20587        json!({
20588            ".git": {},
20589            "file-1": "ONE\n",
20590            "file-2": "TWO\n",
20591            "file-3": "THREE\n",
20592        }),
20593    )
20594    .await;
20595
20596    fs.set_head_for_repo(
20597        path!("/test/.git").as_ref(),
20598        &[
20599            ("file-1".into(), "one\n".into()),
20600            ("file-2".into(), "two\n".into()),
20601            ("file-3".into(), "three\n".into()),
20602        ],
20603        "deadbeef",
20604    );
20605
20606    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20607    let mut buffers = vec![];
20608    for i in 1..=3 {
20609        let buffer = project
20610            .update(cx, |project, cx| {
20611                let path = format!(path!("/test/file-{}"), i);
20612                project.open_local_buffer(path, cx)
20613            })
20614            .await
20615            .unwrap();
20616        buffers.push(buffer);
20617    }
20618
20619    let multibuffer = cx.new(|cx| {
20620        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20621        multibuffer.set_all_diff_hunks_expanded(cx);
20622        for buffer in &buffers {
20623            let snapshot = buffer.read(cx).snapshot();
20624            multibuffer.set_excerpts_for_path(
20625                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20626                buffer.clone(),
20627                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20628                2,
20629                cx,
20630            );
20631        }
20632        multibuffer
20633    });
20634
20635    let editor = cx.add_window(|window, cx| {
20636        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20637    });
20638    cx.run_until_parked();
20639
20640    let snapshot = editor
20641        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20642        .unwrap();
20643    let hunks = snapshot
20644        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20645        .map(|hunk| match hunk {
20646            DisplayDiffHunk::Unfolded {
20647                display_row_range, ..
20648            } => display_row_range,
20649            DisplayDiffHunk::Folded { .. } => unreachable!(),
20650        })
20651        .collect::<Vec<_>>();
20652    assert_eq!(
20653        hunks,
20654        [
20655            DisplayRow(2)..DisplayRow(4),
20656            DisplayRow(7)..DisplayRow(9),
20657            DisplayRow(12)..DisplayRow(14),
20658        ]
20659    );
20660}
20661
20662#[gpui::test]
20663async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
20664    init_test(cx, |_| {});
20665
20666    let mut cx = EditorTestContext::new(cx).await;
20667    cx.set_head_text(indoc! { "
20668        one
20669        two
20670        three
20671        four
20672        five
20673        "
20674    });
20675    cx.set_index_text(indoc! { "
20676        one
20677        two
20678        three
20679        four
20680        five
20681        "
20682    });
20683    cx.set_state(indoc! {"
20684        one
20685        TWO
20686        ˇTHREE
20687        FOUR
20688        five
20689    "});
20690    cx.run_until_parked();
20691    cx.update_editor(|editor, window, cx| {
20692        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20693    });
20694    cx.run_until_parked();
20695    cx.assert_index_text(Some(indoc! {"
20696        one
20697        TWO
20698        THREE
20699        FOUR
20700        five
20701    "}));
20702    cx.set_state(indoc! { "
20703        one
20704        TWO
20705        ˇTHREE-HUNDRED
20706        FOUR
20707        five
20708    "});
20709    cx.run_until_parked();
20710    cx.update_editor(|editor, window, cx| {
20711        let snapshot = editor.snapshot(window, cx);
20712        let hunks = editor
20713            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20714            .collect::<Vec<_>>();
20715        assert_eq!(hunks.len(), 1);
20716        assert_eq!(
20717            hunks[0].status(),
20718            DiffHunkStatus {
20719                kind: DiffHunkStatusKind::Modified,
20720                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
20721            }
20722        );
20723
20724        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20725    });
20726    cx.run_until_parked();
20727    cx.assert_index_text(Some(indoc! {"
20728        one
20729        TWO
20730        THREE-HUNDRED
20731        FOUR
20732        five
20733    "}));
20734}
20735
20736#[gpui::test]
20737fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
20738    init_test(cx, |_| {});
20739
20740    let editor = cx.add_window(|window, cx| {
20741        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
20742        build_editor(buffer, window, cx)
20743    });
20744
20745    let render_args = Arc::new(Mutex::new(None));
20746    let snapshot = editor
20747        .update(cx, |editor, window, cx| {
20748            let snapshot = editor.buffer().read(cx).snapshot(cx);
20749            let range =
20750                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
20751
20752            struct RenderArgs {
20753                row: MultiBufferRow,
20754                folded: bool,
20755                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
20756            }
20757
20758            let crease = Crease::inline(
20759                range,
20760                FoldPlaceholder::test(),
20761                {
20762                    let toggle_callback = render_args.clone();
20763                    move |row, folded, callback, _window, _cx| {
20764                        *toggle_callback.lock() = Some(RenderArgs {
20765                            row,
20766                            folded,
20767                            callback,
20768                        });
20769                        div()
20770                    }
20771                },
20772                |_row, _folded, _window, _cx| div(),
20773            );
20774
20775            editor.insert_creases(Some(crease), cx);
20776            let snapshot = editor.snapshot(window, cx);
20777            let _div =
20778                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
20779            snapshot
20780        })
20781        .unwrap();
20782
20783    let render_args = render_args.lock().take().unwrap();
20784    assert_eq!(render_args.row, MultiBufferRow(1));
20785    assert!(!render_args.folded);
20786    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
20787
20788    cx.update_window(*editor, |_, window, cx| {
20789        (render_args.callback)(true, window, cx)
20790    })
20791    .unwrap();
20792    let snapshot = editor
20793        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20794        .unwrap();
20795    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
20796
20797    cx.update_window(*editor, |_, window, cx| {
20798        (render_args.callback)(false, window, cx)
20799    })
20800    .unwrap();
20801    let snapshot = editor
20802        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20803        .unwrap();
20804    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
20805}
20806
20807#[gpui::test]
20808async fn test_input_text(cx: &mut TestAppContext) {
20809    init_test(cx, |_| {});
20810    let mut cx = EditorTestContext::new(cx).await;
20811
20812    cx.set_state(
20813        &r#"ˇone
20814        two
20815
20816        three
20817        fourˇ
20818        five
20819
20820        siˇx"#
20821            .unindent(),
20822    );
20823
20824    cx.dispatch_action(HandleInput(String::new()));
20825    cx.assert_editor_state(
20826        &r#"ˇone
20827        two
20828
20829        three
20830        fourˇ
20831        five
20832
20833        siˇx"#
20834            .unindent(),
20835    );
20836
20837    cx.dispatch_action(HandleInput("AAAA".to_string()));
20838    cx.assert_editor_state(
20839        &r#"AAAAˇone
20840        two
20841
20842        three
20843        fourAAAAˇ
20844        five
20845
20846        siAAAAˇx"#
20847            .unindent(),
20848    );
20849}
20850
20851#[gpui::test]
20852async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
20853    init_test(cx, |_| {});
20854
20855    let mut cx = EditorTestContext::new(cx).await;
20856    cx.set_state(
20857        r#"let foo = 1;
20858let foo = 2;
20859let foo = 3;
20860let fooˇ = 4;
20861let foo = 5;
20862let foo = 6;
20863let foo = 7;
20864let foo = 8;
20865let foo = 9;
20866let foo = 10;
20867let foo = 11;
20868let foo = 12;
20869let foo = 13;
20870let foo = 14;
20871let foo = 15;"#,
20872    );
20873
20874    cx.update_editor(|e, window, cx| {
20875        assert_eq!(
20876            e.next_scroll_position,
20877            NextScrollCursorCenterTopBottom::Center,
20878            "Default next scroll direction is center",
20879        );
20880
20881        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20882        assert_eq!(
20883            e.next_scroll_position,
20884            NextScrollCursorCenterTopBottom::Top,
20885            "After center, next scroll direction should be top",
20886        );
20887
20888        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20889        assert_eq!(
20890            e.next_scroll_position,
20891            NextScrollCursorCenterTopBottom::Bottom,
20892            "After top, next scroll direction should be bottom",
20893        );
20894
20895        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20896        assert_eq!(
20897            e.next_scroll_position,
20898            NextScrollCursorCenterTopBottom::Center,
20899            "After bottom, scrolling should start over",
20900        );
20901
20902        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20903        assert_eq!(
20904            e.next_scroll_position,
20905            NextScrollCursorCenterTopBottom::Top,
20906            "Scrolling continues if retriggered fast enough"
20907        );
20908    });
20909
20910    cx.executor()
20911        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
20912    cx.executor().run_until_parked();
20913    cx.update_editor(|e, _, _| {
20914        assert_eq!(
20915            e.next_scroll_position,
20916            NextScrollCursorCenterTopBottom::Center,
20917            "If scrolling is not triggered fast enough, it should reset"
20918        );
20919    });
20920}
20921
20922#[gpui::test]
20923async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
20924    init_test(cx, |_| {});
20925    let mut cx = EditorLspTestContext::new_rust(
20926        lsp::ServerCapabilities {
20927            definition_provider: Some(lsp::OneOf::Left(true)),
20928            references_provider: Some(lsp::OneOf::Left(true)),
20929            ..lsp::ServerCapabilities::default()
20930        },
20931        cx,
20932    )
20933    .await;
20934
20935    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
20936        let go_to_definition = cx
20937            .lsp
20938            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20939                move |params, _| async move {
20940                    if empty_go_to_definition {
20941                        Ok(None)
20942                    } else {
20943                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
20944                            uri: params.text_document_position_params.text_document.uri,
20945                            range: lsp::Range::new(
20946                                lsp::Position::new(4, 3),
20947                                lsp::Position::new(4, 6),
20948                            ),
20949                        })))
20950                    }
20951                },
20952            );
20953        let references = cx
20954            .lsp
20955            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
20956                Ok(Some(vec![lsp::Location {
20957                    uri: params.text_document_position.text_document.uri,
20958                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
20959                }]))
20960            });
20961        (go_to_definition, references)
20962    };
20963
20964    cx.set_state(
20965        &r#"fn one() {
20966            let mut a = ˇtwo();
20967        }
20968
20969        fn two() {}"#
20970            .unindent(),
20971    );
20972    set_up_lsp_handlers(false, &mut cx);
20973    let navigated = cx
20974        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20975        .await
20976        .expect("Failed to navigate to definition");
20977    assert_eq!(
20978        navigated,
20979        Navigated::Yes,
20980        "Should have navigated to definition from the GetDefinition response"
20981    );
20982    cx.assert_editor_state(
20983        &r#"fn one() {
20984            let mut a = two();
20985        }
20986
20987        fn «twoˇ»() {}"#
20988            .unindent(),
20989    );
20990
20991    let editors = cx.update_workspace(|workspace, _, cx| {
20992        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20993    });
20994    cx.update_editor(|_, _, test_editor_cx| {
20995        assert_eq!(
20996            editors.len(),
20997            1,
20998            "Initially, only one, test, editor should be open in the workspace"
20999        );
21000        assert_eq!(
21001            test_editor_cx.entity(),
21002            editors.last().expect("Asserted len is 1").clone()
21003        );
21004    });
21005
21006    set_up_lsp_handlers(true, &mut cx);
21007    let navigated = cx
21008        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21009        .await
21010        .expect("Failed to navigate to lookup references");
21011    assert_eq!(
21012        navigated,
21013        Navigated::Yes,
21014        "Should have navigated to references as a fallback after empty GoToDefinition response"
21015    );
21016    // We should not change the selections in the existing file,
21017    // if opening another milti buffer with the references
21018    cx.assert_editor_state(
21019        &r#"fn one() {
21020            let mut a = two();
21021        }
21022
21023        fn «twoˇ»() {}"#
21024            .unindent(),
21025    );
21026    let editors = cx.update_workspace(|workspace, _, cx| {
21027        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21028    });
21029    cx.update_editor(|_, _, test_editor_cx| {
21030        assert_eq!(
21031            editors.len(),
21032            2,
21033            "After falling back to references search, we open a new editor with the results"
21034        );
21035        let references_fallback_text = editors
21036            .into_iter()
21037            .find(|new_editor| *new_editor != test_editor_cx.entity())
21038            .expect("Should have one non-test editor now")
21039            .read(test_editor_cx)
21040            .text(test_editor_cx);
21041        assert_eq!(
21042            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21043            "Should use the range from the references response and not the GoToDefinition one"
21044        );
21045    });
21046}
21047
21048#[gpui::test]
21049async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21050    init_test(cx, |_| {});
21051    cx.update(|cx| {
21052        let mut editor_settings = EditorSettings::get_global(cx).clone();
21053        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21054        EditorSettings::override_global(editor_settings, cx);
21055    });
21056    let mut cx = EditorLspTestContext::new_rust(
21057        lsp::ServerCapabilities {
21058            definition_provider: Some(lsp::OneOf::Left(true)),
21059            references_provider: Some(lsp::OneOf::Left(true)),
21060            ..lsp::ServerCapabilities::default()
21061        },
21062        cx,
21063    )
21064    .await;
21065    let original_state = r#"fn one() {
21066        let mut a = ˇtwo();
21067    }
21068
21069    fn two() {}"#
21070        .unindent();
21071    cx.set_state(&original_state);
21072
21073    let mut go_to_definition = cx
21074        .lsp
21075        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21076            move |_, _| async move { Ok(None) },
21077        );
21078    let _references = cx
21079        .lsp
21080        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21081            panic!("Should not call for references with no go to definition fallback")
21082        });
21083
21084    let navigated = cx
21085        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21086        .await
21087        .expect("Failed to navigate to lookup references");
21088    go_to_definition
21089        .next()
21090        .await
21091        .expect("Should have called the go_to_definition handler");
21092
21093    assert_eq!(
21094        navigated,
21095        Navigated::No,
21096        "Should have navigated to references as a fallback after empty GoToDefinition response"
21097    );
21098    cx.assert_editor_state(&original_state);
21099    let editors = cx.update_workspace(|workspace, _, cx| {
21100        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21101    });
21102    cx.update_editor(|_, _, _| {
21103        assert_eq!(
21104            editors.len(),
21105            1,
21106            "After unsuccessful fallback, no other editor should have been opened"
21107        );
21108    });
21109}
21110
21111#[gpui::test]
21112async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21113    init_test(cx, |_| {});
21114
21115    let language = Arc::new(Language::new(
21116        LanguageConfig::default(),
21117        Some(tree_sitter_rust::LANGUAGE.into()),
21118    ));
21119
21120    let text = r#"
21121        #[cfg(test)]
21122        mod tests() {
21123            #[test]
21124            fn runnable_1() {
21125                let a = 1;
21126            }
21127
21128            #[test]
21129            fn runnable_2() {
21130                let a = 1;
21131                let b = 2;
21132            }
21133        }
21134    "#
21135    .unindent();
21136
21137    let fs = FakeFs::new(cx.executor());
21138    fs.insert_file("/file.rs", Default::default()).await;
21139
21140    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21141    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21142    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21143    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21144    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21145
21146    let editor = cx.new_window_entity(|window, cx| {
21147        Editor::new(
21148            EditorMode::full(),
21149            multi_buffer,
21150            Some(project.clone()),
21151            window,
21152            cx,
21153        )
21154    });
21155
21156    editor.update_in(cx, |editor, window, cx| {
21157        let snapshot = editor.buffer().read(cx).snapshot(cx);
21158        editor.tasks.insert(
21159            (buffer.read(cx).remote_id(), 3),
21160            RunnableTasks {
21161                templates: vec![],
21162                offset: snapshot.anchor_before(43),
21163                column: 0,
21164                extra_variables: HashMap::default(),
21165                context_range: BufferOffset(43)..BufferOffset(85),
21166            },
21167        );
21168        editor.tasks.insert(
21169            (buffer.read(cx).remote_id(), 8),
21170            RunnableTasks {
21171                templates: vec![],
21172                offset: snapshot.anchor_before(86),
21173                column: 0,
21174                extra_variables: HashMap::default(),
21175                context_range: BufferOffset(86)..BufferOffset(191),
21176            },
21177        );
21178
21179        // Test finding task when cursor is inside function body
21180        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21181            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21182        });
21183        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21184        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21185
21186        // Test finding task when cursor is on function name
21187        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21188            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21189        });
21190        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21191        assert_eq!(row, 8, "Should find task when cursor is on function name");
21192    });
21193}
21194
21195#[gpui::test]
21196async fn test_folding_buffers(cx: &mut TestAppContext) {
21197    init_test(cx, |_| {});
21198
21199    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21200    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21201    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21202
21203    let fs = FakeFs::new(cx.executor());
21204    fs.insert_tree(
21205        path!("/a"),
21206        json!({
21207            "first.rs": sample_text_1,
21208            "second.rs": sample_text_2,
21209            "third.rs": sample_text_3,
21210        }),
21211    )
21212    .await;
21213    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21214    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21215    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21216    let worktree = project.update(cx, |project, cx| {
21217        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21218        assert_eq!(worktrees.len(), 1);
21219        worktrees.pop().unwrap()
21220    });
21221    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21222
21223    let buffer_1 = project
21224        .update(cx, |project, cx| {
21225            project.open_buffer((worktree_id, "first.rs"), cx)
21226        })
21227        .await
21228        .unwrap();
21229    let buffer_2 = project
21230        .update(cx, |project, cx| {
21231            project.open_buffer((worktree_id, "second.rs"), cx)
21232        })
21233        .await
21234        .unwrap();
21235    let buffer_3 = project
21236        .update(cx, |project, cx| {
21237            project.open_buffer((worktree_id, "third.rs"), cx)
21238        })
21239        .await
21240        .unwrap();
21241
21242    let multi_buffer = cx.new(|cx| {
21243        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21244        multi_buffer.push_excerpts(
21245            buffer_1.clone(),
21246            [
21247                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21248                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21249                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21250            ],
21251            cx,
21252        );
21253        multi_buffer.push_excerpts(
21254            buffer_2.clone(),
21255            [
21256                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21257                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21258                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21259            ],
21260            cx,
21261        );
21262        multi_buffer.push_excerpts(
21263            buffer_3.clone(),
21264            [
21265                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21266                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21267                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21268            ],
21269            cx,
21270        );
21271        multi_buffer
21272    });
21273    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21274        Editor::new(
21275            EditorMode::full(),
21276            multi_buffer.clone(),
21277            Some(project.clone()),
21278            window,
21279            cx,
21280        )
21281    });
21282
21283    assert_eq!(
21284        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21285        "\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",
21286    );
21287
21288    multi_buffer_editor.update(cx, |editor, cx| {
21289        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21290    });
21291    assert_eq!(
21292        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21293        "\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",
21294        "After folding the first buffer, its text should not be displayed"
21295    );
21296
21297    multi_buffer_editor.update(cx, |editor, cx| {
21298        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21299    });
21300    assert_eq!(
21301        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21302        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21303        "After folding the second buffer, its text should not be displayed"
21304    );
21305
21306    multi_buffer_editor.update(cx, |editor, cx| {
21307        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21308    });
21309    assert_eq!(
21310        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21311        "\n\n\n\n\n",
21312        "After folding the third buffer, its text should not be displayed"
21313    );
21314
21315    // Emulate selection inside the fold logic, that should work
21316    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21317        editor
21318            .snapshot(window, cx)
21319            .next_line_boundary(Point::new(0, 4));
21320    });
21321
21322    multi_buffer_editor.update(cx, |editor, cx| {
21323        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21324    });
21325    assert_eq!(
21326        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21327        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21328        "After unfolding the second buffer, its text should be displayed"
21329    );
21330
21331    // Typing inside of buffer 1 causes that buffer to be unfolded.
21332    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21333        assert_eq!(
21334            multi_buffer
21335                .read(cx)
21336                .snapshot(cx)
21337                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21338                .collect::<String>(),
21339            "bbbb"
21340        );
21341        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21342            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21343        });
21344        editor.handle_input("B", window, cx);
21345    });
21346
21347    assert_eq!(
21348        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21349        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21350        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21351    );
21352
21353    multi_buffer_editor.update(cx, |editor, cx| {
21354        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21355    });
21356    assert_eq!(
21357        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21358        "\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",
21359        "After unfolding the all buffers, all original text should be displayed"
21360    );
21361}
21362
21363#[gpui::test]
21364async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21365    init_test(cx, |_| {});
21366
21367    let sample_text_1 = "1111\n2222\n3333".to_string();
21368    let sample_text_2 = "4444\n5555\n6666".to_string();
21369    let sample_text_3 = "7777\n8888\n9999".to_string();
21370
21371    let fs = FakeFs::new(cx.executor());
21372    fs.insert_tree(
21373        path!("/a"),
21374        json!({
21375            "first.rs": sample_text_1,
21376            "second.rs": sample_text_2,
21377            "third.rs": sample_text_3,
21378        }),
21379    )
21380    .await;
21381    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21382    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21383    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21384    let worktree = project.update(cx, |project, cx| {
21385        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21386        assert_eq!(worktrees.len(), 1);
21387        worktrees.pop().unwrap()
21388    });
21389    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21390
21391    let buffer_1 = project
21392        .update(cx, |project, cx| {
21393            project.open_buffer((worktree_id, "first.rs"), cx)
21394        })
21395        .await
21396        .unwrap();
21397    let buffer_2 = project
21398        .update(cx, |project, cx| {
21399            project.open_buffer((worktree_id, "second.rs"), cx)
21400        })
21401        .await
21402        .unwrap();
21403    let buffer_3 = project
21404        .update(cx, |project, cx| {
21405            project.open_buffer((worktree_id, "third.rs"), cx)
21406        })
21407        .await
21408        .unwrap();
21409
21410    let multi_buffer = cx.new(|cx| {
21411        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21412        multi_buffer.push_excerpts(
21413            buffer_1.clone(),
21414            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21415            cx,
21416        );
21417        multi_buffer.push_excerpts(
21418            buffer_2.clone(),
21419            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21420            cx,
21421        );
21422        multi_buffer.push_excerpts(
21423            buffer_3.clone(),
21424            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21425            cx,
21426        );
21427        multi_buffer
21428    });
21429
21430    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21431        Editor::new(
21432            EditorMode::full(),
21433            multi_buffer,
21434            Some(project.clone()),
21435            window,
21436            cx,
21437        )
21438    });
21439
21440    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21441    assert_eq!(
21442        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21443        full_text,
21444    );
21445
21446    multi_buffer_editor.update(cx, |editor, cx| {
21447        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21448    });
21449    assert_eq!(
21450        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21451        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21452        "After folding the first buffer, its text should not be displayed"
21453    );
21454
21455    multi_buffer_editor.update(cx, |editor, cx| {
21456        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21457    });
21458
21459    assert_eq!(
21460        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21461        "\n\n\n\n\n\n7777\n8888\n9999",
21462        "After folding the second buffer, its text should not be displayed"
21463    );
21464
21465    multi_buffer_editor.update(cx, |editor, cx| {
21466        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21467    });
21468    assert_eq!(
21469        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21470        "\n\n\n\n\n",
21471        "After folding the third buffer, its text should not be displayed"
21472    );
21473
21474    multi_buffer_editor.update(cx, |editor, cx| {
21475        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21476    });
21477    assert_eq!(
21478        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21479        "\n\n\n\n4444\n5555\n6666\n\n",
21480        "After unfolding the second buffer, its text should be displayed"
21481    );
21482
21483    multi_buffer_editor.update(cx, |editor, cx| {
21484        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21485    });
21486    assert_eq!(
21487        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21488        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21489        "After unfolding the first buffer, its text should be displayed"
21490    );
21491
21492    multi_buffer_editor.update(cx, |editor, cx| {
21493        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21494    });
21495    assert_eq!(
21496        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21497        full_text,
21498        "After unfolding all buffers, all original text should be displayed"
21499    );
21500}
21501
21502#[gpui::test]
21503async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21504    init_test(cx, |_| {});
21505
21506    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21507
21508    let fs = FakeFs::new(cx.executor());
21509    fs.insert_tree(
21510        path!("/a"),
21511        json!({
21512            "main.rs": sample_text,
21513        }),
21514    )
21515    .await;
21516    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21517    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21518    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21519    let worktree = project.update(cx, |project, cx| {
21520        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21521        assert_eq!(worktrees.len(), 1);
21522        worktrees.pop().unwrap()
21523    });
21524    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21525
21526    let buffer_1 = project
21527        .update(cx, |project, cx| {
21528            project.open_buffer((worktree_id, "main.rs"), cx)
21529        })
21530        .await
21531        .unwrap();
21532
21533    let multi_buffer = cx.new(|cx| {
21534        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21535        multi_buffer.push_excerpts(
21536            buffer_1.clone(),
21537            [ExcerptRange::new(
21538                Point::new(0, 0)
21539                    ..Point::new(
21540                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
21541                        0,
21542                    ),
21543            )],
21544            cx,
21545        );
21546        multi_buffer
21547    });
21548    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21549        Editor::new(
21550            EditorMode::full(),
21551            multi_buffer,
21552            Some(project.clone()),
21553            window,
21554            cx,
21555        )
21556    });
21557
21558    let selection_range = Point::new(1, 0)..Point::new(2, 0);
21559    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21560        enum TestHighlight {}
21561        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
21562        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
21563        editor.highlight_text::<TestHighlight>(
21564            vec![highlight_range.clone()],
21565            HighlightStyle::color(Hsla::green()),
21566            cx,
21567        );
21568        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21569            s.select_ranges(Some(highlight_range))
21570        });
21571    });
21572
21573    let full_text = format!("\n\n{sample_text}");
21574    assert_eq!(
21575        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21576        full_text,
21577    );
21578}
21579
21580#[gpui::test]
21581async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
21582    init_test(cx, |_| {});
21583    cx.update(|cx| {
21584        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
21585            "keymaps/default-linux.json",
21586            cx,
21587        )
21588        .unwrap();
21589        cx.bind_keys(default_key_bindings);
21590    });
21591
21592    let (editor, cx) = cx.add_window_view(|window, cx| {
21593        let multi_buffer = MultiBuffer::build_multi(
21594            [
21595                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
21596                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
21597                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
21598                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
21599            ],
21600            cx,
21601        );
21602        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
21603
21604        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
21605        // fold all but the second buffer, so that we test navigating between two
21606        // adjacent folded buffers, as well as folded buffers at the start and
21607        // end the multibuffer
21608        editor.fold_buffer(buffer_ids[0], cx);
21609        editor.fold_buffer(buffer_ids[2], cx);
21610        editor.fold_buffer(buffer_ids[3], cx);
21611
21612        editor
21613    });
21614    cx.simulate_resize(size(px(1000.), px(1000.)));
21615
21616    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
21617    cx.assert_excerpts_with_selections(indoc! {"
21618        [EXCERPT]
21619        ˇ[FOLDED]
21620        [EXCERPT]
21621        a1
21622        b1
21623        [EXCERPT]
21624        [FOLDED]
21625        [EXCERPT]
21626        [FOLDED]
21627        "
21628    });
21629    cx.simulate_keystroke("down");
21630    cx.assert_excerpts_with_selections(indoc! {"
21631        [EXCERPT]
21632        [FOLDED]
21633        [EXCERPT]
21634        ˇa1
21635        b1
21636        [EXCERPT]
21637        [FOLDED]
21638        [EXCERPT]
21639        [FOLDED]
21640        "
21641    });
21642    cx.simulate_keystroke("down");
21643    cx.assert_excerpts_with_selections(indoc! {"
21644        [EXCERPT]
21645        [FOLDED]
21646        [EXCERPT]
21647        a1
21648        ˇb1
21649        [EXCERPT]
21650        [FOLDED]
21651        [EXCERPT]
21652        [FOLDED]
21653        "
21654    });
21655    cx.simulate_keystroke("down");
21656    cx.assert_excerpts_with_selections(indoc! {"
21657        [EXCERPT]
21658        [FOLDED]
21659        [EXCERPT]
21660        a1
21661        b1
21662        ˇ[EXCERPT]
21663        [FOLDED]
21664        [EXCERPT]
21665        [FOLDED]
21666        "
21667    });
21668    cx.simulate_keystroke("down");
21669    cx.assert_excerpts_with_selections(indoc! {"
21670        [EXCERPT]
21671        [FOLDED]
21672        [EXCERPT]
21673        a1
21674        b1
21675        [EXCERPT]
21676        ˇ[FOLDED]
21677        [EXCERPT]
21678        [FOLDED]
21679        "
21680    });
21681    for _ in 0..5 {
21682        cx.simulate_keystroke("down");
21683        cx.assert_excerpts_with_selections(indoc! {"
21684            [EXCERPT]
21685            [FOLDED]
21686            [EXCERPT]
21687            a1
21688            b1
21689            [EXCERPT]
21690            [FOLDED]
21691            [EXCERPT]
21692            ˇ[FOLDED]
21693            "
21694        });
21695    }
21696
21697    cx.simulate_keystroke("up");
21698    cx.assert_excerpts_with_selections(indoc! {"
21699        [EXCERPT]
21700        [FOLDED]
21701        [EXCERPT]
21702        a1
21703        b1
21704        [EXCERPT]
21705        ˇ[FOLDED]
21706        [EXCERPT]
21707        [FOLDED]
21708        "
21709    });
21710    cx.simulate_keystroke("up");
21711    cx.assert_excerpts_with_selections(indoc! {"
21712        [EXCERPT]
21713        [FOLDED]
21714        [EXCERPT]
21715        a1
21716        b1
21717        ˇ[EXCERPT]
21718        [FOLDED]
21719        [EXCERPT]
21720        [FOLDED]
21721        "
21722    });
21723    cx.simulate_keystroke("up");
21724    cx.assert_excerpts_with_selections(indoc! {"
21725        [EXCERPT]
21726        [FOLDED]
21727        [EXCERPT]
21728        a1
21729        ˇb1
21730        [EXCERPT]
21731        [FOLDED]
21732        [EXCERPT]
21733        [FOLDED]
21734        "
21735    });
21736    cx.simulate_keystroke("up");
21737    cx.assert_excerpts_with_selections(indoc! {"
21738        [EXCERPT]
21739        [FOLDED]
21740        [EXCERPT]
21741        ˇa1
21742        b1
21743        [EXCERPT]
21744        [FOLDED]
21745        [EXCERPT]
21746        [FOLDED]
21747        "
21748    });
21749    for _ in 0..5 {
21750        cx.simulate_keystroke("up");
21751        cx.assert_excerpts_with_selections(indoc! {"
21752            [EXCERPT]
21753            ˇ[FOLDED]
21754            [EXCERPT]
21755            a1
21756            b1
21757            [EXCERPT]
21758            [FOLDED]
21759            [EXCERPT]
21760            [FOLDED]
21761            "
21762        });
21763    }
21764}
21765
21766#[gpui::test]
21767async fn test_edit_prediction_text(cx: &mut TestAppContext) {
21768    init_test(cx, |_| {});
21769
21770    // Simple insertion
21771    assert_highlighted_edits(
21772        "Hello, world!",
21773        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
21774        true,
21775        cx,
21776        |highlighted_edits, cx| {
21777            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
21778            assert_eq!(highlighted_edits.highlights.len(), 1);
21779            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
21780            assert_eq!(
21781                highlighted_edits.highlights[0].1.background_color,
21782                Some(cx.theme().status().created_background)
21783            );
21784        },
21785    )
21786    .await;
21787
21788    // Replacement
21789    assert_highlighted_edits(
21790        "This is a test.",
21791        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
21792        false,
21793        cx,
21794        |highlighted_edits, cx| {
21795            assert_eq!(highlighted_edits.text, "That is a test.");
21796            assert_eq!(highlighted_edits.highlights.len(), 1);
21797            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
21798            assert_eq!(
21799                highlighted_edits.highlights[0].1.background_color,
21800                Some(cx.theme().status().created_background)
21801            );
21802        },
21803    )
21804    .await;
21805
21806    // Multiple edits
21807    assert_highlighted_edits(
21808        "Hello, world!",
21809        vec![
21810            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
21811            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
21812        ],
21813        false,
21814        cx,
21815        |highlighted_edits, cx| {
21816            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
21817            assert_eq!(highlighted_edits.highlights.len(), 2);
21818            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
21819            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
21820            assert_eq!(
21821                highlighted_edits.highlights[0].1.background_color,
21822                Some(cx.theme().status().created_background)
21823            );
21824            assert_eq!(
21825                highlighted_edits.highlights[1].1.background_color,
21826                Some(cx.theme().status().created_background)
21827            );
21828        },
21829    )
21830    .await;
21831
21832    // Multiple lines with edits
21833    assert_highlighted_edits(
21834        "First line\nSecond line\nThird line\nFourth line",
21835        vec![
21836            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
21837            (
21838                Point::new(2, 0)..Point::new(2, 10),
21839                "New third line".to_string(),
21840            ),
21841            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
21842        ],
21843        false,
21844        cx,
21845        |highlighted_edits, cx| {
21846            assert_eq!(
21847                highlighted_edits.text,
21848                "Second modified\nNew third line\nFourth updated line"
21849            );
21850            assert_eq!(highlighted_edits.highlights.len(), 3);
21851            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
21852            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
21853            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
21854            for highlight in &highlighted_edits.highlights {
21855                assert_eq!(
21856                    highlight.1.background_color,
21857                    Some(cx.theme().status().created_background)
21858                );
21859            }
21860        },
21861    )
21862    .await;
21863}
21864
21865#[gpui::test]
21866async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
21867    init_test(cx, |_| {});
21868
21869    // Deletion
21870    assert_highlighted_edits(
21871        "Hello, world!",
21872        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
21873        true,
21874        cx,
21875        |highlighted_edits, cx| {
21876            assert_eq!(highlighted_edits.text, "Hello, world!");
21877            assert_eq!(highlighted_edits.highlights.len(), 1);
21878            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
21879            assert_eq!(
21880                highlighted_edits.highlights[0].1.background_color,
21881                Some(cx.theme().status().deleted_background)
21882            );
21883        },
21884    )
21885    .await;
21886
21887    // Insertion
21888    assert_highlighted_edits(
21889        "Hello, world!",
21890        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
21891        true,
21892        cx,
21893        |highlighted_edits, cx| {
21894            assert_eq!(highlighted_edits.highlights.len(), 1);
21895            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
21896            assert_eq!(
21897                highlighted_edits.highlights[0].1.background_color,
21898                Some(cx.theme().status().created_background)
21899            );
21900        },
21901    )
21902    .await;
21903}
21904
21905async fn assert_highlighted_edits(
21906    text: &str,
21907    edits: Vec<(Range<Point>, String)>,
21908    include_deletions: bool,
21909    cx: &mut TestAppContext,
21910    assertion_fn: impl Fn(HighlightedText, &App),
21911) {
21912    let window = cx.add_window(|window, cx| {
21913        let buffer = MultiBuffer::build_simple(text, cx);
21914        Editor::new(EditorMode::full(), buffer, None, window, cx)
21915    });
21916    let cx = &mut VisualTestContext::from_window(*window, cx);
21917
21918    let (buffer, snapshot) = window
21919        .update(cx, |editor, _window, cx| {
21920            (
21921                editor.buffer().clone(),
21922                editor.buffer().read(cx).snapshot(cx),
21923            )
21924        })
21925        .unwrap();
21926
21927    let edits = edits
21928        .into_iter()
21929        .map(|(range, edit)| {
21930            (
21931                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
21932                edit,
21933            )
21934        })
21935        .collect::<Vec<_>>();
21936
21937    let text_anchor_edits = edits
21938        .clone()
21939        .into_iter()
21940        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
21941        .collect::<Vec<_>>();
21942
21943    let edit_preview = window
21944        .update(cx, |_, _window, cx| {
21945            buffer
21946                .read(cx)
21947                .as_singleton()
21948                .unwrap()
21949                .read(cx)
21950                .preview_edits(text_anchor_edits.into(), cx)
21951        })
21952        .unwrap()
21953        .await;
21954
21955    cx.update(|_window, cx| {
21956        let highlighted_edits = edit_prediction_edit_text(
21957            snapshot.as_singleton().unwrap().2,
21958            &edits,
21959            &edit_preview,
21960            include_deletions,
21961            cx,
21962        );
21963        assertion_fn(highlighted_edits, cx)
21964    });
21965}
21966
21967#[track_caller]
21968fn assert_breakpoint(
21969    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
21970    path: &Arc<Path>,
21971    expected: Vec<(u32, Breakpoint)>,
21972) {
21973    if expected.is_empty() {
21974        assert!(!breakpoints.contains_key(path), "{}", path.display());
21975    } else {
21976        let mut breakpoint = breakpoints
21977            .get(path)
21978            .unwrap()
21979            .iter()
21980            .map(|breakpoint| {
21981                (
21982                    breakpoint.row,
21983                    Breakpoint {
21984                        message: breakpoint.message.clone(),
21985                        state: breakpoint.state,
21986                        condition: breakpoint.condition.clone(),
21987                        hit_condition: breakpoint.hit_condition.clone(),
21988                    },
21989                )
21990            })
21991            .collect::<Vec<_>>();
21992
21993        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
21994
21995        assert_eq!(expected, breakpoint);
21996    }
21997}
21998
21999fn add_log_breakpoint_at_cursor(
22000    editor: &mut Editor,
22001    log_message: &str,
22002    window: &mut Window,
22003    cx: &mut Context<Editor>,
22004) {
22005    let (anchor, bp) = editor
22006        .breakpoints_at_cursors(window, cx)
22007        .first()
22008        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22009        .unwrap_or_else(|| {
22010            let cursor_position: Point = editor.selections.newest(cx).head();
22011
22012            let breakpoint_position = editor
22013                .snapshot(window, cx)
22014                .display_snapshot
22015                .buffer_snapshot
22016                .anchor_before(Point::new(cursor_position.row, 0));
22017
22018            (breakpoint_position, Breakpoint::new_log(log_message))
22019        });
22020
22021    editor.edit_breakpoint_at_anchor(
22022        anchor,
22023        bp,
22024        BreakpointEditAction::EditLogMessage(log_message.into()),
22025        cx,
22026    );
22027}
22028
22029#[gpui::test]
22030async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22031    init_test(cx, |_| {});
22032
22033    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22034    let fs = FakeFs::new(cx.executor());
22035    fs.insert_tree(
22036        path!("/a"),
22037        json!({
22038            "main.rs": sample_text,
22039        }),
22040    )
22041    .await;
22042    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22043    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22044    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22045
22046    let fs = FakeFs::new(cx.executor());
22047    fs.insert_tree(
22048        path!("/a"),
22049        json!({
22050            "main.rs": sample_text,
22051        }),
22052    )
22053    .await;
22054    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22055    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22056    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22057    let worktree_id = workspace
22058        .update(cx, |workspace, _window, cx| {
22059            workspace.project().update(cx, |project, cx| {
22060                project.worktrees(cx).next().unwrap().read(cx).id()
22061            })
22062        })
22063        .unwrap();
22064
22065    let buffer = project
22066        .update(cx, |project, cx| {
22067            project.open_buffer((worktree_id, "main.rs"), cx)
22068        })
22069        .await
22070        .unwrap();
22071
22072    let (editor, cx) = cx.add_window_view(|window, cx| {
22073        Editor::new(
22074            EditorMode::full(),
22075            MultiBuffer::build_from_buffer(buffer, cx),
22076            Some(project.clone()),
22077            window,
22078            cx,
22079        )
22080    });
22081
22082    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22083    let abs_path = project.read_with(cx, |project, cx| {
22084        project
22085            .absolute_path(&project_path, cx)
22086            .map(Arc::from)
22087            .unwrap()
22088    });
22089
22090    // assert we can add breakpoint on the first line
22091    editor.update_in(cx, |editor, window, cx| {
22092        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22093        editor.move_to_end(&MoveToEnd, window, cx);
22094        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22095    });
22096
22097    let breakpoints = editor.update(cx, |editor, cx| {
22098        editor
22099            .breakpoint_store()
22100            .as_ref()
22101            .unwrap()
22102            .read(cx)
22103            .all_source_breakpoints(cx)
22104    });
22105
22106    assert_eq!(1, breakpoints.len());
22107    assert_breakpoint(
22108        &breakpoints,
22109        &abs_path,
22110        vec![
22111            (0, Breakpoint::new_standard()),
22112            (3, Breakpoint::new_standard()),
22113        ],
22114    );
22115
22116    editor.update_in(cx, |editor, window, cx| {
22117        editor.move_to_beginning(&MoveToBeginning, window, cx);
22118        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22119    });
22120
22121    let breakpoints = editor.update(cx, |editor, cx| {
22122        editor
22123            .breakpoint_store()
22124            .as_ref()
22125            .unwrap()
22126            .read(cx)
22127            .all_source_breakpoints(cx)
22128    });
22129
22130    assert_eq!(1, breakpoints.len());
22131    assert_breakpoint(
22132        &breakpoints,
22133        &abs_path,
22134        vec![(3, Breakpoint::new_standard())],
22135    );
22136
22137    editor.update_in(cx, |editor, window, cx| {
22138        editor.move_to_end(&MoveToEnd, window, cx);
22139        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22140    });
22141
22142    let breakpoints = editor.update(cx, |editor, cx| {
22143        editor
22144            .breakpoint_store()
22145            .as_ref()
22146            .unwrap()
22147            .read(cx)
22148            .all_source_breakpoints(cx)
22149    });
22150
22151    assert_eq!(0, breakpoints.len());
22152    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22153}
22154
22155#[gpui::test]
22156async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22157    init_test(cx, |_| {});
22158
22159    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22160
22161    let fs = FakeFs::new(cx.executor());
22162    fs.insert_tree(
22163        path!("/a"),
22164        json!({
22165            "main.rs": sample_text,
22166        }),
22167    )
22168    .await;
22169    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22170    let (workspace, cx) =
22171        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22172
22173    let worktree_id = workspace.update(cx, |workspace, cx| {
22174        workspace.project().update(cx, |project, cx| {
22175            project.worktrees(cx).next().unwrap().read(cx).id()
22176        })
22177    });
22178
22179    let buffer = project
22180        .update(cx, |project, cx| {
22181            project.open_buffer((worktree_id, "main.rs"), cx)
22182        })
22183        .await
22184        .unwrap();
22185
22186    let (editor, cx) = cx.add_window_view(|window, cx| {
22187        Editor::new(
22188            EditorMode::full(),
22189            MultiBuffer::build_from_buffer(buffer, cx),
22190            Some(project.clone()),
22191            window,
22192            cx,
22193        )
22194    });
22195
22196    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22197    let abs_path = project.read_with(cx, |project, cx| {
22198        project
22199            .absolute_path(&project_path, cx)
22200            .map(Arc::from)
22201            .unwrap()
22202    });
22203
22204    editor.update_in(cx, |editor, window, cx| {
22205        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22206    });
22207
22208    let breakpoints = editor.update(cx, |editor, cx| {
22209        editor
22210            .breakpoint_store()
22211            .as_ref()
22212            .unwrap()
22213            .read(cx)
22214            .all_source_breakpoints(cx)
22215    });
22216
22217    assert_breakpoint(
22218        &breakpoints,
22219        &abs_path,
22220        vec![(0, Breakpoint::new_log("hello world"))],
22221    );
22222
22223    // Removing a log message from a log breakpoint should remove it
22224    editor.update_in(cx, |editor, window, cx| {
22225        add_log_breakpoint_at_cursor(editor, "", window, cx);
22226    });
22227
22228    let breakpoints = editor.update(cx, |editor, cx| {
22229        editor
22230            .breakpoint_store()
22231            .as_ref()
22232            .unwrap()
22233            .read(cx)
22234            .all_source_breakpoints(cx)
22235    });
22236
22237    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22238
22239    editor.update_in(cx, |editor, window, cx| {
22240        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22241        editor.move_to_end(&MoveToEnd, window, cx);
22242        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22243        // Not adding a log message to a standard breakpoint shouldn't remove it
22244        add_log_breakpoint_at_cursor(editor, "", window, cx);
22245    });
22246
22247    let breakpoints = editor.update(cx, |editor, cx| {
22248        editor
22249            .breakpoint_store()
22250            .as_ref()
22251            .unwrap()
22252            .read(cx)
22253            .all_source_breakpoints(cx)
22254    });
22255
22256    assert_breakpoint(
22257        &breakpoints,
22258        &abs_path,
22259        vec![
22260            (0, Breakpoint::new_standard()),
22261            (3, Breakpoint::new_standard()),
22262        ],
22263    );
22264
22265    editor.update_in(cx, |editor, window, cx| {
22266        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22267    });
22268
22269    let breakpoints = editor.update(cx, |editor, cx| {
22270        editor
22271            .breakpoint_store()
22272            .as_ref()
22273            .unwrap()
22274            .read(cx)
22275            .all_source_breakpoints(cx)
22276    });
22277
22278    assert_breakpoint(
22279        &breakpoints,
22280        &abs_path,
22281        vec![
22282            (0, Breakpoint::new_standard()),
22283            (3, Breakpoint::new_log("hello world")),
22284        ],
22285    );
22286
22287    editor.update_in(cx, |editor, window, cx| {
22288        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22289    });
22290
22291    let breakpoints = editor.update(cx, |editor, cx| {
22292        editor
22293            .breakpoint_store()
22294            .as_ref()
22295            .unwrap()
22296            .read(cx)
22297            .all_source_breakpoints(cx)
22298    });
22299
22300    assert_breakpoint(
22301        &breakpoints,
22302        &abs_path,
22303        vec![
22304            (0, Breakpoint::new_standard()),
22305            (3, Breakpoint::new_log("hello Earth!!")),
22306        ],
22307    );
22308}
22309
22310/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22311/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22312/// or when breakpoints were placed out of order. This tests for a regression too
22313#[gpui::test]
22314async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22315    init_test(cx, |_| {});
22316
22317    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22318    let fs = FakeFs::new(cx.executor());
22319    fs.insert_tree(
22320        path!("/a"),
22321        json!({
22322            "main.rs": sample_text,
22323        }),
22324    )
22325    .await;
22326    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22327    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22328    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22329
22330    let fs = FakeFs::new(cx.executor());
22331    fs.insert_tree(
22332        path!("/a"),
22333        json!({
22334            "main.rs": sample_text,
22335        }),
22336    )
22337    .await;
22338    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22339    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22340    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22341    let worktree_id = workspace
22342        .update(cx, |workspace, _window, cx| {
22343            workspace.project().update(cx, |project, cx| {
22344                project.worktrees(cx).next().unwrap().read(cx).id()
22345            })
22346        })
22347        .unwrap();
22348
22349    let buffer = project
22350        .update(cx, |project, cx| {
22351            project.open_buffer((worktree_id, "main.rs"), cx)
22352        })
22353        .await
22354        .unwrap();
22355
22356    let (editor, cx) = cx.add_window_view(|window, cx| {
22357        Editor::new(
22358            EditorMode::full(),
22359            MultiBuffer::build_from_buffer(buffer, cx),
22360            Some(project.clone()),
22361            window,
22362            cx,
22363        )
22364    });
22365
22366    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22367    let abs_path = project.read_with(cx, |project, cx| {
22368        project
22369            .absolute_path(&project_path, cx)
22370            .map(Arc::from)
22371            .unwrap()
22372    });
22373
22374    // assert we can add breakpoint on the first line
22375    editor.update_in(cx, |editor, window, cx| {
22376        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22377        editor.move_to_end(&MoveToEnd, window, cx);
22378        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22379        editor.move_up(&MoveUp, window, cx);
22380        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22381    });
22382
22383    let breakpoints = editor.update(cx, |editor, cx| {
22384        editor
22385            .breakpoint_store()
22386            .as_ref()
22387            .unwrap()
22388            .read(cx)
22389            .all_source_breakpoints(cx)
22390    });
22391
22392    assert_eq!(1, breakpoints.len());
22393    assert_breakpoint(
22394        &breakpoints,
22395        &abs_path,
22396        vec![
22397            (0, Breakpoint::new_standard()),
22398            (2, Breakpoint::new_standard()),
22399            (3, Breakpoint::new_standard()),
22400        ],
22401    );
22402
22403    editor.update_in(cx, |editor, window, cx| {
22404        editor.move_to_beginning(&MoveToBeginning, window, cx);
22405        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22406        editor.move_to_end(&MoveToEnd, window, cx);
22407        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22408        // Disabling a breakpoint that doesn't exist should do nothing
22409        editor.move_up(&MoveUp, window, cx);
22410        editor.move_up(&MoveUp, window, cx);
22411        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22412    });
22413
22414    let breakpoints = editor.update(cx, |editor, cx| {
22415        editor
22416            .breakpoint_store()
22417            .as_ref()
22418            .unwrap()
22419            .read(cx)
22420            .all_source_breakpoints(cx)
22421    });
22422
22423    let disable_breakpoint = {
22424        let mut bp = Breakpoint::new_standard();
22425        bp.state = BreakpointState::Disabled;
22426        bp
22427    };
22428
22429    assert_eq!(1, breakpoints.len());
22430    assert_breakpoint(
22431        &breakpoints,
22432        &abs_path,
22433        vec![
22434            (0, disable_breakpoint.clone()),
22435            (2, Breakpoint::new_standard()),
22436            (3, disable_breakpoint.clone()),
22437        ],
22438    );
22439
22440    editor.update_in(cx, |editor, window, cx| {
22441        editor.move_to_beginning(&MoveToBeginning, window, cx);
22442        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22443        editor.move_to_end(&MoveToEnd, window, cx);
22444        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22445        editor.move_up(&MoveUp, window, cx);
22446        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22447    });
22448
22449    let breakpoints = editor.update(cx, |editor, cx| {
22450        editor
22451            .breakpoint_store()
22452            .as_ref()
22453            .unwrap()
22454            .read(cx)
22455            .all_source_breakpoints(cx)
22456    });
22457
22458    assert_eq!(1, breakpoints.len());
22459    assert_breakpoint(
22460        &breakpoints,
22461        &abs_path,
22462        vec![
22463            (0, Breakpoint::new_standard()),
22464            (2, disable_breakpoint),
22465            (3, Breakpoint::new_standard()),
22466        ],
22467    );
22468}
22469
22470#[gpui::test]
22471async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22472    init_test(cx, |_| {});
22473    let capabilities = lsp::ServerCapabilities {
22474        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22475            prepare_provider: Some(true),
22476            work_done_progress_options: Default::default(),
22477        })),
22478        ..Default::default()
22479    };
22480    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22481
22482    cx.set_state(indoc! {"
22483        struct Fˇoo {}
22484    "});
22485
22486    cx.update_editor(|editor, _, cx| {
22487        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22488        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22489        editor.highlight_background::<DocumentHighlightRead>(
22490            &[highlight_range],
22491            |theme| theme.colors().editor_document_highlight_read_background,
22492            cx,
22493        );
22494    });
22495
22496    let mut prepare_rename_handler = cx
22497        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22498            move |_, _, _| async move {
22499                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22500                    start: lsp::Position {
22501                        line: 0,
22502                        character: 7,
22503                    },
22504                    end: lsp::Position {
22505                        line: 0,
22506                        character: 10,
22507                    },
22508                })))
22509            },
22510        );
22511    let prepare_rename_task = cx
22512        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22513        .expect("Prepare rename was not started");
22514    prepare_rename_handler.next().await.unwrap();
22515    prepare_rename_task.await.expect("Prepare rename failed");
22516
22517    let mut rename_handler =
22518        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22519            let edit = lsp::TextEdit {
22520                range: lsp::Range {
22521                    start: lsp::Position {
22522                        line: 0,
22523                        character: 7,
22524                    },
22525                    end: lsp::Position {
22526                        line: 0,
22527                        character: 10,
22528                    },
22529                },
22530                new_text: "FooRenamed".to_string(),
22531            };
22532            Ok(Some(lsp::WorkspaceEdit::new(
22533                // Specify the same edit twice
22534                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
22535            )))
22536        });
22537    let rename_task = cx
22538        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22539        .expect("Confirm rename was not started");
22540    rename_handler.next().await.unwrap();
22541    rename_task.await.expect("Confirm rename failed");
22542    cx.run_until_parked();
22543
22544    // Despite two edits, only one is actually applied as those are identical
22545    cx.assert_editor_state(indoc! {"
22546        struct FooRenamedˇ {}
22547    "});
22548}
22549
22550#[gpui::test]
22551async fn test_rename_without_prepare(cx: &mut TestAppContext) {
22552    init_test(cx, |_| {});
22553    // These capabilities indicate that the server does not support prepare rename.
22554    let capabilities = lsp::ServerCapabilities {
22555        rename_provider: Some(lsp::OneOf::Left(true)),
22556        ..Default::default()
22557    };
22558    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22559
22560    cx.set_state(indoc! {"
22561        struct Fˇoo {}
22562    "});
22563
22564    cx.update_editor(|editor, _window, cx| {
22565        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22566        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22567        editor.highlight_background::<DocumentHighlightRead>(
22568            &[highlight_range],
22569            |theme| theme.colors().editor_document_highlight_read_background,
22570            cx,
22571        );
22572    });
22573
22574    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22575        .expect("Prepare rename was not started")
22576        .await
22577        .expect("Prepare rename failed");
22578
22579    let mut rename_handler =
22580        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22581            let edit = lsp::TextEdit {
22582                range: lsp::Range {
22583                    start: lsp::Position {
22584                        line: 0,
22585                        character: 7,
22586                    },
22587                    end: lsp::Position {
22588                        line: 0,
22589                        character: 10,
22590                    },
22591                },
22592                new_text: "FooRenamed".to_string(),
22593            };
22594            Ok(Some(lsp::WorkspaceEdit::new(
22595                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
22596            )))
22597        });
22598    let rename_task = cx
22599        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22600        .expect("Confirm rename was not started");
22601    rename_handler.next().await.unwrap();
22602    rename_task.await.expect("Confirm rename failed");
22603    cx.run_until_parked();
22604
22605    // Correct range is renamed, as `surrounding_word` is used to find it.
22606    cx.assert_editor_state(indoc! {"
22607        struct FooRenamedˇ {}
22608    "});
22609}
22610
22611#[gpui::test]
22612async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
22613    init_test(cx, |_| {});
22614    let mut cx = EditorTestContext::new(cx).await;
22615
22616    let language = Arc::new(
22617        Language::new(
22618            LanguageConfig::default(),
22619            Some(tree_sitter_html::LANGUAGE.into()),
22620        )
22621        .with_brackets_query(
22622            r#"
22623            ("<" @open "/>" @close)
22624            ("</" @open ">" @close)
22625            ("<" @open ">" @close)
22626            ("\"" @open "\"" @close)
22627            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
22628        "#,
22629        )
22630        .unwrap(),
22631    );
22632    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22633
22634    cx.set_state(indoc! {"
22635        <span>ˇ</span>
22636    "});
22637    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22638    cx.assert_editor_state(indoc! {"
22639        <span>
22640        ˇ
22641        </span>
22642    "});
22643
22644    cx.set_state(indoc! {"
22645        <span><span></span>ˇ</span>
22646    "});
22647    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22648    cx.assert_editor_state(indoc! {"
22649        <span><span></span>
22650        ˇ</span>
22651    "});
22652
22653    cx.set_state(indoc! {"
22654        <span>ˇ
22655        </span>
22656    "});
22657    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22658    cx.assert_editor_state(indoc! {"
22659        <span>
22660        ˇ
22661        </span>
22662    "});
22663}
22664
22665#[gpui::test(iterations = 10)]
22666async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
22667    init_test(cx, |_| {});
22668
22669    let fs = FakeFs::new(cx.executor());
22670    fs.insert_tree(
22671        path!("/dir"),
22672        json!({
22673            "a.ts": "a",
22674        }),
22675    )
22676    .await;
22677
22678    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
22679    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22680    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22681
22682    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22683    language_registry.add(Arc::new(Language::new(
22684        LanguageConfig {
22685            name: "TypeScript".into(),
22686            matcher: LanguageMatcher {
22687                path_suffixes: vec!["ts".to_string()],
22688                ..Default::default()
22689            },
22690            ..Default::default()
22691        },
22692        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
22693    )));
22694    let mut fake_language_servers = language_registry.register_fake_lsp(
22695        "TypeScript",
22696        FakeLspAdapter {
22697            capabilities: lsp::ServerCapabilities {
22698                code_lens_provider: Some(lsp::CodeLensOptions {
22699                    resolve_provider: Some(true),
22700                }),
22701                execute_command_provider: Some(lsp::ExecuteCommandOptions {
22702                    commands: vec!["_the/command".to_string()],
22703                    ..lsp::ExecuteCommandOptions::default()
22704                }),
22705                ..lsp::ServerCapabilities::default()
22706            },
22707            ..FakeLspAdapter::default()
22708        },
22709    );
22710
22711    let editor = workspace
22712        .update(cx, |workspace, window, cx| {
22713            workspace.open_abs_path(
22714                PathBuf::from(path!("/dir/a.ts")),
22715                OpenOptions::default(),
22716                window,
22717                cx,
22718            )
22719        })
22720        .unwrap()
22721        .await
22722        .unwrap()
22723        .downcast::<Editor>()
22724        .unwrap();
22725    cx.executor().run_until_parked();
22726
22727    let fake_server = fake_language_servers.next().await.unwrap();
22728
22729    let buffer = editor.update(cx, |editor, cx| {
22730        editor
22731            .buffer()
22732            .read(cx)
22733            .as_singleton()
22734            .expect("have opened a single file by path")
22735    });
22736
22737    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
22738    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
22739    drop(buffer_snapshot);
22740    let actions = cx
22741        .update_window(*workspace, |_, window, cx| {
22742            project.code_actions(&buffer, anchor..anchor, window, cx)
22743        })
22744        .unwrap();
22745
22746    fake_server
22747        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
22748            Ok(Some(vec![
22749                lsp::CodeLens {
22750                    range: lsp::Range::default(),
22751                    command: Some(lsp::Command {
22752                        title: "Code lens command".to_owned(),
22753                        command: "_the/command".to_owned(),
22754                        arguments: None,
22755                    }),
22756                    data: None,
22757                },
22758                lsp::CodeLens {
22759                    range: lsp::Range::default(),
22760                    command: Some(lsp::Command {
22761                        title: "Command not in capabilities".to_owned(),
22762                        command: "not in capabilities".to_owned(),
22763                        arguments: None,
22764                    }),
22765                    data: None,
22766                },
22767                lsp::CodeLens {
22768                    range: lsp::Range {
22769                        start: lsp::Position {
22770                            line: 1,
22771                            character: 1,
22772                        },
22773                        end: lsp::Position {
22774                            line: 1,
22775                            character: 1,
22776                        },
22777                    },
22778                    command: Some(lsp::Command {
22779                        title: "Command not in range".to_owned(),
22780                        command: "_the/command".to_owned(),
22781                        arguments: None,
22782                    }),
22783                    data: None,
22784                },
22785            ]))
22786        })
22787        .next()
22788        .await;
22789
22790    let actions = actions.await.unwrap();
22791    assert_eq!(
22792        actions.len(),
22793        1,
22794        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
22795    );
22796    let action = actions[0].clone();
22797    let apply = project.update(cx, |project, cx| {
22798        project.apply_code_action(buffer.clone(), action, true, cx)
22799    });
22800
22801    // Resolving the code action does not populate its edits. In absence of
22802    // edits, we must execute the given command.
22803    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
22804        |mut lens, _| async move {
22805            let lens_command = lens.command.as_mut().expect("should have a command");
22806            assert_eq!(lens_command.title, "Code lens command");
22807            lens_command.arguments = Some(vec![json!("the-argument")]);
22808            Ok(lens)
22809        },
22810    );
22811
22812    // While executing the command, the language server sends the editor
22813    // a `workspaceEdit` request.
22814    fake_server
22815        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
22816            let fake = fake_server.clone();
22817            move |params, _| {
22818                assert_eq!(params.command, "_the/command");
22819                let fake = fake.clone();
22820                async move {
22821                    fake.server
22822                        .request::<lsp::request::ApplyWorkspaceEdit>(
22823                            lsp::ApplyWorkspaceEditParams {
22824                                label: None,
22825                                edit: lsp::WorkspaceEdit {
22826                                    changes: Some(
22827                                        [(
22828                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
22829                                            vec![lsp::TextEdit {
22830                                                range: lsp::Range::new(
22831                                                    lsp::Position::new(0, 0),
22832                                                    lsp::Position::new(0, 0),
22833                                                ),
22834                                                new_text: "X".into(),
22835                                            }],
22836                                        )]
22837                                        .into_iter()
22838                                        .collect(),
22839                                    ),
22840                                    ..lsp::WorkspaceEdit::default()
22841                                },
22842                            },
22843                        )
22844                        .await
22845                        .into_response()
22846                        .unwrap();
22847                    Ok(Some(json!(null)))
22848                }
22849            }
22850        })
22851        .next()
22852        .await;
22853
22854    // Applying the code lens command returns a project transaction containing the edits
22855    // sent by the language server in its `workspaceEdit` request.
22856    let transaction = apply.await.unwrap();
22857    assert!(transaction.0.contains_key(&buffer));
22858    buffer.update(cx, |buffer, cx| {
22859        assert_eq!(buffer.text(), "Xa");
22860        buffer.undo(cx);
22861        assert_eq!(buffer.text(), "a");
22862    });
22863
22864    let actions_after_edits = cx
22865        .update_window(*workspace, |_, window, cx| {
22866            project.code_actions(&buffer, anchor..anchor, window, cx)
22867        })
22868        .unwrap()
22869        .await
22870        .unwrap();
22871    assert_eq!(
22872        actions, actions_after_edits,
22873        "For the same selection, same code lens actions should be returned"
22874    );
22875
22876    let _responses =
22877        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
22878            panic!("No more code lens requests are expected");
22879        });
22880    editor.update_in(cx, |editor, window, cx| {
22881        editor.select_all(&SelectAll, window, cx);
22882    });
22883    cx.executor().run_until_parked();
22884    let new_actions = cx
22885        .update_window(*workspace, |_, window, cx| {
22886            project.code_actions(&buffer, anchor..anchor, window, cx)
22887        })
22888        .unwrap()
22889        .await
22890        .unwrap();
22891    assert_eq!(
22892        actions, new_actions,
22893        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
22894    );
22895}
22896
22897#[gpui::test]
22898async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
22899    init_test(cx, |_| {});
22900
22901    let fs = FakeFs::new(cx.executor());
22902    let main_text = r#"fn main() {
22903println!("1");
22904println!("2");
22905println!("3");
22906println!("4");
22907println!("5");
22908}"#;
22909    let lib_text = "mod foo {}";
22910    fs.insert_tree(
22911        path!("/a"),
22912        json!({
22913            "lib.rs": lib_text,
22914            "main.rs": main_text,
22915        }),
22916    )
22917    .await;
22918
22919    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22920    let (workspace, cx) =
22921        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22922    let worktree_id = workspace.update(cx, |workspace, cx| {
22923        workspace.project().update(cx, |project, cx| {
22924            project.worktrees(cx).next().unwrap().read(cx).id()
22925        })
22926    });
22927
22928    let expected_ranges = vec![
22929        Point::new(0, 0)..Point::new(0, 0),
22930        Point::new(1, 0)..Point::new(1, 1),
22931        Point::new(2, 0)..Point::new(2, 2),
22932        Point::new(3, 0)..Point::new(3, 3),
22933    ];
22934
22935    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22936    let editor_1 = workspace
22937        .update_in(cx, |workspace, window, cx| {
22938            workspace.open_path(
22939                (worktree_id, "main.rs"),
22940                Some(pane_1.downgrade()),
22941                true,
22942                window,
22943                cx,
22944            )
22945        })
22946        .unwrap()
22947        .await
22948        .downcast::<Editor>()
22949        .unwrap();
22950    pane_1.update(cx, |pane, cx| {
22951        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22952        open_editor.update(cx, |editor, cx| {
22953            assert_eq!(
22954                editor.display_text(cx),
22955                main_text,
22956                "Original main.rs text on initial open",
22957            );
22958            assert_eq!(
22959                editor
22960                    .selections
22961                    .all::<Point>(cx)
22962                    .into_iter()
22963                    .map(|s| s.range())
22964                    .collect::<Vec<_>>(),
22965                vec![Point::zero()..Point::zero()],
22966                "Default selections on initial open",
22967            );
22968        })
22969    });
22970    editor_1.update_in(cx, |editor, window, cx| {
22971        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22972            s.select_ranges(expected_ranges.clone());
22973        });
22974    });
22975
22976    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
22977        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
22978    });
22979    let editor_2 = workspace
22980        .update_in(cx, |workspace, window, cx| {
22981            workspace.open_path(
22982                (worktree_id, "main.rs"),
22983                Some(pane_2.downgrade()),
22984                true,
22985                window,
22986                cx,
22987            )
22988        })
22989        .unwrap()
22990        .await
22991        .downcast::<Editor>()
22992        .unwrap();
22993    pane_2.update(cx, |pane, cx| {
22994        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22995        open_editor.update(cx, |editor, cx| {
22996            assert_eq!(
22997                editor.display_text(cx),
22998                main_text,
22999                "Original main.rs text on initial open in another panel",
23000            );
23001            assert_eq!(
23002                editor
23003                    .selections
23004                    .all::<Point>(cx)
23005                    .into_iter()
23006                    .map(|s| s.range())
23007                    .collect::<Vec<_>>(),
23008                vec![Point::zero()..Point::zero()],
23009                "Default selections on initial open in another panel",
23010            );
23011        })
23012    });
23013
23014    editor_2.update_in(cx, |editor, window, cx| {
23015        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23016    });
23017
23018    let _other_editor_1 = workspace
23019        .update_in(cx, |workspace, window, cx| {
23020            workspace.open_path(
23021                (worktree_id, "lib.rs"),
23022                Some(pane_1.downgrade()),
23023                true,
23024                window,
23025                cx,
23026            )
23027        })
23028        .unwrap()
23029        .await
23030        .downcast::<Editor>()
23031        .unwrap();
23032    pane_1
23033        .update_in(cx, |pane, window, cx| {
23034            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23035        })
23036        .await
23037        .unwrap();
23038    drop(editor_1);
23039    pane_1.update(cx, |pane, cx| {
23040        pane.active_item()
23041            .unwrap()
23042            .downcast::<Editor>()
23043            .unwrap()
23044            .update(cx, |editor, cx| {
23045                assert_eq!(
23046                    editor.display_text(cx),
23047                    lib_text,
23048                    "Other file should be open and active",
23049                );
23050            });
23051        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23052    });
23053
23054    let _other_editor_2 = workspace
23055        .update_in(cx, |workspace, window, cx| {
23056            workspace.open_path(
23057                (worktree_id, "lib.rs"),
23058                Some(pane_2.downgrade()),
23059                true,
23060                window,
23061                cx,
23062            )
23063        })
23064        .unwrap()
23065        .await
23066        .downcast::<Editor>()
23067        .unwrap();
23068    pane_2
23069        .update_in(cx, |pane, window, cx| {
23070            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23071        })
23072        .await
23073        .unwrap();
23074    drop(editor_2);
23075    pane_2.update(cx, |pane, cx| {
23076        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23077        open_editor.update(cx, |editor, cx| {
23078            assert_eq!(
23079                editor.display_text(cx),
23080                lib_text,
23081                "Other file should be open and active in another panel too",
23082            );
23083        });
23084        assert_eq!(
23085            pane.items().count(),
23086            1,
23087            "No other editors should be open in another pane",
23088        );
23089    });
23090
23091    let _editor_1_reopened = workspace
23092        .update_in(cx, |workspace, window, cx| {
23093            workspace.open_path(
23094                (worktree_id, "main.rs"),
23095                Some(pane_1.downgrade()),
23096                true,
23097                window,
23098                cx,
23099            )
23100        })
23101        .unwrap()
23102        .await
23103        .downcast::<Editor>()
23104        .unwrap();
23105    let _editor_2_reopened = workspace
23106        .update_in(cx, |workspace, window, cx| {
23107            workspace.open_path(
23108                (worktree_id, "main.rs"),
23109                Some(pane_2.downgrade()),
23110                true,
23111                window,
23112                cx,
23113            )
23114        })
23115        .unwrap()
23116        .await
23117        .downcast::<Editor>()
23118        .unwrap();
23119    pane_1.update(cx, |pane, cx| {
23120        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23121        open_editor.update(cx, |editor, cx| {
23122            assert_eq!(
23123                editor.display_text(cx),
23124                main_text,
23125                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23126            );
23127            assert_eq!(
23128                editor
23129                    .selections
23130                    .all::<Point>(cx)
23131                    .into_iter()
23132                    .map(|s| s.range())
23133                    .collect::<Vec<_>>(),
23134                expected_ranges,
23135                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23136            );
23137        })
23138    });
23139    pane_2.update(cx, |pane, cx| {
23140        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23141        open_editor.update(cx, |editor, cx| {
23142            assert_eq!(
23143                editor.display_text(cx),
23144                r#"fn main() {
23145⋯rintln!("1");
23146⋯intln!("2");
23147⋯ntln!("3");
23148println!("4");
23149println!("5");
23150}"#,
23151                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23152            );
23153            assert_eq!(
23154                editor
23155                    .selections
23156                    .all::<Point>(cx)
23157                    .into_iter()
23158                    .map(|s| s.range())
23159                    .collect::<Vec<_>>(),
23160                vec![Point::zero()..Point::zero()],
23161                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23162            );
23163        })
23164    });
23165}
23166
23167#[gpui::test]
23168async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23169    init_test(cx, |_| {});
23170
23171    let fs = FakeFs::new(cx.executor());
23172    let main_text = r#"fn main() {
23173println!("1");
23174println!("2");
23175println!("3");
23176println!("4");
23177println!("5");
23178}"#;
23179    let lib_text = "mod foo {}";
23180    fs.insert_tree(
23181        path!("/a"),
23182        json!({
23183            "lib.rs": lib_text,
23184            "main.rs": main_text,
23185        }),
23186    )
23187    .await;
23188
23189    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23190    let (workspace, cx) =
23191        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23192    let worktree_id = workspace.update(cx, |workspace, cx| {
23193        workspace.project().update(cx, |project, cx| {
23194            project.worktrees(cx).next().unwrap().read(cx).id()
23195        })
23196    });
23197
23198    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23199    let editor = workspace
23200        .update_in(cx, |workspace, window, cx| {
23201            workspace.open_path(
23202                (worktree_id, "main.rs"),
23203                Some(pane.downgrade()),
23204                true,
23205                window,
23206                cx,
23207            )
23208        })
23209        .unwrap()
23210        .await
23211        .downcast::<Editor>()
23212        .unwrap();
23213    pane.update(cx, |pane, cx| {
23214        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23215        open_editor.update(cx, |editor, cx| {
23216            assert_eq!(
23217                editor.display_text(cx),
23218                main_text,
23219                "Original main.rs text on initial open",
23220            );
23221        })
23222    });
23223    editor.update_in(cx, |editor, window, cx| {
23224        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23225    });
23226
23227    cx.update_global(|store: &mut SettingsStore, cx| {
23228        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23229            s.restore_on_file_reopen = Some(false);
23230        });
23231    });
23232    editor.update_in(cx, |editor, window, cx| {
23233        editor.fold_ranges(
23234            vec![
23235                Point::new(1, 0)..Point::new(1, 1),
23236                Point::new(2, 0)..Point::new(2, 2),
23237                Point::new(3, 0)..Point::new(3, 3),
23238            ],
23239            false,
23240            window,
23241            cx,
23242        );
23243    });
23244    pane.update_in(cx, |pane, window, cx| {
23245        pane.close_all_items(&CloseAllItems::default(), window, cx)
23246    })
23247    .await
23248    .unwrap();
23249    pane.update(cx, |pane, _| {
23250        assert!(pane.active_item().is_none());
23251    });
23252    cx.update_global(|store: &mut SettingsStore, cx| {
23253        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23254            s.restore_on_file_reopen = Some(true);
23255        });
23256    });
23257
23258    let _editor_reopened = workspace
23259        .update_in(cx, |workspace, window, cx| {
23260            workspace.open_path(
23261                (worktree_id, "main.rs"),
23262                Some(pane.downgrade()),
23263                true,
23264                window,
23265                cx,
23266            )
23267        })
23268        .unwrap()
23269        .await
23270        .downcast::<Editor>()
23271        .unwrap();
23272    pane.update(cx, |pane, cx| {
23273        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23274        open_editor.update(cx, |editor, cx| {
23275            assert_eq!(
23276                editor.display_text(cx),
23277                main_text,
23278                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23279            );
23280        })
23281    });
23282}
23283
23284#[gpui::test]
23285async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23286    struct EmptyModalView {
23287        focus_handle: gpui::FocusHandle,
23288    }
23289    impl EventEmitter<DismissEvent> for EmptyModalView {}
23290    impl Render for EmptyModalView {
23291        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23292            div()
23293        }
23294    }
23295    impl Focusable for EmptyModalView {
23296        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23297            self.focus_handle.clone()
23298        }
23299    }
23300    impl workspace::ModalView for EmptyModalView {}
23301    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23302        EmptyModalView {
23303            focus_handle: cx.focus_handle(),
23304        }
23305    }
23306
23307    init_test(cx, |_| {});
23308
23309    let fs = FakeFs::new(cx.executor());
23310    let project = Project::test(fs, [], cx).await;
23311    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23312    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23313    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23314    let editor = cx.new_window_entity(|window, cx| {
23315        Editor::new(
23316            EditorMode::full(),
23317            buffer,
23318            Some(project.clone()),
23319            window,
23320            cx,
23321        )
23322    });
23323    workspace
23324        .update(cx, |workspace, window, cx| {
23325            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23326        })
23327        .unwrap();
23328    editor.update_in(cx, |editor, window, cx| {
23329        editor.open_context_menu(&OpenContextMenu, window, cx);
23330        assert!(editor.mouse_context_menu.is_some());
23331    });
23332    workspace
23333        .update(cx, |workspace, window, cx| {
23334            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23335        })
23336        .unwrap();
23337    cx.read(|cx| {
23338        assert!(editor.read(cx).mouse_context_menu.is_none());
23339    });
23340}
23341
23342#[gpui::test]
23343async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23344    init_test(cx, |_| {});
23345
23346    let fs = FakeFs::new(cx.executor());
23347    fs.insert_file(path!("/file.html"), Default::default())
23348        .await;
23349
23350    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23351
23352    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23353    let html_language = Arc::new(Language::new(
23354        LanguageConfig {
23355            name: "HTML".into(),
23356            matcher: LanguageMatcher {
23357                path_suffixes: vec!["html".to_string()],
23358                ..LanguageMatcher::default()
23359            },
23360            brackets: BracketPairConfig {
23361                pairs: vec![BracketPair {
23362                    start: "<".into(),
23363                    end: ">".into(),
23364                    close: true,
23365                    ..Default::default()
23366                }],
23367                ..Default::default()
23368            },
23369            ..Default::default()
23370        },
23371        Some(tree_sitter_html::LANGUAGE.into()),
23372    ));
23373    language_registry.add(html_language);
23374    let mut fake_servers = language_registry.register_fake_lsp(
23375        "HTML",
23376        FakeLspAdapter {
23377            capabilities: lsp::ServerCapabilities {
23378                completion_provider: Some(lsp::CompletionOptions {
23379                    resolve_provider: Some(true),
23380                    ..Default::default()
23381                }),
23382                ..Default::default()
23383            },
23384            ..Default::default()
23385        },
23386    );
23387
23388    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23389    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23390
23391    let worktree_id = workspace
23392        .update(cx, |workspace, _window, cx| {
23393            workspace.project().update(cx, |project, cx| {
23394                project.worktrees(cx).next().unwrap().read(cx).id()
23395            })
23396        })
23397        .unwrap();
23398    project
23399        .update(cx, |project, cx| {
23400            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23401        })
23402        .await
23403        .unwrap();
23404    let editor = workspace
23405        .update(cx, |workspace, window, cx| {
23406            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23407        })
23408        .unwrap()
23409        .await
23410        .unwrap()
23411        .downcast::<Editor>()
23412        .unwrap();
23413
23414    let fake_server = fake_servers.next().await.unwrap();
23415    editor.update_in(cx, |editor, window, cx| {
23416        editor.set_text("<ad></ad>", window, cx);
23417        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23418            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23419        });
23420        let Some((buffer, _)) = editor
23421            .buffer
23422            .read(cx)
23423            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23424        else {
23425            panic!("Failed to get buffer for selection position");
23426        };
23427        let buffer = buffer.read(cx);
23428        let buffer_id = buffer.remote_id();
23429        let opening_range =
23430            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
23431        let closing_range =
23432            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
23433        let mut linked_ranges = HashMap::default();
23434        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23435        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23436    });
23437    let mut completion_handle =
23438        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23439            Ok(Some(lsp::CompletionResponse::Array(vec![
23440                lsp::CompletionItem {
23441                    label: "head".to_string(),
23442                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23443                        lsp::InsertReplaceEdit {
23444                            new_text: "head".to_string(),
23445                            insert: lsp::Range::new(
23446                                lsp::Position::new(0, 1),
23447                                lsp::Position::new(0, 3),
23448                            ),
23449                            replace: lsp::Range::new(
23450                                lsp::Position::new(0, 1),
23451                                lsp::Position::new(0, 3),
23452                            ),
23453                        },
23454                    )),
23455                    ..Default::default()
23456                },
23457            ])))
23458        });
23459    editor.update_in(cx, |editor, window, cx| {
23460        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23461    });
23462    cx.run_until_parked();
23463    completion_handle.next().await.unwrap();
23464    editor.update(cx, |editor, _| {
23465        assert!(
23466            editor.context_menu_visible(),
23467            "Completion menu should be visible"
23468        );
23469    });
23470    editor.update_in(cx, |editor, window, cx| {
23471        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23472    });
23473    cx.executor().run_until_parked();
23474    editor.update(cx, |editor, cx| {
23475        assert_eq!(editor.text(cx), "<head></head>");
23476    });
23477}
23478
23479#[gpui::test]
23480async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
23481    init_test(cx, |_| {});
23482
23483    let fs = FakeFs::new(cx.executor());
23484    fs.insert_tree(
23485        path!("/root"),
23486        json!({
23487            "a": {
23488                "main.rs": "fn main() {}",
23489            },
23490            "foo": {
23491                "bar": {
23492                    "external_file.rs": "pub mod external {}",
23493                }
23494            }
23495        }),
23496    )
23497    .await;
23498
23499    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
23500    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23501    language_registry.add(rust_lang());
23502    let _fake_servers = language_registry.register_fake_lsp(
23503        "Rust",
23504        FakeLspAdapter {
23505            ..FakeLspAdapter::default()
23506        },
23507    );
23508    let (workspace, cx) =
23509        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23510    let worktree_id = workspace.update(cx, |workspace, cx| {
23511        workspace.project().update(cx, |project, cx| {
23512            project.worktrees(cx).next().unwrap().read(cx).id()
23513        })
23514    });
23515
23516    let assert_language_servers_count =
23517        |expected: usize, context: &str, cx: &mut VisualTestContext| {
23518            project.update(cx, |project, cx| {
23519                let current = project
23520                    .lsp_store()
23521                    .read(cx)
23522                    .as_local()
23523                    .unwrap()
23524                    .language_servers
23525                    .len();
23526                assert_eq!(expected, current, "{context}");
23527            });
23528        };
23529
23530    assert_language_servers_count(
23531        0,
23532        "No servers should be running before any file is open",
23533        cx,
23534    );
23535    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23536    let main_editor = workspace
23537        .update_in(cx, |workspace, window, cx| {
23538            workspace.open_path(
23539                (worktree_id, "main.rs"),
23540                Some(pane.downgrade()),
23541                true,
23542                window,
23543                cx,
23544            )
23545        })
23546        .unwrap()
23547        .await
23548        .downcast::<Editor>()
23549        .unwrap();
23550    pane.update(cx, |pane, cx| {
23551        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23552        open_editor.update(cx, |editor, cx| {
23553            assert_eq!(
23554                editor.display_text(cx),
23555                "fn main() {}",
23556                "Original main.rs text on initial open",
23557            );
23558        });
23559        assert_eq!(open_editor, main_editor);
23560    });
23561    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
23562
23563    let external_editor = workspace
23564        .update_in(cx, |workspace, window, cx| {
23565            workspace.open_abs_path(
23566                PathBuf::from("/root/foo/bar/external_file.rs"),
23567                OpenOptions::default(),
23568                window,
23569                cx,
23570            )
23571        })
23572        .await
23573        .expect("opening external file")
23574        .downcast::<Editor>()
23575        .expect("downcasted external file's open element to editor");
23576    pane.update(cx, |pane, cx| {
23577        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23578        open_editor.update(cx, |editor, cx| {
23579            assert_eq!(
23580                editor.display_text(cx),
23581                "pub mod external {}",
23582                "External file is open now",
23583            );
23584        });
23585        assert_eq!(open_editor, external_editor);
23586    });
23587    assert_language_servers_count(
23588        1,
23589        "Second, external, *.rs file should join the existing server",
23590        cx,
23591    );
23592
23593    pane.update_in(cx, |pane, window, cx| {
23594        pane.close_active_item(&CloseActiveItem::default(), window, cx)
23595    })
23596    .await
23597    .unwrap();
23598    pane.update_in(cx, |pane, window, cx| {
23599        pane.navigate_backward(&Default::default(), window, cx);
23600    });
23601    cx.run_until_parked();
23602    pane.update(cx, |pane, cx| {
23603        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23604        open_editor.update(cx, |editor, cx| {
23605            assert_eq!(
23606                editor.display_text(cx),
23607                "pub mod external {}",
23608                "External file is open now",
23609            );
23610        });
23611    });
23612    assert_language_servers_count(
23613        1,
23614        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
23615        cx,
23616    );
23617
23618    cx.update(|_, cx| {
23619        workspace::reload(cx);
23620    });
23621    assert_language_servers_count(
23622        1,
23623        "After reloading the worktree with local and external files opened, only one project should be started",
23624        cx,
23625    );
23626}
23627
23628#[gpui::test]
23629async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
23630    init_test(cx, |_| {});
23631
23632    let mut cx = EditorTestContext::new(cx).await;
23633    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23634    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23635
23636    // test cursor move to start of each line on tab
23637    // for `if`, `elif`, `else`, `while`, `with` and `for`
23638    cx.set_state(indoc! {"
23639        def main():
23640        ˇ    for item in items:
23641        ˇ        while item.active:
23642        ˇ            if item.value > 10:
23643        ˇ                continue
23644        ˇ            elif item.value < 0:
23645        ˇ                break
23646        ˇ            else:
23647        ˇ                with item.context() as ctx:
23648        ˇ                    yield count
23649        ˇ        else:
23650        ˇ            log('while else')
23651        ˇ    else:
23652        ˇ        log('for else')
23653    "});
23654    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23655    cx.assert_editor_state(indoc! {"
23656        def main():
23657            ˇfor item in items:
23658                ˇwhile item.active:
23659                    ˇif item.value > 10:
23660                        ˇcontinue
23661                    ˇelif item.value < 0:
23662                        ˇbreak
23663                    ˇelse:
23664                        ˇwith item.context() as ctx:
23665                            ˇyield count
23666                ˇelse:
23667                    ˇlog('while else')
23668            ˇelse:
23669                ˇlog('for else')
23670    "});
23671    // test relative indent is preserved when tab
23672    // for `if`, `elif`, `else`, `while`, `with` and `for`
23673    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23674    cx.assert_editor_state(indoc! {"
23675        def main():
23676                ˇfor item in items:
23677                    ˇwhile item.active:
23678                        ˇif item.value > 10:
23679                            ˇcontinue
23680                        ˇelif item.value < 0:
23681                            ˇbreak
23682                        ˇelse:
23683                            ˇwith item.context() as ctx:
23684                                ˇyield count
23685                    ˇelse:
23686                        ˇlog('while else')
23687                ˇelse:
23688                    ˇlog('for else')
23689    "});
23690
23691    // test cursor move to start of each line on tab
23692    // for `try`, `except`, `else`, `finally`, `match` and `def`
23693    cx.set_state(indoc! {"
23694        def main():
23695        ˇ    try:
23696        ˇ        fetch()
23697        ˇ    except ValueError:
23698        ˇ        handle_error()
23699        ˇ    else:
23700        ˇ        match value:
23701        ˇ            case _:
23702        ˇ    finally:
23703        ˇ        def status():
23704        ˇ            return 0
23705    "});
23706    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23707    cx.assert_editor_state(indoc! {"
23708        def main():
23709            ˇtry:
23710                ˇfetch()
23711            ˇexcept ValueError:
23712                ˇhandle_error()
23713            ˇelse:
23714                ˇmatch value:
23715                    ˇcase _:
23716            ˇfinally:
23717                ˇdef status():
23718                    ˇreturn 0
23719    "});
23720    // test relative indent is preserved when tab
23721    // for `try`, `except`, `else`, `finally`, `match` and `def`
23722    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23723    cx.assert_editor_state(indoc! {"
23724        def main():
23725                ˇtry:
23726                    ˇfetch()
23727                ˇexcept ValueError:
23728                    ˇhandle_error()
23729                ˇelse:
23730                    ˇmatch value:
23731                        ˇcase _:
23732                ˇfinally:
23733                    ˇdef status():
23734                        ˇreturn 0
23735    "});
23736}
23737
23738#[gpui::test]
23739async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
23740    init_test(cx, |_| {});
23741
23742    let mut cx = EditorTestContext::new(cx).await;
23743    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23744    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23745
23746    // test `else` auto outdents when typed inside `if` block
23747    cx.set_state(indoc! {"
23748        def main():
23749            if i == 2:
23750                return
23751                ˇ
23752    "});
23753    cx.update_editor(|editor, window, cx| {
23754        editor.handle_input("else:", window, cx);
23755    });
23756    cx.assert_editor_state(indoc! {"
23757        def main():
23758            if i == 2:
23759                return
23760            else:ˇ
23761    "});
23762
23763    // test `except` auto outdents when typed inside `try` block
23764    cx.set_state(indoc! {"
23765        def main():
23766            try:
23767                i = 2
23768                ˇ
23769    "});
23770    cx.update_editor(|editor, window, cx| {
23771        editor.handle_input("except:", window, cx);
23772    });
23773    cx.assert_editor_state(indoc! {"
23774        def main():
23775            try:
23776                i = 2
23777            except:ˇ
23778    "});
23779
23780    // test `else` auto outdents when typed inside `except` block
23781    cx.set_state(indoc! {"
23782        def main():
23783            try:
23784                i = 2
23785            except:
23786                j = 2
23787                ˇ
23788    "});
23789    cx.update_editor(|editor, window, cx| {
23790        editor.handle_input("else:", window, cx);
23791    });
23792    cx.assert_editor_state(indoc! {"
23793        def main():
23794            try:
23795                i = 2
23796            except:
23797                j = 2
23798            else:ˇ
23799    "});
23800
23801    // test `finally` auto outdents when typed inside `else` block
23802    cx.set_state(indoc! {"
23803        def main():
23804            try:
23805                i = 2
23806            except:
23807                j = 2
23808            else:
23809                k = 2
23810                ˇ
23811    "});
23812    cx.update_editor(|editor, window, cx| {
23813        editor.handle_input("finally:", window, cx);
23814    });
23815    cx.assert_editor_state(indoc! {"
23816        def main():
23817            try:
23818                i = 2
23819            except:
23820                j = 2
23821            else:
23822                k = 2
23823            finally:ˇ
23824    "});
23825
23826    // test `else` does not outdents when typed inside `except` block right after for block
23827    cx.set_state(indoc! {"
23828        def main():
23829            try:
23830                i = 2
23831            except:
23832                for i in range(n):
23833                    pass
23834                ˇ
23835    "});
23836    cx.update_editor(|editor, window, cx| {
23837        editor.handle_input("else:", window, cx);
23838    });
23839    cx.assert_editor_state(indoc! {"
23840        def main():
23841            try:
23842                i = 2
23843            except:
23844                for i in range(n):
23845                    pass
23846                else:ˇ
23847    "});
23848
23849    // test `finally` auto outdents when typed inside `else` block right after for block
23850    cx.set_state(indoc! {"
23851        def main():
23852            try:
23853                i = 2
23854            except:
23855                j = 2
23856            else:
23857                for i in range(n):
23858                    pass
23859                ˇ
23860    "});
23861    cx.update_editor(|editor, window, cx| {
23862        editor.handle_input("finally:", window, cx);
23863    });
23864    cx.assert_editor_state(indoc! {"
23865        def main():
23866            try:
23867                i = 2
23868            except:
23869                j = 2
23870            else:
23871                for i in range(n):
23872                    pass
23873            finally:ˇ
23874    "});
23875
23876    // test `except` outdents to inner "try" block
23877    cx.set_state(indoc! {"
23878        def main():
23879            try:
23880                i = 2
23881                if i == 2:
23882                    try:
23883                        i = 3
23884                        ˇ
23885    "});
23886    cx.update_editor(|editor, window, cx| {
23887        editor.handle_input("except:", window, cx);
23888    });
23889    cx.assert_editor_state(indoc! {"
23890        def main():
23891            try:
23892                i = 2
23893                if i == 2:
23894                    try:
23895                        i = 3
23896                    except:ˇ
23897    "});
23898
23899    // test `except` outdents to outer "try" block
23900    cx.set_state(indoc! {"
23901        def main():
23902            try:
23903                i = 2
23904                if i == 2:
23905                    try:
23906                        i = 3
23907                ˇ
23908    "});
23909    cx.update_editor(|editor, window, cx| {
23910        editor.handle_input("except:", window, cx);
23911    });
23912    cx.assert_editor_state(indoc! {"
23913        def main():
23914            try:
23915                i = 2
23916                if i == 2:
23917                    try:
23918                        i = 3
23919            except:ˇ
23920    "});
23921
23922    // test `else` stays at correct indent when typed after `for` block
23923    cx.set_state(indoc! {"
23924        def main():
23925            for i in range(10):
23926                if i == 3:
23927                    break
23928            ˇ
23929    "});
23930    cx.update_editor(|editor, window, cx| {
23931        editor.handle_input("else:", window, cx);
23932    });
23933    cx.assert_editor_state(indoc! {"
23934        def main():
23935            for i in range(10):
23936                if i == 3:
23937                    break
23938            else:ˇ
23939    "});
23940
23941    // test does not outdent on typing after line with square brackets
23942    cx.set_state(indoc! {"
23943        def f() -> list[str]:
23944            ˇ
23945    "});
23946    cx.update_editor(|editor, window, cx| {
23947        editor.handle_input("a", window, cx);
23948    });
23949    cx.assert_editor_state(indoc! {"
23950        def f() -> list[str]:
2395123952    "});
23953
23954    // test does not outdent on typing : after case keyword
23955    cx.set_state(indoc! {"
23956        match 1:
23957            caseˇ
23958    "});
23959    cx.update_editor(|editor, window, cx| {
23960        editor.handle_input(":", window, cx);
23961    });
23962    cx.assert_editor_state(indoc! {"
23963        match 1:
23964            case:ˇ
23965    "});
23966}
23967
23968#[gpui::test]
23969async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
23970    init_test(cx, |_| {});
23971    update_test_language_settings(cx, |settings| {
23972        settings.defaults.extend_comment_on_newline = Some(false);
23973    });
23974    let mut cx = EditorTestContext::new(cx).await;
23975    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23976    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23977
23978    // test correct indent after newline on comment
23979    cx.set_state(indoc! {"
23980        # COMMENT:ˇ
23981    "});
23982    cx.update_editor(|editor, window, cx| {
23983        editor.newline(&Newline, window, cx);
23984    });
23985    cx.assert_editor_state(indoc! {"
23986        # COMMENT:
23987        ˇ
23988    "});
23989
23990    // test correct indent after newline in brackets
23991    cx.set_state(indoc! {"
23992        {ˇ}
23993    "});
23994    cx.update_editor(|editor, window, cx| {
23995        editor.newline(&Newline, window, cx);
23996    });
23997    cx.run_until_parked();
23998    cx.assert_editor_state(indoc! {"
23999        {
24000            ˇ
24001        }
24002    "});
24003
24004    cx.set_state(indoc! {"
24005        (ˇ)
24006    "});
24007    cx.update_editor(|editor, window, cx| {
24008        editor.newline(&Newline, window, cx);
24009    });
24010    cx.run_until_parked();
24011    cx.assert_editor_state(indoc! {"
24012        (
24013            ˇ
24014        )
24015    "});
24016
24017    // do not indent after empty lists or dictionaries
24018    cx.set_state(indoc! {"
24019        a = []ˇ
24020    "});
24021    cx.update_editor(|editor, window, cx| {
24022        editor.newline(&Newline, window, cx);
24023    });
24024    cx.run_until_parked();
24025    cx.assert_editor_state(indoc! {"
24026        a = []
24027        ˇ
24028    "});
24029}
24030
24031#[gpui::test]
24032async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24033    init_test(cx, |_| {});
24034
24035    let mut cx = EditorTestContext::new(cx).await;
24036    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24037    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24038
24039    // test cursor move to start of each line on tab
24040    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24041    cx.set_state(indoc! {"
24042        function main() {
24043        ˇ    for item in $items; do
24044        ˇ        while [ -n \"$item\" ]; do
24045        ˇ            if [ \"$value\" -gt 10 ]; then
24046        ˇ                continue
24047        ˇ            elif [ \"$value\" -lt 0 ]; then
24048        ˇ                break
24049        ˇ            else
24050        ˇ                echo \"$item\"
24051        ˇ            fi
24052        ˇ        done
24053        ˇ    done
24054        ˇ}
24055    "});
24056    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24057    cx.assert_editor_state(indoc! {"
24058        function main() {
24059            ˇfor item in $items; do
24060                ˇwhile [ -n \"$item\" ]; do
24061                    ˇif [ \"$value\" -gt 10 ]; then
24062                        ˇcontinue
24063                    ˇelif [ \"$value\" -lt 0 ]; then
24064                        ˇbreak
24065                    ˇelse
24066                        ˇecho \"$item\"
24067                    ˇfi
24068                ˇdone
24069            ˇdone
24070        ˇ}
24071    "});
24072    // test relative indent is preserved when tab
24073    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24074    cx.assert_editor_state(indoc! {"
24075        function main() {
24076                ˇfor item in $items; do
24077                    ˇwhile [ -n \"$item\" ]; do
24078                        ˇif [ \"$value\" -gt 10 ]; then
24079                            ˇcontinue
24080                        ˇelif [ \"$value\" -lt 0 ]; then
24081                            ˇbreak
24082                        ˇelse
24083                            ˇecho \"$item\"
24084                        ˇfi
24085                    ˇdone
24086                ˇdone
24087            ˇ}
24088    "});
24089
24090    // test cursor move to start of each line on tab
24091    // for `case` statement with patterns
24092    cx.set_state(indoc! {"
24093        function handle() {
24094        ˇ    case \"$1\" in
24095        ˇ        start)
24096        ˇ            echo \"a\"
24097        ˇ            ;;
24098        ˇ        stop)
24099        ˇ            echo \"b\"
24100        ˇ            ;;
24101        ˇ        *)
24102        ˇ            echo \"c\"
24103        ˇ            ;;
24104        ˇ    esac
24105        ˇ}
24106    "});
24107    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24108    cx.assert_editor_state(indoc! {"
24109        function handle() {
24110            ˇcase \"$1\" in
24111                ˇstart)
24112                    ˇecho \"a\"
24113                    ˇ;;
24114                ˇstop)
24115                    ˇecho \"b\"
24116                    ˇ;;
24117                ˇ*)
24118                    ˇecho \"c\"
24119                    ˇ;;
24120            ˇesac
24121        ˇ}
24122    "});
24123}
24124
24125#[gpui::test]
24126async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24127    init_test(cx, |_| {});
24128
24129    let mut cx = EditorTestContext::new(cx).await;
24130    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24131    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24132
24133    // test indents on comment insert
24134    cx.set_state(indoc! {"
24135        function main() {
24136        ˇ    for item in $items; do
24137        ˇ        while [ -n \"$item\" ]; do
24138        ˇ            if [ \"$value\" -gt 10 ]; then
24139        ˇ                continue
24140        ˇ            elif [ \"$value\" -lt 0 ]; then
24141        ˇ                break
24142        ˇ            else
24143        ˇ                echo \"$item\"
24144        ˇ            fi
24145        ˇ        done
24146        ˇ    done
24147        ˇ}
24148    "});
24149    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24150    cx.assert_editor_state(indoc! {"
24151        function main() {
24152        #ˇ    for item in $items; do
24153        #ˇ        while [ -n \"$item\" ]; do
24154        #ˇ            if [ \"$value\" -gt 10 ]; then
24155        #ˇ                continue
24156        #ˇ            elif [ \"$value\" -lt 0 ]; then
24157        #ˇ                break
24158        #ˇ            else
24159        #ˇ                echo \"$item\"
24160        #ˇ            fi
24161        #ˇ        done
24162        #ˇ    done
24163        #ˇ}
24164    "});
24165}
24166
24167#[gpui::test]
24168async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24169    init_test(cx, |_| {});
24170
24171    let mut cx = EditorTestContext::new(cx).await;
24172    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24173    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24174
24175    // test `else` auto outdents when typed inside `if` block
24176    cx.set_state(indoc! {"
24177        if [ \"$1\" = \"test\" ]; then
24178            echo \"foo bar\"
24179            ˇ
24180    "});
24181    cx.update_editor(|editor, window, cx| {
24182        editor.handle_input("else", window, cx);
24183    });
24184    cx.assert_editor_state(indoc! {"
24185        if [ \"$1\" = \"test\" ]; then
24186            echo \"foo bar\"
24187        elseˇ
24188    "});
24189
24190    // test `elif` auto outdents when typed inside `if` block
24191    cx.set_state(indoc! {"
24192        if [ \"$1\" = \"test\" ]; then
24193            echo \"foo bar\"
24194            ˇ
24195    "});
24196    cx.update_editor(|editor, window, cx| {
24197        editor.handle_input("elif", window, cx);
24198    });
24199    cx.assert_editor_state(indoc! {"
24200        if [ \"$1\" = \"test\" ]; then
24201            echo \"foo bar\"
24202        elifˇ
24203    "});
24204
24205    // test `fi` auto outdents when typed inside `else` block
24206    cx.set_state(indoc! {"
24207        if [ \"$1\" = \"test\" ]; then
24208            echo \"foo bar\"
24209        else
24210            echo \"bar baz\"
24211            ˇ
24212    "});
24213    cx.update_editor(|editor, window, cx| {
24214        editor.handle_input("fi", window, cx);
24215    });
24216    cx.assert_editor_state(indoc! {"
24217        if [ \"$1\" = \"test\" ]; then
24218            echo \"foo bar\"
24219        else
24220            echo \"bar baz\"
24221        fiˇ
24222    "});
24223
24224    // test `done` auto outdents when typed inside `while` block
24225    cx.set_state(indoc! {"
24226        while read line; do
24227            echo \"$line\"
24228            ˇ
24229    "});
24230    cx.update_editor(|editor, window, cx| {
24231        editor.handle_input("done", window, cx);
24232    });
24233    cx.assert_editor_state(indoc! {"
24234        while read line; do
24235            echo \"$line\"
24236        doneˇ
24237    "});
24238
24239    // test `done` auto outdents when typed inside `for` block
24240    cx.set_state(indoc! {"
24241        for file in *.txt; do
24242            cat \"$file\"
24243            ˇ
24244    "});
24245    cx.update_editor(|editor, window, cx| {
24246        editor.handle_input("done", window, cx);
24247    });
24248    cx.assert_editor_state(indoc! {"
24249        for file in *.txt; do
24250            cat \"$file\"
24251        doneˇ
24252    "});
24253
24254    // test `esac` auto outdents when typed inside `case` block
24255    cx.set_state(indoc! {"
24256        case \"$1\" in
24257            start)
24258                echo \"foo bar\"
24259                ;;
24260            stop)
24261                echo \"bar baz\"
24262                ;;
24263            ˇ
24264    "});
24265    cx.update_editor(|editor, window, cx| {
24266        editor.handle_input("esac", window, cx);
24267    });
24268    cx.assert_editor_state(indoc! {"
24269        case \"$1\" in
24270            start)
24271                echo \"foo bar\"
24272                ;;
24273            stop)
24274                echo \"bar baz\"
24275                ;;
24276        esacˇ
24277    "});
24278
24279    // test `*)` auto outdents when typed inside `case` block
24280    cx.set_state(indoc! {"
24281        case \"$1\" in
24282            start)
24283                echo \"foo bar\"
24284                ;;
24285                ˇ
24286    "});
24287    cx.update_editor(|editor, window, cx| {
24288        editor.handle_input("*)", window, cx);
24289    });
24290    cx.assert_editor_state(indoc! {"
24291        case \"$1\" in
24292            start)
24293                echo \"foo bar\"
24294                ;;
24295            *)ˇ
24296    "});
24297
24298    // test `fi` outdents to correct level with nested if blocks
24299    cx.set_state(indoc! {"
24300        if [ \"$1\" = \"test\" ]; then
24301            echo \"outer if\"
24302            if [ \"$2\" = \"debug\" ]; then
24303                echo \"inner if\"
24304                ˇ
24305    "});
24306    cx.update_editor(|editor, window, cx| {
24307        editor.handle_input("fi", window, cx);
24308    });
24309    cx.assert_editor_state(indoc! {"
24310        if [ \"$1\" = \"test\" ]; then
24311            echo \"outer if\"
24312            if [ \"$2\" = \"debug\" ]; then
24313                echo \"inner if\"
24314            fiˇ
24315    "});
24316}
24317
24318#[gpui::test]
24319async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24320    init_test(cx, |_| {});
24321    update_test_language_settings(cx, |settings| {
24322        settings.defaults.extend_comment_on_newline = Some(false);
24323    });
24324    let mut cx = EditorTestContext::new(cx).await;
24325    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24326    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24327
24328    // test correct indent after newline on comment
24329    cx.set_state(indoc! {"
24330        # COMMENT:ˇ
24331    "});
24332    cx.update_editor(|editor, window, cx| {
24333        editor.newline(&Newline, window, cx);
24334    });
24335    cx.assert_editor_state(indoc! {"
24336        # COMMENT:
24337        ˇ
24338    "});
24339
24340    // test correct indent after newline after `then`
24341    cx.set_state(indoc! {"
24342
24343        if [ \"$1\" = \"test\" ]; thenˇ
24344    "});
24345    cx.update_editor(|editor, window, cx| {
24346        editor.newline(&Newline, window, cx);
24347    });
24348    cx.run_until_parked();
24349    cx.assert_editor_state(indoc! {"
24350
24351        if [ \"$1\" = \"test\" ]; then
24352            ˇ
24353    "});
24354
24355    // test correct indent after newline after `else`
24356    cx.set_state(indoc! {"
24357        if [ \"$1\" = \"test\" ]; then
24358        elseˇ
24359    "});
24360    cx.update_editor(|editor, window, cx| {
24361        editor.newline(&Newline, window, cx);
24362    });
24363    cx.run_until_parked();
24364    cx.assert_editor_state(indoc! {"
24365        if [ \"$1\" = \"test\" ]; then
24366        else
24367            ˇ
24368    "});
24369
24370    // test correct indent after newline after `elif`
24371    cx.set_state(indoc! {"
24372        if [ \"$1\" = \"test\" ]; then
24373        elifˇ
24374    "});
24375    cx.update_editor(|editor, window, cx| {
24376        editor.newline(&Newline, window, cx);
24377    });
24378    cx.run_until_parked();
24379    cx.assert_editor_state(indoc! {"
24380        if [ \"$1\" = \"test\" ]; then
24381        elif
24382            ˇ
24383    "});
24384
24385    // test correct indent after newline after `do`
24386    cx.set_state(indoc! {"
24387        for file in *.txt; doˇ
24388    "});
24389    cx.update_editor(|editor, window, cx| {
24390        editor.newline(&Newline, window, cx);
24391    });
24392    cx.run_until_parked();
24393    cx.assert_editor_state(indoc! {"
24394        for file in *.txt; do
24395            ˇ
24396    "});
24397
24398    // test correct indent after newline after case pattern
24399    cx.set_state(indoc! {"
24400        case \"$1\" in
24401            start)ˇ
24402    "});
24403    cx.update_editor(|editor, window, cx| {
24404        editor.newline(&Newline, window, cx);
24405    });
24406    cx.run_until_parked();
24407    cx.assert_editor_state(indoc! {"
24408        case \"$1\" in
24409            start)
24410                ˇ
24411    "});
24412
24413    // test correct indent after newline after case pattern
24414    cx.set_state(indoc! {"
24415        case \"$1\" in
24416            start)
24417                ;;
24418            *)ˇ
24419    "});
24420    cx.update_editor(|editor, window, cx| {
24421        editor.newline(&Newline, window, cx);
24422    });
24423    cx.run_until_parked();
24424    cx.assert_editor_state(indoc! {"
24425        case \"$1\" in
24426            start)
24427                ;;
24428            *)
24429                ˇ
24430    "});
24431
24432    // test correct indent after newline after function opening brace
24433    cx.set_state(indoc! {"
24434        function test() {ˇ}
24435    "});
24436    cx.update_editor(|editor, window, cx| {
24437        editor.newline(&Newline, window, cx);
24438    });
24439    cx.run_until_parked();
24440    cx.assert_editor_state(indoc! {"
24441        function test() {
24442            ˇ
24443        }
24444    "});
24445
24446    // test no extra indent after semicolon on same line
24447    cx.set_state(indoc! {"
24448        echo \"test\"24449    "});
24450    cx.update_editor(|editor, window, cx| {
24451        editor.newline(&Newline, window, cx);
24452    });
24453    cx.run_until_parked();
24454    cx.assert_editor_state(indoc! {"
24455        echo \"test\";
24456        ˇ
24457    "});
24458}
24459
24460fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24461    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24462    point..point
24463}
24464
24465#[track_caller]
24466fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24467    let (text, ranges) = marked_text_ranges(marked_text, true);
24468    assert_eq!(editor.text(cx), text);
24469    assert_eq!(
24470        editor.selections.ranges(cx),
24471        ranges,
24472        "Assert selections are {}",
24473        marked_text
24474    );
24475}
24476
24477pub fn handle_signature_help_request(
24478    cx: &mut EditorLspTestContext,
24479    mocked_response: lsp::SignatureHelp,
24480) -> impl Future<Output = ()> + use<> {
24481    let mut request =
24482        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
24483            let mocked_response = mocked_response.clone();
24484            async move { Ok(Some(mocked_response)) }
24485        });
24486
24487    async move {
24488        request.next().await;
24489    }
24490}
24491
24492#[track_caller]
24493pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
24494    cx.update_editor(|editor, _, _| {
24495        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
24496            let entries = menu.entries.borrow();
24497            let entries = entries
24498                .iter()
24499                .map(|entry| entry.string.as_str())
24500                .collect::<Vec<_>>();
24501            assert_eq!(entries, expected);
24502        } else {
24503            panic!("Expected completions menu");
24504        }
24505    });
24506}
24507
24508/// Handle completion request passing a marked string specifying where the completion
24509/// should be triggered from using '|' character, what range should be replaced, and what completions
24510/// should be returned using '<' and '>' to delimit the range.
24511///
24512/// Also see `handle_completion_request_with_insert_and_replace`.
24513#[track_caller]
24514pub fn handle_completion_request(
24515    marked_string: &str,
24516    completions: Vec<&'static str>,
24517    is_incomplete: bool,
24518    counter: Arc<AtomicUsize>,
24519    cx: &mut EditorLspTestContext,
24520) -> impl Future<Output = ()> {
24521    let complete_from_marker: TextRangeMarker = '|'.into();
24522    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24523    let (_, mut marked_ranges) = marked_text_ranges_by(
24524        marked_string,
24525        vec![complete_from_marker.clone(), replace_range_marker.clone()],
24526    );
24527
24528    let complete_from_position =
24529        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24530    let replace_range =
24531        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24532
24533    let mut request =
24534        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24535            let completions = completions.clone();
24536            counter.fetch_add(1, atomic::Ordering::Release);
24537            async move {
24538                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24539                assert_eq!(
24540                    params.text_document_position.position,
24541                    complete_from_position
24542                );
24543                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
24544                    is_incomplete,
24545                    item_defaults: None,
24546                    items: completions
24547                        .iter()
24548                        .map(|completion_text| lsp::CompletionItem {
24549                            label: completion_text.to_string(),
24550                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
24551                                range: replace_range,
24552                                new_text: completion_text.to_string(),
24553                            })),
24554                            ..Default::default()
24555                        })
24556                        .collect(),
24557                })))
24558            }
24559        });
24560
24561    async move {
24562        request.next().await;
24563    }
24564}
24565
24566/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
24567/// given instead, which also contains an `insert` range.
24568///
24569/// This function uses markers to define ranges:
24570/// - `|` marks the cursor position
24571/// - `<>` marks the replace range
24572/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
24573pub fn handle_completion_request_with_insert_and_replace(
24574    cx: &mut EditorLspTestContext,
24575    marked_string: &str,
24576    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
24577    counter: Arc<AtomicUsize>,
24578) -> impl Future<Output = ()> {
24579    let complete_from_marker: TextRangeMarker = '|'.into();
24580    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24581    let insert_range_marker: TextRangeMarker = ('{', '}').into();
24582
24583    let (_, mut marked_ranges) = marked_text_ranges_by(
24584        marked_string,
24585        vec![
24586            complete_from_marker.clone(),
24587            replace_range_marker.clone(),
24588            insert_range_marker.clone(),
24589        ],
24590    );
24591
24592    let complete_from_position =
24593        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24594    let replace_range =
24595        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24596
24597    let insert_range = match marked_ranges.remove(&insert_range_marker) {
24598        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
24599        _ => lsp::Range {
24600            start: replace_range.start,
24601            end: complete_from_position,
24602        },
24603    };
24604
24605    let mut request =
24606        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24607            let completions = completions.clone();
24608            counter.fetch_add(1, atomic::Ordering::Release);
24609            async move {
24610                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24611                assert_eq!(
24612                    params.text_document_position.position, complete_from_position,
24613                    "marker `|` position doesn't match",
24614                );
24615                Ok(Some(lsp::CompletionResponse::Array(
24616                    completions
24617                        .iter()
24618                        .map(|(label, new_text)| lsp::CompletionItem {
24619                            label: label.to_string(),
24620                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24621                                lsp::InsertReplaceEdit {
24622                                    insert: insert_range,
24623                                    replace: replace_range,
24624                                    new_text: new_text.to_string(),
24625                                },
24626                            )),
24627                            ..Default::default()
24628                        })
24629                        .collect(),
24630                )))
24631            }
24632        });
24633
24634    async move {
24635        request.next().await;
24636    }
24637}
24638
24639fn handle_resolve_completion_request(
24640    cx: &mut EditorLspTestContext,
24641    edits: Option<Vec<(&'static str, &'static str)>>,
24642) -> impl Future<Output = ()> {
24643    let edits = edits.map(|edits| {
24644        edits
24645            .iter()
24646            .map(|(marked_string, new_text)| {
24647                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
24648                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
24649                lsp::TextEdit::new(replace_range, new_text.to_string())
24650            })
24651            .collect::<Vec<_>>()
24652    });
24653
24654    let mut request =
24655        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
24656            let edits = edits.clone();
24657            async move {
24658                Ok(lsp::CompletionItem {
24659                    additional_text_edits: edits,
24660                    ..Default::default()
24661                })
24662            }
24663        });
24664
24665    async move {
24666        request.next().await;
24667    }
24668}
24669
24670pub(crate) fn update_test_language_settings(
24671    cx: &mut TestAppContext,
24672    f: impl Fn(&mut AllLanguageSettingsContent),
24673) {
24674    cx.update(|cx| {
24675        SettingsStore::update_global(cx, |store, cx| {
24676            store.update_user_settings::<AllLanguageSettings>(cx, f);
24677        });
24678    });
24679}
24680
24681pub(crate) fn update_test_project_settings(
24682    cx: &mut TestAppContext,
24683    f: impl Fn(&mut ProjectSettings),
24684) {
24685    cx.update(|cx| {
24686        SettingsStore::update_global(cx, |store, cx| {
24687            store.update_user_settings::<ProjectSettings>(cx, f);
24688        });
24689    });
24690}
24691
24692pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
24693    cx.update(|cx| {
24694        assets::Assets.load_test_fonts(cx);
24695        let store = SettingsStore::test(cx);
24696        cx.set_global(store);
24697        theme::init(theme::LoadThemes::JustBase, cx);
24698        release_channel::init(SemanticVersion::default(), cx);
24699        client::init_settings(cx);
24700        language::init(cx);
24701        Project::init_settings(cx);
24702        workspace::init_settings(cx);
24703        crate::init(cx);
24704    });
24705    zlog::init_test();
24706    update_test_language_settings(cx, f);
24707}
24708
24709#[track_caller]
24710fn assert_hunk_revert(
24711    not_reverted_text_with_selections: &str,
24712    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
24713    expected_reverted_text_with_selections: &str,
24714    base_text: &str,
24715    cx: &mut EditorLspTestContext,
24716) {
24717    cx.set_state(not_reverted_text_with_selections);
24718    cx.set_head_text(base_text);
24719    cx.executor().run_until_parked();
24720
24721    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
24722        let snapshot = editor.snapshot(window, cx);
24723        let reverted_hunk_statuses = snapshot
24724            .buffer_snapshot
24725            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
24726            .map(|hunk| hunk.status().kind)
24727            .collect::<Vec<_>>();
24728
24729        editor.git_restore(&Default::default(), window, cx);
24730        reverted_hunk_statuses
24731    });
24732    cx.executor().run_until_parked();
24733    cx.assert_editor_state(expected_reverted_text_with_selections);
24734    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
24735}
24736
24737#[gpui::test(iterations = 10)]
24738async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
24739    init_test(cx, |_| {});
24740
24741    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
24742    let counter = diagnostic_requests.clone();
24743
24744    let fs = FakeFs::new(cx.executor());
24745    fs.insert_tree(
24746        path!("/a"),
24747        json!({
24748            "first.rs": "fn main() { let a = 5; }",
24749            "second.rs": "// Test file",
24750        }),
24751    )
24752    .await;
24753
24754    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24755    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24756    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24757
24758    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24759    language_registry.add(rust_lang());
24760    let mut fake_servers = language_registry.register_fake_lsp(
24761        "Rust",
24762        FakeLspAdapter {
24763            capabilities: lsp::ServerCapabilities {
24764                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
24765                    lsp::DiagnosticOptions {
24766                        identifier: None,
24767                        inter_file_dependencies: true,
24768                        workspace_diagnostics: true,
24769                        work_done_progress_options: Default::default(),
24770                    },
24771                )),
24772                ..Default::default()
24773            },
24774            ..Default::default()
24775        },
24776    );
24777
24778    let editor = workspace
24779        .update(cx, |workspace, window, cx| {
24780            workspace.open_abs_path(
24781                PathBuf::from(path!("/a/first.rs")),
24782                OpenOptions::default(),
24783                window,
24784                cx,
24785            )
24786        })
24787        .unwrap()
24788        .await
24789        .unwrap()
24790        .downcast::<Editor>()
24791        .unwrap();
24792    let fake_server = fake_servers.next().await.unwrap();
24793    let server_id = fake_server.server.server_id();
24794    let mut first_request = fake_server
24795        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
24796            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
24797            let result_id = Some(new_result_id.to_string());
24798            assert_eq!(
24799                params.text_document.uri,
24800                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
24801            );
24802            async move {
24803                Ok(lsp::DocumentDiagnosticReportResult::Report(
24804                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
24805                        related_documents: None,
24806                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
24807                            items: Vec::new(),
24808                            result_id,
24809                        },
24810                    }),
24811                ))
24812            }
24813        });
24814
24815    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
24816        project.update(cx, |project, cx| {
24817            let buffer_id = editor
24818                .read(cx)
24819                .buffer()
24820                .read(cx)
24821                .as_singleton()
24822                .expect("created a singleton buffer")
24823                .read(cx)
24824                .remote_id();
24825            let buffer_result_id = project
24826                .lsp_store()
24827                .read(cx)
24828                .result_id(server_id, buffer_id, cx);
24829            assert_eq!(expected, buffer_result_id);
24830        });
24831    };
24832
24833    ensure_result_id(None, cx);
24834    cx.executor().advance_clock(Duration::from_millis(60));
24835    cx.executor().run_until_parked();
24836    assert_eq!(
24837        diagnostic_requests.load(atomic::Ordering::Acquire),
24838        1,
24839        "Opening file should trigger diagnostic request"
24840    );
24841    first_request
24842        .next()
24843        .await
24844        .expect("should have sent the first diagnostics pull request");
24845    ensure_result_id(Some("1".to_string()), cx);
24846
24847    // Editing should trigger diagnostics
24848    editor.update_in(cx, |editor, window, cx| {
24849        editor.handle_input("2", window, cx)
24850    });
24851    cx.executor().advance_clock(Duration::from_millis(60));
24852    cx.executor().run_until_parked();
24853    assert_eq!(
24854        diagnostic_requests.load(atomic::Ordering::Acquire),
24855        2,
24856        "Editing should trigger diagnostic request"
24857    );
24858    ensure_result_id(Some("2".to_string()), cx);
24859
24860    // Moving cursor should not trigger diagnostic request
24861    editor.update_in(cx, |editor, window, cx| {
24862        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24863            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
24864        });
24865    });
24866    cx.executor().advance_clock(Duration::from_millis(60));
24867    cx.executor().run_until_parked();
24868    assert_eq!(
24869        diagnostic_requests.load(atomic::Ordering::Acquire),
24870        2,
24871        "Cursor movement should not trigger diagnostic request"
24872    );
24873    ensure_result_id(Some("2".to_string()), cx);
24874    // Multiple rapid edits should be debounced
24875    for _ in 0..5 {
24876        editor.update_in(cx, |editor, window, cx| {
24877            editor.handle_input("x", window, cx)
24878        });
24879    }
24880    cx.executor().advance_clock(Duration::from_millis(60));
24881    cx.executor().run_until_parked();
24882
24883    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
24884    assert!(
24885        final_requests <= 4,
24886        "Multiple rapid edits should be debounced (got {final_requests} requests)",
24887    );
24888    ensure_result_id(Some(final_requests.to_string()), cx);
24889}
24890
24891#[gpui::test]
24892async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
24893    // Regression test for issue #11671
24894    // Previously, adding a cursor after moving multiple cursors would reset
24895    // the cursor count instead of adding to the existing cursors.
24896    init_test(cx, |_| {});
24897    let mut cx = EditorTestContext::new(cx).await;
24898
24899    // Create a simple buffer with cursor at start
24900    cx.set_state(indoc! {"
24901        ˇaaaa
24902        bbbb
24903        cccc
24904        dddd
24905        eeee
24906        ffff
24907        gggg
24908        hhhh"});
24909
24910    // Add 2 cursors below (so we have 3 total)
24911    cx.update_editor(|editor, window, cx| {
24912        editor.add_selection_below(&Default::default(), window, cx);
24913        editor.add_selection_below(&Default::default(), window, cx);
24914    });
24915
24916    // Verify we have 3 cursors
24917    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
24918    assert_eq!(
24919        initial_count, 3,
24920        "Should have 3 cursors after adding 2 below"
24921    );
24922
24923    // Move down one line
24924    cx.update_editor(|editor, window, cx| {
24925        editor.move_down(&MoveDown, window, cx);
24926    });
24927
24928    // Add another cursor below
24929    cx.update_editor(|editor, window, cx| {
24930        editor.add_selection_below(&Default::default(), window, cx);
24931    });
24932
24933    // Should now have 4 cursors (3 original + 1 new)
24934    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
24935    assert_eq!(
24936        final_count, 4,
24937        "Should have 4 cursors after moving and adding another"
24938    );
24939}
24940
24941#[gpui::test(iterations = 10)]
24942async fn test_document_colors(cx: &mut TestAppContext) {
24943    let expected_color = Rgba {
24944        r: 0.33,
24945        g: 0.33,
24946        b: 0.33,
24947        a: 0.33,
24948    };
24949
24950    init_test(cx, |_| {});
24951
24952    let fs = FakeFs::new(cx.executor());
24953    fs.insert_tree(
24954        path!("/a"),
24955        json!({
24956            "first.rs": "fn main() { let a = 5; }",
24957        }),
24958    )
24959    .await;
24960
24961    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24962    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24963    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24964
24965    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24966    language_registry.add(rust_lang());
24967    let mut fake_servers = language_registry.register_fake_lsp(
24968        "Rust",
24969        FakeLspAdapter {
24970            capabilities: lsp::ServerCapabilities {
24971                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
24972                ..lsp::ServerCapabilities::default()
24973            },
24974            name: "rust-analyzer",
24975            ..FakeLspAdapter::default()
24976        },
24977    );
24978    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
24979        "Rust",
24980        FakeLspAdapter {
24981            capabilities: lsp::ServerCapabilities {
24982                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
24983                ..lsp::ServerCapabilities::default()
24984            },
24985            name: "not-rust-analyzer",
24986            ..FakeLspAdapter::default()
24987        },
24988    );
24989
24990    let editor = workspace
24991        .update(cx, |workspace, window, cx| {
24992            workspace.open_abs_path(
24993                PathBuf::from(path!("/a/first.rs")),
24994                OpenOptions::default(),
24995                window,
24996                cx,
24997            )
24998        })
24999        .unwrap()
25000        .await
25001        .unwrap()
25002        .downcast::<Editor>()
25003        .unwrap();
25004    let fake_language_server = fake_servers.next().await.unwrap();
25005    let fake_language_server_without_capabilities =
25006        fake_servers_without_capabilities.next().await.unwrap();
25007    let requests_made = Arc::new(AtomicUsize::new(0));
25008    let closure_requests_made = Arc::clone(&requests_made);
25009    let mut color_request_handle = fake_language_server
25010        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25011            let requests_made = Arc::clone(&closure_requests_made);
25012            async move {
25013                assert_eq!(
25014                    params.text_document.uri,
25015                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25016                );
25017                requests_made.fetch_add(1, atomic::Ordering::Release);
25018                Ok(vec![
25019                    lsp::ColorInformation {
25020                        range: lsp::Range {
25021                            start: lsp::Position {
25022                                line: 0,
25023                                character: 0,
25024                            },
25025                            end: lsp::Position {
25026                                line: 0,
25027                                character: 1,
25028                            },
25029                        },
25030                        color: lsp::Color {
25031                            red: 0.33,
25032                            green: 0.33,
25033                            blue: 0.33,
25034                            alpha: 0.33,
25035                        },
25036                    },
25037                    lsp::ColorInformation {
25038                        range: lsp::Range {
25039                            start: lsp::Position {
25040                                line: 0,
25041                                character: 0,
25042                            },
25043                            end: lsp::Position {
25044                                line: 0,
25045                                character: 1,
25046                            },
25047                        },
25048                        color: lsp::Color {
25049                            red: 0.33,
25050                            green: 0.33,
25051                            blue: 0.33,
25052                            alpha: 0.33,
25053                        },
25054                    },
25055                ])
25056            }
25057        });
25058
25059    let _handle = fake_language_server_without_capabilities
25060        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25061            panic!("Should not be called");
25062        });
25063    cx.executor().advance_clock(Duration::from_millis(100));
25064    color_request_handle.next().await.unwrap();
25065    cx.run_until_parked();
25066    assert_eq!(
25067        1,
25068        requests_made.load(atomic::Ordering::Acquire),
25069        "Should query for colors once per editor open"
25070    );
25071    editor.update_in(cx, |editor, _, cx| {
25072        assert_eq!(
25073            vec![expected_color],
25074            extract_color_inlays(editor, cx),
25075            "Should have an initial inlay"
25076        );
25077    });
25078
25079    // opening another file in a split should not influence the LSP query counter
25080    workspace
25081        .update(cx, |workspace, window, cx| {
25082            assert_eq!(
25083                workspace.panes().len(),
25084                1,
25085                "Should have one pane with one editor"
25086            );
25087            workspace.move_item_to_pane_in_direction(
25088                &MoveItemToPaneInDirection {
25089                    direction: SplitDirection::Right,
25090                    focus: false,
25091                    clone: true,
25092                },
25093                window,
25094                cx,
25095            );
25096        })
25097        .unwrap();
25098    cx.run_until_parked();
25099    workspace
25100        .update(cx, |workspace, _, cx| {
25101            let panes = workspace.panes();
25102            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25103            for pane in panes {
25104                let editor = pane
25105                    .read(cx)
25106                    .active_item()
25107                    .and_then(|item| item.downcast::<Editor>())
25108                    .expect("Should have opened an editor in each split");
25109                let editor_file = editor
25110                    .read(cx)
25111                    .buffer()
25112                    .read(cx)
25113                    .as_singleton()
25114                    .expect("test deals with singleton buffers")
25115                    .read(cx)
25116                    .file()
25117                    .expect("test buffese should have a file")
25118                    .path();
25119                assert_eq!(
25120                    editor_file.as_ref(),
25121                    Path::new("first.rs"),
25122                    "Both editors should be opened for the same file"
25123                )
25124            }
25125        })
25126        .unwrap();
25127
25128    cx.executor().advance_clock(Duration::from_millis(500));
25129    let save = editor.update_in(cx, |editor, window, cx| {
25130        editor.move_to_end(&MoveToEnd, window, cx);
25131        editor.handle_input("dirty", window, cx);
25132        editor.save(
25133            SaveOptions {
25134                format: true,
25135                autosave: true,
25136            },
25137            project.clone(),
25138            window,
25139            cx,
25140        )
25141    });
25142    save.await.unwrap();
25143
25144    color_request_handle.next().await.unwrap();
25145    cx.run_until_parked();
25146    assert_eq!(
25147        3,
25148        requests_made.load(atomic::Ordering::Acquire),
25149        "Should query for colors once per save and once per formatting after save"
25150    );
25151
25152    drop(editor);
25153    let close = workspace
25154        .update(cx, |workspace, window, cx| {
25155            workspace.active_pane().update(cx, |pane, cx| {
25156                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25157            })
25158        })
25159        .unwrap();
25160    close.await.unwrap();
25161    let close = workspace
25162        .update(cx, |workspace, window, cx| {
25163            workspace.active_pane().update(cx, |pane, cx| {
25164                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25165            })
25166        })
25167        .unwrap();
25168    close.await.unwrap();
25169    assert_eq!(
25170        3,
25171        requests_made.load(atomic::Ordering::Acquire),
25172        "After saving and closing all editors, no extra requests should be made"
25173    );
25174    workspace
25175        .update(cx, |workspace, _, cx| {
25176            assert!(
25177                workspace.active_item(cx).is_none(),
25178                "Should close all editors"
25179            )
25180        })
25181        .unwrap();
25182
25183    workspace
25184        .update(cx, |workspace, window, cx| {
25185            workspace.active_pane().update(cx, |pane, cx| {
25186                pane.navigate_backward(&Default::default(), window, cx);
25187            })
25188        })
25189        .unwrap();
25190    cx.executor().advance_clock(Duration::from_millis(100));
25191    cx.run_until_parked();
25192    let editor = workspace
25193        .update(cx, |workspace, _, cx| {
25194            workspace
25195                .active_item(cx)
25196                .expect("Should have reopened the editor again after navigating back")
25197                .downcast::<Editor>()
25198                .expect("Should be an editor")
25199        })
25200        .unwrap();
25201    color_request_handle.next().await.unwrap();
25202    assert_eq!(
25203        3,
25204        requests_made.load(atomic::Ordering::Acquire),
25205        "Cache should be reused on buffer close and reopen"
25206    );
25207    editor.update(cx, |editor, cx| {
25208        assert_eq!(
25209            vec![expected_color],
25210            extract_color_inlays(editor, cx),
25211            "Should have an initial inlay"
25212        );
25213    });
25214}
25215
25216#[gpui::test]
25217async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25218    init_test(cx, |_| {});
25219    let (editor, cx) = cx.add_window_view(Editor::single_line);
25220    editor.update_in(cx, |editor, window, cx| {
25221        editor.set_text("oops\n\nwow\n", window, cx)
25222    });
25223    cx.run_until_parked();
25224    editor.update(cx, |editor, cx| {
25225        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25226    });
25227    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25228    cx.run_until_parked();
25229    editor.update(cx, |editor, cx| {
25230        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25231    });
25232}
25233
25234#[gpui::test]
25235async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25236    init_test(cx, |_| {});
25237
25238    cx.update(|cx| {
25239        register_project_item::<Editor>(cx);
25240    });
25241
25242    let fs = FakeFs::new(cx.executor());
25243    fs.insert_tree("/root1", json!({})).await;
25244    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25245        .await;
25246
25247    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25248    let (workspace, cx) =
25249        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25250
25251    let worktree_id = project.update(cx, |project, cx| {
25252        project.worktrees(cx).next().unwrap().read(cx).id()
25253    });
25254
25255    let handle = workspace
25256        .update_in(cx, |workspace, window, cx| {
25257            let project_path = (worktree_id, "one.pdf");
25258            workspace.open_path(project_path, None, true, window, cx)
25259        })
25260        .await
25261        .unwrap();
25262
25263    assert_eq!(
25264        handle.to_any().entity_type(),
25265        TypeId::of::<InvalidBufferView>()
25266    );
25267}
25268
25269#[track_caller]
25270fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
25271    editor
25272        .all_inlays(cx)
25273        .into_iter()
25274        .filter_map(|inlay| inlay.get_color())
25275        .map(Rgba::from)
25276        .collect()
25277}