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
 5368#[gpui::test]
 5369fn test_duplicate_line(cx: &mut TestAppContext) {
 5370    init_test(cx, |_| {});
 5371
 5372    let editor = cx.add_window(|window, cx| {
 5373        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5374        build_editor(buffer, window, cx)
 5375    });
 5376    _ = editor.update(cx, |editor, window, cx| {
 5377        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5378            s.select_display_ranges([
 5379                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5380                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5381                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5382                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5383            ])
 5384        });
 5385        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5386        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5387        assert_eq!(
 5388            editor.selections.display_ranges(cx),
 5389            vec![
 5390                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5391                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5392                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5393                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5394            ]
 5395        );
 5396    });
 5397
 5398    let editor = cx.add_window(|window, cx| {
 5399        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5400        build_editor(buffer, window, cx)
 5401    });
 5402    _ = editor.update(cx, |editor, window, cx| {
 5403        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5404            s.select_display_ranges([
 5405                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5406                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5407            ])
 5408        });
 5409        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5410        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5411        assert_eq!(
 5412            editor.selections.display_ranges(cx),
 5413            vec![
 5414                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5415                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5416            ]
 5417        );
 5418    });
 5419
 5420    // With `move_upwards` the selections stay in place, except for
 5421    // the lines inserted above them
 5422    let editor = cx.add_window(|window, cx| {
 5423        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5424        build_editor(buffer, window, cx)
 5425    });
 5426    _ = editor.update(cx, |editor, window, cx| {
 5427        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5428            s.select_display_ranges([
 5429                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5430                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5431                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5432                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5433            ])
 5434        });
 5435        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5436        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5437        assert_eq!(
 5438            editor.selections.display_ranges(cx),
 5439            vec![
 5440                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5441                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5442                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5443                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5444            ]
 5445        );
 5446    });
 5447
 5448    let editor = cx.add_window(|window, cx| {
 5449        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5450        build_editor(buffer, window, cx)
 5451    });
 5452    _ = editor.update(cx, |editor, window, cx| {
 5453        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5454            s.select_display_ranges([
 5455                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5456                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5457            ])
 5458        });
 5459        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5460        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5461        assert_eq!(
 5462            editor.selections.display_ranges(cx),
 5463            vec![
 5464                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5465                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5466            ]
 5467        );
 5468    });
 5469
 5470    let editor = cx.add_window(|window, cx| {
 5471        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5472        build_editor(buffer, window, cx)
 5473    });
 5474    _ = editor.update(cx, |editor, window, cx| {
 5475        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5476            s.select_display_ranges([
 5477                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5478                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5479            ])
 5480        });
 5481        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5482        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5483        assert_eq!(
 5484            editor.selections.display_ranges(cx),
 5485            vec![
 5486                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5487                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5488            ]
 5489        );
 5490    });
 5491}
 5492
 5493#[gpui::test]
 5494fn test_move_line_up_down(cx: &mut TestAppContext) {
 5495    init_test(cx, |_| {});
 5496
 5497    let editor = cx.add_window(|window, cx| {
 5498        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5499        build_editor(buffer, window, cx)
 5500    });
 5501    _ = editor.update(cx, |editor, window, cx| {
 5502        editor.fold_creases(
 5503            vec![
 5504                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5505                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5506                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5507            ],
 5508            true,
 5509            window,
 5510            cx,
 5511        );
 5512        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5513            s.select_display_ranges([
 5514                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5515                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5516                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5517                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5518            ])
 5519        });
 5520        assert_eq!(
 5521            editor.display_text(cx),
 5522            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5523        );
 5524
 5525        editor.move_line_up(&MoveLineUp, window, cx);
 5526        assert_eq!(
 5527            editor.display_text(cx),
 5528            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5529        );
 5530        assert_eq!(
 5531            editor.selections.display_ranges(cx),
 5532            vec![
 5533                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5534                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5535                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5536                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5537            ]
 5538        );
 5539    });
 5540
 5541    _ = editor.update(cx, |editor, window, cx| {
 5542        editor.move_line_down(&MoveLineDown, window, cx);
 5543        assert_eq!(
 5544            editor.display_text(cx),
 5545            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5546        );
 5547        assert_eq!(
 5548            editor.selections.display_ranges(cx),
 5549            vec![
 5550                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5551                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5552                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5553                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5554            ]
 5555        );
 5556    });
 5557
 5558    _ = editor.update(cx, |editor, window, cx| {
 5559        editor.move_line_down(&MoveLineDown, window, cx);
 5560        assert_eq!(
 5561            editor.display_text(cx),
 5562            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5563        );
 5564        assert_eq!(
 5565            editor.selections.display_ranges(cx),
 5566            vec![
 5567                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5568                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5569                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5570                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5571            ]
 5572        );
 5573    });
 5574
 5575    _ = editor.update(cx, |editor, window, cx| {
 5576        editor.move_line_up(&MoveLineUp, window, cx);
 5577        assert_eq!(
 5578            editor.display_text(cx),
 5579            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5580        );
 5581        assert_eq!(
 5582            editor.selections.display_ranges(cx),
 5583            vec![
 5584                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5585                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5586                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5587                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5588            ]
 5589        );
 5590    });
 5591}
 5592
 5593#[gpui::test]
 5594fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5595    init_test(cx, |_| {});
 5596    let editor = cx.add_window(|window, cx| {
 5597        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5598        build_editor(buffer, window, cx)
 5599    });
 5600    _ = editor.update(cx, |editor, window, cx| {
 5601        editor.fold_creases(
 5602            vec![Crease::simple(
 5603                Point::new(6, 4)..Point::new(7, 4),
 5604                FoldPlaceholder::test(),
 5605            )],
 5606            true,
 5607            window,
 5608            cx,
 5609        );
 5610        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5611            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5612        });
 5613        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5614        editor.move_line_up(&MoveLineUp, window, cx);
 5615        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5616        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5617    });
 5618}
 5619
 5620#[gpui::test]
 5621fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5622    init_test(cx, |_| {});
 5623
 5624    let editor = cx.add_window(|window, cx| {
 5625        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5626        build_editor(buffer, window, cx)
 5627    });
 5628    _ = editor.update(cx, |editor, window, cx| {
 5629        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5630        editor.insert_blocks(
 5631            [BlockProperties {
 5632                style: BlockStyle::Fixed,
 5633                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5634                height: Some(1),
 5635                render: Arc::new(|_| div().into_any()),
 5636                priority: 0,
 5637            }],
 5638            Some(Autoscroll::fit()),
 5639            cx,
 5640        );
 5641        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5642            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5643        });
 5644        editor.move_line_down(&MoveLineDown, window, cx);
 5645    });
 5646}
 5647
 5648#[gpui::test]
 5649async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5650    init_test(cx, |_| {});
 5651
 5652    let mut cx = EditorTestContext::new(cx).await;
 5653    cx.set_state(
 5654        &"
 5655            ˇzero
 5656            one
 5657            two
 5658            three
 5659            four
 5660            five
 5661        "
 5662        .unindent(),
 5663    );
 5664
 5665    // Create a four-line block that replaces three lines of text.
 5666    cx.update_editor(|editor, window, cx| {
 5667        let snapshot = editor.snapshot(window, cx);
 5668        let snapshot = &snapshot.buffer_snapshot;
 5669        let placement = BlockPlacement::Replace(
 5670            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5671        );
 5672        editor.insert_blocks(
 5673            [BlockProperties {
 5674                placement,
 5675                height: Some(4),
 5676                style: BlockStyle::Sticky,
 5677                render: Arc::new(|_| gpui::div().into_any_element()),
 5678                priority: 0,
 5679            }],
 5680            None,
 5681            cx,
 5682        );
 5683    });
 5684
 5685    // Move down so that the cursor touches the block.
 5686    cx.update_editor(|editor, window, cx| {
 5687        editor.move_down(&Default::default(), window, cx);
 5688    });
 5689    cx.assert_editor_state(
 5690        &"
 5691            zero
 5692            «one
 5693            two
 5694            threeˇ»
 5695            four
 5696            five
 5697        "
 5698        .unindent(),
 5699    );
 5700
 5701    // Move down past the block.
 5702    cx.update_editor(|editor, window, cx| {
 5703        editor.move_down(&Default::default(), window, cx);
 5704    });
 5705    cx.assert_editor_state(
 5706        &"
 5707            zero
 5708            one
 5709            two
 5710            three
 5711            ˇfour
 5712            five
 5713        "
 5714        .unindent(),
 5715    );
 5716}
 5717
 5718#[gpui::test]
 5719fn test_transpose(cx: &mut TestAppContext) {
 5720    init_test(cx, |_| {});
 5721
 5722    _ = cx.add_window(|window, cx| {
 5723        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5724        editor.set_style(EditorStyle::default(), window, cx);
 5725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5726            s.select_ranges([1..1])
 5727        });
 5728        editor.transpose(&Default::default(), window, cx);
 5729        assert_eq!(editor.text(cx), "bac");
 5730        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5731
 5732        editor.transpose(&Default::default(), window, cx);
 5733        assert_eq!(editor.text(cx), "bca");
 5734        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5735
 5736        editor.transpose(&Default::default(), window, cx);
 5737        assert_eq!(editor.text(cx), "bac");
 5738        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5739
 5740        editor
 5741    });
 5742
 5743    _ = cx.add_window(|window, cx| {
 5744        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5745        editor.set_style(EditorStyle::default(), window, cx);
 5746        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5747            s.select_ranges([3..3])
 5748        });
 5749        editor.transpose(&Default::default(), window, cx);
 5750        assert_eq!(editor.text(cx), "acb\nde");
 5751        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5752
 5753        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5754            s.select_ranges([4..4])
 5755        });
 5756        editor.transpose(&Default::default(), window, cx);
 5757        assert_eq!(editor.text(cx), "acbd\ne");
 5758        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5759
 5760        editor.transpose(&Default::default(), window, cx);
 5761        assert_eq!(editor.text(cx), "acbde\n");
 5762        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5763
 5764        editor.transpose(&Default::default(), window, cx);
 5765        assert_eq!(editor.text(cx), "acbd\ne");
 5766        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5767
 5768        editor
 5769    });
 5770
 5771    _ = cx.add_window(|window, cx| {
 5772        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5773        editor.set_style(EditorStyle::default(), window, cx);
 5774        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5775            s.select_ranges([1..1, 2..2, 4..4])
 5776        });
 5777        editor.transpose(&Default::default(), window, cx);
 5778        assert_eq!(editor.text(cx), "bacd\ne");
 5779        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5780
 5781        editor.transpose(&Default::default(), window, cx);
 5782        assert_eq!(editor.text(cx), "bcade\n");
 5783        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5784
 5785        editor.transpose(&Default::default(), window, cx);
 5786        assert_eq!(editor.text(cx), "bcda\ne");
 5787        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5788
 5789        editor.transpose(&Default::default(), window, cx);
 5790        assert_eq!(editor.text(cx), "bcade\n");
 5791        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5792
 5793        editor.transpose(&Default::default(), window, cx);
 5794        assert_eq!(editor.text(cx), "bcaed\n");
 5795        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5796
 5797        editor
 5798    });
 5799
 5800    _ = cx.add_window(|window, cx| {
 5801        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5802        editor.set_style(EditorStyle::default(), window, cx);
 5803        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5804            s.select_ranges([4..4])
 5805        });
 5806        editor.transpose(&Default::default(), window, cx);
 5807        assert_eq!(editor.text(cx), "🏀🍐✋");
 5808        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5809
 5810        editor.transpose(&Default::default(), window, cx);
 5811        assert_eq!(editor.text(cx), "🏀✋🍐");
 5812        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5813
 5814        editor.transpose(&Default::default(), window, cx);
 5815        assert_eq!(editor.text(cx), "🏀🍐✋");
 5816        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5817
 5818        editor
 5819    });
 5820}
 5821
 5822#[gpui::test]
 5823async fn test_rewrap(cx: &mut TestAppContext) {
 5824    init_test(cx, |settings| {
 5825        settings.languages.0.extend([
 5826            (
 5827                "Markdown".into(),
 5828                LanguageSettingsContent {
 5829                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5830                    preferred_line_length: Some(40),
 5831                    ..Default::default()
 5832                },
 5833            ),
 5834            (
 5835                "Plain Text".into(),
 5836                LanguageSettingsContent {
 5837                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5838                    preferred_line_length: Some(40),
 5839                    ..Default::default()
 5840                },
 5841            ),
 5842            (
 5843                "C++".into(),
 5844                LanguageSettingsContent {
 5845                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5846                    preferred_line_length: Some(40),
 5847                    ..Default::default()
 5848                },
 5849            ),
 5850            (
 5851                "Python".into(),
 5852                LanguageSettingsContent {
 5853                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5854                    preferred_line_length: Some(40),
 5855                    ..Default::default()
 5856                },
 5857            ),
 5858            (
 5859                "Rust".into(),
 5860                LanguageSettingsContent {
 5861                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5862                    preferred_line_length: Some(40),
 5863                    ..Default::default()
 5864                },
 5865            ),
 5866        ])
 5867    });
 5868
 5869    let mut cx = EditorTestContext::new(cx).await;
 5870
 5871    let cpp_language = Arc::new(Language::new(
 5872        LanguageConfig {
 5873            name: "C++".into(),
 5874            line_comments: vec!["// ".into()],
 5875            ..LanguageConfig::default()
 5876        },
 5877        None,
 5878    ));
 5879    let python_language = Arc::new(Language::new(
 5880        LanguageConfig {
 5881            name: "Python".into(),
 5882            line_comments: vec!["# ".into()],
 5883            ..LanguageConfig::default()
 5884        },
 5885        None,
 5886    ));
 5887    let markdown_language = Arc::new(Language::new(
 5888        LanguageConfig {
 5889            name: "Markdown".into(),
 5890            rewrap_prefixes: vec![
 5891                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5892                regex::Regex::new("[-*+]\\s+").unwrap(),
 5893            ],
 5894            ..LanguageConfig::default()
 5895        },
 5896        None,
 5897    ));
 5898    let rust_language = Arc::new(
 5899        Language::new(
 5900            LanguageConfig {
 5901                name: "Rust".into(),
 5902                line_comments: vec!["// ".into(), "/// ".into()],
 5903                ..LanguageConfig::default()
 5904            },
 5905            Some(tree_sitter_rust::LANGUAGE.into()),
 5906        )
 5907        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 5908        .unwrap(),
 5909    );
 5910
 5911    let plaintext_language = Arc::new(Language::new(
 5912        LanguageConfig {
 5913            name: "Plain Text".into(),
 5914            ..LanguageConfig::default()
 5915        },
 5916        None,
 5917    ));
 5918
 5919    // Test basic rewrapping of a long line with a cursor
 5920    assert_rewrap(
 5921        indoc! {"
 5922            // ˇThis is a long comment that needs to be wrapped.
 5923        "},
 5924        indoc! {"
 5925            // ˇThis is a long comment that needs to
 5926            // be wrapped.
 5927        "},
 5928        cpp_language.clone(),
 5929        &mut cx,
 5930    );
 5931
 5932    // Test rewrapping a full selection
 5933    assert_rewrap(
 5934        indoc! {"
 5935            «// This selected long comment needs to be wrapped.ˇ»"
 5936        },
 5937        indoc! {"
 5938            «// This selected long comment needs to
 5939            // be wrapped.ˇ»"
 5940        },
 5941        cpp_language.clone(),
 5942        &mut cx,
 5943    );
 5944
 5945    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5946    assert_rewrap(
 5947        indoc! {"
 5948            // ˇThis is the first line.
 5949            // Thisˇ is the second line.
 5950            // This is the thirdˇ line, all part of one paragraph.
 5951         "},
 5952        indoc! {"
 5953            // ˇThis is the first line. Thisˇ is the
 5954            // second line. This is the thirdˇ line,
 5955            // all part of one paragraph.
 5956         "},
 5957        cpp_language.clone(),
 5958        &mut cx,
 5959    );
 5960
 5961    // Test multiple cursors in different paragraphs trigger separate rewraps
 5962    assert_rewrap(
 5963        indoc! {"
 5964            // ˇThis is the first paragraph, first line.
 5965            // ˇThis is the first paragraph, second line.
 5966
 5967            // ˇThis is the second paragraph, first line.
 5968            // ˇThis is the second paragraph, second line.
 5969        "},
 5970        indoc! {"
 5971            // ˇThis is the first paragraph, first
 5972            // line. ˇThis is the first paragraph,
 5973            // second line.
 5974
 5975            // ˇThis is the second paragraph, first
 5976            // line. ˇThis is the second paragraph,
 5977            // second line.
 5978        "},
 5979        cpp_language.clone(),
 5980        &mut cx,
 5981    );
 5982
 5983    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5984    assert_rewrap(
 5985        indoc! {"
 5986            «// A regular long long comment to be wrapped.
 5987            /// A documentation long comment to be wrapped.ˇ»
 5988          "},
 5989        indoc! {"
 5990            «// A regular long long comment to be
 5991            // wrapped.
 5992            /// A documentation long comment to be
 5993            /// wrapped.ˇ»
 5994          "},
 5995        rust_language.clone(),
 5996        &mut cx,
 5997    );
 5998
 5999    // Test that change in indentation level trigger seperate rewraps
 6000    assert_rewrap(
 6001        indoc! {"
 6002            fn foo() {
 6003                «// This is a long comment at the base indent.
 6004                    // This is a long comment at the next indent.ˇ»
 6005            }
 6006        "},
 6007        indoc! {"
 6008            fn foo() {
 6009                «// This is a long comment at the
 6010                // base indent.
 6011                    // This is a long comment at the
 6012                    // next indent.ˇ»
 6013            }
 6014        "},
 6015        rust_language.clone(),
 6016        &mut cx,
 6017    );
 6018
 6019    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6020    assert_rewrap(
 6021        indoc! {"
 6022            # ˇThis is a long comment using a pound sign.
 6023        "},
 6024        indoc! {"
 6025            # ˇThis is a long comment using a pound
 6026            # sign.
 6027        "},
 6028        python_language,
 6029        &mut cx,
 6030    );
 6031
 6032    // Test rewrapping only affects comments, not code even when selected
 6033    assert_rewrap(
 6034        indoc! {"
 6035            «/// This doc comment is long and should be wrapped.
 6036            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6037        "},
 6038        indoc! {"
 6039            «/// This doc comment is long and should
 6040            /// be wrapped.
 6041            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6042        "},
 6043        rust_language.clone(),
 6044        &mut cx,
 6045    );
 6046
 6047    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6048    assert_rewrap(
 6049        indoc! {"
 6050            # Header
 6051
 6052            A long long long line of markdown text to wrap.ˇ
 6053         "},
 6054        indoc! {"
 6055            # Header
 6056
 6057            A long long long line of markdown text
 6058            to wrap.ˇ
 6059         "},
 6060        markdown_language.clone(),
 6061        &mut cx,
 6062    );
 6063
 6064    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6065    assert_rewrap(
 6066        indoc! {"
 6067            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6068            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6069            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6070        "},
 6071        indoc! {"
 6072            «1. This is a numbered list item that is
 6073               very long and needs to be wrapped
 6074               properly.
 6075            2. This is a numbered list item that is
 6076               very long and needs to be wrapped
 6077               properly.
 6078            - This is an unordered list item that is
 6079              also very long and should not merge
 6080              with the numbered item.ˇ»
 6081        "},
 6082        markdown_language.clone(),
 6083        &mut cx,
 6084    );
 6085
 6086    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6087    assert_rewrap(
 6088        indoc! {"
 6089            «1. This is a numbered list item that is
 6090            very long and needs to be wrapped
 6091            properly.
 6092            2. This is a numbered list item that is
 6093            very long and needs to be wrapped
 6094            properly.
 6095            - This is an unordered list item that is
 6096            also very long and should not merge with
 6097            the numbered item.ˇ»
 6098        "},
 6099        indoc! {"
 6100            «1. This is a numbered list item that is
 6101               very long and needs to be wrapped
 6102               properly.
 6103            2. This is a numbered list item that is
 6104               very long and needs to be wrapped
 6105               properly.
 6106            - This is an unordered list item that is
 6107              also very long and should not merge
 6108              with the numbered item.ˇ»
 6109        "},
 6110        markdown_language.clone(),
 6111        &mut cx,
 6112    );
 6113
 6114    // Test that rewrapping maintain indents even when they already exists.
 6115    assert_rewrap(
 6116        indoc! {"
 6117            «1. This is a numbered list
 6118               item that is very long and needs to be wrapped properly.
 6119            2. This is a numbered list
 6120               item that is very long and needs to be wrapped properly.
 6121            - This is an unordered list item that is also very long and
 6122              should not merge with the numbered item.ˇ»
 6123        "},
 6124        indoc! {"
 6125            «1. This is a numbered list item that is
 6126               very long and needs to be wrapped
 6127               properly.
 6128            2. This is a numbered list item that is
 6129               very long and needs to be wrapped
 6130               properly.
 6131            - This is an unordered list item that is
 6132              also very long and should not merge
 6133              with the numbered item.ˇ»
 6134        "},
 6135        markdown_language,
 6136        &mut cx,
 6137    );
 6138
 6139    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6140    assert_rewrap(
 6141        indoc! {"
 6142            ˇThis is a very long line of plain text that will be wrapped.
 6143        "},
 6144        indoc! {"
 6145            ˇThis is a very long line of plain text
 6146            that will be wrapped.
 6147        "},
 6148        plaintext_language.clone(),
 6149        &mut cx,
 6150    );
 6151
 6152    // Test that non-commented code acts as a paragraph boundary within a selection
 6153    assert_rewrap(
 6154        indoc! {"
 6155               «// This is the first long comment block to be wrapped.
 6156               fn my_func(a: u32);
 6157               // This is the second long comment block to be wrapped.ˇ»
 6158           "},
 6159        indoc! {"
 6160               «// This is the first long comment block
 6161               // to be wrapped.
 6162               fn my_func(a: u32);
 6163               // This is the second long comment block
 6164               // to be wrapped.ˇ»
 6165           "},
 6166        rust_language,
 6167        &mut cx,
 6168    );
 6169
 6170    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6171    assert_rewrap(
 6172        indoc! {"
 6173            «ˇThis is a very long line that will be wrapped.
 6174
 6175            This is another paragraph in the same selection.»
 6176
 6177            «\tThis is a very long indented line that will be wrapped.ˇ»
 6178         "},
 6179        indoc! {"
 6180            «ˇThis is a very long line that will be
 6181            wrapped.
 6182
 6183            This is another paragraph in the same
 6184            selection.»
 6185
 6186            «\tThis is a very long indented line
 6187            \tthat will be wrapped.ˇ»
 6188         "},
 6189        plaintext_language,
 6190        &mut cx,
 6191    );
 6192
 6193    // Test that an empty comment line acts as a paragraph boundary
 6194    assert_rewrap(
 6195        indoc! {"
 6196            // ˇThis is a long comment that will be wrapped.
 6197            //
 6198            // And this is another long comment that will also be wrapped.ˇ
 6199         "},
 6200        indoc! {"
 6201            // ˇThis is a long comment that will be
 6202            // wrapped.
 6203            //
 6204            // And this is another long comment that
 6205            // will also be wrapped.ˇ
 6206         "},
 6207        cpp_language,
 6208        &mut cx,
 6209    );
 6210
 6211    #[track_caller]
 6212    fn assert_rewrap(
 6213        unwrapped_text: &str,
 6214        wrapped_text: &str,
 6215        language: Arc<Language>,
 6216        cx: &mut EditorTestContext,
 6217    ) {
 6218        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6219        cx.set_state(unwrapped_text);
 6220        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6221        cx.assert_editor_state(wrapped_text);
 6222    }
 6223}
 6224
 6225#[gpui::test]
 6226async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6227    init_test(cx, |settings| {
 6228        settings.languages.0.extend([(
 6229            "Rust".into(),
 6230            LanguageSettingsContent {
 6231                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6232                preferred_line_length: Some(40),
 6233                ..Default::default()
 6234            },
 6235        )])
 6236    });
 6237
 6238    let mut cx = EditorTestContext::new(cx).await;
 6239
 6240    let rust_lang = Arc::new(
 6241        Language::new(
 6242            LanguageConfig {
 6243                name: "Rust".into(),
 6244                line_comments: vec!["// ".into()],
 6245                block_comment: Some(BlockCommentConfig {
 6246                    start: "/*".into(),
 6247                    end: "*/".into(),
 6248                    prefix: "* ".into(),
 6249                    tab_size: 1,
 6250                }),
 6251                documentation_comment: Some(BlockCommentConfig {
 6252                    start: "/**".into(),
 6253                    end: "*/".into(),
 6254                    prefix: "* ".into(),
 6255                    tab_size: 1,
 6256                }),
 6257
 6258                ..LanguageConfig::default()
 6259            },
 6260            Some(tree_sitter_rust::LANGUAGE.into()),
 6261        )
 6262        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6263        .unwrap(),
 6264    );
 6265
 6266    // regular block comment
 6267    assert_rewrap(
 6268        indoc! {"
 6269            /*
 6270             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6271             */
 6272            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6273        "},
 6274        indoc! {"
 6275            /*
 6276             *ˇ Lorem ipsum dolor sit amet,
 6277             * consectetur adipiscing elit.
 6278             */
 6279            /*
 6280             *ˇ Lorem ipsum dolor sit amet,
 6281             * consectetur adipiscing elit.
 6282             */
 6283        "},
 6284        rust_lang.clone(),
 6285        &mut cx,
 6286    );
 6287
 6288    // indent is respected
 6289    assert_rewrap(
 6290        indoc! {"
 6291            {}
 6292                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6293        "},
 6294        indoc! {"
 6295            {}
 6296                /*
 6297                 *ˇ Lorem ipsum dolor sit amet,
 6298                 * consectetur adipiscing elit.
 6299                 */
 6300        "},
 6301        rust_lang.clone(),
 6302        &mut cx,
 6303    );
 6304
 6305    // short block comments with inline delimiters
 6306    assert_rewrap(
 6307        indoc! {"
 6308            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6309            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6310             */
 6311            /*
 6312             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6313        "},
 6314        indoc! {"
 6315            /*
 6316             *ˇ Lorem ipsum dolor sit amet,
 6317             * consectetur adipiscing elit.
 6318             */
 6319            /*
 6320             *ˇ Lorem ipsum dolor sit amet,
 6321             * consectetur adipiscing elit.
 6322             */
 6323            /*
 6324             *ˇ Lorem ipsum dolor sit amet,
 6325             * consectetur adipiscing elit.
 6326             */
 6327        "},
 6328        rust_lang.clone(),
 6329        &mut cx,
 6330    );
 6331
 6332    // multiline block comment with inline start/end delimiters
 6333    assert_rewrap(
 6334        indoc! {"
 6335            /*ˇ Lorem ipsum dolor sit amet,
 6336             * consectetur adipiscing elit. */
 6337        "},
 6338        indoc! {"
 6339            /*
 6340             *ˇ Lorem ipsum dolor sit amet,
 6341             * consectetur adipiscing elit.
 6342             */
 6343        "},
 6344        rust_lang.clone(),
 6345        &mut cx,
 6346    );
 6347
 6348    // block comment rewrap still respects paragraph bounds
 6349    assert_rewrap(
 6350        indoc! {"
 6351            /*
 6352             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6353             *
 6354             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6355             */
 6356        "},
 6357        indoc! {"
 6358            /*
 6359             *ˇ Lorem ipsum dolor sit amet,
 6360             * consectetur adipiscing elit.
 6361             *
 6362             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6363             */
 6364        "},
 6365        rust_lang.clone(),
 6366        &mut cx,
 6367    );
 6368
 6369    // documentation comments
 6370    assert_rewrap(
 6371        indoc! {"
 6372            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6373            /**
 6374             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6375             */
 6376        "},
 6377        indoc! {"
 6378            /**
 6379             *ˇ Lorem ipsum dolor sit amet,
 6380             * consectetur adipiscing elit.
 6381             */
 6382            /**
 6383             *ˇ Lorem ipsum dolor sit amet,
 6384             * consectetur adipiscing elit.
 6385             */
 6386        "},
 6387        rust_lang.clone(),
 6388        &mut cx,
 6389    );
 6390
 6391    // different, adjacent comments
 6392    assert_rewrap(
 6393        indoc! {"
 6394            /**
 6395             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6396             */
 6397            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6398            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6399        "},
 6400        indoc! {"
 6401            /**
 6402             *ˇ Lorem ipsum dolor sit amet,
 6403             * consectetur adipiscing elit.
 6404             */
 6405            /*
 6406             *ˇ Lorem ipsum dolor sit amet,
 6407             * consectetur adipiscing elit.
 6408             */
 6409            //ˇ Lorem ipsum dolor sit amet,
 6410            // consectetur adipiscing elit.
 6411        "},
 6412        rust_lang.clone(),
 6413        &mut cx,
 6414    );
 6415
 6416    // selection w/ single short block comment
 6417    assert_rewrap(
 6418        indoc! {"
 6419            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6420        "},
 6421        indoc! {"
 6422            «/*
 6423             * Lorem ipsum dolor sit amet,
 6424             * consectetur adipiscing elit.
 6425             */ˇ»
 6426        "},
 6427        rust_lang.clone(),
 6428        &mut cx,
 6429    );
 6430
 6431    // rewrapping a single comment w/ abutting comments
 6432    assert_rewrap(
 6433        indoc! {"
 6434            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6435            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6436        "},
 6437        indoc! {"
 6438            /*
 6439             * ˇLorem ipsum dolor sit amet,
 6440             * consectetur adipiscing elit.
 6441             */
 6442            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6443        "},
 6444        rust_lang.clone(),
 6445        &mut cx,
 6446    );
 6447
 6448    // selection w/ non-abutting short block comments
 6449    assert_rewrap(
 6450        indoc! {"
 6451            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6452
 6453            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6454        "},
 6455        indoc! {"
 6456            «/*
 6457             * Lorem ipsum dolor sit amet,
 6458             * consectetur adipiscing elit.
 6459             */
 6460
 6461            /*
 6462             * Lorem ipsum dolor sit amet,
 6463             * consectetur adipiscing elit.
 6464             */ˇ»
 6465        "},
 6466        rust_lang.clone(),
 6467        &mut cx,
 6468    );
 6469
 6470    // selection of multiline block comments
 6471    assert_rewrap(
 6472        indoc! {"
 6473            «/* Lorem ipsum dolor sit amet,
 6474             * consectetur adipiscing elit. */ˇ»
 6475        "},
 6476        indoc! {"
 6477            «/*
 6478             * Lorem ipsum dolor sit amet,
 6479             * consectetur adipiscing elit.
 6480             */ˇ»
 6481        "},
 6482        rust_lang.clone(),
 6483        &mut cx,
 6484    );
 6485
 6486    // partial selection of multiline block comments
 6487    assert_rewrap(
 6488        indoc! {"
 6489            «/* Lorem ipsum dolor sit amet,ˇ»
 6490             * consectetur adipiscing elit. */
 6491            /* Lorem ipsum dolor sit amet,
 6492             «* consectetur adipiscing elit. */ˇ»
 6493        "},
 6494        indoc! {"
 6495            «/*
 6496             * Lorem ipsum dolor sit amet,ˇ»
 6497             * consectetur adipiscing elit. */
 6498            /* Lorem ipsum dolor sit amet,
 6499             «* consectetur adipiscing elit.
 6500             */ˇ»
 6501        "},
 6502        rust_lang.clone(),
 6503        &mut cx,
 6504    );
 6505
 6506    // selection w/ abutting short block comments
 6507    // TODO: should not be combined; should rewrap as 2 comments
 6508    assert_rewrap(
 6509        indoc! {"
 6510            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6511            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6512        "},
 6513        // desired behavior:
 6514        // indoc! {"
 6515        //     «/*
 6516        //      * Lorem ipsum dolor sit amet,
 6517        //      * consectetur adipiscing elit.
 6518        //      */
 6519        //     /*
 6520        //      * Lorem ipsum dolor sit amet,
 6521        //      * consectetur adipiscing elit.
 6522        //      */ˇ»
 6523        // "},
 6524        // actual behaviour:
 6525        indoc! {"
 6526            «/*
 6527             * Lorem ipsum dolor sit amet,
 6528             * consectetur adipiscing elit. Lorem
 6529             * ipsum dolor sit amet, consectetur
 6530             * adipiscing elit.
 6531             */ˇ»
 6532        "},
 6533        rust_lang.clone(),
 6534        &mut cx,
 6535    );
 6536
 6537    // TODO: same as above, but with delimiters on separate line
 6538    // assert_rewrap(
 6539    //     indoc! {"
 6540    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6541    //          */
 6542    //         /*
 6543    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6544    //     "},
 6545    //     // desired:
 6546    //     // indoc! {"
 6547    //     //     «/*
 6548    //     //      * Lorem ipsum dolor sit amet,
 6549    //     //      * consectetur adipiscing elit.
 6550    //     //      */
 6551    //     //     /*
 6552    //     //      * Lorem ipsum dolor sit amet,
 6553    //     //      * consectetur adipiscing elit.
 6554    //     //      */ˇ»
 6555    //     // "},
 6556    //     // actual: (but with trailing w/s on the empty lines)
 6557    //     indoc! {"
 6558    //         «/*
 6559    //          * Lorem ipsum dolor sit amet,
 6560    //          * consectetur adipiscing elit.
 6561    //          *
 6562    //          */
 6563    //         /*
 6564    //          *
 6565    //          * Lorem ipsum dolor sit amet,
 6566    //          * consectetur adipiscing elit.
 6567    //          */ˇ»
 6568    //     "},
 6569    //     rust_lang.clone(),
 6570    //     &mut cx,
 6571    // );
 6572
 6573    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6574    assert_rewrap(
 6575        indoc! {"
 6576            /*
 6577             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6578             */
 6579            /*
 6580             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6581            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6582        "},
 6583        // desired:
 6584        // indoc! {"
 6585        //     /*
 6586        //      *ˇ Lorem ipsum dolor sit amet,
 6587        //      * consectetur adipiscing elit.
 6588        //      */
 6589        //     /*
 6590        //      *ˇ Lorem ipsum dolor sit amet,
 6591        //      * consectetur adipiscing elit.
 6592        //      */
 6593        //     /*
 6594        //      *ˇ Lorem ipsum dolor sit amet
 6595        //      */ /* consectetur adipiscing elit. */
 6596        // "},
 6597        // actual:
 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        "},
 6612        rust_lang,
 6613        &mut cx,
 6614    );
 6615
 6616    #[track_caller]
 6617    fn assert_rewrap(
 6618        unwrapped_text: &str,
 6619        wrapped_text: &str,
 6620        language: Arc<Language>,
 6621        cx: &mut EditorTestContext,
 6622    ) {
 6623        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6624        cx.set_state(unwrapped_text);
 6625        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6626        cx.assert_editor_state(wrapped_text);
 6627    }
 6628}
 6629
 6630#[gpui::test]
 6631async fn test_hard_wrap(cx: &mut TestAppContext) {
 6632    init_test(cx, |_| {});
 6633    let mut cx = EditorTestContext::new(cx).await;
 6634
 6635    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6636    cx.update_editor(|editor, _, cx| {
 6637        editor.set_hard_wrap(Some(14), cx);
 6638    });
 6639
 6640    cx.set_state(indoc!(
 6641        "
 6642        one two three ˇ
 6643        "
 6644    ));
 6645    cx.simulate_input("four");
 6646    cx.run_until_parked();
 6647
 6648    cx.assert_editor_state(indoc!(
 6649        "
 6650        one two three
 6651        fourˇ
 6652        "
 6653    ));
 6654
 6655    cx.update_editor(|editor, window, cx| {
 6656        editor.newline(&Default::default(), window, cx);
 6657    });
 6658    cx.run_until_parked();
 6659    cx.assert_editor_state(indoc!(
 6660        "
 6661        one two three
 6662        four
 6663        ˇ
 6664        "
 6665    ));
 6666
 6667    cx.simulate_input("five");
 6668    cx.run_until_parked();
 6669    cx.assert_editor_state(indoc!(
 6670        "
 6671        one two three
 6672        four
 6673        fiveˇ
 6674        "
 6675    ));
 6676
 6677    cx.update_editor(|editor, window, cx| {
 6678        editor.newline(&Default::default(), window, cx);
 6679    });
 6680    cx.run_until_parked();
 6681    cx.simulate_input("# ");
 6682    cx.run_until_parked();
 6683    cx.assert_editor_state(indoc!(
 6684        "
 6685        one two three
 6686        four
 6687        five
 6688        # ˇ
 6689        "
 6690    ));
 6691
 6692    cx.update_editor(|editor, window, cx| {
 6693        editor.newline(&Default::default(), window, cx);
 6694    });
 6695    cx.run_until_parked();
 6696    cx.assert_editor_state(indoc!(
 6697        "
 6698        one two three
 6699        four
 6700        five
 6701        #\x20
 6702 6703        "
 6704    ));
 6705
 6706    cx.simulate_input(" 6");
 6707    cx.run_until_parked();
 6708    cx.assert_editor_state(indoc!(
 6709        "
 6710        one two three
 6711        four
 6712        five
 6713        #
 6714        # 6ˇ
 6715        "
 6716    ));
 6717}
 6718
 6719#[gpui::test]
 6720async fn test_clipboard(cx: &mut TestAppContext) {
 6721    init_test(cx, |_| {});
 6722
 6723    let mut cx = EditorTestContext::new(cx).await;
 6724
 6725    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6726    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6727    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6728
 6729    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6730    cx.set_state("two ˇfour ˇsix ˇ");
 6731    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6732    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6733
 6734    // Paste again but with only two cursors. Since the number of cursors doesn't
 6735    // match the number of slices in the clipboard, the entire clipboard text
 6736    // is pasted at each cursor.
 6737    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6738    cx.update_editor(|e, window, cx| {
 6739        e.handle_input("( ", window, cx);
 6740        e.paste(&Paste, window, cx);
 6741        e.handle_input(") ", window, cx);
 6742    });
 6743    cx.assert_editor_state(
 6744        &([
 6745            "( one✅ ",
 6746            "three ",
 6747            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6748            "three ",
 6749            "five ) ˇ",
 6750        ]
 6751        .join("\n")),
 6752    );
 6753
 6754    // Cut with three selections, one of which is full-line.
 6755    cx.set_state(indoc! {"
 6756        1«2ˇ»3
 6757        4ˇ567
 6758        «8ˇ»9"});
 6759    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6760    cx.assert_editor_state(indoc! {"
 6761        1ˇ3
 6762        ˇ9"});
 6763
 6764    // Paste with three selections, noticing how the copied selection that was full-line
 6765    // gets inserted before the second cursor.
 6766    cx.set_state(indoc! {"
 6767        1ˇ3
 6768 6769        «oˇ»ne"});
 6770    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6771    cx.assert_editor_state(indoc! {"
 6772        12ˇ3
 6773        4567
 6774 6775        8ˇne"});
 6776
 6777    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6778    cx.set_state(indoc! {"
 6779        The quick brown
 6780        fox juˇmps over
 6781        the lazy dog"});
 6782    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6783    assert_eq!(
 6784        cx.read_from_clipboard()
 6785            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6786        Some("fox jumps over\n".to_string())
 6787    );
 6788
 6789    // Paste with three selections, noticing how the copied full-line selection is inserted
 6790    // before the empty selections but replaces the selection that is non-empty.
 6791    cx.set_state(indoc! {"
 6792        Tˇhe quick brown
 6793        «foˇ»x jumps over
 6794        tˇhe lazy dog"});
 6795    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6796    cx.assert_editor_state(indoc! {"
 6797        fox jumps over
 6798        Tˇhe quick brown
 6799        fox jumps over
 6800        ˇx jumps over
 6801        fox jumps over
 6802        tˇhe lazy dog"});
 6803}
 6804
 6805#[gpui::test]
 6806async fn test_copy_trim(cx: &mut TestAppContext) {
 6807    init_test(cx, |_| {});
 6808
 6809    let mut cx = EditorTestContext::new(cx).await;
 6810    cx.set_state(
 6811        r#"            «for selection in selections.iter() {
 6812            let mut start = selection.start;
 6813            let mut end = selection.end;
 6814            let is_entire_line = selection.is_empty();
 6815            if is_entire_line {
 6816                start = Point::new(start.row, 0);ˇ»
 6817                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6818            }
 6819        "#,
 6820    );
 6821    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6822    assert_eq!(
 6823        cx.read_from_clipboard()
 6824            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6825        Some(
 6826            "for selection in selections.iter() {
 6827            let mut start = selection.start;
 6828            let mut end = selection.end;
 6829            let is_entire_line = selection.is_empty();
 6830            if is_entire_line {
 6831                start = Point::new(start.row, 0);"
 6832                .to_string()
 6833        ),
 6834        "Regular copying preserves all indentation selected",
 6835    );
 6836    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6837    assert_eq!(
 6838        cx.read_from_clipboard()
 6839            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6840        Some(
 6841            "for selection in selections.iter() {
 6842let mut start = selection.start;
 6843let mut end = selection.end;
 6844let is_entire_line = selection.is_empty();
 6845if is_entire_line {
 6846    start = Point::new(start.row, 0);"
 6847                .to_string()
 6848        ),
 6849        "Copying with stripping should strip all leading whitespaces"
 6850    );
 6851
 6852    cx.set_state(
 6853        r#"       «     for selection in selections.iter() {
 6854            let mut start = selection.start;
 6855            let mut end = selection.end;
 6856            let is_entire_line = selection.is_empty();
 6857            if is_entire_line {
 6858                start = Point::new(start.row, 0);ˇ»
 6859                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6860            }
 6861        "#,
 6862    );
 6863    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6864    assert_eq!(
 6865        cx.read_from_clipboard()
 6866            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6867        Some(
 6868            "     for selection in selections.iter() {
 6869            let mut start = selection.start;
 6870            let mut end = selection.end;
 6871            let is_entire_line = selection.is_empty();
 6872            if is_entire_line {
 6873                start = Point::new(start.row, 0);"
 6874                .to_string()
 6875        ),
 6876        "Regular copying preserves all indentation selected",
 6877    );
 6878    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6879    assert_eq!(
 6880        cx.read_from_clipboard()
 6881            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6882        Some(
 6883            "for selection in selections.iter() {
 6884let mut start = selection.start;
 6885let mut end = selection.end;
 6886let is_entire_line = selection.is_empty();
 6887if is_entire_line {
 6888    start = Point::new(start.row, 0);"
 6889                .to_string()
 6890        ),
 6891        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6892    );
 6893
 6894    cx.set_state(
 6895        r#"       «ˇ     for selection in selections.iter() {
 6896            let mut start = selection.start;
 6897            let mut end = selection.end;
 6898            let is_entire_line = selection.is_empty();
 6899            if is_entire_line {
 6900                start = Point::new(start.row, 0);»
 6901                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6902            }
 6903        "#,
 6904    );
 6905    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6906    assert_eq!(
 6907        cx.read_from_clipboard()
 6908            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6909        Some(
 6910            "     for selection in selections.iter() {
 6911            let mut start = selection.start;
 6912            let mut end = selection.end;
 6913            let is_entire_line = selection.is_empty();
 6914            if is_entire_line {
 6915                start = Point::new(start.row, 0);"
 6916                .to_string()
 6917        ),
 6918        "Regular copying for reverse selection works the same",
 6919    );
 6920    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6921    assert_eq!(
 6922        cx.read_from_clipboard()
 6923            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6924        Some(
 6925            "for selection in selections.iter() {
 6926let mut start = selection.start;
 6927let mut end = selection.end;
 6928let is_entire_line = selection.is_empty();
 6929if is_entire_line {
 6930    start = Point::new(start.row, 0);"
 6931                .to_string()
 6932        ),
 6933        "Copying with stripping for reverse selection works the same"
 6934    );
 6935
 6936    cx.set_state(
 6937        r#"            for selection «in selections.iter() {
 6938            let mut start = selection.start;
 6939            let mut end = selection.end;
 6940            let is_entire_line = selection.is_empty();
 6941            if is_entire_line {
 6942                start = Point::new(start.row, 0);ˇ»
 6943                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6944            }
 6945        "#,
 6946    );
 6947    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6948    assert_eq!(
 6949        cx.read_from_clipboard()
 6950            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6951        Some(
 6952            "in selections.iter() {
 6953            let mut start = selection.start;
 6954            let mut end = selection.end;
 6955            let is_entire_line = selection.is_empty();
 6956            if is_entire_line {
 6957                start = Point::new(start.row, 0);"
 6958                .to_string()
 6959        ),
 6960        "When selecting past the indent, the copying works as usual",
 6961    );
 6962    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6963    assert_eq!(
 6964        cx.read_from_clipboard()
 6965            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6966        Some(
 6967            "in selections.iter() {
 6968            let mut start = selection.start;
 6969            let mut end = selection.end;
 6970            let is_entire_line = selection.is_empty();
 6971            if is_entire_line {
 6972                start = Point::new(start.row, 0);"
 6973                .to_string()
 6974        ),
 6975        "When selecting past the indent, nothing is trimmed"
 6976    );
 6977
 6978    cx.set_state(
 6979        r#"            «for selection in selections.iter() {
 6980            let mut start = selection.start;
 6981
 6982            let mut end = selection.end;
 6983            let is_entire_line = selection.is_empty();
 6984            if is_entire_line {
 6985                start = Point::new(start.row, 0);
 6986ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6987            }
 6988        "#,
 6989    );
 6990    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6991    assert_eq!(
 6992        cx.read_from_clipboard()
 6993            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6994        Some(
 6995            "for selection in selections.iter() {
 6996let mut start = selection.start;
 6997
 6998let mut end = selection.end;
 6999let is_entire_line = selection.is_empty();
 7000if is_entire_line {
 7001    start = Point::new(start.row, 0);
 7002"
 7003            .to_string()
 7004        ),
 7005        "Copying with stripping should ignore empty lines"
 7006    );
 7007}
 7008
 7009#[gpui::test]
 7010async fn test_paste_multiline(cx: &mut TestAppContext) {
 7011    init_test(cx, |_| {});
 7012
 7013    let mut cx = EditorTestContext::new(cx).await;
 7014    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7015
 7016    // Cut an indented block, without the leading whitespace.
 7017    cx.set_state(indoc! {"
 7018        const a: B = (
 7019            c(),
 7020            «d(
 7021                e,
 7022                f
 7023            )ˇ»
 7024        );
 7025    "});
 7026    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7027    cx.assert_editor_state(indoc! {"
 7028        const a: B = (
 7029            c(),
 7030            ˇ
 7031        );
 7032    "});
 7033
 7034    // Paste it at the same position.
 7035    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7036    cx.assert_editor_state(indoc! {"
 7037        const a: B = (
 7038            c(),
 7039            d(
 7040                e,
 7041                f
 7042 7043        );
 7044    "});
 7045
 7046    // Paste it at a line with a lower indent level.
 7047    cx.set_state(indoc! {"
 7048        ˇ
 7049        const a: B = (
 7050            c(),
 7051        );
 7052    "});
 7053    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7054    cx.assert_editor_state(indoc! {"
 7055        d(
 7056            e,
 7057            f
 7058 7059        const a: B = (
 7060            c(),
 7061        );
 7062    "});
 7063
 7064    // Cut an indented block, with the leading whitespace.
 7065    cx.set_state(indoc! {"
 7066        const a: B = (
 7067            c(),
 7068        «    d(
 7069                e,
 7070                f
 7071            )
 7072        ˇ»);
 7073    "});
 7074    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7075    cx.assert_editor_state(indoc! {"
 7076        const a: B = (
 7077            c(),
 7078        ˇ);
 7079    "});
 7080
 7081    // Paste it at the same position.
 7082    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7083    cx.assert_editor_state(indoc! {"
 7084        const a: B = (
 7085            c(),
 7086            d(
 7087                e,
 7088                f
 7089            )
 7090        ˇ);
 7091    "});
 7092
 7093    // Paste it at a line with a higher indent level.
 7094    cx.set_state(indoc! {"
 7095        const a: B = (
 7096            c(),
 7097            d(
 7098                e,
 7099 7100            )
 7101        );
 7102    "});
 7103    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7104    cx.assert_editor_state(indoc! {"
 7105        const a: B = (
 7106            c(),
 7107            d(
 7108                e,
 7109                f    d(
 7110                    e,
 7111                    f
 7112                )
 7113        ˇ
 7114            )
 7115        );
 7116    "});
 7117
 7118    // Copy an indented block, starting mid-line
 7119    cx.set_state(indoc! {"
 7120        const a: B = (
 7121            c(),
 7122            somethin«g(
 7123                e,
 7124                f
 7125            )ˇ»
 7126        );
 7127    "});
 7128    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7129
 7130    // Paste it on a line with a lower indent level
 7131    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7132    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7133    cx.assert_editor_state(indoc! {"
 7134        const a: B = (
 7135            c(),
 7136            something(
 7137                e,
 7138                f
 7139            )
 7140        );
 7141        g(
 7142            e,
 7143            f
 7144"});
 7145}
 7146
 7147#[gpui::test]
 7148async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7149    init_test(cx, |_| {});
 7150
 7151    cx.write_to_clipboard(ClipboardItem::new_string(
 7152        "    d(\n        e\n    );\n".into(),
 7153    ));
 7154
 7155    let mut cx = EditorTestContext::new(cx).await;
 7156    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7157
 7158    cx.set_state(indoc! {"
 7159        fn a() {
 7160            b();
 7161            if c() {
 7162                ˇ
 7163            }
 7164        }
 7165    "});
 7166
 7167    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7168    cx.assert_editor_state(indoc! {"
 7169        fn a() {
 7170            b();
 7171            if c() {
 7172                d(
 7173                    e
 7174                );
 7175        ˇ
 7176            }
 7177        }
 7178    "});
 7179
 7180    cx.set_state(indoc! {"
 7181        fn a() {
 7182            b();
 7183            ˇ
 7184        }
 7185    "});
 7186
 7187    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7188    cx.assert_editor_state(indoc! {"
 7189        fn a() {
 7190            b();
 7191            d(
 7192                e
 7193            );
 7194        ˇ
 7195        }
 7196    "});
 7197}
 7198
 7199#[gpui::test]
 7200fn test_select_all(cx: &mut TestAppContext) {
 7201    init_test(cx, |_| {});
 7202
 7203    let editor = cx.add_window(|window, cx| {
 7204        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7205        build_editor(buffer, window, cx)
 7206    });
 7207    _ = editor.update(cx, |editor, window, cx| {
 7208        editor.select_all(&SelectAll, window, cx);
 7209        assert_eq!(
 7210            editor.selections.display_ranges(cx),
 7211            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7212        );
 7213    });
 7214}
 7215
 7216#[gpui::test]
 7217fn test_select_line(cx: &mut TestAppContext) {
 7218    init_test(cx, |_| {});
 7219
 7220    let editor = cx.add_window(|window, cx| {
 7221        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7222        build_editor(buffer, window, cx)
 7223    });
 7224    _ = editor.update(cx, |editor, window, cx| {
 7225        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7226            s.select_display_ranges([
 7227                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7228                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7229                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7230                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7231            ])
 7232        });
 7233        editor.select_line(&SelectLine, window, cx);
 7234        assert_eq!(
 7235            editor.selections.display_ranges(cx),
 7236            vec![
 7237                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7238                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7239            ]
 7240        );
 7241    });
 7242
 7243    _ = editor.update(cx, |editor, window, cx| {
 7244        editor.select_line(&SelectLine, window, cx);
 7245        assert_eq!(
 7246            editor.selections.display_ranges(cx),
 7247            vec![
 7248                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7249                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7250            ]
 7251        );
 7252    });
 7253
 7254    _ = editor.update(cx, |editor, window, cx| {
 7255        editor.select_line(&SelectLine, window, cx);
 7256        assert_eq!(
 7257            editor.selections.display_ranges(cx),
 7258            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7259        );
 7260    });
 7261}
 7262
 7263#[gpui::test]
 7264async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7265    init_test(cx, |_| {});
 7266    let mut cx = EditorTestContext::new(cx).await;
 7267
 7268    #[track_caller]
 7269    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7270        cx.set_state(initial_state);
 7271        cx.update_editor(|e, window, cx| {
 7272            e.split_selection_into_lines(&Default::default(), window, cx)
 7273        });
 7274        cx.assert_editor_state(expected_state);
 7275    }
 7276
 7277    // Selection starts and ends at the middle of lines, left-to-right
 7278    test(
 7279        &mut cx,
 7280        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7281        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7282    );
 7283    // Same thing, right-to-left
 7284    test(
 7285        &mut cx,
 7286        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7287        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7288    );
 7289
 7290    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7291    test(
 7292        &mut cx,
 7293        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7294        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7295    );
 7296    // Same thing, right-to-left
 7297    test(
 7298        &mut cx,
 7299        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7300        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7301    );
 7302
 7303    // Whole buffer, left-to-right, last line ends with newline
 7304    test(
 7305        &mut cx,
 7306        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7307        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7308    );
 7309    // Same thing, right-to-left
 7310    test(
 7311        &mut cx,
 7312        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7313        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7314    );
 7315
 7316    // Starts at the end of a line, ends at the start of another
 7317    test(
 7318        &mut cx,
 7319        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7320        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7321    );
 7322}
 7323
 7324#[gpui::test]
 7325async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7326    init_test(cx, |_| {});
 7327
 7328    let editor = cx.add_window(|window, cx| {
 7329        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7330        build_editor(buffer, window, cx)
 7331    });
 7332
 7333    // setup
 7334    _ = editor.update(cx, |editor, window, cx| {
 7335        editor.fold_creases(
 7336            vec![
 7337                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7338                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7339                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7340            ],
 7341            true,
 7342            window,
 7343            cx,
 7344        );
 7345        assert_eq!(
 7346            editor.display_text(cx),
 7347            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7348        );
 7349    });
 7350
 7351    _ = editor.update(cx, |editor, window, cx| {
 7352        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7353            s.select_display_ranges([
 7354                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7355                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7356                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7357                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7358            ])
 7359        });
 7360        editor.split_selection_into_lines(&Default::default(), window, cx);
 7361        assert_eq!(
 7362            editor.display_text(cx),
 7363            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7364        );
 7365    });
 7366    EditorTestContext::for_editor(editor, cx)
 7367        .await
 7368        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7369
 7370    _ = editor.update(cx, |editor, window, cx| {
 7371        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7372            s.select_display_ranges([
 7373                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7374            ])
 7375        });
 7376        editor.split_selection_into_lines(&Default::default(), window, cx);
 7377        assert_eq!(
 7378            editor.display_text(cx),
 7379            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7380        );
 7381        assert_eq!(
 7382            editor.selections.display_ranges(cx),
 7383            [
 7384                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7385                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7386                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7387                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7388                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7389                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7390                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7391            ]
 7392        );
 7393    });
 7394    EditorTestContext::for_editor(editor, cx)
 7395        .await
 7396        .assert_editor_state(
 7397            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7398        );
 7399}
 7400
 7401#[gpui::test]
 7402async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7403    init_test(cx, |_| {});
 7404
 7405    let mut cx = EditorTestContext::new(cx).await;
 7406
 7407    cx.set_state(indoc!(
 7408        r#"abc
 7409           defˇghi
 7410
 7411           jk
 7412           nlmo
 7413           "#
 7414    ));
 7415
 7416    cx.update_editor(|editor, window, cx| {
 7417        editor.add_selection_above(&Default::default(), window, cx);
 7418    });
 7419
 7420    cx.assert_editor_state(indoc!(
 7421        r#"abcˇ
 7422           defˇghi
 7423
 7424           jk
 7425           nlmo
 7426           "#
 7427    ));
 7428
 7429    cx.update_editor(|editor, window, cx| {
 7430        editor.add_selection_above(&Default::default(), window, cx);
 7431    });
 7432
 7433    cx.assert_editor_state(indoc!(
 7434        r#"abcˇ
 7435            defˇghi
 7436
 7437            jk
 7438            nlmo
 7439            "#
 7440    ));
 7441
 7442    cx.update_editor(|editor, window, cx| {
 7443        editor.add_selection_below(&Default::default(), window, cx);
 7444    });
 7445
 7446    cx.assert_editor_state(indoc!(
 7447        r#"abc
 7448           defˇghi
 7449
 7450           jk
 7451           nlmo
 7452           "#
 7453    ));
 7454
 7455    cx.update_editor(|editor, window, cx| {
 7456        editor.undo_selection(&Default::default(), window, cx);
 7457    });
 7458
 7459    cx.assert_editor_state(indoc!(
 7460        r#"abcˇ
 7461           defˇghi
 7462
 7463           jk
 7464           nlmo
 7465           "#
 7466    ));
 7467
 7468    cx.update_editor(|editor, window, cx| {
 7469        editor.redo_selection(&Default::default(), window, cx);
 7470    });
 7471
 7472    cx.assert_editor_state(indoc!(
 7473        r#"abc
 7474           defˇghi
 7475
 7476           jk
 7477           nlmo
 7478           "#
 7479    ));
 7480
 7481    cx.update_editor(|editor, window, cx| {
 7482        editor.add_selection_below(&Default::default(), window, cx);
 7483    });
 7484
 7485    cx.assert_editor_state(indoc!(
 7486        r#"abc
 7487           defˇghi
 7488           ˇ
 7489           jk
 7490           nlmo
 7491           "#
 7492    ));
 7493
 7494    cx.update_editor(|editor, window, cx| {
 7495        editor.add_selection_below(&Default::default(), window, cx);
 7496    });
 7497
 7498    cx.assert_editor_state(indoc!(
 7499        r#"abc
 7500           defˇghi
 7501           ˇ
 7502           jkˇ
 7503           nlmo
 7504           "#
 7505    ));
 7506
 7507    cx.update_editor(|editor, window, cx| {
 7508        editor.add_selection_below(&Default::default(), window, cx);
 7509    });
 7510
 7511    cx.assert_editor_state(indoc!(
 7512        r#"abc
 7513           defˇghi
 7514           ˇ
 7515           jkˇ
 7516           nlmˇo
 7517           "#
 7518    ));
 7519
 7520    cx.update_editor(|editor, window, cx| {
 7521        editor.add_selection_below(&Default::default(), window, cx);
 7522    });
 7523
 7524    cx.assert_editor_state(indoc!(
 7525        r#"abc
 7526           defˇghi
 7527           ˇ
 7528           jkˇ
 7529           nlmˇo
 7530           ˇ"#
 7531    ));
 7532
 7533    // change selections
 7534    cx.set_state(indoc!(
 7535        r#"abc
 7536           def«ˇg»hi
 7537
 7538           jk
 7539           nlmo
 7540           "#
 7541    ));
 7542
 7543    cx.update_editor(|editor, window, cx| {
 7544        editor.add_selection_below(&Default::default(), window, cx);
 7545    });
 7546
 7547    cx.assert_editor_state(indoc!(
 7548        r#"abc
 7549           def«ˇg»hi
 7550
 7551           jk
 7552           nlm«ˇo»
 7553           "#
 7554    ));
 7555
 7556    cx.update_editor(|editor, window, cx| {
 7557        editor.add_selection_below(&Default::default(), window, cx);
 7558    });
 7559
 7560    cx.assert_editor_state(indoc!(
 7561        r#"abc
 7562           def«ˇg»hi
 7563
 7564           jk
 7565           nlm«ˇo»
 7566           "#
 7567    ));
 7568
 7569    cx.update_editor(|editor, window, cx| {
 7570        editor.add_selection_above(&Default::default(), window, cx);
 7571    });
 7572
 7573    cx.assert_editor_state(indoc!(
 7574        r#"abc
 7575           def«ˇg»hi
 7576
 7577           jk
 7578           nlmo
 7579           "#
 7580    ));
 7581
 7582    cx.update_editor(|editor, window, cx| {
 7583        editor.add_selection_above(&Default::default(), window, cx);
 7584    });
 7585
 7586    cx.assert_editor_state(indoc!(
 7587        r#"abc
 7588           def«ˇg»hi
 7589
 7590           jk
 7591           nlmo
 7592           "#
 7593    ));
 7594
 7595    // Change selections again
 7596    cx.set_state(indoc!(
 7597        r#"a«bc
 7598           defgˇ»hi
 7599
 7600           jk
 7601           nlmo
 7602           "#
 7603    ));
 7604
 7605    cx.update_editor(|editor, window, cx| {
 7606        editor.add_selection_below(&Default::default(), window, cx);
 7607    });
 7608
 7609    cx.assert_editor_state(indoc!(
 7610        r#"a«bcˇ»
 7611           d«efgˇ»hi
 7612
 7613           j«kˇ»
 7614           nlmo
 7615           "#
 7616    ));
 7617
 7618    cx.update_editor(|editor, window, cx| {
 7619        editor.add_selection_below(&Default::default(), window, cx);
 7620    });
 7621    cx.assert_editor_state(indoc!(
 7622        r#"a«bcˇ»
 7623           d«efgˇ»hi
 7624
 7625           j«kˇ»
 7626           n«lmoˇ»
 7627           "#
 7628    ));
 7629    cx.update_editor(|editor, window, cx| {
 7630        editor.add_selection_above(&Default::default(), window, cx);
 7631    });
 7632
 7633    cx.assert_editor_state(indoc!(
 7634        r#"a«bcˇ»
 7635           d«efgˇ»hi
 7636
 7637           j«kˇ»
 7638           nlmo
 7639           "#
 7640    ));
 7641
 7642    // Change selections again
 7643    cx.set_state(indoc!(
 7644        r#"abc
 7645           d«ˇefghi
 7646
 7647           jk
 7648           nlm»o
 7649           "#
 7650    ));
 7651
 7652    cx.update_editor(|editor, window, cx| {
 7653        editor.add_selection_above(&Default::default(), window, cx);
 7654    });
 7655
 7656    cx.assert_editor_state(indoc!(
 7657        r#"a«ˇbc»
 7658           d«ˇef»ghi
 7659
 7660           j«ˇk»
 7661           n«ˇlm»o
 7662           "#
 7663    ));
 7664
 7665    cx.update_editor(|editor, window, cx| {
 7666        editor.add_selection_below(&Default::default(), window, cx);
 7667    });
 7668
 7669    cx.assert_editor_state(indoc!(
 7670        r#"abc
 7671           d«ˇef»ghi
 7672
 7673           j«ˇk»
 7674           n«ˇlm»o
 7675           "#
 7676    ));
 7677}
 7678
 7679#[gpui::test]
 7680async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7681    init_test(cx, |_| {});
 7682    let mut cx = EditorTestContext::new(cx).await;
 7683
 7684    cx.set_state(indoc!(
 7685        r#"line onˇe
 7686           liˇne two
 7687           line three
 7688           line four"#
 7689    ));
 7690
 7691    cx.update_editor(|editor, window, cx| {
 7692        editor.add_selection_below(&Default::default(), window, cx);
 7693    });
 7694
 7695    // test multiple cursors expand in the same direction
 7696    cx.assert_editor_state(indoc!(
 7697        r#"line onˇe
 7698           liˇne twˇo
 7699           liˇne three
 7700           line four"#
 7701    ));
 7702
 7703    cx.update_editor(|editor, window, cx| {
 7704        editor.add_selection_below(&Default::default(), window, cx);
 7705    });
 7706
 7707    cx.update_editor(|editor, window, cx| {
 7708        editor.add_selection_below(&Default::default(), window, cx);
 7709    });
 7710
 7711    // test multiple cursors expand below overflow
 7712    cx.assert_editor_state(indoc!(
 7713        r#"line onˇe
 7714           liˇne twˇo
 7715           liˇne thˇree
 7716           liˇne foˇur"#
 7717    ));
 7718
 7719    cx.update_editor(|editor, window, cx| {
 7720        editor.add_selection_above(&Default::default(), window, cx);
 7721    });
 7722
 7723    // test multiple cursors retrieves back correctly
 7724    cx.assert_editor_state(indoc!(
 7725        r#"line onˇe
 7726           liˇne twˇo
 7727           liˇne thˇree
 7728           line four"#
 7729    ));
 7730
 7731    cx.update_editor(|editor, window, cx| {
 7732        editor.add_selection_above(&Default::default(), window, cx);
 7733    });
 7734
 7735    cx.update_editor(|editor, window, cx| {
 7736        editor.add_selection_above(&Default::default(), window, cx);
 7737    });
 7738
 7739    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7740    cx.assert_editor_state(indoc!(
 7741        r#"liˇne onˇe
 7742           liˇne two
 7743           line three
 7744           line four"#
 7745    ));
 7746
 7747    cx.update_editor(|editor, window, cx| {
 7748        editor.undo_selection(&Default::default(), window, cx);
 7749    });
 7750
 7751    // test undo
 7752    cx.assert_editor_state(indoc!(
 7753        r#"line onˇe
 7754           liˇne twˇo
 7755           line three
 7756           line four"#
 7757    ));
 7758
 7759    cx.update_editor(|editor, window, cx| {
 7760        editor.redo_selection(&Default::default(), window, cx);
 7761    });
 7762
 7763    // test redo
 7764    cx.assert_editor_state(indoc!(
 7765        r#"liˇne onˇe
 7766           liˇne two
 7767           line three
 7768           line four"#
 7769    ));
 7770
 7771    cx.set_state(indoc!(
 7772        r#"abcd
 7773           ef«ghˇ»
 7774           ijkl
 7775           «mˇ»nop"#
 7776    ));
 7777
 7778    cx.update_editor(|editor, window, cx| {
 7779        editor.add_selection_above(&Default::default(), window, cx);
 7780    });
 7781
 7782    // test multiple selections expand in the same direction
 7783    cx.assert_editor_state(indoc!(
 7784        r#"ab«cdˇ»
 7785           ef«ghˇ»
 7786           «iˇ»jkl
 7787           «mˇ»nop"#
 7788    ));
 7789
 7790    cx.update_editor(|editor, window, cx| {
 7791        editor.add_selection_above(&Default::default(), window, cx);
 7792    });
 7793
 7794    // test multiple selection upward overflow
 7795    cx.assert_editor_state(indoc!(
 7796        r#"ab«cdˇ»
 7797           «eˇ»f«ghˇ»
 7798           «iˇ»jkl
 7799           «mˇ»nop"#
 7800    ));
 7801
 7802    cx.update_editor(|editor, window, cx| {
 7803        editor.add_selection_below(&Default::default(), window, cx);
 7804    });
 7805
 7806    // test multiple selection retrieves back correctly
 7807    cx.assert_editor_state(indoc!(
 7808        r#"abcd
 7809           ef«ghˇ»
 7810           «iˇ»jkl
 7811           «mˇ»nop"#
 7812    ));
 7813
 7814    cx.update_editor(|editor, window, cx| {
 7815        editor.add_selection_below(&Default::default(), window, cx);
 7816    });
 7817
 7818    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7819    cx.assert_editor_state(indoc!(
 7820        r#"abcd
 7821           ef«ghˇ»
 7822           ij«klˇ»
 7823           «mˇ»nop"#
 7824    ));
 7825
 7826    cx.update_editor(|editor, window, cx| {
 7827        editor.undo_selection(&Default::default(), window, cx);
 7828    });
 7829
 7830    // test undo
 7831    cx.assert_editor_state(indoc!(
 7832        r#"abcd
 7833           ef«ghˇ»
 7834           «iˇ»jkl
 7835           «mˇ»nop"#
 7836    ));
 7837
 7838    cx.update_editor(|editor, window, cx| {
 7839        editor.redo_selection(&Default::default(), window, cx);
 7840    });
 7841
 7842    // test redo
 7843    cx.assert_editor_state(indoc!(
 7844        r#"abcd
 7845           ef«ghˇ»
 7846           ij«klˇ»
 7847           «mˇ»nop"#
 7848    ));
 7849}
 7850
 7851#[gpui::test]
 7852async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7853    init_test(cx, |_| {});
 7854    let mut cx = EditorTestContext::new(cx).await;
 7855
 7856    cx.set_state(indoc!(
 7857        r#"line onˇe
 7858           liˇne two
 7859           line three
 7860           line four"#
 7861    ));
 7862
 7863    cx.update_editor(|editor, window, cx| {
 7864        editor.add_selection_below(&Default::default(), window, cx);
 7865        editor.add_selection_below(&Default::default(), window, cx);
 7866        editor.add_selection_below(&Default::default(), window, cx);
 7867    });
 7868
 7869    // initial state with two multi cursor groups
 7870    cx.assert_editor_state(indoc!(
 7871        r#"line onˇe
 7872           liˇne twˇo
 7873           liˇne thˇree
 7874           liˇne foˇur"#
 7875    ));
 7876
 7877    // add single cursor in middle - simulate opt click
 7878    cx.update_editor(|editor, window, cx| {
 7879        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7880        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7881        editor.end_selection(window, cx);
 7882    });
 7883
 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    cx.update_editor(|editor, window, cx| {
 7892        editor.add_selection_above(&Default::default(), window, cx);
 7893    });
 7894
 7895    // test new added selection expands above and existing selection shrinks
 7896    cx.assert_editor_state(indoc!(
 7897        r#"line onˇe
 7898           liˇneˇ twˇo
 7899           liˇneˇ thˇree
 7900           line four"#
 7901    ));
 7902
 7903    cx.update_editor(|editor, window, cx| {
 7904        editor.add_selection_above(&Default::default(), window, cx);
 7905    });
 7906
 7907    // test new added selection expands above and existing selection shrinks
 7908    cx.assert_editor_state(indoc!(
 7909        r#"lineˇ onˇe
 7910           liˇneˇ twˇo
 7911           lineˇ three
 7912           line four"#
 7913    ));
 7914
 7915    // intial state with two selection groups
 7916    cx.set_state(indoc!(
 7917        r#"abcd
 7918           ef«ghˇ»
 7919           ijkl
 7920           «mˇ»nop"#
 7921    ));
 7922
 7923    cx.update_editor(|editor, window, cx| {
 7924        editor.add_selection_above(&Default::default(), window, cx);
 7925        editor.add_selection_above(&Default::default(), window, cx);
 7926    });
 7927
 7928    cx.assert_editor_state(indoc!(
 7929        r#"ab«cdˇ»
 7930           «eˇ»f«ghˇ»
 7931           «iˇ»jkl
 7932           «mˇ»nop"#
 7933    ));
 7934
 7935    // add single selection in middle - simulate opt drag
 7936    cx.update_editor(|editor, window, cx| {
 7937        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7938        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7939        editor.update_selection(
 7940            DisplayPoint::new(DisplayRow(2), 4),
 7941            0,
 7942            gpui::Point::<f32>::default(),
 7943            window,
 7944            cx,
 7945        );
 7946        editor.end_selection(window, cx);
 7947    });
 7948
 7949    cx.assert_editor_state(indoc!(
 7950        r#"ab«cdˇ»
 7951           «eˇ»f«ghˇ»
 7952           «iˇ»jk«lˇ»
 7953           «mˇ»nop"#
 7954    ));
 7955
 7956    cx.update_editor(|editor, window, cx| {
 7957        editor.add_selection_below(&Default::default(), window, cx);
 7958    });
 7959
 7960    // test new added selection expands below, others shrinks from above
 7961    cx.assert_editor_state(indoc!(
 7962        r#"abcd
 7963           ef«ghˇ»
 7964           «iˇ»jk«lˇ»
 7965           «mˇ»no«pˇ»"#
 7966    ));
 7967}
 7968
 7969#[gpui::test]
 7970async fn test_select_next(cx: &mut TestAppContext) {
 7971    init_test(cx, |_| {});
 7972
 7973    let mut cx = EditorTestContext::new(cx).await;
 7974    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7975
 7976    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7977        .unwrap();
 7978    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7979
 7980    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7981        .unwrap();
 7982    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7983
 7984    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7985    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7986
 7987    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7988    cx.assert_editor_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\n«abcˇ»");
 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\n«abcˇ»");
 7997
 7998    // Test selection direction should be preserved
 7999    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8000
 8001    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8002        .unwrap();
 8003    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8004}
 8005
 8006#[gpui::test]
 8007async fn test_select_all_matches(cx: &mut TestAppContext) {
 8008    init_test(cx, |_| {});
 8009
 8010    let mut cx = EditorTestContext::new(cx).await;
 8011
 8012    // Test caret-only selections
 8013    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8014    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8015        .unwrap();
 8016    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8017
 8018    // Test left-to-right selections
 8019    cx.set_state("abc\n«abcˇ»\nabc");
 8020    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8021        .unwrap();
 8022    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8023
 8024    // Test right-to-left selections
 8025    cx.set_state("abc\n«ˇabc»\nabc");
 8026    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8027        .unwrap();
 8028    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8029
 8030    // Test selecting whitespace with caret selection
 8031    cx.set_state("abc\nˇ   abc\nabc");
 8032    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8033        .unwrap();
 8034    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8035
 8036    // Test selecting whitespace with left-to-right selection
 8037    cx.set_state("abc\n«ˇ  »abc\nabc");
 8038    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8039        .unwrap();
 8040    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8041
 8042    // Test no matches with right-to-left selection
 8043    cx.set_state("abc\n«  ˇ»abc\nabc");
 8044    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8045        .unwrap();
 8046    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8047
 8048    // Test with a single word and clip_at_line_ends=true (#29823)
 8049    cx.set_state("aˇbc");
 8050    cx.update_editor(|e, window, cx| {
 8051        e.set_clip_at_line_ends(true, cx);
 8052        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8053        e.set_clip_at_line_ends(false, cx);
 8054    });
 8055    cx.assert_editor_state("«abcˇ»");
 8056}
 8057
 8058#[gpui::test]
 8059async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8060    init_test(cx, |_| {});
 8061
 8062    let mut cx = EditorTestContext::new(cx).await;
 8063
 8064    let large_body_1 = "\nd".repeat(200);
 8065    let large_body_2 = "\ne".repeat(200);
 8066
 8067    cx.set_state(&format!(
 8068        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8069    ));
 8070    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8071        let scroll_position = editor.scroll_position(cx);
 8072        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8073        scroll_position
 8074    });
 8075
 8076    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8077        .unwrap();
 8078    cx.assert_editor_state(&format!(
 8079        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8080    ));
 8081    let scroll_position_after_selection =
 8082        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8083    assert_eq!(
 8084        initial_scroll_position, scroll_position_after_selection,
 8085        "Scroll position should not change after selecting all matches"
 8086    );
 8087}
 8088
 8089#[gpui::test]
 8090async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8091    init_test(cx, |_| {});
 8092
 8093    let mut cx = EditorLspTestContext::new_rust(
 8094        lsp::ServerCapabilities {
 8095            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8096            ..Default::default()
 8097        },
 8098        cx,
 8099    )
 8100    .await;
 8101
 8102    cx.set_state(indoc! {"
 8103        line 1
 8104        line 2
 8105        linˇe 3
 8106        line 4
 8107        line 5
 8108    "});
 8109
 8110    // Make an edit
 8111    cx.update_editor(|editor, window, cx| {
 8112        editor.handle_input("X", window, cx);
 8113    });
 8114
 8115    // Move cursor to a different position
 8116    cx.update_editor(|editor, window, cx| {
 8117        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8118            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8119        });
 8120    });
 8121
 8122    cx.assert_editor_state(indoc! {"
 8123        line 1
 8124        line 2
 8125        linXe 3
 8126        line 4
 8127        liˇne 5
 8128    "});
 8129
 8130    cx.lsp
 8131        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8132            Ok(Some(vec![lsp::TextEdit::new(
 8133                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8134                "PREFIX ".to_string(),
 8135            )]))
 8136        });
 8137
 8138    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8139        .unwrap()
 8140        .await
 8141        .unwrap();
 8142
 8143    cx.assert_editor_state(indoc! {"
 8144        PREFIX line 1
 8145        line 2
 8146        linXe 3
 8147        line 4
 8148        liˇne 5
 8149    "});
 8150
 8151    // Undo formatting
 8152    cx.update_editor(|editor, window, cx| {
 8153        editor.undo(&Default::default(), window, cx);
 8154    });
 8155
 8156    // Verify cursor moved back to position after edit
 8157    cx.assert_editor_state(indoc! {"
 8158        line 1
 8159        line 2
 8160        linXˇe 3
 8161        line 4
 8162        line 5
 8163    "});
 8164}
 8165
 8166#[gpui::test]
 8167async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8168    init_test(cx, |_| {});
 8169
 8170    let mut cx = EditorTestContext::new(cx).await;
 8171
 8172    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8173    cx.update_editor(|editor, window, cx| {
 8174        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8175    });
 8176
 8177    cx.set_state(indoc! {"
 8178        line 1
 8179        line 2
 8180        linˇe 3
 8181        line 4
 8182        line 5
 8183        line 6
 8184        line 7
 8185        line 8
 8186        line 9
 8187        line 10
 8188    "});
 8189
 8190    let snapshot = cx.buffer_snapshot();
 8191    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8192
 8193    cx.update(|_, cx| {
 8194        provider.update(cx, |provider, _| {
 8195            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 8196                id: None,
 8197                edits: vec![(edit_position..edit_position, "X".into())],
 8198                edit_preview: None,
 8199            }))
 8200        })
 8201    });
 8202
 8203    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8204    cx.update_editor(|editor, window, cx| {
 8205        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8206    });
 8207
 8208    cx.assert_editor_state(indoc! {"
 8209        line 1
 8210        line 2
 8211        lineXˇ 3
 8212        line 4
 8213        line 5
 8214        line 6
 8215        line 7
 8216        line 8
 8217        line 9
 8218        line 10
 8219    "});
 8220
 8221    cx.update_editor(|editor, window, cx| {
 8222        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8223            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8224        });
 8225    });
 8226
 8227    cx.assert_editor_state(indoc! {"
 8228        line 1
 8229        line 2
 8230        lineX 3
 8231        line 4
 8232        line 5
 8233        line 6
 8234        line 7
 8235        line 8
 8236        line 9
 8237        liˇne 10
 8238    "});
 8239
 8240    cx.update_editor(|editor, window, cx| {
 8241        editor.undo(&Default::default(), window, cx);
 8242    });
 8243
 8244    cx.assert_editor_state(indoc! {"
 8245        line 1
 8246        line 2
 8247        lineˇ 3
 8248        line 4
 8249        line 5
 8250        line 6
 8251        line 7
 8252        line 8
 8253        line 9
 8254        line 10
 8255    "});
 8256}
 8257
 8258#[gpui::test]
 8259async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8260    init_test(cx, |_| {});
 8261
 8262    let mut cx = EditorTestContext::new(cx).await;
 8263    cx.set_state(
 8264        r#"let foo = 2;
 8265lˇet foo = 2;
 8266let fooˇ = 2;
 8267let foo = 2;
 8268let foo = ˇ2;"#,
 8269    );
 8270
 8271    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8272        .unwrap();
 8273    cx.assert_editor_state(
 8274        r#"let foo = 2;
 8275«letˇ» foo = 2;
 8276let «fooˇ» = 2;
 8277let foo = 2;
 8278let foo = «2ˇ»;"#,
 8279    );
 8280
 8281    // noop for multiple selections with different contents
 8282    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8283        .unwrap();
 8284    cx.assert_editor_state(
 8285        r#"let foo = 2;
 8286«letˇ» foo = 2;
 8287let «fooˇ» = 2;
 8288let foo = 2;
 8289let foo = «2ˇ»;"#,
 8290    );
 8291
 8292    // Test last selection direction should be preserved
 8293    cx.set_state(
 8294        r#"let foo = 2;
 8295let foo = 2;
 8296let «fooˇ» = 2;
 8297let «ˇfoo» = 2;
 8298let foo = 2;"#,
 8299    );
 8300
 8301    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8302        .unwrap();
 8303    cx.assert_editor_state(
 8304        r#"let foo = 2;
 8305let foo = 2;
 8306let «fooˇ» = 2;
 8307let «ˇfoo» = 2;
 8308let «ˇfoo» = 2;"#,
 8309    );
 8310}
 8311
 8312#[gpui::test]
 8313async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8314    init_test(cx, |_| {});
 8315
 8316    let mut cx =
 8317        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8318
 8319    cx.assert_editor_state(indoc! {"
 8320        ˇbbb
 8321        ccc
 8322
 8323        bbb
 8324        ccc
 8325        "});
 8326    cx.dispatch_action(SelectPrevious::default());
 8327    cx.assert_editor_state(indoc! {"
 8328                «bbbˇ»
 8329                ccc
 8330
 8331                bbb
 8332                ccc
 8333                "});
 8334    cx.dispatch_action(SelectPrevious::default());
 8335    cx.assert_editor_state(indoc! {"
 8336                «bbbˇ»
 8337                ccc
 8338
 8339                «bbbˇ»
 8340                ccc
 8341                "});
 8342}
 8343
 8344#[gpui::test]
 8345async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8346    init_test(cx, |_| {});
 8347
 8348    let mut cx = EditorTestContext::new(cx).await;
 8349    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8350
 8351    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8352        .unwrap();
 8353    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8354
 8355    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8356        .unwrap();
 8357    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8358
 8359    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8360    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8361
 8362    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8363    cx.assert_editor_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\n«abcˇ»");
 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\n«abcˇ»");
 8372}
 8373
 8374#[gpui::test]
 8375async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8376    init_test(cx, |_| {});
 8377
 8378    let mut cx = EditorTestContext::new(cx).await;
 8379    cx.set_state("");
 8380
 8381    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8382        .unwrap();
 8383    cx.assert_editor_state("«aˇ»");
 8384    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8385        .unwrap();
 8386    cx.assert_editor_state("«aˇ»");
 8387}
 8388
 8389#[gpui::test]
 8390async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8391    init_test(cx, |_| {});
 8392
 8393    let mut cx = EditorTestContext::new(cx).await;
 8394    cx.set_state(
 8395        r#"let foo = 2;
 8396lˇet foo = 2;
 8397let fooˇ = 2;
 8398let foo = 2;
 8399let foo = ˇ2;"#,
 8400    );
 8401
 8402    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8403        .unwrap();
 8404    cx.assert_editor_state(
 8405        r#"let foo = 2;
 8406«letˇ» foo = 2;
 8407let «fooˇ» = 2;
 8408let foo = 2;
 8409let foo = «2ˇ»;"#,
 8410    );
 8411
 8412    // noop for multiple selections with different contents
 8413    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8414        .unwrap();
 8415    cx.assert_editor_state(
 8416        r#"let foo = 2;
 8417«letˇ» foo = 2;
 8418let «fooˇ» = 2;
 8419let foo = 2;
 8420let foo = «2ˇ»;"#,
 8421    );
 8422}
 8423
 8424#[gpui::test]
 8425async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8426    init_test(cx, |_| {});
 8427
 8428    let mut cx = EditorTestContext::new(cx).await;
 8429    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8430
 8431    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8432        .unwrap();
 8433    // selection direction is preserved
 8434    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8435
 8436    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8437        .unwrap();
 8438    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8439
 8440    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8441    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8442
 8443    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8444    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8445
 8446    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8447        .unwrap();
 8448    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 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»\ndef«ˇabc»\n«ˇabc»");
 8453}
 8454
 8455#[gpui::test]
 8456async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8457    init_test(cx, |_| {});
 8458
 8459    let language = Arc::new(Language::new(
 8460        LanguageConfig::default(),
 8461        Some(tree_sitter_rust::LANGUAGE.into()),
 8462    ));
 8463
 8464    let text = r#"
 8465        use mod1::mod2::{mod3, mod4};
 8466
 8467        fn fn_1(param1: bool, param2: &str) {
 8468            let var1 = "text";
 8469        }
 8470    "#
 8471    .unindent();
 8472
 8473    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8474    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8475    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8476
 8477    editor
 8478        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8479        .await;
 8480
 8481    editor.update_in(cx, |editor, window, cx| {
 8482        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8483            s.select_display_ranges([
 8484                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8485                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8486                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8487            ]);
 8488        });
 8489        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8490    });
 8491    editor.update(cx, |editor, cx| {
 8492        assert_text_with_selections(
 8493            editor,
 8494            indoc! {r#"
 8495                use mod1::mod2::{mod3, «mod4ˇ»};
 8496
 8497                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8498                    let var1 = "«ˇtext»";
 8499                }
 8500            "#},
 8501            cx,
 8502        );
 8503    });
 8504
 8505    editor.update_in(cx, |editor, window, cx| {
 8506        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8507    });
 8508    editor.update(cx, |editor, cx| {
 8509        assert_text_with_selections(
 8510            editor,
 8511            indoc! {r#"
 8512                use mod1::mod2::«{mod3, mod4}ˇ»;
 8513
 8514                «ˇfn fn_1(param1: bool, param2: &str) {
 8515                    let var1 = "text";
 8516 8517            "#},
 8518            cx,
 8519        );
 8520    });
 8521
 8522    editor.update_in(cx, |editor, window, cx| {
 8523        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8524    });
 8525    assert_eq!(
 8526        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8527        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8528    );
 8529
 8530    // Trying to expand the selected syntax node one more time has no effect.
 8531    editor.update_in(cx, |editor, window, cx| {
 8532        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8533    });
 8534    assert_eq!(
 8535        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8536        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8537    );
 8538
 8539    editor.update_in(cx, |editor, window, cx| {
 8540        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8541    });
 8542    editor.update(cx, |editor, cx| {
 8543        assert_text_with_selections(
 8544            editor,
 8545            indoc! {r#"
 8546                use mod1::mod2::«{mod3, mod4}ˇ»;
 8547
 8548                «ˇfn fn_1(param1: bool, param2: &str) {
 8549                    let var1 = "text";
 8550 8551            "#},
 8552            cx,
 8553        );
 8554    });
 8555
 8556    editor.update_in(cx, |editor, window, cx| {
 8557        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8558    });
 8559    editor.update(cx, |editor, cx| {
 8560        assert_text_with_selections(
 8561            editor,
 8562            indoc! {r#"
 8563                use mod1::mod2::{mod3, «mod4ˇ»};
 8564
 8565                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8566                    let var1 = "«ˇtext»";
 8567                }
 8568            "#},
 8569            cx,
 8570        );
 8571    });
 8572
 8573    editor.update_in(cx, |editor, window, cx| {
 8574        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8575    });
 8576    editor.update(cx, |editor, cx| {
 8577        assert_text_with_selections(
 8578            editor,
 8579            indoc! {r#"
 8580                use mod1::mod2::{mod3, mo«ˇ»d4};
 8581
 8582                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8583                    let var1 = "te«ˇ»xt";
 8584                }
 8585            "#},
 8586            cx,
 8587        );
 8588    });
 8589
 8590    // Trying to shrink the selected syntax node one more time has no effect.
 8591    editor.update_in(cx, |editor, window, cx| {
 8592        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8593    });
 8594    editor.update_in(cx, |editor, _, cx| {
 8595        assert_text_with_selections(
 8596            editor,
 8597            indoc! {r#"
 8598                use mod1::mod2::{mod3, mo«ˇ»d4};
 8599
 8600                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8601                    let var1 = "te«ˇ»xt";
 8602                }
 8603            "#},
 8604            cx,
 8605        );
 8606    });
 8607
 8608    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8609    // a fold.
 8610    editor.update_in(cx, |editor, window, cx| {
 8611        editor.fold_creases(
 8612            vec![
 8613                Crease::simple(
 8614                    Point::new(0, 21)..Point::new(0, 24),
 8615                    FoldPlaceholder::test(),
 8616                ),
 8617                Crease::simple(
 8618                    Point::new(3, 20)..Point::new(3, 22),
 8619                    FoldPlaceholder::test(),
 8620                ),
 8621            ],
 8622            true,
 8623            window,
 8624            cx,
 8625        );
 8626        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8627    });
 8628    editor.update(cx, |editor, cx| {
 8629        assert_text_with_selections(
 8630            editor,
 8631            indoc! {r#"
 8632                use mod1::mod2::«{mod3, mod4}ˇ»;
 8633
 8634                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8635                    let var1 = "«ˇtext»";
 8636                }
 8637            "#},
 8638            cx,
 8639        );
 8640    });
 8641}
 8642
 8643#[gpui::test]
 8644async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8645    init_test(cx, |_| {});
 8646
 8647    let language = Arc::new(Language::new(
 8648        LanguageConfig::default(),
 8649        Some(tree_sitter_rust::LANGUAGE.into()),
 8650    ));
 8651
 8652    let text = "let a = 2;";
 8653
 8654    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8655    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8656    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8657
 8658    editor
 8659        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8660        .await;
 8661
 8662    // Test case 1: Cursor at end of word
 8663    editor.update_in(cx, |editor, window, cx| {
 8664        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8665            s.select_display_ranges([
 8666                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8667            ]);
 8668        });
 8669    });
 8670    editor.update(cx, |editor, cx| {
 8671        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8672    });
 8673    editor.update_in(cx, |editor, window, cx| {
 8674        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8675    });
 8676    editor.update(cx, |editor, cx| {
 8677        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8678    });
 8679    editor.update_in(cx, |editor, window, cx| {
 8680        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8681    });
 8682    editor.update(cx, |editor, cx| {
 8683        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8684    });
 8685
 8686    // Test case 2: Cursor at end of statement
 8687    editor.update_in(cx, |editor, window, cx| {
 8688        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8689            s.select_display_ranges([
 8690                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8691            ]);
 8692        });
 8693    });
 8694    editor.update(cx, |editor, cx| {
 8695        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8696    });
 8697    editor.update_in(cx, |editor, window, cx| {
 8698        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8699    });
 8700    editor.update(cx, |editor, cx| {
 8701        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8702    });
 8703}
 8704
 8705#[gpui::test]
 8706async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8707    init_test(cx, |_| {});
 8708
 8709    let language = Arc::new(Language::new(
 8710        LanguageConfig::default(),
 8711        Some(tree_sitter_rust::LANGUAGE.into()),
 8712    ));
 8713
 8714    let text = r#"
 8715        use mod1::mod2::{mod3, mod4};
 8716
 8717        fn fn_1(param1: bool, param2: &str) {
 8718            let var1 = "hello world";
 8719        }
 8720    "#
 8721    .unindent();
 8722
 8723    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8724    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8725    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8726
 8727    editor
 8728        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8729        .await;
 8730
 8731    // Test 1: Cursor on a letter of a string word
 8732    editor.update_in(cx, |editor, window, cx| {
 8733        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8734            s.select_display_ranges([
 8735                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8736            ]);
 8737        });
 8738    });
 8739    editor.update_in(cx, |editor, window, cx| {
 8740        assert_text_with_selections(
 8741            editor,
 8742            indoc! {r#"
 8743                use mod1::mod2::{mod3, mod4};
 8744
 8745                fn fn_1(param1: bool, param2: &str) {
 8746                    let var1 = "hˇello world";
 8747                }
 8748            "#},
 8749            cx,
 8750        );
 8751        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8752        assert_text_with_selections(
 8753            editor,
 8754            indoc! {r#"
 8755                use mod1::mod2::{mod3, mod4};
 8756
 8757                fn fn_1(param1: bool, param2: &str) {
 8758                    let var1 = "«ˇhello» world";
 8759                }
 8760            "#},
 8761            cx,
 8762        );
 8763    });
 8764
 8765    // Test 2: Partial selection within a word
 8766    editor.update_in(cx, |editor, window, cx| {
 8767        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8768            s.select_display_ranges([
 8769                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 8770            ]);
 8771        });
 8772    });
 8773    editor.update_in(cx, |editor, window, cx| {
 8774        assert_text_with_selections(
 8775            editor,
 8776            indoc! {r#"
 8777                use mod1::mod2::{mod3, mod4};
 8778
 8779                fn fn_1(param1: bool, param2: &str) {
 8780                    let var1 = "h«elˇ»lo world";
 8781                }
 8782            "#},
 8783            cx,
 8784        );
 8785        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8786        assert_text_with_selections(
 8787            editor,
 8788            indoc! {r#"
 8789                use mod1::mod2::{mod3, mod4};
 8790
 8791                fn fn_1(param1: bool, param2: &str) {
 8792                    let var1 = "«ˇhello» world";
 8793                }
 8794            "#},
 8795            cx,
 8796        );
 8797    });
 8798
 8799    // Test 3: Complete word already selected
 8800    editor.update_in(cx, |editor, window, cx| {
 8801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8802            s.select_display_ranges([
 8803                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 8804            ]);
 8805        });
 8806    });
 8807    editor.update_in(cx, |editor, window, cx| {
 8808        assert_text_with_selections(
 8809            editor,
 8810            indoc! {r#"
 8811                use mod1::mod2::{mod3, mod4};
 8812
 8813                fn fn_1(param1: bool, param2: &str) {
 8814                    let var1 = "«helloˇ» world";
 8815                }
 8816            "#},
 8817            cx,
 8818        );
 8819        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8820        assert_text_with_selections(
 8821            editor,
 8822            indoc! {r#"
 8823                use mod1::mod2::{mod3, mod4};
 8824
 8825                fn fn_1(param1: bool, param2: &str) {
 8826                    let var1 = "«hello worldˇ»";
 8827                }
 8828            "#},
 8829            cx,
 8830        );
 8831    });
 8832
 8833    // Test 4: Selection spanning across words
 8834    editor.update_in(cx, |editor, window, cx| {
 8835        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8836            s.select_display_ranges([
 8837                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 8838            ]);
 8839        });
 8840    });
 8841    editor.update_in(cx, |editor, window, cx| {
 8842        assert_text_with_selections(
 8843            editor,
 8844            indoc! {r#"
 8845                use mod1::mod2::{mod3, mod4};
 8846
 8847                fn fn_1(param1: bool, param2: &str) {
 8848                    let var1 = "hel«lo woˇ»rld";
 8849                }
 8850            "#},
 8851            cx,
 8852        );
 8853        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8854        assert_text_with_selections(
 8855            editor,
 8856            indoc! {r#"
 8857                use mod1::mod2::{mod3, mod4};
 8858
 8859                fn fn_1(param1: bool, param2: &str) {
 8860                    let var1 = "«ˇhello world»";
 8861                }
 8862            "#},
 8863            cx,
 8864        );
 8865    });
 8866
 8867    // Test 5: Expansion beyond string
 8868    editor.update_in(cx, |editor, window, cx| {
 8869        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8870        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8871        assert_text_with_selections(
 8872            editor,
 8873            indoc! {r#"
 8874                use mod1::mod2::{mod3, mod4};
 8875
 8876                fn fn_1(param1: bool, param2: &str) {
 8877                    «ˇlet var1 = "hello world";»
 8878                }
 8879            "#},
 8880            cx,
 8881        );
 8882    });
 8883}
 8884
 8885#[gpui::test]
 8886async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 8887    init_test(cx, |_| {});
 8888
 8889    let mut cx = EditorTestContext::new(cx).await;
 8890
 8891    let language = Arc::new(Language::new(
 8892        LanguageConfig::default(),
 8893        Some(tree_sitter_rust::LANGUAGE.into()),
 8894    ));
 8895
 8896    cx.update_buffer(|buffer, cx| {
 8897        buffer.set_language(Some(language), cx);
 8898    });
 8899
 8900    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 8901    cx.update_editor(|editor, window, cx| {
 8902        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 8903    });
 8904
 8905    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 8906}
 8907
 8908#[gpui::test]
 8909async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 8910    init_test(cx, |_| {});
 8911
 8912    let base_text = r#"
 8913        impl A {
 8914            // this is an uncommitted comment
 8915
 8916            fn b() {
 8917                c();
 8918            }
 8919
 8920            // this is another uncommitted comment
 8921
 8922            fn d() {
 8923                // e
 8924                // f
 8925            }
 8926        }
 8927
 8928        fn g() {
 8929            // h
 8930        }
 8931    "#
 8932    .unindent();
 8933
 8934    let text = r#"
 8935        ˇimpl A {
 8936
 8937            fn b() {
 8938                c();
 8939            }
 8940
 8941            fn d() {
 8942                // e
 8943                // f
 8944            }
 8945        }
 8946
 8947        fn g() {
 8948            // h
 8949        }
 8950    "#
 8951    .unindent();
 8952
 8953    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8954    cx.set_state(&text);
 8955    cx.set_head_text(&base_text);
 8956    cx.update_editor(|editor, window, cx| {
 8957        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8958    });
 8959
 8960    cx.assert_state_with_diff(
 8961        "
 8962        ˇimpl A {
 8963      -     // this is an uncommitted comment
 8964
 8965            fn b() {
 8966                c();
 8967            }
 8968
 8969      -     // this is another uncommitted comment
 8970      -
 8971            fn d() {
 8972                // e
 8973                // f
 8974            }
 8975        }
 8976
 8977        fn g() {
 8978            // h
 8979        }
 8980    "
 8981        .unindent(),
 8982    );
 8983
 8984    let expected_display_text = "
 8985        impl A {
 8986            // this is an uncommitted comment
 8987
 8988            fn b() {
 8989 8990            }
 8991
 8992            // this is another uncommitted comment
 8993
 8994            fn d() {
 8995 8996            }
 8997        }
 8998
 8999        fn g() {
 9000 9001        }
 9002        "
 9003    .unindent();
 9004
 9005    cx.update_editor(|editor, window, cx| {
 9006        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9007        assert_eq!(editor.display_text(cx), expected_display_text);
 9008    });
 9009}
 9010
 9011#[gpui::test]
 9012async fn test_autoindent(cx: &mut TestAppContext) {
 9013    init_test(cx, |_| {});
 9014
 9015    let language = Arc::new(
 9016        Language::new(
 9017            LanguageConfig {
 9018                brackets: BracketPairConfig {
 9019                    pairs: vec![
 9020                        BracketPair {
 9021                            start: "{".to_string(),
 9022                            end: "}".to_string(),
 9023                            close: false,
 9024                            surround: false,
 9025                            newline: true,
 9026                        },
 9027                        BracketPair {
 9028                            start: "(".to_string(),
 9029                            end: ")".to_string(),
 9030                            close: false,
 9031                            surround: false,
 9032                            newline: true,
 9033                        },
 9034                    ],
 9035                    ..Default::default()
 9036                },
 9037                ..Default::default()
 9038            },
 9039            Some(tree_sitter_rust::LANGUAGE.into()),
 9040        )
 9041        .with_indents_query(
 9042            r#"
 9043                (_ "(" ")" @end) @indent
 9044                (_ "{" "}" @end) @indent
 9045            "#,
 9046        )
 9047        .unwrap(),
 9048    );
 9049
 9050    let text = "fn a() {}";
 9051
 9052    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9053    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9054    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9055    editor
 9056        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9057        .await;
 9058
 9059    editor.update_in(cx, |editor, window, cx| {
 9060        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9061            s.select_ranges([5..5, 8..8, 9..9])
 9062        });
 9063        editor.newline(&Newline, window, cx);
 9064        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9065        assert_eq!(
 9066            editor.selections.ranges(cx),
 9067            &[
 9068                Point::new(1, 4)..Point::new(1, 4),
 9069                Point::new(3, 4)..Point::new(3, 4),
 9070                Point::new(5, 0)..Point::new(5, 0)
 9071            ]
 9072        );
 9073    });
 9074}
 9075
 9076#[gpui::test]
 9077async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9078    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9079
 9080    let language = Arc::new(
 9081        Language::new(
 9082            LanguageConfig {
 9083                brackets: BracketPairConfig {
 9084                    pairs: vec![
 9085                        BracketPair {
 9086                            start: "{".to_string(),
 9087                            end: "}".to_string(),
 9088                            close: false,
 9089                            surround: false,
 9090                            newline: true,
 9091                        },
 9092                        BracketPair {
 9093                            start: "(".to_string(),
 9094                            end: ")".to_string(),
 9095                            close: false,
 9096                            surround: false,
 9097                            newline: true,
 9098                        },
 9099                    ],
 9100                    ..Default::default()
 9101                },
 9102                ..Default::default()
 9103            },
 9104            Some(tree_sitter_rust::LANGUAGE.into()),
 9105        )
 9106        .with_indents_query(
 9107            r#"
 9108                (_ "(" ")" @end) @indent
 9109                (_ "{" "}" @end) @indent
 9110            "#,
 9111        )
 9112        .unwrap(),
 9113    );
 9114
 9115    let text = "fn a() {}";
 9116
 9117    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9118    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9119    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9120    editor
 9121        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9122        .await;
 9123
 9124    editor.update_in(cx, |editor, window, cx| {
 9125        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9126            s.select_ranges([5..5, 8..8, 9..9])
 9127        });
 9128        editor.newline(&Newline, window, cx);
 9129        assert_eq!(
 9130            editor.text(cx),
 9131            indoc!(
 9132                "
 9133                fn a(
 9134
 9135                ) {
 9136
 9137                }
 9138                "
 9139            )
 9140        );
 9141        assert_eq!(
 9142            editor.selections.ranges(cx),
 9143            &[
 9144                Point::new(1, 0)..Point::new(1, 0),
 9145                Point::new(3, 0)..Point::new(3, 0),
 9146                Point::new(5, 0)..Point::new(5, 0)
 9147            ]
 9148        );
 9149    });
 9150}
 9151
 9152#[gpui::test]
 9153async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9154    init_test(cx, |settings| {
 9155        settings.defaults.auto_indent = Some(true);
 9156        settings.languages.0.insert(
 9157            "python".into(),
 9158            LanguageSettingsContent {
 9159                auto_indent: Some(false),
 9160                ..Default::default()
 9161            },
 9162        );
 9163    });
 9164
 9165    let mut cx = EditorTestContext::new(cx).await;
 9166
 9167    let injected_language = Arc::new(
 9168        Language::new(
 9169            LanguageConfig {
 9170                brackets: BracketPairConfig {
 9171                    pairs: vec![
 9172                        BracketPair {
 9173                            start: "{".to_string(),
 9174                            end: "}".to_string(),
 9175                            close: false,
 9176                            surround: false,
 9177                            newline: true,
 9178                        },
 9179                        BracketPair {
 9180                            start: "(".to_string(),
 9181                            end: ")".to_string(),
 9182                            close: true,
 9183                            surround: false,
 9184                            newline: true,
 9185                        },
 9186                    ],
 9187                    ..Default::default()
 9188                },
 9189                name: "python".into(),
 9190                ..Default::default()
 9191            },
 9192            Some(tree_sitter_python::LANGUAGE.into()),
 9193        )
 9194        .with_indents_query(
 9195            r#"
 9196                (_ "(" ")" @end) @indent
 9197                (_ "{" "}" @end) @indent
 9198            "#,
 9199        )
 9200        .unwrap(),
 9201    );
 9202
 9203    let language = Arc::new(
 9204        Language::new(
 9205            LanguageConfig {
 9206                brackets: BracketPairConfig {
 9207                    pairs: vec![
 9208                        BracketPair {
 9209                            start: "{".to_string(),
 9210                            end: "}".to_string(),
 9211                            close: false,
 9212                            surround: false,
 9213                            newline: true,
 9214                        },
 9215                        BracketPair {
 9216                            start: "(".to_string(),
 9217                            end: ")".to_string(),
 9218                            close: true,
 9219                            surround: false,
 9220                            newline: true,
 9221                        },
 9222                    ],
 9223                    ..Default::default()
 9224                },
 9225                name: LanguageName::new("rust"),
 9226                ..Default::default()
 9227            },
 9228            Some(tree_sitter_rust::LANGUAGE.into()),
 9229        )
 9230        .with_indents_query(
 9231            r#"
 9232                (_ "(" ")" @end) @indent
 9233                (_ "{" "}" @end) @indent
 9234            "#,
 9235        )
 9236        .unwrap()
 9237        .with_injection_query(
 9238            r#"
 9239            (macro_invocation
 9240                macro: (identifier) @_macro_name
 9241                (token_tree) @injection.content
 9242                (#set! injection.language "python"))
 9243           "#,
 9244        )
 9245        .unwrap(),
 9246    );
 9247
 9248    cx.language_registry().add(injected_language);
 9249    cx.language_registry().add(language.clone());
 9250
 9251    cx.update_buffer(|buffer, cx| {
 9252        buffer.set_language(Some(language), cx);
 9253    });
 9254
 9255    cx.set_state(r#"struct A {ˇ}"#);
 9256
 9257    cx.update_editor(|editor, window, cx| {
 9258        editor.newline(&Default::default(), window, cx);
 9259    });
 9260
 9261    cx.assert_editor_state(indoc!(
 9262        "struct A {
 9263            ˇ
 9264        }"
 9265    ));
 9266
 9267    cx.set_state(r#"select_biased!(ˇ)"#);
 9268
 9269    cx.update_editor(|editor, window, cx| {
 9270        editor.newline(&Default::default(), window, cx);
 9271        editor.handle_input("def ", window, cx);
 9272        editor.handle_input("(", window, cx);
 9273        editor.newline(&Default::default(), window, cx);
 9274        editor.handle_input("a", window, cx);
 9275    });
 9276
 9277    cx.assert_editor_state(indoc!(
 9278        "select_biased!(
 9279        def (
 9280 9281        )
 9282        )"
 9283    ));
 9284}
 9285
 9286#[gpui::test]
 9287async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9288    init_test(cx, |_| {});
 9289
 9290    {
 9291        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9292        cx.set_state(indoc! {"
 9293            impl A {
 9294
 9295                fn b() {}
 9296
 9297            «fn c() {
 9298
 9299            }ˇ»
 9300            }
 9301        "});
 9302
 9303        cx.update_editor(|editor, window, cx| {
 9304            editor.autoindent(&Default::default(), window, cx);
 9305        });
 9306
 9307        cx.assert_editor_state(indoc! {"
 9308            impl A {
 9309
 9310                fn b() {}
 9311
 9312                «fn c() {
 9313
 9314                }ˇ»
 9315            }
 9316        "});
 9317    }
 9318
 9319    {
 9320        let mut cx = EditorTestContext::new_multibuffer(
 9321            cx,
 9322            [indoc! { "
 9323                impl A {
 9324                «
 9325                // a
 9326                fn b(){}
 9327                »
 9328                «
 9329                    }
 9330                    fn c(){}
 9331                »
 9332            "}],
 9333        );
 9334
 9335        let buffer = cx.update_editor(|editor, _, cx| {
 9336            let buffer = editor.buffer().update(cx, |buffer, _| {
 9337                buffer.all_buffers().iter().next().unwrap().clone()
 9338            });
 9339            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9340            buffer
 9341        });
 9342
 9343        cx.run_until_parked();
 9344        cx.update_editor(|editor, window, cx| {
 9345            editor.select_all(&Default::default(), window, cx);
 9346            editor.autoindent(&Default::default(), window, cx)
 9347        });
 9348        cx.run_until_parked();
 9349
 9350        cx.update(|_, cx| {
 9351            assert_eq!(
 9352                buffer.read(cx).text(),
 9353                indoc! { "
 9354                    impl A {
 9355
 9356                        // a
 9357                        fn b(){}
 9358
 9359
 9360                    }
 9361                    fn c(){}
 9362
 9363                " }
 9364            )
 9365        });
 9366    }
 9367}
 9368
 9369#[gpui::test]
 9370async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9371    init_test(cx, |_| {});
 9372
 9373    let mut cx = EditorTestContext::new(cx).await;
 9374
 9375    let language = Arc::new(Language::new(
 9376        LanguageConfig {
 9377            brackets: BracketPairConfig {
 9378                pairs: vec![
 9379                    BracketPair {
 9380                        start: "{".to_string(),
 9381                        end: "}".to_string(),
 9382                        close: true,
 9383                        surround: true,
 9384                        newline: true,
 9385                    },
 9386                    BracketPair {
 9387                        start: "(".to_string(),
 9388                        end: ")".to_string(),
 9389                        close: true,
 9390                        surround: true,
 9391                        newline: true,
 9392                    },
 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: false,
 9404                        surround: false,
 9405                        newline: true,
 9406                    },
 9407                    BracketPair {
 9408                        start: "\"".to_string(),
 9409                        end: "\"".to_string(),
 9410                        close: true,
 9411                        surround: true,
 9412                        newline: false,
 9413                    },
 9414                    BracketPair {
 9415                        start: "<".to_string(),
 9416                        end: ">".to_string(),
 9417                        close: false,
 9418                        surround: true,
 9419                        newline: true,
 9420                    },
 9421                ],
 9422                ..Default::default()
 9423            },
 9424            autoclose_before: "})]".to_string(),
 9425            ..Default::default()
 9426        },
 9427        Some(tree_sitter_rust::LANGUAGE.into()),
 9428    ));
 9429
 9430    cx.language_registry().add(language.clone());
 9431    cx.update_buffer(|buffer, cx| {
 9432        buffer.set_language(Some(language), cx);
 9433    });
 9434
 9435    cx.set_state(
 9436        &r#"
 9437            🏀ˇ
 9438            εˇ
 9439            ❤️ˇ
 9440        "#
 9441        .unindent(),
 9442    );
 9443
 9444    // autoclose multiple nested brackets at multiple cursors
 9445    cx.update_editor(|editor, window, cx| {
 9446        editor.handle_input("{", window, cx);
 9447        editor.handle_input("{", window, cx);
 9448        editor.handle_input("{", window, cx);
 9449    });
 9450    cx.assert_editor_state(
 9451        &"
 9452            🏀{{{ˇ}}}
 9453            ε{{{ˇ}}}
 9454            ❤️{{{ˇ}}}
 9455        "
 9456        .unindent(),
 9457    );
 9458
 9459    // insert a different closing bracket
 9460    cx.update_editor(|editor, window, cx| {
 9461        editor.handle_input(")", window, cx);
 9462    });
 9463    cx.assert_editor_state(
 9464        &"
 9465            🏀{{{)ˇ}}}
 9466            ε{{{)ˇ}}}
 9467            ❤️{{{)ˇ}}}
 9468        "
 9469        .unindent(),
 9470    );
 9471
 9472    // skip over the auto-closed brackets when typing a closing bracket
 9473    cx.update_editor(|editor, window, cx| {
 9474        editor.move_right(&MoveRight, window, cx);
 9475        editor.handle_input("}", window, cx);
 9476        editor.handle_input("}", window, cx);
 9477        editor.handle_input("}", window, cx);
 9478    });
 9479    cx.assert_editor_state(
 9480        &"
 9481            🏀{{{)}}}}ˇ
 9482            ε{{{)}}}}ˇ
 9483            ❤️{{{)}}}}ˇ
 9484        "
 9485        .unindent(),
 9486    );
 9487
 9488    // autoclose multi-character pairs
 9489    cx.set_state(
 9490        &"
 9491            ˇ
 9492            ˇ
 9493        "
 9494        .unindent(),
 9495    );
 9496    cx.update_editor(|editor, window, cx| {
 9497        editor.handle_input("/", window, cx);
 9498        editor.handle_input("*", window, cx);
 9499    });
 9500    cx.assert_editor_state(
 9501        &"
 9502            /*ˇ */
 9503            /*ˇ */
 9504        "
 9505        .unindent(),
 9506    );
 9507
 9508    // one cursor autocloses a multi-character pair, one cursor
 9509    // does not autoclose.
 9510    cx.set_state(
 9511        &"
 9512 9513            ˇ
 9514        "
 9515        .unindent(),
 9516    );
 9517    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9518    cx.assert_editor_state(
 9519        &"
 9520            /*ˇ */
 9521 9522        "
 9523        .unindent(),
 9524    );
 9525
 9526    // Don't autoclose if the next character isn't whitespace and isn't
 9527    // listed in the language's "autoclose_before" section.
 9528    cx.set_state("ˇa b");
 9529    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9530    cx.assert_editor_state("{ˇa b");
 9531
 9532    // Don't autoclose if `close` is false for the bracket pair
 9533    cx.set_state("ˇ");
 9534    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9535    cx.assert_editor_state("");
 9536
 9537    // Surround with brackets if text is selected
 9538    cx.set_state("«aˇ» b");
 9539    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9540    cx.assert_editor_state("{«aˇ»} b");
 9541
 9542    // Autoclose when not immediately after a word character
 9543    cx.set_state("a ˇ");
 9544    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9545    cx.assert_editor_state("a \"ˇ\"");
 9546
 9547    // Autoclose pair where the start and end characters are the same
 9548    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9549    cx.assert_editor_state("a \"\"ˇ");
 9550
 9551    // Don't autoclose when immediately after a word character
 9552    cx.set_state("");
 9553    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9554    cx.assert_editor_state("a\"ˇ");
 9555
 9556    // Do autoclose when after a non-word character
 9557    cx.set_state("");
 9558    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9559    cx.assert_editor_state("{\"ˇ\"");
 9560
 9561    // Non identical pairs autoclose regardless of preceding character
 9562    cx.set_state("");
 9563    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9564    cx.assert_editor_state("a{ˇ}");
 9565
 9566    // Don't autoclose pair if autoclose is disabled
 9567    cx.set_state("ˇ");
 9568    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9569    cx.assert_editor_state("");
 9570
 9571    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9572    cx.set_state("«aˇ» b");
 9573    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9574    cx.assert_editor_state("<«aˇ»> b");
 9575}
 9576
 9577#[gpui::test]
 9578async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9579    init_test(cx, |settings| {
 9580        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9581    });
 9582
 9583    let mut cx = EditorTestContext::new(cx).await;
 9584
 9585    let language = Arc::new(Language::new(
 9586        LanguageConfig {
 9587            brackets: BracketPairConfig {
 9588                pairs: vec![
 9589                    BracketPair {
 9590                        start: "{".to_string(),
 9591                        end: "}".to_string(),
 9592                        close: true,
 9593                        surround: true,
 9594                        newline: true,
 9595                    },
 9596                    BracketPair {
 9597                        start: "(".to_string(),
 9598                        end: ")".to_string(),
 9599                        close: true,
 9600                        surround: true,
 9601                        newline: true,
 9602                    },
 9603                    BracketPair {
 9604                        start: "[".to_string(),
 9605                        end: "]".to_string(),
 9606                        close: false,
 9607                        surround: false,
 9608                        newline: true,
 9609                    },
 9610                ],
 9611                ..Default::default()
 9612            },
 9613            autoclose_before: "})]".to_string(),
 9614            ..Default::default()
 9615        },
 9616        Some(tree_sitter_rust::LANGUAGE.into()),
 9617    ));
 9618
 9619    cx.language_registry().add(language.clone());
 9620    cx.update_buffer(|buffer, cx| {
 9621        buffer.set_language(Some(language), cx);
 9622    });
 9623
 9624    cx.set_state(
 9625        &"
 9626            ˇ
 9627            ˇ
 9628            ˇ
 9629        "
 9630        .unindent(),
 9631    );
 9632
 9633    // ensure only matching closing brackets are skipped over
 9634    cx.update_editor(|editor, window, cx| {
 9635        editor.handle_input("}", window, cx);
 9636        editor.move_left(&MoveLeft, window, cx);
 9637        editor.handle_input(")", window, cx);
 9638        editor.move_left(&MoveLeft, window, cx);
 9639    });
 9640    cx.assert_editor_state(
 9641        &"
 9642            ˇ)}
 9643            ˇ)}
 9644            ˇ)}
 9645        "
 9646        .unindent(),
 9647    );
 9648
 9649    // skip-over closing brackets at multiple cursors
 9650    cx.update_editor(|editor, window, cx| {
 9651        editor.handle_input(")", window, cx);
 9652        editor.handle_input("}", window, cx);
 9653    });
 9654    cx.assert_editor_state(
 9655        &"
 9656            )}ˇ
 9657            )}ˇ
 9658            )}ˇ
 9659        "
 9660        .unindent(),
 9661    );
 9662
 9663    // ignore non-close brackets
 9664    cx.update_editor(|editor, window, cx| {
 9665        editor.handle_input("]", window, cx);
 9666        editor.move_left(&MoveLeft, window, cx);
 9667        editor.handle_input("]", window, cx);
 9668    });
 9669    cx.assert_editor_state(
 9670        &"
 9671            )}]ˇ]
 9672            )}]ˇ]
 9673            )}]ˇ]
 9674        "
 9675        .unindent(),
 9676    );
 9677}
 9678
 9679#[gpui::test]
 9680async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9681    init_test(cx, |_| {});
 9682
 9683    let mut cx = EditorTestContext::new(cx).await;
 9684
 9685    let html_language = Arc::new(
 9686        Language::new(
 9687            LanguageConfig {
 9688                name: "HTML".into(),
 9689                brackets: BracketPairConfig {
 9690                    pairs: vec![
 9691                        BracketPair {
 9692                            start: "<".into(),
 9693                            end: ">".into(),
 9694                            close: true,
 9695                            ..Default::default()
 9696                        },
 9697                        BracketPair {
 9698                            start: "{".into(),
 9699                            end: "}".into(),
 9700                            close: true,
 9701                            ..Default::default()
 9702                        },
 9703                        BracketPair {
 9704                            start: "(".into(),
 9705                            end: ")".into(),
 9706                            close: true,
 9707                            ..Default::default()
 9708                        },
 9709                    ],
 9710                    ..Default::default()
 9711                },
 9712                autoclose_before: "})]>".into(),
 9713                ..Default::default()
 9714            },
 9715            Some(tree_sitter_html::LANGUAGE.into()),
 9716        )
 9717        .with_injection_query(
 9718            r#"
 9719            (script_element
 9720                (raw_text) @injection.content
 9721                (#set! injection.language "javascript"))
 9722            "#,
 9723        )
 9724        .unwrap(),
 9725    );
 9726
 9727    let javascript_language = Arc::new(Language::new(
 9728        LanguageConfig {
 9729            name: "JavaScript".into(),
 9730            brackets: BracketPairConfig {
 9731                pairs: vec![
 9732                    BracketPair {
 9733                        start: "/*".into(),
 9734                        end: " */".into(),
 9735                        close: true,
 9736                        ..Default::default()
 9737                    },
 9738                    BracketPair {
 9739                        start: "{".into(),
 9740                        end: "}".into(),
 9741                        close: true,
 9742                        ..Default::default()
 9743                    },
 9744                    BracketPair {
 9745                        start: "(".into(),
 9746                        end: ")".into(),
 9747                        close: true,
 9748                        ..Default::default()
 9749                    },
 9750                ],
 9751                ..Default::default()
 9752            },
 9753            autoclose_before: "})]>".into(),
 9754            ..Default::default()
 9755        },
 9756        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9757    ));
 9758
 9759    cx.language_registry().add(html_language.clone());
 9760    cx.language_registry().add(javascript_language);
 9761    cx.executor().run_until_parked();
 9762
 9763    cx.update_buffer(|buffer, cx| {
 9764        buffer.set_language(Some(html_language), cx);
 9765    });
 9766
 9767    cx.set_state(
 9768        &r#"
 9769            <body>ˇ
 9770                <script>
 9771                    var x = 1;ˇ
 9772                </script>
 9773            </body>ˇ
 9774        "#
 9775        .unindent(),
 9776    );
 9777
 9778    // Precondition: different languages are active at different locations.
 9779    cx.update_editor(|editor, window, cx| {
 9780        let snapshot = editor.snapshot(window, cx);
 9781        let cursors = editor.selections.ranges::<usize>(cx);
 9782        let languages = cursors
 9783            .iter()
 9784            .map(|c| snapshot.language_at(c.start).unwrap().name())
 9785            .collect::<Vec<_>>();
 9786        assert_eq!(
 9787            languages,
 9788            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 9789        );
 9790    });
 9791
 9792    // Angle brackets autoclose in HTML, but not JavaScript.
 9793    cx.update_editor(|editor, window, cx| {
 9794        editor.handle_input("<", window, cx);
 9795        editor.handle_input("a", window, cx);
 9796    });
 9797    cx.assert_editor_state(
 9798        &r#"
 9799            <body><aˇ>
 9800                <script>
 9801                    var x = 1;<aˇ
 9802                </script>
 9803            </body><aˇ>
 9804        "#
 9805        .unindent(),
 9806    );
 9807
 9808    // Curly braces and parens autoclose in both HTML and JavaScript.
 9809    cx.update_editor(|editor, window, cx| {
 9810        editor.handle_input(" b=", window, cx);
 9811        editor.handle_input("{", window, cx);
 9812        editor.handle_input("c", window, cx);
 9813        editor.handle_input("(", window, cx);
 9814    });
 9815    cx.assert_editor_state(
 9816        &r#"
 9817            <body><a b={c(ˇ)}>
 9818                <script>
 9819                    var x = 1;<a b={c(ˇ)}
 9820                </script>
 9821            </body><a b={c(ˇ)}>
 9822        "#
 9823        .unindent(),
 9824    );
 9825
 9826    // Brackets that were already autoclosed are skipped.
 9827    cx.update_editor(|editor, window, cx| {
 9828        editor.handle_input(")", window, cx);
 9829        editor.handle_input("d", window, cx);
 9830        editor.handle_input("}", window, cx);
 9831    });
 9832    cx.assert_editor_state(
 9833        &r#"
 9834            <body><a b={c()d}ˇ>
 9835                <script>
 9836                    var x = 1;<a b={c()d}ˇ
 9837                </script>
 9838            </body><a b={c()d}ˇ>
 9839        "#
 9840        .unindent(),
 9841    );
 9842    cx.update_editor(|editor, window, cx| {
 9843        editor.handle_input(">", window, cx);
 9844    });
 9845    cx.assert_editor_state(
 9846        &r#"
 9847            <body><a b={c()d}>ˇ
 9848                <script>
 9849                    var x = 1;<a b={c()d}>ˇ
 9850                </script>
 9851            </body><a b={c()d}>ˇ
 9852        "#
 9853        .unindent(),
 9854    );
 9855
 9856    // Reset
 9857    cx.set_state(
 9858        &r#"
 9859            <body>ˇ
 9860                <script>
 9861                    var x = 1;ˇ
 9862                </script>
 9863            </body>ˇ
 9864        "#
 9865        .unindent(),
 9866    );
 9867
 9868    cx.update_editor(|editor, window, cx| {
 9869        editor.handle_input("<", window, cx);
 9870    });
 9871    cx.assert_editor_state(
 9872        &r#"
 9873            <body><ˇ>
 9874                <script>
 9875                    var x = 1;<ˇ
 9876                </script>
 9877            </body><ˇ>
 9878        "#
 9879        .unindent(),
 9880    );
 9881
 9882    // When backspacing, the closing angle brackets are removed.
 9883    cx.update_editor(|editor, window, cx| {
 9884        editor.backspace(&Backspace, window, cx);
 9885    });
 9886    cx.assert_editor_state(
 9887        &r#"
 9888            <body>ˇ
 9889                <script>
 9890                    var x = 1;ˇ
 9891                </script>
 9892            </body>ˇ
 9893        "#
 9894        .unindent(),
 9895    );
 9896
 9897    // Block comments autoclose in JavaScript, but not HTML.
 9898    cx.update_editor(|editor, window, cx| {
 9899        editor.handle_input("/", window, cx);
 9900        editor.handle_input("*", window, cx);
 9901    });
 9902    cx.assert_editor_state(
 9903        &r#"
 9904            <body>/*ˇ
 9905                <script>
 9906                    var x = 1;/*ˇ */
 9907                </script>
 9908            </body>/*ˇ
 9909        "#
 9910        .unindent(),
 9911    );
 9912}
 9913
 9914#[gpui::test]
 9915async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 9916    init_test(cx, |_| {});
 9917
 9918    let mut cx = EditorTestContext::new(cx).await;
 9919
 9920    let rust_language = Arc::new(
 9921        Language::new(
 9922            LanguageConfig {
 9923                name: "Rust".into(),
 9924                brackets: serde_json::from_value(json!([
 9925                    { "start": "{", "end": "}", "close": true, "newline": true },
 9926                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 9927                ]))
 9928                .unwrap(),
 9929                autoclose_before: "})]>".into(),
 9930                ..Default::default()
 9931            },
 9932            Some(tree_sitter_rust::LANGUAGE.into()),
 9933        )
 9934        .with_override_query("(string_literal) @string")
 9935        .unwrap(),
 9936    );
 9937
 9938    cx.language_registry().add(rust_language.clone());
 9939    cx.update_buffer(|buffer, cx| {
 9940        buffer.set_language(Some(rust_language), cx);
 9941    });
 9942
 9943    cx.set_state(
 9944        &r#"
 9945            let x = ˇ
 9946        "#
 9947        .unindent(),
 9948    );
 9949
 9950    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 9951    cx.update_editor(|editor, window, cx| {
 9952        editor.handle_input("\"", window, cx);
 9953    });
 9954    cx.assert_editor_state(
 9955        &r#"
 9956            let x = "ˇ"
 9957        "#
 9958        .unindent(),
 9959    );
 9960
 9961    // Inserting another quotation mark. The cursor moves across the existing
 9962    // automatically-inserted quotation mark.
 9963    cx.update_editor(|editor, window, cx| {
 9964        editor.handle_input("\"", window, cx);
 9965    });
 9966    cx.assert_editor_state(
 9967        &r#"
 9968            let x = ""ˇ
 9969        "#
 9970        .unindent(),
 9971    );
 9972
 9973    // Reset
 9974    cx.set_state(
 9975        &r#"
 9976            let x = ˇ
 9977        "#
 9978        .unindent(),
 9979    );
 9980
 9981    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 9982    cx.update_editor(|editor, window, cx| {
 9983        editor.handle_input("\"", window, cx);
 9984        editor.handle_input(" ", window, cx);
 9985        editor.move_left(&Default::default(), window, cx);
 9986        editor.handle_input("\\", window, cx);
 9987        editor.handle_input("\"", window, cx);
 9988    });
 9989    cx.assert_editor_state(
 9990        &r#"
 9991            let x = "\"ˇ "
 9992        "#
 9993        .unindent(),
 9994    );
 9995
 9996    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 9997    // mark. Nothing is inserted.
 9998    cx.update_editor(|editor, window, cx| {
 9999        editor.move_right(&Default::default(), window, cx);
10000        editor.handle_input("\"", window, cx);
10001    });
10002    cx.assert_editor_state(
10003        &r#"
10004            let x = "\" "ˇ
10005        "#
10006        .unindent(),
10007    );
10008}
10009
10010#[gpui::test]
10011async fn test_surround_with_pair(cx: &mut TestAppContext) {
10012    init_test(cx, |_| {});
10013
10014    let language = Arc::new(Language::new(
10015        LanguageConfig {
10016            brackets: BracketPairConfig {
10017                pairs: vec![
10018                    BracketPair {
10019                        start: "{".to_string(),
10020                        end: "}".to_string(),
10021                        close: true,
10022                        surround: true,
10023                        newline: true,
10024                    },
10025                    BracketPair {
10026                        start: "/* ".to_string(),
10027                        end: "*/".to_string(),
10028                        close: true,
10029                        surround: true,
10030                        ..Default::default()
10031                    },
10032                ],
10033                ..Default::default()
10034            },
10035            ..Default::default()
10036        },
10037        Some(tree_sitter_rust::LANGUAGE.into()),
10038    ));
10039
10040    let text = r#"
10041        a
10042        b
10043        c
10044    "#
10045    .unindent();
10046
10047    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10048    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10049    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10050    editor
10051        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10052        .await;
10053
10054    editor.update_in(cx, |editor, window, cx| {
10055        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10056            s.select_display_ranges([
10057                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10058                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10059                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10060            ])
10061        });
10062
10063        editor.handle_input("{", window, cx);
10064        editor.handle_input("{", window, cx);
10065        editor.handle_input("{", window, cx);
10066        assert_eq!(
10067            editor.text(cx),
10068            "
10069                {{{a}}}
10070                {{{b}}}
10071                {{{c}}}
10072            "
10073            .unindent()
10074        );
10075        assert_eq!(
10076            editor.selections.display_ranges(cx),
10077            [
10078                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10079                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10080                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10081            ]
10082        );
10083
10084        editor.undo(&Undo, window, cx);
10085        editor.undo(&Undo, window, cx);
10086        editor.undo(&Undo, window, cx);
10087        assert_eq!(
10088            editor.text(cx),
10089            "
10090                a
10091                b
10092                c
10093            "
10094            .unindent()
10095        );
10096        assert_eq!(
10097            editor.selections.display_ranges(cx),
10098            [
10099                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10100                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10101                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10102            ]
10103        );
10104
10105        // Ensure inserting the first character of a multi-byte bracket pair
10106        // doesn't surround the selections with the bracket.
10107        editor.handle_input("/", window, cx);
10108        assert_eq!(
10109            editor.text(cx),
10110            "
10111                /
10112                /
10113                /
10114            "
10115            .unindent()
10116        );
10117        assert_eq!(
10118            editor.selections.display_ranges(cx),
10119            [
10120                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10121                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10122                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10123            ]
10124        );
10125
10126        editor.undo(&Undo, window, cx);
10127        assert_eq!(
10128            editor.text(cx),
10129            "
10130                a
10131                b
10132                c
10133            "
10134            .unindent()
10135        );
10136        assert_eq!(
10137            editor.selections.display_ranges(cx),
10138            [
10139                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10140                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10141                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10142            ]
10143        );
10144
10145        // Ensure inserting the last character of a multi-byte bracket pair
10146        // doesn't surround the selections with the bracket.
10147        editor.handle_input("*", window, cx);
10148        assert_eq!(
10149            editor.text(cx),
10150            "
10151                *
10152                *
10153                *
10154            "
10155            .unindent()
10156        );
10157        assert_eq!(
10158            editor.selections.display_ranges(cx),
10159            [
10160                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10161                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10162                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10163            ]
10164        );
10165    });
10166}
10167
10168#[gpui::test]
10169async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10170    init_test(cx, |_| {});
10171
10172    let language = Arc::new(Language::new(
10173        LanguageConfig {
10174            brackets: BracketPairConfig {
10175                pairs: vec![BracketPair {
10176                    start: "{".to_string(),
10177                    end: "}".to_string(),
10178                    close: true,
10179                    surround: true,
10180                    newline: true,
10181                }],
10182                ..Default::default()
10183            },
10184            autoclose_before: "}".to_string(),
10185            ..Default::default()
10186        },
10187        Some(tree_sitter_rust::LANGUAGE.into()),
10188    ));
10189
10190    let text = r#"
10191        a
10192        b
10193        c
10194    "#
10195    .unindent();
10196
10197    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10198    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10199    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10200    editor
10201        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10202        .await;
10203
10204    editor.update_in(cx, |editor, window, cx| {
10205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10206            s.select_ranges([
10207                Point::new(0, 1)..Point::new(0, 1),
10208                Point::new(1, 1)..Point::new(1, 1),
10209                Point::new(2, 1)..Point::new(2, 1),
10210            ])
10211        });
10212
10213        editor.handle_input("{", window, cx);
10214        editor.handle_input("{", window, cx);
10215        editor.handle_input("_", window, cx);
10216        assert_eq!(
10217            editor.text(cx),
10218            "
10219                a{{_}}
10220                b{{_}}
10221                c{{_}}
10222            "
10223            .unindent()
10224        );
10225        assert_eq!(
10226            editor.selections.ranges::<Point>(cx),
10227            [
10228                Point::new(0, 4)..Point::new(0, 4),
10229                Point::new(1, 4)..Point::new(1, 4),
10230                Point::new(2, 4)..Point::new(2, 4)
10231            ]
10232        );
10233
10234        editor.backspace(&Default::default(), window, cx);
10235        editor.backspace(&Default::default(), window, cx);
10236        assert_eq!(
10237            editor.text(cx),
10238            "
10239                a{}
10240                b{}
10241                c{}
10242            "
10243            .unindent()
10244        );
10245        assert_eq!(
10246            editor.selections.ranges::<Point>(cx),
10247            [
10248                Point::new(0, 2)..Point::new(0, 2),
10249                Point::new(1, 2)..Point::new(1, 2),
10250                Point::new(2, 2)..Point::new(2, 2)
10251            ]
10252        );
10253
10254        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10255        assert_eq!(
10256            editor.text(cx),
10257            "
10258                a
10259                b
10260                c
10261            "
10262            .unindent()
10263        );
10264        assert_eq!(
10265            editor.selections.ranges::<Point>(cx),
10266            [
10267                Point::new(0, 1)..Point::new(0, 1),
10268                Point::new(1, 1)..Point::new(1, 1),
10269                Point::new(2, 1)..Point::new(2, 1)
10270            ]
10271        );
10272    });
10273}
10274
10275#[gpui::test]
10276async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10277    init_test(cx, |settings| {
10278        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10279    });
10280
10281    let mut cx = EditorTestContext::new(cx).await;
10282
10283    let language = Arc::new(Language::new(
10284        LanguageConfig {
10285            brackets: BracketPairConfig {
10286                pairs: vec![
10287                    BracketPair {
10288                        start: "{".to_string(),
10289                        end: "}".to_string(),
10290                        close: true,
10291                        surround: true,
10292                        newline: true,
10293                    },
10294                    BracketPair {
10295                        start: "(".to_string(),
10296                        end: ")".to_string(),
10297                        close: true,
10298                        surround: true,
10299                        newline: true,
10300                    },
10301                    BracketPair {
10302                        start: "[".to_string(),
10303                        end: "]".to_string(),
10304                        close: false,
10305                        surround: true,
10306                        newline: true,
10307                    },
10308                ],
10309                ..Default::default()
10310            },
10311            autoclose_before: "})]".to_string(),
10312            ..Default::default()
10313        },
10314        Some(tree_sitter_rust::LANGUAGE.into()),
10315    ));
10316
10317    cx.language_registry().add(language.clone());
10318    cx.update_buffer(|buffer, cx| {
10319        buffer.set_language(Some(language), cx);
10320    });
10321
10322    cx.set_state(
10323        &"
10324            {(ˇ)}
10325            [[ˇ]]
10326            {(ˇ)}
10327        "
10328        .unindent(),
10329    );
10330
10331    cx.update_editor(|editor, window, cx| {
10332        editor.backspace(&Default::default(), window, cx);
10333        editor.backspace(&Default::default(), window, cx);
10334    });
10335
10336    cx.assert_editor_state(
10337        &"
10338            ˇ
10339            ˇ]]
10340            ˇ
10341        "
10342        .unindent(),
10343    );
10344
10345    cx.update_editor(|editor, window, cx| {
10346        editor.handle_input("{", window, cx);
10347        editor.handle_input("{", window, cx);
10348        editor.move_right(&MoveRight, window, cx);
10349        editor.move_right(&MoveRight, window, cx);
10350        editor.move_left(&MoveLeft, window, cx);
10351        editor.move_left(&MoveLeft, window, cx);
10352        editor.backspace(&Default::default(), window, cx);
10353    });
10354
10355    cx.assert_editor_state(
10356        &"
10357            {ˇ}
10358            {ˇ}]]
10359            {ˇ}
10360        "
10361        .unindent(),
10362    );
10363
10364    cx.update_editor(|editor, window, cx| {
10365        editor.backspace(&Default::default(), window, cx);
10366    });
10367
10368    cx.assert_editor_state(
10369        &"
10370            ˇ
10371            ˇ]]
10372            ˇ
10373        "
10374        .unindent(),
10375    );
10376}
10377
10378#[gpui::test]
10379async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10380    init_test(cx, |_| {});
10381
10382    let language = Arc::new(Language::new(
10383        LanguageConfig::default(),
10384        Some(tree_sitter_rust::LANGUAGE.into()),
10385    ));
10386
10387    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10388    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10389    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10390    editor
10391        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10392        .await;
10393
10394    editor.update_in(cx, |editor, window, cx| {
10395        editor.set_auto_replace_emoji_shortcode(true);
10396
10397        editor.handle_input("Hello ", window, cx);
10398        editor.handle_input(":wave", window, cx);
10399        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10400
10401        editor.handle_input(":", window, cx);
10402        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10403
10404        editor.handle_input(" :smile", window, cx);
10405        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10406
10407        editor.handle_input(":", window, cx);
10408        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10409
10410        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10411        editor.handle_input(":wave", window, cx);
10412        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10413
10414        editor.handle_input(":", window, cx);
10415        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10416
10417        editor.handle_input(":1", window, cx);
10418        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10419
10420        editor.handle_input(":", window, cx);
10421        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10422
10423        // Ensure shortcode does not get replaced when it is part of a word
10424        editor.handle_input(" Test:wave", window, cx);
10425        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10426
10427        editor.handle_input(":", window, cx);
10428        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10429
10430        editor.set_auto_replace_emoji_shortcode(false);
10431
10432        // Ensure shortcode does not get replaced when auto replace is off
10433        editor.handle_input(" :wave", window, cx);
10434        assert_eq!(
10435            editor.text(cx),
10436            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10437        );
10438
10439        editor.handle_input(":", window, cx);
10440        assert_eq!(
10441            editor.text(cx),
10442            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10443        );
10444    });
10445}
10446
10447#[gpui::test]
10448async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10449    init_test(cx, |_| {});
10450
10451    let (text, insertion_ranges) = marked_text_ranges(
10452        indoc! {"
10453            ˇ
10454        "},
10455        false,
10456    );
10457
10458    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10459    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10460
10461    _ = editor.update_in(cx, |editor, window, cx| {
10462        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10463
10464        editor
10465            .insert_snippet(&insertion_ranges, snippet, window, cx)
10466            .unwrap();
10467
10468        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10469            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10470            assert_eq!(editor.text(cx), expected_text);
10471            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10472        }
10473
10474        assert(
10475            editor,
10476            cx,
10477            indoc! {"
10478            type «» =•
10479            "},
10480        );
10481
10482        assert!(editor.context_menu_visible(), "There should be a matches");
10483    });
10484}
10485
10486#[gpui::test]
10487async fn test_snippets(cx: &mut TestAppContext) {
10488    init_test(cx, |_| {});
10489
10490    let mut cx = EditorTestContext::new(cx).await;
10491
10492    cx.set_state(indoc! {"
10493        a.ˇ b
10494        a.ˇ b
10495        a.ˇ b
10496    "});
10497
10498    cx.update_editor(|editor, window, cx| {
10499        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10500        let insertion_ranges = editor
10501            .selections
10502            .all(cx)
10503            .iter()
10504            .map(|s| s.range())
10505            .collect::<Vec<_>>();
10506        editor
10507            .insert_snippet(&insertion_ranges, snippet, window, cx)
10508            .unwrap();
10509    });
10510
10511    cx.assert_editor_state(indoc! {"
10512        a.f(«oneˇ», two, «threeˇ») b
10513        a.f(«oneˇ», two, «threeˇ») b
10514        a.f(«oneˇ», two, «threeˇ») b
10515    "});
10516
10517    // Can't move earlier than the first tab stop
10518    cx.update_editor(|editor, window, cx| {
10519        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10520    });
10521    cx.assert_editor_state(indoc! {"
10522        a.f(«oneˇ», two, «threeˇ») b
10523        a.f(«oneˇ», two, «threeˇ») b
10524        a.f(«oneˇ», two, «threeˇ») b
10525    "});
10526
10527    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10528    cx.assert_editor_state(indoc! {"
10529        a.f(one, «twoˇ», three) b
10530        a.f(one, «twoˇ», three) b
10531        a.f(one, «twoˇ», three) b
10532    "});
10533
10534    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
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    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10548    cx.assert_editor_state(indoc! {"
10549        a.f(one, two, three)ˇ b
10550        a.f(one, two, three)ˇ b
10551        a.f(one, two, three)ˇ b
10552    "});
10553
10554    // As soon as the last tab stop is reached, snippet state is gone
10555    cx.update_editor(|editor, window, cx| {
10556        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10557    });
10558    cx.assert_editor_state(indoc! {"
10559        a.f(one, two, three)ˇ b
10560        a.f(one, two, three)ˇ b
10561        a.f(one, two, three)ˇ b
10562    "});
10563}
10564
10565#[gpui::test]
10566async fn test_snippet_indentation(cx: &mut TestAppContext) {
10567    init_test(cx, |_| {});
10568
10569    let mut cx = EditorTestContext::new(cx).await;
10570
10571    cx.update_editor(|editor, window, cx| {
10572        let snippet = Snippet::parse(indoc! {"
10573            /*
10574             * Multiline comment with leading indentation
10575             *
10576             * $1
10577             */
10578            $0"})
10579        .unwrap();
10580        let insertion_ranges = editor
10581            .selections
10582            .all(cx)
10583            .iter()
10584            .map(|s| s.range())
10585            .collect::<Vec<_>>();
10586        editor
10587            .insert_snippet(&insertion_ranges, snippet, window, cx)
10588            .unwrap();
10589    });
10590
10591    cx.assert_editor_state(indoc! {"
10592        /*
10593         * Multiline comment with leading indentation
10594         *
10595         * ˇ
10596         */
10597    "});
10598
10599    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10600    cx.assert_editor_state(indoc! {"
10601        /*
10602         * Multiline comment with leading indentation
10603         *
10604         *•
10605         */
10606        ˇ"});
10607}
10608
10609#[gpui::test]
10610async fn test_document_format_during_save(cx: &mut TestAppContext) {
10611    init_test(cx, |_| {});
10612
10613    let fs = FakeFs::new(cx.executor());
10614    fs.insert_file(path!("/file.rs"), Default::default()).await;
10615
10616    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10617
10618    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10619    language_registry.add(rust_lang());
10620    let mut fake_servers = language_registry.register_fake_lsp(
10621        "Rust",
10622        FakeLspAdapter {
10623            capabilities: lsp::ServerCapabilities {
10624                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10625                ..Default::default()
10626            },
10627            ..Default::default()
10628        },
10629    );
10630
10631    let buffer = project
10632        .update(cx, |project, cx| {
10633            project.open_local_buffer(path!("/file.rs"), cx)
10634        })
10635        .await
10636        .unwrap();
10637
10638    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10639    let (editor, cx) = cx.add_window_view(|window, cx| {
10640        build_editor_with_project(project.clone(), buffer, window, cx)
10641    });
10642    editor.update_in(cx, |editor, window, cx| {
10643        editor.set_text("one\ntwo\nthree\n", window, cx)
10644    });
10645    assert!(cx.read(|cx| editor.is_dirty(cx)));
10646
10647    cx.executor().start_waiting();
10648    let fake_server = fake_servers.next().await.unwrap();
10649
10650    {
10651        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10652            move |params, _| async move {
10653                assert_eq!(
10654                    params.text_document.uri,
10655                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10656                );
10657                assert_eq!(params.options.tab_size, 4);
10658                Ok(Some(vec![lsp::TextEdit::new(
10659                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10660                    ", ".to_string(),
10661                )]))
10662            },
10663        );
10664        let save = editor
10665            .update_in(cx, |editor, window, cx| {
10666                editor.save(
10667                    SaveOptions {
10668                        format: true,
10669                        autosave: false,
10670                    },
10671                    project.clone(),
10672                    window,
10673                    cx,
10674                )
10675            })
10676            .unwrap();
10677        cx.executor().start_waiting();
10678        save.await;
10679
10680        assert_eq!(
10681            editor.update(cx, |editor, cx| editor.text(cx)),
10682            "one, two\nthree\n"
10683        );
10684        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10685    }
10686
10687    {
10688        editor.update_in(cx, |editor, window, cx| {
10689            editor.set_text("one\ntwo\nthree\n", window, cx)
10690        });
10691        assert!(cx.read(|cx| editor.is_dirty(cx)));
10692
10693        // Ensure we can still save even if formatting hangs.
10694        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10695            move |params, _| async move {
10696                assert_eq!(
10697                    params.text_document.uri,
10698                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10699                );
10700                futures::future::pending::<()>().await;
10701                unreachable!()
10702            },
10703        );
10704        let save = editor
10705            .update_in(cx, |editor, window, cx| {
10706                editor.save(
10707                    SaveOptions {
10708                        format: true,
10709                        autosave: false,
10710                    },
10711                    project.clone(),
10712                    window,
10713                    cx,
10714                )
10715            })
10716            .unwrap();
10717        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10718        cx.executor().start_waiting();
10719        save.await;
10720        assert_eq!(
10721            editor.update(cx, |editor, cx| editor.text(cx)),
10722            "one\ntwo\nthree\n"
10723        );
10724    }
10725
10726    // Set rust language override and assert overridden tabsize is sent to language server
10727    update_test_language_settings(cx, |settings| {
10728        settings.languages.0.insert(
10729            "Rust".into(),
10730            LanguageSettingsContent {
10731                tab_size: NonZeroU32::new(8),
10732                ..Default::default()
10733            },
10734        );
10735    });
10736
10737    {
10738        editor.update_in(cx, |editor, window, cx| {
10739            editor.set_text("somehting_new\n", window, cx)
10740        });
10741        assert!(cx.read(|cx| editor.is_dirty(cx)));
10742        let _formatting_request_signal = fake_server
10743            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10744                assert_eq!(
10745                    params.text_document.uri,
10746                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10747                );
10748                assert_eq!(params.options.tab_size, 8);
10749                Ok(Some(vec![]))
10750            });
10751        let save = editor
10752            .update_in(cx, |editor, window, cx| {
10753                editor.save(
10754                    SaveOptions {
10755                        format: true,
10756                        autosave: false,
10757                    },
10758                    project.clone(),
10759                    window,
10760                    cx,
10761                )
10762            })
10763            .unwrap();
10764        cx.executor().start_waiting();
10765        save.await;
10766    }
10767}
10768
10769#[gpui::test]
10770async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
10771    init_test(cx, |settings| {
10772        settings.defaults.ensure_final_newline_on_save = Some(false);
10773    });
10774
10775    let fs = FakeFs::new(cx.executor());
10776    fs.insert_file(path!("/file.txt"), "foo".into()).await;
10777
10778    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
10779
10780    let buffer = project
10781        .update(cx, |project, cx| {
10782            project.open_local_buffer(path!("/file.txt"), cx)
10783        })
10784        .await
10785        .unwrap();
10786
10787    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10788    let (editor, cx) = cx.add_window_view(|window, cx| {
10789        build_editor_with_project(project.clone(), buffer, window, cx)
10790    });
10791    editor.update_in(cx, |editor, window, cx| {
10792        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
10793            s.select_ranges([0..0])
10794        });
10795    });
10796    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10797
10798    editor.update_in(cx, |editor, window, cx| {
10799        editor.handle_input("\n", window, cx)
10800    });
10801    cx.run_until_parked();
10802    save(&editor, &project, cx).await;
10803    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10804
10805    editor.update_in(cx, |editor, window, cx| {
10806        editor.undo(&Default::default(), window, cx);
10807    });
10808    save(&editor, &project, cx).await;
10809    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10810
10811    editor.update_in(cx, |editor, window, cx| {
10812        editor.redo(&Default::default(), window, cx);
10813    });
10814    cx.run_until_parked();
10815    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10816
10817    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
10818        let save = editor
10819            .update_in(cx, |editor, window, cx| {
10820                editor.save(
10821                    SaveOptions {
10822                        format: true,
10823                        autosave: false,
10824                    },
10825                    project.clone(),
10826                    window,
10827                    cx,
10828                )
10829            })
10830            .unwrap();
10831        cx.executor().start_waiting();
10832        save.await;
10833        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10834    }
10835}
10836
10837#[gpui::test]
10838async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
10839    init_test(cx, |_| {});
10840
10841    let cols = 4;
10842    let rows = 10;
10843    let sample_text_1 = sample_text(rows, cols, 'a');
10844    assert_eq!(
10845        sample_text_1,
10846        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10847    );
10848    let sample_text_2 = sample_text(rows, cols, 'l');
10849    assert_eq!(
10850        sample_text_2,
10851        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10852    );
10853    let sample_text_3 = sample_text(rows, cols, 'v');
10854    assert_eq!(
10855        sample_text_3,
10856        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10857    );
10858
10859    let fs = FakeFs::new(cx.executor());
10860    fs.insert_tree(
10861        path!("/a"),
10862        json!({
10863            "main.rs": sample_text_1,
10864            "other.rs": sample_text_2,
10865            "lib.rs": sample_text_3,
10866        }),
10867    )
10868    .await;
10869
10870    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10871    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10872    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10873
10874    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10875    language_registry.add(rust_lang());
10876    let mut fake_servers = language_registry.register_fake_lsp(
10877        "Rust",
10878        FakeLspAdapter {
10879            capabilities: lsp::ServerCapabilities {
10880                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10881                ..Default::default()
10882            },
10883            ..Default::default()
10884        },
10885    );
10886
10887    let worktree = project.update(cx, |project, cx| {
10888        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
10889        assert_eq!(worktrees.len(), 1);
10890        worktrees.pop().unwrap()
10891    });
10892    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10893
10894    let buffer_1 = project
10895        .update(cx, |project, cx| {
10896            project.open_buffer((worktree_id, "main.rs"), cx)
10897        })
10898        .await
10899        .unwrap();
10900    let buffer_2 = project
10901        .update(cx, |project, cx| {
10902            project.open_buffer((worktree_id, "other.rs"), cx)
10903        })
10904        .await
10905        .unwrap();
10906    let buffer_3 = project
10907        .update(cx, |project, cx| {
10908            project.open_buffer((worktree_id, "lib.rs"), cx)
10909        })
10910        .await
10911        .unwrap();
10912
10913    let multi_buffer = cx.new(|cx| {
10914        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10915        multi_buffer.push_excerpts(
10916            buffer_1.clone(),
10917            [
10918                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10919                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10920                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10921            ],
10922            cx,
10923        );
10924        multi_buffer.push_excerpts(
10925            buffer_2.clone(),
10926            [
10927                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10928                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10929                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10930            ],
10931            cx,
10932        );
10933        multi_buffer.push_excerpts(
10934            buffer_3.clone(),
10935            [
10936                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10937                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10938                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10939            ],
10940            cx,
10941        );
10942        multi_buffer
10943    });
10944    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
10945        Editor::new(
10946            EditorMode::full(),
10947            multi_buffer,
10948            Some(project.clone()),
10949            window,
10950            cx,
10951        )
10952    });
10953
10954    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10955        editor.change_selections(
10956            SelectionEffects::scroll(Autoscroll::Next),
10957            window,
10958            cx,
10959            |s| s.select_ranges(Some(1..2)),
10960        );
10961        editor.insert("|one|two|three|", window, cx);
10962    });
10963    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10964    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10965        editor.change_selections(
10966            SelectionEffects::scroll(Autoscroll::Next),
10967            window,
10968            cx,
10969            |s| s.select_ranges(Some(60..70)),
10970        );
10971        editor.insert("|four|five|six|", window, cx);
10972    });
10973    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10974
10975    // First two buffers should be edited, but not the third one.
10976    assert_eq!(
10977        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10978        "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}",
10979    );
10980    buffer_1.update(cx, |buffer, _| {
10981        assert!(buffer.is_dirty());
10982        assert_eq!(
10983            buffer.text(),
10984            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
10985        )
10986    });
10987    buffer_2.update(cx, |buffer, _| {
10988        assert!(buffer.is_dirty());
10989        assert_eq!(
10990            buffer.text(),
10991            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
10992        )
10993    });
10994    buffer_3.update(cx, |buffer, _| {
10995        assert!(!buffer.is_dirty());
10996        assert_eq!(buffer.text(), sample_text_3,)
10997    });
10998    cx.executor().run_until_parked();
10999
11000    cx.executor().start_waiting();
11001    let save = multi_buffer_editor
11002        .update_in(cx, |editor, window, cx| {
11003            editor.save(
11004                SaveOptions {
11005                    format: true,
11006                    autosave: false,
11007                },
11008                project.clone(),
11009                window,
11010                cx,
11011            )
11012        })
11013        .unwrap();
11014
11015    let fake_server = fake_servers.next().await.unwrap();
11016    fake_server
11017        .server
11018        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11019            Ok(Some(vec![lsp::TextEdit::new(
11020                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11021                format!("[{} formatted]", params.text_document.uri),
11022            )]))
11023        })
11024        .detach();
11025    save.await;
11026
11027    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11028    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11029    assert_eq!(
11030        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11031        uri!(
11032            "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}"
11033        ),
11034    );
11035    buffer_1.update(cx, |buffer, _| {
11036        assert!(!buffer.is_dirty());
11037        assert_eq!(
11038            buffer.text(),
11039            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11040        )
11041    });
11042    buffer_2.update(cx, |buffer, _| {
11043        assert!(!buffer.is_dirty());
11044        assert_eq!(
11045            buffer.text(),
11046            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11047        )
11048    });
11049    buffer_3.update(cx, |buffer, _| {
11050        assert!(!buffer.is_dirty());
11051        assert_eq!(buffer.text(), sample_text_3,)
11052    });
11053}
11054
11055#[gpui::test]
11056async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11057    init_test(cx, |_| {});
11058
11059    let fs = FakeFs::new(cx.executor());
11060    fs.insert_tree(
11061        path!("/dir"),
11062        json!({
11063            "file1.rs": "fn main() { println!(\"hello\"); }",
11064            "file2.rs": "fn test() { println!(\"test\"); }",
11065            "file3.rs": "fn other() { println!(\"other\"); }\n",
11066        }),
11067    )
11068    .await;
11069
11070    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11071    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11072    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11073
11074    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11075    language_registry.add(rust_lang());
11076
11077    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11078    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11079
11080    // Open three buffers
11081    let buffer_1 = project
11082        .update(cx, |project, cx| {
11083            project.open_buffer((worktree_id, "file1.rs"), cx)
11084        })
11085        .await
11086        .unwrap();
11087    let buffer_2 = project
11088        .update(cx, |project, cx| {
11089            project.open_buffer((worktree_id, "file2.rs"), cx)
11090        })
11091        .await
11092        .unwrap();
11093    let buffer_3 = project
11094        .update(cx, |project, cx| {
11095            project.open_buffer((worktree_id, "file3.rs"), cx)
11096        })
11097        .await
11098        .unwrap();
11099
11100    // Create a multi-buffer with all three buffers
11101    let multi_buffer = cx.new(|cx| {
11102        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11103        multi_buffer.push_excerpts(
11104            buffer_1.clone(),
11105            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11106            cx,
11107        );
11108        multi_buffer.push_excerpts(
11109            buffer_2.clone(),
11110            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11111            cx,
11112        );
11113        multi_buffer.push_excerpts(
11114            buffer_3.clone(),
11115            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11116            cx,
11117        );
11118        multi_buffer
11119    });
11120
11121    let editor = cx.new_window_entity(|window, cx| {
11122        Editor::new(
11123            EditorMode::full(),
11124            multi_buffer,
11125            Some(project.clone()),
11126            window,
11127            cx,
11128        )
11129    });
11130
11131    // Edit only the first buffer
11132    editor.update_in(cx, |editor, window, cx| {
11133        editor.change_selections(
11134            SelectionEffects::scroll(Autoscroll::Next),
11135            window,
11136            cx,
11137            |s| s.select_ranges(Some(10..10)),
11138        );
11139        editor.insert("// edited", window, cx);
11140    });
11141
11142    // Verify that only buffer 1 is dirty
11143    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11144    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11145    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11146
11147    // Get write counts after file creation (files were created with initial content)
11148    // We expect each file to have been written once during creation
11149    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11150    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11151    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11152
11153    // Perform autosave
11154    let save_task = editor.update_in(cx, |editor, window, cx| {
11155        editor.save(
11156            SaveOptions {
11157                format: true,
11158                autosave: true,
11159            },
11160            project.clone(),
11161            window,
11162            cx,
11163        )
11164    });
11165    save_task.await.unwrap();
11166
11167    // Only the dirty buffer should have been saved
11168    assert_eq!(
11169        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11170        1,
11171        "Buffer 1 was dirty, so it should have been written once during autosave"
11172    );
11173    assert_eq!(
11174        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11175        0,
11176        "Buffer 2 was clean, so it should not have been written during autosave"
11177    );
11178    assert_eq!(
11179        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11180        0,
11181        "Buffer 3 was clean, so it should not have been written during autosave"
11182    );
11183
11184    // Verify buffer states after autosave
11185    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11186    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11187    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11188
11189    // Now perform a manual save (format = true)
11190    let save_task = editor.update_in(cx, |editor, window, cx| {
11191        editor.save(
11192            SaveOptions {
11193                format: true,
11194                autosave: false,
11195            },
11196            project.clone(),
11197            window,
11198            cx,
11199        )
11200    });
11201    save_task.await.unwrap();
11202
11203    // During manual save, clean buffers don't get written to disk
11204    // They just get did_save called for language server notifications
11205    assert_eq!(
11206        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11207        1,
11208        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11209    );
11210    assert_eq!(
11211        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11212        0,
11213        "Buffer 2 should not have been written at all"
11214    );
11215    assert_eq!(
11216        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11217        0,
11218        "Buffer 3 should not have been written at all"
11219    );
11220}
11221
11222async fn setup_range_format_test(
11223    cx: &mut TestAppContext,
11224) -> (
11225    Entity<Project>,
11226    Entity<Editor>,
11227    &mut gpui::VisualTestContext,
11228    lsp::FakeLanguageServer,
11229) {
11230    init_test(cx, |_| {});
11231
11232    let fs = FakeFs::new(cx.executor());
11233    fs.insert_file(path!("/file.rs"), Default::default()).await;
11234
11235    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11236
11237    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11238    language_registry.add(rust_lang());
11239    let mut fake_servers = language_registry.register_fake_lsp(
11240        "Rust",
11241        FakeLspAdapter {
11242            capabilities: lsp::ServerCapabilities {
11243                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11244                ..lsp::ServerCapabilities::default()
11245            },
11246            ..FakeLspAdapter::default()
11247        },
11248    );
11249
11250    let buffer = project
11251        .update(cx, |project, cx| {
11252            project.open_local_buffer(path!("/file.rs"), cx)
11253        })
11254        .await
11255        .unwrap();
11256
11257    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11258    let (editor, cx) = cx.add_window_view(|window, cx| {
11259        build_editor_with_project(project.clone(), buffer, window, cx)
11260    });
11261
11262    cx.executor().start_waiting();
11263    let fake_server = fake_servers.next().await.unwrap();
11264
11265    (project, editor, cx, fake_server)
11266}
11267
11268#[gpui::test]
11269async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11270    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11271
11272    editor.update_in(cx, |editor, window, cx| {
11273        editor.set_text("one\ntwo\nthree\n", window, cx)
11274    });
11275    assert!(cx.read(|cx| editor.is_dirty(cx)));
11276
11277    let save = editor
11278        .update_in(cx, |editor, window, cx| {
11279            editor.save(
11280                SaveOptions {
11281                    format: true,
11282                    autosave: false,
11283                },
11284                project.clone(),
11285                window,
11286                cx,
11287            )
11288        })
11289        .unwrap();
11290    fake_server
11291        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11292            assert_eq!(
11293                params.text_document.uri,
11294                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11295            );
11296            assert_eq!(params.options.tab_size, 4);
11297            Ok(Some(vec![lsp::TextEdit::new(
11298                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11299                ", ".to_string(),
11300            )]))
11301        })
11302        .next()
11303        .await;
11304    cx.executor().start_waiting();
11305    save.await;
11306    assert_eq!(
11307        editor.update(cx, |editor, cx| editor.text(cx)),
11308        "one, two\nthree\n"
11309    );
11310    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11311}
11312
11313#[gpui::test]
11314async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11315    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11316
11317    editor.update_in(cx, |editor, window, cx| {
11318        editor.set_text("one\ntwo\nthree\n", window, cx)
11319    });
11320    assert!(cx.read(|cx| editor.is_dirty(cx)));
11321
11322    // Test that save still works when formatting hangs
11323    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11324        move |params, _| async move {
11325            assert_eq!(
11326                params.text_document.uri,
11327                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11328            );
11329            futures::future::pending::<()>().await;
11330            unreachable!()
11331        },
11332    );
11333    let save = editor
11334        .update_in(cx, |editor, window, cx| {
11335            editor.save(
11336                SaveOptions {
11337                    format: true,
11338                    autosave: false,
11339                },
11340                project.clone(),
11341                window,
11342                cx,
11343            )
11344        })
11345        .unwrap();
11346    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11347    cx.executor().start_waiting();
11348    save.await;
11349    assert_eq!(
11350        editor.update(cx, |editor, cx| editor.text(cx)),
11351        "one\ntwo\nthree\n"
11352    );
11353    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11354}
11355
11356#[gpui::test]
11357async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11358    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11359
11360    // Buffer starts clean, no formatting should be requested
11361    let save = editor
11362        .update_in(cx, |editor, window, cx| {
11363            editor.save(
11364                SaveOptions {
11365                    format: false,
11366                    autosave: false,
11367                },
11368                project.clone(),
11369                window,
11370                cx,
11371            )
11372        })
11373        .unwrap();
11374    let _pending_format_request = fake_server
11375        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11376            panic!("Should not be invoked");
11377        })
11378        .next();
11379    cx.executor().start_waiting();
11380    save.await;
11381    cx.run_until_parked();
11382}
11383
11384#[gpui::test]
11385async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11386    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11387
11388    // Set Rust language override and assert overridden tabsize is sent to language server
11389    update_test_language_settings(cx, |settings| {
11390        settings.languages.0.insert(
11391            "Rust".into(),
11392            LanguageSettingsContent {
11393                tab_size: NonZeroU32::new(8),
11394                ..Default::default()
11395            },
11396        );
11397    });
11398
11399    editor.update_in(cx, |editor, window, cx| {
11400        editor.set_text("something_new\n", window, cx)
11401    });
11402    assert!(cx.read(|cx| editor.is_dirty(cx)));
11403    let save = editor
11404        .update_in(cx, |editor, window, cx| {
11405            editor.save(
11406                SaveOptions {
11407                    format: true,
11408                    autosave: false,
11409                },
11410                project.clone(),
11411                window,
11412                cx,
11413            )
11414        })
11415        .unwrap();
11416    fake_server
11417        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11418            assert_eq!(
11419                params.text_document.uri,
11420                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11421            );
11422            assert_eq!(params.options.tab_size, 8);
11423            Ok(Some(Vec::new()))
11424        })
11425        .next()
11426        .await;
11427    save.await;
11428}
11429
11430#[gpui::test]
11431async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11432    init_test(cx, |settings| {
11433        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11434            Formatter::LanguageServer { name: None },
11435        )))
11436    });
11437
11438    let fs = FakeFs::new(cx.executor());
11439    fs.insert_file(path!("/file.rs"), Default::default()).await;
11440
11441    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11442
11443    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11444    language_registry.add(Arc::new(Language::new(
11445        LanguageConfig {
11446            name: "Rust".into(),
11447            matcher: LanguageMatcher {
11448                path_suffixes: vec!["rs".to_string()],
11449                ..Default::default()
11450            },
11451            ..LanguageConfig::default()
11452        },
11453        Some(tree_sitter_rust::LANGUAGE.into()),
11454    )));
11455    update_test_language_settings(cx, |settings| {
11456        // Enable Prettier formatting for the same buffer, and ensure
11457        // LSP is called instead of Prettier.
11458        settings.defaults.prettier = Some(PrettierSettings {
11459            allowed: true,
11460            ..PrettierSettings::default()
11461        });
11462    });
11463    let mut fake_servers = language_registry.register_fake_lsp(
11464        "Rust",
11465        FakeLspAdapter {
11466            capabilities: lsp::ServerCapabilities {
11467                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11468                ..Default::default()
11469            },
11470            ..Default::default()
11471        },
11472    );
11473
11474    let buffer = project
11475        .update(cx, |project, cx| {
11476            project.open_local_buffer(path!("/file.rs"), cx)
11477        })
11478        .await
11479        .unwrap();
11480
11481    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11482    let (editor, cx) = cx.add_window_view(|window, cx| {
11483        build_editor_with_project(project.clone(), buffer, window, cx)
11484    });
11485    editor.update_in(cx, |editor, window, cx| {
11486        editor.set_text("one\ntwo\nthree\n", window, cx)
11487    });
11488
11489    cx.executor().start_waiting();
11490    let fake_server = fake_servers.next().await.unwrap();
11491
11492    let format = editor
11493        .update_in(cx, |editor, window, cx| {
11494            editor.perform_format(
11495                project.clone(),
11496                FormatTrigger::Manual,
11497                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11498                window,
11499                cx,
11500            )
11501        })
11502        .unwrap();
11503    fake_server
11504        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11505            assert_eq!(
11506                params.text_document.uri,
11507                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11508            );
11509            assert_eq!(params.options.tab_size, 4);
11510            Ok(Some(vec![lsp::TextEdit::new(
11511                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11512                ", ".to_string(),
11513            )]))
11514        })
11515        .next()
11516        .await;
11517    cx.executor().start_waiting();
11518    format.await;
11519    assert_eq!(
11520        editor.update(cx, |editor, cx| editor.text(cx)),
11521        "one, two\nthree\n"
11522    );
11523
11524    editor.update_in(cx, |editor, window, cx| {
11525        editor.set_text("one\ntwo\nthree\n", window, cx)
11526    });
11527    // Ensure we don't lock if formatting hangs.
11528    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11529        move |params, _| async move {
11530            assert_eq!(
11531                params.text_document.uri,
11532                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11533            );
11534            futures::future::pending::<()>().await;
11535            unreachable!()
11536        },
11537    );
11538    let format = editor
11539        .update_in(cx, |editor, window, cx| {
11540            editor.perform_format(
11541                project,
11542                FormatTrigger::Manual,
11543                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11544                window,
11545                cx,
11546            )
11547        })
11548        .unwrap();
11549    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11550    cx.executor().start_waiting();
11551    format.await;
11552    assert_eq!(
11553        editor.update(cx, |editor, cx| editor.text(cx)),
11554        "one\ntwo\nthree\n"
11555    );
11556}
11557
11558#[gpui::test]
11559async fn test_multiple_formatters(cx: &mut TestAppContext) {
11560    init_test(cx, |settings| {
11561        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11562        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11563            Formatter::LanguageServer { name: None },
11564            Formatter::CodeActions(
11565                [
11566                    ("code-action-1".into(), true),
11567                    ("code-action-2".into(), true),
11568                ]
11569                .into_iter()
11570                .collect(),
11571            ),
11572        ])))
11573    });
11574
11575    let fs = FakeFs::new(cx.executor());
11576    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11577        .await;
11578
11579    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11580    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11581    language_registry.add(rust_lang());
11582
11583    let mut fake_servers = language_registry.register_fake_lsp(
11584        "Rust",
11585        FakeLspAdapter {
11586            capabilities: lsp::ServerCapabilities {
11587                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11588                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11589                    commands: vec!["the-command-for-code-action-1".into()],
11590                    ..Default::default()
11591                }),
11592                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11593                ..Default::default()
11594            },
11595            ..Default::default()
11596        },
11597    );
11598
11599    let buffer = project
11600        .update(cx, |project, cx| {
11601            project.open_local_buffer(path!("/file.rs"), cx)
11602        })
11603        .await
11604        .unwrap();
11605
11606    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11607    let (editor, cx) = cx.add_window_view(|window, cx| {
11608        build_editor_with_project(project.clone(), buffer, window, cx)
11609    });
11610
11611    cx.executor().start_waiting();
11612
11613    let fake_server = fake_servers.next().await.unwrap();
11614    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11615        move |_params, _| async move {
11616            Ok(Some(vec![lsp::TextEdit::new(
11617                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11618                "applied-formatting\n".to_string(),
11619            )]))
11620        },
11621    );
11622    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11623        move |params, _| async move {
11624            assert_eq!(
11625                params.context.only,
11626                Some(vec!["code-action-1".into(), "code-action-2".into()])
11627            );
11628            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11629            Ok(Some(vec![
11630                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11631                    kind: Some("code-action-1".into()),
11632                    edit: Some(lsp::WorkspaceEdit::new(
11633                        [(
11634                            uri.clone(),
11635                            vec![lsp::TextEdit::new(
11636                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11637                                "applied-code-action-1-edit\n".to_string(),
11638                            )],
11639                        )]
11640                        .into_iter()
11641                        .collect(),
11642                    )),
11643                    command: Some(lsp::Command {
11644                        command: "the-command-for-code-action-1".into(),
11645                        ..Default::default()
11646                    }),
11647                    ..Default::default()
11648                }),
11649                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11650                    kind: Some("code-action-2".into()),
11651                    edit: Some(lsp::WorkspaceEdit::new(
11652                        [(
11653                            uri,
11654                            vec![lsp::TextEdit::new(
11655                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11656                                "applied-code-action-2-edit\n".to_string(),
11657                            )],
11658                        )]
11659                        .into_iter()
11660                        .collect(),
11661                    )),
11662                    ..Default::default()
11663                }),
11664            ]))
11665        },
11666    );
11667
11668    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11669        move |params, _| async move { Ok(params) }
11670    });
11671
11672    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11673    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11674        let fake = fake_server.clone();
11675        let lock = command_lock.clone();
11676        move |params, _| {
11677            assert_eq!(params.command, "the-command-for-code-action-1");
11678            let fake = fake.clone();
11679            let lock = lock.clone();
11680            async move {
11681                lock.lock().await;
11682                fake.server
11683                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11684                        label: None,
11685                        edit: lsp::WorkspaceEdit {
11686                            changes: Some(
11687                                [(
11688                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11689                                    vec![lsp::TextEdit {
11690                                        range: lsp::Range::new(
11691                                            lsp::Position::new(0, 0),
11692                                            lsp::Position::new(0, 0),
11693                                        ),
11694                                        new_text: "applied-code-action-1-command\n".into(),
11695                                    }],
11696                                )]
11697                                .into_iter()
11698                                .collect(),
11699                            ),
11700                            ..Default::default()
11701                        },
11702                    })
11703                    .await
11704                    .into_response()
11705                    .unwrap();
11706                Ok(Some(json!(null)))
11707            }
11708        }
11709    });
11710
11711    cx.executor().start_waiting();
11712    editor
11713        .update_in(cx, |editor, window, cx| {
11714            editor.perform_format(
11715                project.clone(),
11716                FormatTrigger::Manual,
11717                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11718                window,
11719                cx,
11720            )
11721        })
11722        .unwrap()
11723        .await;
11724    editor.update(cx, |editor, cx| {
11725        assert_eq!(
11726            editor.text(cx),
11727            r#"
11728                applied-code-action-2-edit
11729                applied-code-action-1-command
11730                applied-code-action-1-edit
11731                applied-formatting
11732                one
11733                two
11734                three
11735            "#
11736            .unindent()
11737        );
11738    });
11739
11740    editor.update_in(cx, |editor, window, cx| {
11741        editor.undo(&Default::default(), window, cx);
11742        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11743    });
11744
11745    // Perform a manual edit while waiting for an LSP command
11746    // that's being run as part of a formatting code action.
11747    let lock_guard = command_lock.lock().await;
11748    let format = editor
11749        .update_in(cx, |editor, window, cx| {
11750            editor.perform_format(
11751                project.clone(),
11752                FormatTrigger::Manual,
11753                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11754                window,
11755                cx,
11756            )
11757        })
11758        .unwrap();
11759    cx.run_until_parked();
11760    editor.update(cx, |editor, cx| {
11761        assert_eq!(
11762            editor.text(cx),
11763            r#"
11764                applied-code-action-1-edit
11765                applied-formatting
11766                one
11767                two
11768                three
11769            "#
11770            .unindent()
11771        );
11772
11773        editor.buffer.update(cx, |buffer, cx| {
11774            let ix = buffer.len(cx);
11775            buffer.edit([(ix..ix, "edited\n")], None, cx);
11776        });
11777    });
11778
11779    // Allow the LSP command to proceed. Because the buffer was edited,
11780    // the second code action will not be run.
11781    drop(lock_guard);
11782    format.await;
11783    editor.update_in(cx, |editor, window, cx| {
11784        assert_eq!(
11785            editor.text(cx),
11786            r#"
11787                applied-code-action-1-command
11788                applied-code-action-1-edit
11789                applied-formatting
11790                one
11791                two
11792                three
11793                edited
11794            "#
11795            .unindent()
11796        );
11797
11798        // The manual edit is undone first, because it is the last thing the user did
11799        // (even though the command completed afterwards).
11800        editor.undo(&Default::default(), window, cx);
11801        assert_eq!(
11802            editor.text(cx),
11803            r#"
11804                applied-code-action-1-command
11805                applied-code-action-1-edit
11806                applied-formatting
11807                one
11808                two
11809                three
11810            "#
11811            .unindent()
11812        );
11813
11814        // All the formatting (including the command, which completed after the manual edit)
11815        // is undone together.
11816        editor.undo(&Default::default(), window, cx);
11817        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11818    });
11819}
11820
11821#[gpui::test]
11822async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
11823    init_test(cx, |settings| {
11824        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11825            Formatter::LanguageServer { name: None },
11826        ])))
11827    });
11828
11829    let fs = FakeFs::new(cx.executor());
11830    fs.insert_file(path!("/file.ts"), Default::default()).await;
11831
11832    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11833
11834    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11835    language_registry.add(Arc::new(Language::new(
11836        LanguageConfig {
11837            name: "TypeScript".into(),
11838            matcher: LanguageMatcher {
11839                path_suffixes: vec!["ts".to_string()],
11840                ..Default::default()
11841            },
11842            ..LanguageConfig::default()
11843        },
11844        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11845    )));
11846    update_test_language_settings(cx, |settings| {
11847        settings.defaults.prettier = Some(PrettierSettings {
11848            allowed: true,
11849            ..PrettierSettings::default()
11850        });
11851    });
11852    let mut fake_servers = language_registry.register_fake_lsp(
11853        "TypeScript",
11854        FakeLspAdapter {
11855            capabilities: lsp::ServerCapabilities {
11856                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11857                ..Default::default()
11858            },
11859            ..Default::default()
11860        },
11861    );
11862
11863    let buffer = project
11864        .update(cx, |project, cx| {
11865            project.open_local_buffer(path!("/file.ts"), cx)
11866        })
11867        .await
11868        .unwrap();
11869
11870    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11871    let (editor, cx) = cx.add_window_view(|window, cx| {
11872        build_editor_with_project(project.clone(), buffer, window, cx)
11873    });
11874    editor.update_in(cx, |editor, window, cx| {
11875        editor.set_text(
11876            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11877            window,
11878            cx,
11879        )
11880    });
11881
11882    cx.executor().start_waiting();
11883    let fake_server = fake_servers.next().await.unwrap();
11884
11885    let format = editor
11886        .update_in(cx, |editor, window, cx| {
11887            editor.perform_code_action_kind(
11888                project.clone(),
11889                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11890                window,
11891                cx,
11892            )
11893        })
11894        .unwrap();
11895    fake_server
11896        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
11897            assert_eq!(
11898                params.text_document.uri,
11899                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
11900            );
11901            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11902                lsp::CodeAction {
11903                    title: "Organize Imports".to_string(),
11904                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
11905                    edit: Some(lsp::WorkspaceEdit {
11906                        changes: Some(
11907                            [(
11908                                params.text_document.uri.clone(),
11909                                vec![lsp::TextEdit::new(
11910                                    lsp::Range::new(
11911                                        lsp::Position::new(1, 0),
11912                                        lsp::Position::new(2, 0),
11913                                    ),
11914                                    "".to_string(),
11915                                )],
11916                            )]
11917                            .into_iter()
11918                            .collect(),
11919                        ),
11920                        ..Default::default()
11921                    }),
11922                    ..Default::default()
11923                },
11924            )]))
11925        })
11926        .next()
11927        .await;
11928    cx.executor().start_waiting();
11929    format.await;
11930    assert_eq!(
11931        editor.update(cx, |editor, cx| editor.text(cx)),
11932        "import { a } from 'module';\n\nconst x = a;\n"
11933    );
11934
11935    editor.update_in(cx, |editor, window, cx| {
11936        editor.set_text(
11937            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11938            window,
11939            cx,
11940        )
11941    });
11942    // Ensure we don't lock if code action hangs.
11943    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11944        move |params, _| async move {
11945            assert_eq!(
11946                params.text_document.uri,
11947                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
11948            );
11949            futures::future::pending::<()>().await;
11950            unreachable!()
11951        },
11952    );
11953    let format = editor
11954        .update_in(cx, |editor, window, cx| {
11955            editor.perform_code_action_kind(
11956                project,
11957                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11958                window,
11959                cx,
11960            )
11961        })
11962        .unwrap();
11963    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
11964    cx.executor().start_waiting();
11965    format.await;
11966    assert_eq!(
11967        editor.update(cx, |editor, cx| editor.text(cx)),
11968        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
11969    );
11970}
11971
11972#[gpui::test]
11973async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
11974    init_test(cx, |_| {});
11975
11976    let mut cx = EditorLspTestContext::new_rust(
11977        lsp::ServerCapabilities {
11978            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11979            ..Default::default()
11980        },
11981        cx,
11982    )
11983    .await;
11984
11985    cx.set_state(indoc! {"
11986        one.twoˇ
11987    "});
11988
11989    // The format request takes a long time. When it completes, it inserts
11990    // a newline and an indent before the `.`
11991    cx.lsp
11992        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
11993            let executor = cx.background_executor().clone();
11994            async move {
11995                executor.timer(Duration::from_millis(100)).await;
11996                Ok(Some(vec![lsp::TextEdit {
11997                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
11998                    new_text: "\n    ".into(),
11999                }]))
12000            }
12001        });
12002
12003    // Submit a format request.
12004    let format_1 = cx
12005        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12006        .unwrap();
12007    cx.executor().run_until_parked();
12008
12009    // Submit a second format request.
12010    let format_2 = cx
12011        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12012        .unwrap();
12013    cx.executor().run_until_parked();
12014
12015    // Wait for both format requests to complete
12016    cx.executor().advance_clock(Duration::from_millis(200));
12017    cx.executor().start_waiting();
12018    format_1.await.unwrap();
12019    cx.executor().start_waiting();
12020    format_2.await.unwrap();
12021
12022    // The formatting edits only happens once.
12023    cx.assert_editor_state(indoc! {"
12024        one
12025            .twoˇ
12026    "});
12027}
12028
12029#[gpui::test]
12030async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12031    init_test(cx, |settings| {
12032        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12033    });
12034
12035    let mut cx = EditorLspTestContext::new_rust(
12036        lsp::ServerCapabilities {
12037            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12038            ..Default::default()
12039        },
12040        cx,
12041    )
12042    .await;
12043
12044    // Set up a buffer white some trailing whitespace and no trailing newline.
12045    cx.set_state(
12046        &[
12047            "one ",   //
12048            "twoˇ",   //
12049            "three ", //
12050            "four",   //
12051        ]
12052        .join("\n"),
12053    );
12054
12055    // Submit a format request.
12056    let format = cx
12057        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12058        .unwrap();
12059
12060    // Record which buffer changes have been sent to the language server
12061    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12062    cx.lsp
12063        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12064            let buffer_changes = buffer_changes.clone();
12065            move |params, _| {
12066                buffer_changes.lock().extend(
12067                    params
12068                        .content_changes
12069                        .into_iter()
12070                        .map(|e| (e.range.unwrap(), e.text)),
12071                );
12072            }
12073        });
12074
12075    // Handle formatting requests to the language server.
12076    cx.lsp
12077        .set_request_handler::<lsp::request::Formatting, _, _>({
12078            let buffer_changes = buffer_changes.clone();
12079            move |_, _| {
12080                // When formatting is requested, trailing whitespace has already been stripped,
12081                // and the trailing newline has already been added.
12082                assert_eq!(
12083                    &buffer_changes.lock()[1..],
12084                    &[
12085                        (
12086                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12087                            "".into()
12088                        ),
12089                        (
12090                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12091                            "".into()
12092                        ),
12093                        (
12094                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12095                            "\n".into()
12096                        ),
12097                    ]
12098                );
12099
12100                // Insert blank lines between each line of the buffer.
12101                async move {
12102                    Ok(Some(vec![
12103                        lsp::TextEdit {
12104                            range: lsp::Range::new(
12105                                lsp::Position::new(1, 0),
12106                                lsp::Position::new(1, 0),
12107                            ),
12108                            new_text: "\n".into(),
12109                        },
12110                        lsp::TextEdit {
12111                            range: lsp::Range::new(
12112                                lsp::Position::new(2, 0),
12113                                lsp::Position::new(2, 0),
12114                            ),
12115                            new_text: "\n".into(),
12116                        },
12117                    ]))
12118                }
12119            }
12120        });
12121
12122    // After formatting the buffer, the trailing whitespace is stripped,
12123    // a newline is appended, and the edits provided by the language server
12124    // have been applied.
12125    format.await.unwrap();
12126    cx.assert_editor_state(
12127        &[
12128            "one",   //
12129            "",      //
12130            "twoˇ",  //
12131            "",      //
12132            "three", //
12133            "four",  //
12134            "",      //
12135        ]
12136        .join("\n"),
12137    );
12138
12139    // Undoing the formatting undoes the trailing whitespace removal, the
12140    // trailing newline, and the LSP edits.
12141    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12142    cx.assert_editor_state(
12143        &[
12144            "one ",   //
12145            "twoˇ",   //
12146            "three ", //
12147            "four",   //
12148        ]
12149        .join("\n"),
12150    );
12151}
12152
12153#[gpui::test]
12154async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12155    cx: &mut TestAppContext,
12156) {
12157    init_test(cx, |_| {});
12158
12159    cx.update(|cx| {
12160        cx.update_global::<SettingsStore, _>(|settings, cx| {
12161            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12162                settings.auto_signature_help = Some(true);
12163            });
12164        });
12165    });
12166
12167    let mut cx = EditorLspTestContext::new_rust(
12168        lsp::ServerCapabilities {
12169            signature_help_provider: Some(lsp::SignatureHelpOptions {
12170                ..Default::default()
12171            }),
12172            ..Default::default()
12173        },
12174        cx,
12175    )
12176    .await;
12177
12178    let language = Language::new(
12179        LanguageConfig {
12180            name: "Rust".into(),
12181            brackets: BracketPairConfig {
12182                pairs: vec![
12183                    BracketPair {
12184                        start: "{".to_string(),
12185                        end: "}".to_string(),
12186                        close: true,
12187                        surround: true,
12188                        newline: true,
12189                    },
12190                    BracketPair {
12191                        start: "(".to_string(),
12192                        end: ")".to_string(),
12193                        close: true,
12194                        surround: true,
12195                        newline: true,
12196                    },
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: false,
12208                        surround: false,
12209                        newline: true,
12210                    },
12211                    BracketPair {
12212                        start: "\"".to_string(),
12213                        end: "\"".to_string(),
12214                        close: true,
12215                        surround: true,
12216                        newline: false,
12217                    },
12218                    BracketPair {
12219                        start: "<".to_string(),
12220                        end: ">".to_string(),
12221                        close: false,
12222                        surround: true,
12223                        newline: true,
12224                    },
12225                ],
12226                ..Default::default()
12227            },
12228            autoclose_before: "})]".to_string(),
12229            ..Default::default()
12230        },
12231        Some(tree_sitter_rust::LANGUAGE.into()),
12232    );
12233    let language = Arc::new(language);
12234
12235    cx.language_registry().add(language.clone());
12236    cx.update_buffer(|buffer, cx| {
12237        buffer.set_language(Some(language), cx);
12238    });
12239
12240    cx.set_state(
12241        &r#"
12242            fn main() {
12243                sampleˇ
12244            }
12245        "#
12246        .unindent(),
12247    );
12248
12249    cx.update_editor(|editor, window, cx| {
12250        editor.handle_input("(", window, cx);
12251    });
12252    cx.assert_editor_state(
12253        &"
12254            fn main() {
12255                sample(ˇ)
12256            }
12257        "
12258        .unindent(),
12259    );
12260
12261    let mocked_response = lsp::SignatureHelp {
12262        signatures: vec![lsp::SignatureInformation {
12263            label: "fn sample(param1: u8, param2: u8)".to_string(),
12264            documentation: None,
12265            parameters: Some(vec![
12266                lsp::ParameterInformation {
12267                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12268                    documentation: None,
12269                },
12270                lsp::ParameterInformation {
12271                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12272                    documentation: None,
12273                },
12274            ]),
12275            active_parameter: None,
12276        }],
12277        active_signature: Some(0),
12278        active_parameter: Some(0),
12279    };
12280    handle_signature_help_request(&mut cx, mocked_response).await;
12281
12282    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12283        .await;
12284
12285    cx.editor(|editor, _, _| {
12286        let signature_help_state = editor.signature_help_state.popover().cloned();
12287        let signature = signature_help_state.unwrap();
12288        assert_eq!(
12289            signature.signatures[signature.current_signature].label,
12290            "fn sample(param1: u8, param2: u8)"
12291        );
12292    });
12293}
12294
12295#[gpui::test]
12296async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12297    init_test(cx, |_| {});
12298
12299    cx.update(|cx| {
12300        cx.update_global::<SettingsStore, _>(|settings, cx| {
12301            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12302                settings.auto_signature_help = Some(false);
12303                settings.show_signature_help_after_edits = Some(false);
12304            });
12305        });
12306    });
12307
12308    let mut cx = EditorLspTestContext::new_rust(
12309        lsp::ServerCapabilities {
12310            signature_help_provider: Some(lsp::SignatureHelpOptions {
12311                ..Default::default()
12312            }),
12313            ..Default::default()
12314        },
12315        cx,
12316    )
12317    .await;
12318
12319    let language = Language::new(
12320        LanguageConfig {
12321            name: "Rust".into(),
12322            brackets: BracketPairConfig {
12323                pairs: vec![
12324                    BracketPair {
12325                        start: "{".to_string(),
12326                        end: "}".to_string(),
12327                        close: true,
12328                        surround: true,
12329                        newline: true,
12330                    },
12331                    BracketPair {
12332                        start: "(".to_string(),
12333                        end: ")".to_string(),
12334                        close: true,
12335                        surround: true,
12336                        newline: true,
12337                    },
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: false,
12349                        surround: false,
12350                        newline: true,
12351                    },
12352                    BracketPair {
12353                        start: "\"".to_string(),
12354                        end: "\"".to_string(),
12355                        close: true,
12356                        surround: true,
12357                        newline: false,
12358                    },
12359                    BracketPair {
12360                        start: "<".to_string(),
12361                        end: ">".to_string(),
12362                        close: false,
12363                        surround: true,
12364                        newline: true,
12365                    },
12366                ],
12367                ..Default::default()
12368            },
12369            autoclose_before: "})]".to_string(),
12370            ..Default::default()
12371        },
12372        Some(tree_sitter_rust::LANGUAGE.into()),
12373    );
12374    let language = Arc::new(language);
12375
12376    cx.language_registry().add(language.clone());
12377    cx.update_buffer(|buffer, cx| {
12378        buffer.set_language(Some(language), cx);
12379    });
12380
12381    // Ensure that signature_help is not called when no signature help is enabled.
12382    cx.set_state(
12383        &r#"
12384            fn main() {
12385                sampleˇ
12386            }
12387        "#
12388        .unindent(),
12389    );
12390    cx.update_editor(|editor, window, cx| {
12391        editor.handle_input("(", window, cx);
12392    });
12393    cx.assert_editor_state(
12394        &"
12395            fn main() {
12396                sample(ˇ)
12397            }
12398        "
12399        .unindent(),
12400    );
12401    cx.editor(|editor, _, _| {
12402        assert!(editor.signature_help_state.task().is_none());
12403    });
12404
12405    let mocked_response = lsp::SignatureHelp {
12406        signatures: vec![lsp::SignatureInformation {
12407            label: "fn sample(param1: u8, param2: u8)".to_string(),
12408            documentation: None,
12409            parameters: Some(vec![
12410                lsp::ParameterInformation {
12411                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12412                    documentation: None,
12413                },
12414                lsp::ParameterInformation {
12415                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12416                    documentation: None,
12417                },
12418            ]),
12419            active_parameter: None,
12420        }],
12421        active_signature: Some(0),
12422        active_parameter: Some(0),
12423    };
12424
12425    // Ensure that signature_help is called when enabled afte edits
12426    cx.update(|_, cx| {
12427        cx.update_global::<SettingsStore, _>(|settings, cx| {
12428            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12429                settings.auto_signature_help = Some(false);
12430                settings.show_signature_help_after_edits = Some(true);
12431            });
12432        });
12433    });
12434    cx.set_state(
12435        &r#"
12436            fn main() {
12437                sampleˇ
12438            }
12439        "#
12440        .unindent(),
12441    );
12442    cx.update_editor(|editor, window, cx| {
12443        editor.handle_input("(", window, cx);
12444    });
12445    cx.assert_editor_state(
12446        &"
12447            fn main() {
12448                sample(ˇ)
12449            }
12450        "
12451        .unindent(),
12452    );
12453    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12454    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12455        .await;
12456    cx.update_editor(|editor, _, _| {
12457        let signature_help_state = editor.signature_help_state.popover().cloned();
12458        assert!(signature_help_state.is_some());
12459        let signature = signature_help_state.unwrap();
12460        assert_eq!(
12461            signature.signatures[signature.current_signature].label,
12462            "fn sample(param1: u8, param2: u8)"
12463        );
12464        editor.signature_help_state = SignatureHelpState::default();
12465    });
12466
12467    // Ensure that signature_help is called when auto signature help override is enabled
12468    cx.update(|_, cx| {
12469        cx.update_global::<SettingsStore, _>(|settings, cx| {
12470            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12471                settings.auto_signature_help = Some(true);
12472                settings.show_signature_help_after_edits = Some(false);
12473            });
12474        });
12475    });
12476    cx.set_state(
12477        &r#"
12478            fn main() {
12479                sampleˇ
12480            }
12481        "#
12482        .unindent(),
12483    );
12484    cx.update_editor(|editor, window, cx| {
12485        editor.handle_input("(", window, cx);
12486    });
12487    cx.assert_editor_state(
12488        &"
12489            fn main() {
12490                sample(ˇ)
12491            }
12492        "
12493        .unindent(),
12494    );
12495    handle_signature_help_request(&mut cx, mocked_response).await;
12496    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12497        .await;
12498    cx.editor(|editor, _, _| {
12499        let signature_help_state = editor.signature_help_state.popover().cloned();
12500        assert!(signature_help_state.is_some());
12501        let signature = signature_help_state.unwrap();
12502        assert_eq!(
12503            signature.signatures[signature.current_signature].label,
12504            "fn sample(param1: u8, param2: u8)"
12505        );
12506    });
12507}
12508
12509#[gpui::test]
12510async fn test_signature_help(cx: &mut TestAppContext) {
12511    init_test(cx, |_| {});
12512    cx.update(|cx| {
12513        cx.update_global::<SettingsStore, _>(|settings, cx| {
12514            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12515                settings.auto_signature_help = Some(true);
12516            });
12517        });
12518    });
12519
12520    let mut cx = EditorLspTestContext::new_rust(
12521        lsp::ServerCapabilities {
12522            signature_help_provider: Some(lsp::SignatureHelpOptions {
12523                ..Default::default()
12524            }),
12525            ..Default::default()
12526        },
12527        cx,
12528    )
12529    .await;
12530
12531    // A test that directly calls `show_signature_help`
12532    cx.update_editor(|editor, window, cx| {
12533        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12534    });
12535
12536    let mocked_response = lsp::SignatureHelp {
12537        signatures: vec![lsp::SignatureInformation {
12538            label: "fn sample(param1: u8, param2: u8)".to_string(),
12539            documentation: None,
12540            parameters: Some(vec![
12541                lsp::ParameterInformation {
12542                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12543                    documentation: None,
12544                },
12545                lsp::ParameterInformation {
12546                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12547                    documentation: None,
12548                },
12549            ]),
12550            active_parameter: None,
12551        }],
12552        active_signature: Some(0),
12553        active_parameter: Some(0),
12554    };
12555    handle_signature_help_request(&mut cx, mocked_response).await;
12556
12557    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12558        .await;
12559
12560    cx.editor(|editor, _, _| {
12561        let signature_help_state = editor.signature_help_state.popover().cloned();
12562        assert!(signature_help_state.is_some());
12563        let signature = signature_help_state.unwrap();
12564        assert_eq!(
12565            signature.signatures[signature.current_signature].label,
12566            "fn sample(param1: u8, param2: u8)"
12567        );
12568    });
12569
12570    // When exiting outside from inside the brackets, `signature_help` is closed.
12571    cx.set_state(indoc! {"
12572        fn main() {
12573            sample(ˇ);
12574        }
12575
12576        fn sample(param1: u8, param2: u8) {}
12577    "});
12578
12579    cx.update_editor(|editor, window, cx| {
12580        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12581            s.select_ranges([0..0])
12582        });
12583    });
12584
12585    let mocked_response = lsp::SignatureHelp {
12586        signatures: Vec::new(),
12587        active_signature: None,
12588        active_parameter: None,
12589    };
12590    handle_signature_help_request(&mut cx, mocked_response).await;
12591
12592    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12593        .await;
12594
12595    cx.editor(|editor, _, _| {
12596        assert!(!editor.signature_help_state.is_shown());
12597    });
12598
12599    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12600    cx.set_state(indoc! {"
12601        fn main() {
12602            sample(ˇ);
12603        }
12604
12605        fn sample(param1: u8, param2: u8) {}
12606    "});
12607
12608    let mocked_response = lsp::SignatureHelp {
12609        signatures: vec![lsp::SignatureInformation {
12610            label: "fn sample(param1: u8, param2: u8)".to_string(),
12611            documentation: None,
12612            parameters: Some(vec![
12613                lsp::ParameterInformation {
12614                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12615                    documentation: None,
12616                },
12617                lsp::ParameterInformation {
12618                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12619                    documentation: None,
12620                },
12621            ]),
12622            active_parameter: None,
12623        }],
12624        active_signature: Some(0),
12625        active_parameter: Some(0),
12626    };
12627    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12628    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12629        .await;
12630    cx.editor(|editor, _, _| {
12631        assert!(editor.signature_help_state.is_shown());
12632    });
12633
12634    // Restore the popover with more parameter input
12635    cx.set_state(indoc! {"
12636        fn main() {
12637            sample(param1, param2ˇ);
12638        }
12639
12640        fn sample(param1: u8, param2: u8) {}
12641    "});
12642
12643    let mocked_response = lsp::SignatureHelp {
12644        signatures: vec![lsp::SignatureInformation {
12645            label: "fn sample(param1: u8, param2: u8)".to_string(),
12646            documentation: None,
12647            parameters: Some(vec![
12648                lsp::ParameterInformation {
12649                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12650                    documentation: None,
12651                },
12652                lsp::ParameterInformation {
12653                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12654                    documentation: None,
12655                },
12656            ]),
12657            active_parameter: None,
12658        }],
12659        active_signature: Some(0),
12660        active_parameter: Some(1),
12661    };
12662    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12663    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12664        .await;
12665
12666    // When selecting a range, the popover is gone.
12667    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12668    cx.update_editor(|editor, window, cx| {
12669        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12670            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12671        })
12672    });
12673    cx.assert_editor_state(indoc! {"
12674        fn main() {
12675            sample(param1, «ˇparam2»);
12676        }
12677
12678        fn sample(param1: u8, param2: u8) {}
12679    "});
12680    cx.editor(|editor, _, _| {
12681        assert!(!editor.signature_help_state.is_shown());
12682    });
12683
12684    // When unselecting again, the popover is back if within the brackets.
12685    cx.update_editor(|editor, window, cx| {
12686        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12687            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12688        })
12689    });
12690    cx.assert_editor_state(indoc! {"
12691        fn main() {
12692            sample(param1, ˇparam2);
12693        }
12694
12695        fn sample(param1: u8, param2: u8) {}
12696    "});
12697    handle_signature_help_request(&mut cx, mocked_response).await;
12698    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12699        .await;
12700    cx.editor(|editor, _, _| {
12701        assert!(editor.signature_help_state.is_shown());
12702    });
12703
12704    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
12705    cx.update_editor(|editor, window, cx| {
12706        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12707            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
12708            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12709        })
12710    });
12711    cx.assert_editor_state(indoc! {"
12712        fn main() {
12713            sample(param1, ˇparam2);
12714        }
12715
12716        fn sample(param1: u8, param2: u8) {}
12717    "});
12718
12719    let mocked_response = lsp::SignatureHelp {
12720        signatures: vec![lsp::SignatureInformation {
12721            label: "fn sample(param1: u8, param2: u8)".to_string(),
12722            documentation: None,
12723            parameters: Some(vec![
12724                lsp::ParameterInformation {
12725                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12726                    documentation: None,
12727                },
12728                lsp::ParameterInformation {
12729                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12730                    documentation: None,
12731                },
12732            ]),
12733            active_parameter: None,
12734        }],
12735        active_signature: Some(0),
12736        active_parameter: Some(1),
12737    };
12738    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12739    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12740        .await;
12741    cx.update_editor(|editor, _, cx| {
12742        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
12743    });
12744    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12745        .await;
12746    cx.update_editor(|editor, window, cx| {
12747        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12748            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12749        })
12750    });
12751    cx.assert_editor_state(indoc! {"
12752        fn main() {
12753            sample(param1, «ˇparam2»);
12754        }
12755
12756        fn sample(param1: u8, param2: u8) {}
12757    "});
12758    cx.update_editor(|editor, window, cx| {
12759        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12760            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12761        })
12762    });
12763    cx.assert_editor_state(indoc! {"
12764        fn main() {
12765            sample(param1, ˇparam2);
12766        }
12767
12768        fn sample(param1: u8, param2: u8) {}
12769    "});
12770    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
12771        .await;
12772}
12773
12774#[gpui::test]
12775async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
12776    init_test(cx, |_| {});
12777
12778    let mut cx = EditorLspTestContext::new_rust(
12779        lsp::ServerCapabilities {
12780            signature_help_provider: Some(lsp::SignatureHelpOptions {
12781                ..Default::default()
12782            }),
12783            ..Default::default()
12784        },
12785        cx,
12786    )
12787    .await;
12788
12789    cx.set_state(indoc! {"
12790        fn main() {
12791            overloadedˇ
12792        }
12793    "});
12794
12795    cx.update_editor(|editor, window, cx| {
12796        editor.handle_input("(", window, cx);
12797        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12798    });
12799
12800    // Mock response with 3 signatures
12801    let mocked_response = lsp::SignatureHelp {
12802        signatures: vec![
12803            lsp::SignatureInformation {
12804                label: "fn overloaded(x: i32)".to_string(),
12805                documentation: None,
12806                parameters: Some(vec![lsp::ParameterInformation {
12807                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12808                    documentation: None,
12809                }]),
12810                active_parameter: None,
12811            },
12812            lsp::SignatureInformation {
12813                label: "fn overloaded(x: i32, y: i32)".to_string(),
12814                documentation: None,
12815                parameters: Some(vec![
12816                    lsp::ParameterInformation {
12817                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12818                        documentation: None,
12819                    },
12820                    lsp::ParameterInformation {
12821                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
12822                        documentation: None,
12823                    },
12824                ]),
12825                active_parameter: None,
12826            },
12827            lsp::SignatureInformation {
12828                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
12829                documentation: None,
12830                parameters: Some(vec![
12831                    lsp::ParameterInformation {
12832                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12833                        documentation: None,
12834                    },
12835                    lsp::ParameterInformation {
12836                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
12837                        documentation: None,
12838                    },
12839                    lsp::ParameterInformation {
12840                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
12841                        documentation: None,
12842                    },
12843                ]),
12844                active_parameter: None,
12845            },
12846        ],
12847        active_signature: Some(1),
12848        active_parameter: Some(0),
12849    };
12850    handle_signature_help_request(&mut cx, mocked_response).await;
12851
12852    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12853        .await;
12854
12855    // Verify we have multiple signatures and the right one is selected
12856    cx.editor(|editor, _, _| {
12857        let popover = editor.signature_help_state.popover().cloned().unwrap();
12858        assert_eq!(popover.signatures.len(), 3);
12859        // active_signature was 1, so that should be the current
12860        assert_eq!(popover.current_signature, 1);
12861        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
12862        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
12863        assert_eq!(
12864            popover.signatures[2].label,
12865            "fn overloaded(x: i32, y: i32, z: i32)"
12866        );
12867    });
12868
12869    // Test navigation functionality
12870    cx.update_editor(|editor, window, cx| {
12871        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12872    });
12873
12874    cx.editor(|editor, _, _| {
12875        let popover = editor.signature_help_state.popover().cloned().unwrap();
12876        assert_eq!(popover.current_signature, 2);
12877    });
12878
12879    // Test wrap around
12880    cx.update_editor(|editor, window, cx| {
12881        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12882    });
12883
12884    cx.editor(|editor, _, _| {
12885        let popover = editor.signature_help_state.popover().cloned().unwrap();
12886        assert_eq!(popover.current_signature, 0);
12887    });
12888
12889    // Test previous navigation
12890    cx.update_editor(|editor, window, cx| {
12891        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
12892    });
12893
12894    cx.editor(|editor, _, _| {
12895        let popover = editor.signature_help_state.popover().cloned().unwrap();
12896        assert_eq!(popover.current_signature, 2);
12897    });
12898}
12899
12900#[gpui::test]
12901async fn test_completion_mode(cx: &mut TestAppContext) {
12902    init_test(cx, |_| {});
12903    let mut cx = EditorLspTestContext::new_rust(
12904        lsp::ServerCapabilities {
12905            completion_provider: Some(lsp::CompletionOptions {
12906                resolve_provider: Some(true),
12907                ..Default::default()
12908            }),
12909            ..Default::default()
12910        },
12911        cx,
12912    )
12913    .await;
12914
12915    struct Run {
12916        run_description: &'static str,
12917        initial_state: String,
12918        buffer_marked_text: String,
12919        completion_label: &'static str,
12920        completion_text: &'static str,
12921        expected_with_insert_mode: String,
12922        expected_with_replace_mode: String,
12923        expected_with_replace_subsequence_mode: String,
12924        expected_with_replace_suffix_mode: String,
12925    }
12926
12927    let runs = [
12928        Run {
12929            run_description: "Start of word matches completion text",
12930            initial_state: "before ediˇ after".into(),
12931            buffer_marked_text: "before <edi|> after".into(),
12932            completion_label: "editor",
12933            completion_text: "editor",
12934            expected_with_insert_mode: "before editorˇ after".into(),
12935            expected_with_replace_mode: "before editorˇ after".into(),
12936            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12937            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12938        },
12939        Run {
12940            run_description: "Accept same text at the middle of the word",
12941            initial_state: "before ediˇtor after".into(),
12942            buffer_marked_text: "before <edi|tor> after".into(),
12943            completion_label: "editor",
12944            completion_text: "editor",
12945            expected_with_insert_mode: "before editorˇtor after".into(),
12946            expected_with_replace_mode: "before editorˇ after".into(),
12947            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12948            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12949        },
12950        Run {
12951            run_description: "End of word matches completion text -- cursor at end",
12952            initial_state: "before torˇ after".into(),
12953            buffer_marked_text: "before <tor|> after".into(),
12954            completion_label: "editor",
12955            completion_text: "editor",
12956            expected_with_insert_mode: "before editorˇ after".into(),
12957            expected_with_replace_mode: "before editorˇ after".into(),
12958            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12959            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12960        },
12961        Run {
12962            run_description: "End of word matches completion text -- cursor at start",
12963            initial_state: "before ˇtor after".into(),
12964            buffer_marked_text: "before <|tor> after".into(),
12965            completion_label: "editor",
12966            completion_text: "editor",
12967            expected_with_insert_mode: "before editorˇtor after".into(),
12968            expected_with_replace_mode: "before editorˇ after".into(),
12969            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12970            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12971        },
12972        Run {
12973            run_description: "Prepend text containing whitespace",
12974            initial_state: "pˇfield: bool".into(),
12975            buffer_marked_text: "<p|field>: bool".into(),
12976            completion_label: "pub ",
12977            completion_text: "pub ",
12978            expected_with_insert_mode: "pub ˇfield: bool".into(),
12979            expected_with_replace_mode: "pub ˇ: bool".into(),
12980            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
12981            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
12982        },
12983        Run {
12984            run_description: "Add element to start of list",
12985            initial_state: "[element_ˇelement_2]".into(),
12986            buffer_marked_text: "[<element_|element_2>]".into(),
12987            completion_label: "element_1",
12988            completion_text: "element_1",
12989            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
12990            expected_with_replace_mode: "[element_1ˇ]".into(),
12991            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
12992            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
12993        },
12994        Run {
12995            run_description: "Add element to start of list -- first and second elements are equal",
12996            initial_state: "[elˇelement]".into(),
12997            buffer_marked_text: "[<el|element>]".into(),
12998            completion_label: "element",
12999            completion_text: "element",
13000            expected_with_insert_mode: "[elementˇelement]".into(),
13001            expected_with_replace_mode: "[elementˇ]".into(),
13002            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13003            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13004        },
13005        Run {
13006            run_description: "Ends with matching suffix",
13007            initial_state: "SubˇError".into(),
13008            buffer_marked_text: "<Sub|Error>".into(),
13009            completion_label: "SubscriptionError",
13010            completion_text: "SubscriptionError",
13011            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13012            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13013            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13014            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13015        },
13016        Run {
13017            run_description: "Suffix is a subsequence -- contiguous",
13018            initial_state: "SubˇErr".into(),
13019            buffer_marked_text: "<Sub|Err>".into(),
13020            completion_label: "SubscriptionError",
13021            completion_text: "SubscriptionError",
13022            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13023            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13024            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13025            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13026        },
13027        Run {
13028            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13029            initial_state: "Suˇscrirr".into(),
13030            buffer_marked_text: "<Su|scrirr>".into(),
13031            completion_label: "SubscriptionError",
13032            completion_text: "SubscriptionError",
13033            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13034            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13035            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13036            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13037        },
13038        Run {
13039            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13040            initial_state: "foo(indˇix)".into(),
13041            buffer_marked_text: "foo(<ind|ix>)".into(),
13042            completion_label: "node_index",
13043            completion_text: "node_index",
13044            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13045            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13046            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13047            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13048        },
13049        Run {
13050            run_description: "Replace range ends before cursor - should extend to cursor",
13051            initial_state: "before editˇo after".into(),
13052            buffer_marked_text: "before <{ed}>it|o after".into(),
13053            completion_label: "editor",
13054            completion_text: "editor",
13055            expected_with_insert_mode: "before editorˇo after".into(),
13056            expected_with_replace_mode: "before editorˇo after".into(),
13057            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13058            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13059        },
13060        Run {
13061            run_description: "Uses label for suffix matching",
13062            initial_state: "before ediˇtor after".into(),
13063            buffer_marked_text: "before <edi|tor> after".into(),
13064            completion_label: "editor",
13065            completion_text: "editor()",
13066            expected_with_insert_mode: "before editor()ˇtor after".into(),
13067            expected_with_replace_mode: "before editor()ˇ after".into(),
13068            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13069            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13070        },
13071        Run {
13072            run_description: "Case insensitive subsequence and suffix matching",
13073            initial_state: "before EDiˇtoR after".into(),
13074            buffer_marked_text: "before <EDi|toR> after".into(),
13075            completion_label: "editor",
13076            completion_text: "editor",
13077            expected_with_insert_mode: "before editorˇtoR after".into(),
13078            expected_with_replace_mode: "before editorˇ after".into(),
13079            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13080            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13081        },
13082    ];
13083
13084    for run in runs {
13085        let run_variations = [
13086            (LspInsertMode::Insert, run.expected_with_insert_mode),
13087            (LspInsertMode::Replace, run.expected_with_replace_mode),
13088            (
13089                LspInsertMode::ReplaceSubsequence,
13090                run.expected_with_replace_subsequence_mode,
13091            ),
13092            (
13093                LspInsertMode::ReplaceSuffix,
13094                run.expected_with_replace_suffix_mode,
13095            ),
13096        ];
13097
13098        for (lsp_insert_mode, expected_text) in run_variations {
13099            eprintln!(
13100                "run = {:?}, mode = {lsp_insert_mode:.?}",
13101                run.run_description,
13102            );
13103
13104            update_test_language_settings(&mut cx, |settings| {
13105                settings.defaults.completions = Some(CompletionSettings {
13106                    lsp_insert_mode,
13107                    words: WordsCompletionMode::Disabled,
13108                    words_min_length: 0,
13109                    lsp: true,
13110                    lsp_fetch_timeout_ms: 0,
13111                });
13112            });
13113
13114            cx.set_state(&run.initial_state);
13115            cx.update_editor(|editor, window, cx| {
13116                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13117            });
13118
13119            let counter = Arc::new(AtomicUsize::new(0));
13120            handle_completion_request_with_insert_and_replace(
13121                &mut cx,
13122                &run.buffer_marked_text,
13123                vec![(run.completion_label, run.completion_text)],
13124                counter.clone(),
13125            )
13126            .await;
13127            cx.condition(|editor, _| editor.context_menu_visible())
13128                .await;
13129            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13130
13131            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13132                editor
13133                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13134                    .unwrap()
13135            });
13136            cx.assert_editor_state(&expected_text);
13137            handle_resolve_completion_request(&mut cx, None).await;
13138            apply_additional_edits.await.unwrap();
13139        }
13140    }
13141}
13142
13143#[gpui::test]
13144async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13145    init_test(cx, |_| {});
13146    let mut cx = EditorLspTestContext::new_rust(
13147        lsp::ServerCapabilities {
13148            completion_provider: Some(lsp::CompletionOptions {
13149                resolve_provider: Some(true),
13150                ..Default::default()
13151            }),
13152            ..Default::default()
13153        },
13154        cx,
13155    )
13156    .await;
13157
13158    let initial_state = "SubˇError";
13159    let buffer_marked_text = "<Sub|Error>";
13160    let completion_text = "SubscriptionError";
13161    let expected_with_insert_mode = "SubscriptionErrorˇError";
13162    let expected_with_replace_mode = "SubscriptionErrorˇ";
13163
13164    update_test_language_settings(&mut cx, |settings| {
13165        settings.defaults.completions = Some(CompletionSettings {
13166            words: WordsCompletionMode::Disabled,
13167            words_min_length: 0,
13168            // set the opposite here to ensure that the action is overriding the default behavior
13169            lsp_insert_mode: LspInsertMode::Insert,
13170            lsp: true,
13171            lsp_fetch_timeout_ms: 0,
13172        });
13173    });
13174
13175    cx.set_state(initial_state);
13176    cx.update_editor(|editor, window, cx| {
13177        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13178    });
13179
13180    let counter = Arc::new(AtomicUsize::new(0));
13181    handle_completion_request_with_insert_and_replace(
13182        &mut cx,
13183        buffer_marked_text,
13184        vec![(completion_text, completion_text)],
13185        counter.clone(),
13186    )
13187    .await;
13188    cx.condition(|editor, _| editor.context_menu_visible())
13189        .await;
13190    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13191
13192    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13193        editor
13194            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13195            .unwrap()
13196    });
13197    cx.assert_editor_state(expected_with_replace_mode);
13198    handle_resolve_completion_request(&mut cx, None).await;
13199    apply_additional_edits.await.unwrap();
13200
13201    update_test_language_settings(&mut cx, |settings| {
13202        settings.defaults.completions = Some(CompletionSettings {
13203            words: WordsCompletionMode::Disabled,
13204            words_min_length: 0,
13205            // set the opposite here to ensure that the action is overriding the default behavior
13206            lsp_insert_mode: LspInsertMode::Replace,
13207            lsp: true,
13208            lsp_fetch_timeout_ms: 0,
13209        });
13210    });
13211
13212    cx.set_state(initial_state);
13213    cx.update_editor(|editor, window, cx| {
13214        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13215    });
13216    handle_completion_request_with_insert_and_replace(
13217        &mut cx,
13218        buffer_marked_text,
13219        vec![(completion_text, completion_text)],
13220        counter.clone(),
13221    )
13222    .await;
13223    cx.condition(|editor, _| editor.context_menu_visible())
13224        .await;
13225    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13226
13227    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13228        editor
13229            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13230            .unwrap()
13231    });
13232    cx.assert_editor_state(expected_with_insert_mode);
13233    handle_resolve_completion_request(&mut cx, None).await;
13234    apply_additional_edits.await.unwrap();
13235}
13236
13237#[gpui::test]
13238async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13239    init_test(cx, |_| {});
13240    let mut cx = EditorLspTestContext::new_rust(
13241        lsp::ServerCapabilities {
13242            completion_provider: Some(lsp::CompletionOptions {
13243                resolve_provider: Some(true),
13244                ..Default::default()
13245            }),
13246            ..Default::default()
13247        },
13248        cx,
13249    )
13250    .await;
13251
13252    // scenario: surrounding text matches completion text
13253    let completion_text = "to_offset";
13254    let initial_state = indoc! {"
13255        1. buf.to_offˇsuffix
13256        2. buf.to_offˇsuf
13257        3. buf.to_offˇfix
13258        4. buf.to_offˇ
13259        5. into_offˇensive
13260        6. ˇsuffix
13261        7. let ˇ //
13262        8. aaˇzz
13263        9. buf.to_off«zzzzzˇ»suffix
13264        10. buf.«ˇzzzzz»suffix
13265        11. to_off«ˇzzzzz»
13266
13267        buf.to_offˇsuffix  // newest cursor
13268    "};
13269    let completion_marked_buffer = indoc! {"
13270        1. buf.to_offsuffix
13271        2. buf.to_offsuf
13272        3. buf.to_offfix
13273        4. buf.to_off
13274        5. into_offensive
13275        6. suffix
13276        7. let  //
13277        8. aazz
13278        9. buf.to_offzzzzzsuffix
13279        10. buf.zzzzzsuffix
13280        11. to_offzzzzz
13281
13282        buf.<to_off|suffix>  // newest cursor
13283    "};
13284    let expected = indoc! {"
13285        1. buf.to_offsetˇ
13286        2. buf.to_offsetˇsuf
13287        3. buf.to_offsetˇfix
13288        4. buf.to_offsetˇ
13289        5. into_offsetˇensive
13290        6. to_offsetˇsuffix
13291        7. let to_offsetˇ //
13292        8. aato_offsetˇzz
13293        9. buf.to_offsetˇ
13294        10. buf.to_offsetˇsuffix
13295        11. to_offsetˇ
13296
13297        buf.to_offsetˇ  // newest cursor
13298    "};
13299    cx.set_state(initial_state);
13300    cx.update_editor(|editor, window, cx| {
13301        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13302    });
13303    handle_completion_request_with_insert_and_replace(
13304        &mut cx,
13305        completion_marked_buffer,
13306        vec![(completion_text, completion_text)],
13307        Arc::new(AtomicUsize::new(0)),
13308    )
13309    .await;
13310    cx.condition(|editor, _| editor.context_menu_visible())
13311        .await;
13312    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13313        editor
13314            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13315            .unwrap()
13316    });
13317    cx.assert_editor_state(expected);
13318    handle_resolve_completion_request(&mut cx, None).await;
13319    apply_additional_edits.await.unwrap();
13320
13321    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13322    let completion_text = "foo_and_bar";
13323    let initial_state = indoc! {"
13324        1. ooanbˇ
13325        2. zooanbˇ
13326        3. ooanbˇz
13327        4. zooanbˇz
13328        5. ooanˇ
13329        6. oanbˇ
13330
13331        ooanbˇ
13332    "};
13333    let completion_marked_buffer = indoc! {"
13334        1. ooanb
13335        2. zooanb
13336        3. ooanbz
13337        4. zooanbz
13338        5. ooan
13339        6. oanb
13340
13341        <ooanb|>
13342    "};
13343    let expected = indoc! {"
13344        1. foo_and_barˇ
13345        2. zfoo_and_barˇ
13346        3. foo_and_barˇz
13347        4. zfoo_and_barˇz
13348        5. ooanfoo_and_barˇ
13349        6. oanbfoo_and_barˇ
13350
13351        foo_and_barˇ
13352    "};
13353    cx.set_state(initial_state);
13354    cx.update_editor(|editor, window, cx| {
13355        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13356    });
13357    handle_completion_request_with_insert_and_replace(
13358        &mut cx,
13359        completion_marked_buffer,
13360        vec![(completion_text, completion_text)],
13361        Arc::new(AtomicUsize::new(0)),
13362    )
13363    .await;
13364    cx.condition(|editor, _| editor.context_menu_visible())
13365        .await;
13366    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13367        editor
13368            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13369            .unwrap()
13370    });
13371    cx.assert_editor_state(expected);
13372    handle_resolve_completion_request(&mut cx, None).await;
13373    apply_additional_edits.await.unwrap();
13374
13375    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13376    // (expects the same as if it was inserted at the end)
13377    let completion_text = "foo_and_bar";
13378    let initial_state = indoc! {"
13379        1. ooˇanb
13380        2. zooˇanb
13381        3. ooˇanbz
13382        4. zooˇanbz
13383
13384        ooˇanb
13385    "};
13386    let completion_marked_buffer = indoc! {"
13387        1. ooanb
13388        2. zooanb
13389        3. ooanbz
13390        4. zooanbz
13391
13392        <oo|anb>
13393    "};
13394    let expected = indoc! {"
13395        1. foo_and_barˇ
13396        2. zfoo_and_barˇ
13397        3. foo_and_barˇz
13398        4. zfoo_and_barˇz
13399
13400        foo_and_barˇ
13401    "};
13402    cx.set_state(initial_state);
13403    cx.update_editor(|editor, window, cx| {
13404        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13405    });
13406    handle_completion_request_with_insert_and_replace(
13407        &mut cx,
13408        completion_marked_buffer,
13409        vec![(completion_text, completion_text)],
13410        Arc::new(AtomicUsize::new(0)),
13411    )
13412    .await;
13413    cx.condition(|editor, _| editor.context_menu_visible())
13414        .await;
13415    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13416        editor
13417            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13418            .unwrap()
13419    });
13420    cx.assert_editor_state(expected);
13421    handle_resolve_completion_request(&mut cx, None).await;
13422    apply_additional_edits.await.unwrap();
13423}
13424
13425// This used to crash
13426#[gpui::test]
13427async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13428    init_test(cx, |_| {});
13429
13430    let buffer_text = indoc! {"
13431        fn main() {
13432            10.satu;
13433
13434            //
13435            // separate cursors so they open in different excerpts (manually reproducible)
13436            //
13437
13438            10.satu20;
13439        }
13440    "};
13441    let multibuffer_text_with_selections = indoc! {"
13442        fn main() {
13443            10.satuˇ;
13444
13445            //
13446
13447            //
13448
13449            10.satuˇ20;
13450        }
13451    "};
13452    let expected_multibuffer = indoc! {"
13453        fn main() {
13454            10.saturating_sub()ˇ;
13455
13456            //
13457
13458            //
13459
13460            10.saturating_sub()ˇ;
13461        }
13462    "};
13463
13464    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13465    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13466
13467    let fs = FakeFs::new(cx.executor());
13468    fs.insert_tree(
13469        path!("/a"),
13470        json!({
13471            "main.rs": buffer_text,
13472        }),
13473    )
13474    .await;
13475
13476    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13477    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13478    language_registry.add(rust_lang());
13479    let mut fake_servers = language_registry.register_fake_lsp(
13480        "Rust",
13481        FakeLspAdapter {
13482            capabilities: lsp::ServerCapabilities {
13483                completion_provider: Some(lsp::CompletionOptions {
13484                    resolve_provider: None,
13485                    ..lsp::CompletionOptions::default()
13486                }),
13487                ..lsp::ServerCapabilities::default()
13488            },
13489            ..FakeLspAdapter::default()
13490        },
13491    );
13492    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13493    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13494    let buffer = project
13495        .update(cx, |project, cx| {
13496            project.open_local_buffer(path!("/a/main.rs"), cx)
13497        })
13498        .await
13499        .unwrap();
13500
13501    let multi_buffer = cx.new(|cx| {
13502        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13503        multi_buffer.push_excerpts(
13504            buffer.clone(),
13505            [ExcerptRange::new(0..first_excerpt_end)],
13506            cx,
13507        );
13508        multi_buffer.push_excerpts(
13509            buffer.clone(),
13510            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13511            cx,
13512        );
13513        multi_buffer
13514    });
13515
13516    let editor = workspace
13517        .update(cx, |_, window, cx| {
13518            cx.new(|cx| {
13519                Editor::new(
13520                    EditorMode::Full {
13521                        scale_ui_elements_with_buffer_font_size: false,
13522                        show_active_line_background: false,
13523                        sized_by_content: false,
13524                    },
13525                    multi_buffer.clone(),
13526                    Some(project.clone()),
13527                    window,
13528                    cx,
13529                )
13530            })
13531        })
13532        .unwrap();
13533
13534    let pane = workspace
13535        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13536        .unwrap();
13537    pane.update_in(cx, |pane, window, cx| {
13538        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13539    });
13540
13541    let fake_server = fake_servers.next().await.unwrap();
13542
13543    editor.update_in(cx, |editor, window, cx| {
13544        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13545            s.select_ranges([
13546                Point::new(1, 11)..Point::new(1, 11),
13547                Point::new(7, 11)..Point::new(7, 11),
13548            ])
13549        });
13550
13551        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13552    });
13553
13554    editor.update_in(cx, |editor, window, cx| {
13555        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13556    });
13557
13558    fake_server
13559        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13560            let completion_item = lsp::CompletionItem {
13561                label: "saturating_sub()".into(),
13562                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13563                    lsp::InsertReplaceEdit {
13564                        new_text: "saturating_sub()".to_owned(),
13565                        insert: lsp::Range::new(
13566                            lsp::Position::new(7, 7),
13567                            lsp::Position::new(7, 11),
13568                        ),
13569                        replace: lsp::Range::new(
13570                            lsp::Position::new(7, 7),
13571                            lsp::Position::new(7, 13),
13572                        ),
13573                    },
13574                )),
13575                ..lsp::CompletionItem::default()
13576            };
13577
13578            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13579        })
13580        .next()
13581        .await
13582        .unwrap();
13583
13584    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13585        .await;
13586
13587    editor
13588        .update_in(cx, |editor, window, cx| {
13589            editor
13590                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13591                .unwrap()
13592        })
13593        .await
13594        .unwrap();
13595
13596    editor.update(cx, |editor, cx| {
13597        assert_text_with_selections(editor, expected_multibuffer, cx);
13598    })
13599}
13600
13601#[gpui::test]
13602async fn test_completion(cx: &mut TestAppContext) {
13603    init_test(cx, |_| {});
13604
13605    let mut cx = EditorLspTestContext::new_rust(
13606        lsp::ServerCapabilities {
13607            completion_provider: Some(lsp::CompletionOptions {
13608                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13609                resolve_provider: Some(true),
13610                ..Default::default()
13611            }),
13612            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13613            ..Default::default()
13614        },
13615        cx,
13616    )
13617    .await;
13618    let counter = Arc::new(AtomicUsize::new(0));
13619
13620    cx.set_state(indoc! {"
13621        oneˇ
13622        two
13623        three
13624    "});
13625    cx.simulate_keystroke(".");
13626    handle_completion_request(
13627        indoc! {"
13628            one.|<>
13629            two
13630            three
13631        "},
13632        vec!["first_completion", "second_completion"],
13633        true,
13634        counter.clone(),
13635        &mut cx,
13636    )
13637    .await;
13638    cx.condition(|editor, _| editor.context_menu_visible())
13639        .await;
13640    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13641
13642    let _handler = handle_signature_help_request(
13643        &mut cx,
13644        lsp::SignatureHelp {
13645            signatures: vec![lsp::SignatureInformation {
13646                label: "test signature".to_string(),
13647                documentation: None,
13648                parameters: Some(vec![lsp::ParameterInformation {
13649                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13650                    documentation: None,
13651                }]),
13652                active_parameter: None,
13653            }],
13654            active_signature: None,
13655            active_parameter: None,
13656        },
13657    );
13658    cx.update_editor(|editor, window, cx| {
13659        assert!(
13660            !editor.signature_help_state.is_shown(),
13661            "No signature help was called for"
13662        );
13663        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13664    });
13665    cx.run_until_parked();
13666    cx.update_editor(|editor, _, _| {
13667        assert!(
13668            !editor.signature_help_state.is_shown(),
13669            "No signature help should be shown when completions menu is open"
13670        );
13671    });
13672
13673    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13674        editor.context_menu_next(&Default::default(), window, cx);
13675        editor
13676            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13677            .unwrap()
13678    });
13679    cx.assert_editor_state(indoc! {"
13680        one.second_completionˇ
13681        two
13682        three
13683    "});
13684
13685    handle_resolve_completion_request(
13686        &mut cx,
13687        Some(vec![
13688            (
13689                //This overlaps with the primary completion edit which is
13690                //misbehavior from the LSP spec, test that we filter it out
13691                indoc! {"
13692                    one.second_ˇcompletion
13693                    two
13694                    threeˇ
13695                "},
13696                "overlapping additional edit",
13697            ),
13698            (
13699                indoc! {"
13700                    one.second_completion
13701                    two
13702                    threeˇ
13703                "},
13704                "\nadditional edit",
13705            ),
13706        ]),
13707    )
13708    .await;
13709    apply_additional_edits.await.unwrap();
13710    cx.assert_editor_state(indoc! {"
13711        one.second_completionˇ
13712        two
13713        three
13714        additional edit
13715    "});
13716
13717    cx.set_state(indoc! {"
13718        one.second_completion
13719        twoˇ
13720        threeˇ
13721        additional edit
13722    "});
13723    cx.simulate_keystroke(" ");
13724    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13725    cx.simulate_keystroke("s");
13726    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13727
13728    cx.assert_editor_state(indoc! {"
13729        one.second_completion
13730        two sˇ
13731        three sˇ
13732        additional edit
13733    "});
13734    handle_completion_request(
13735        indoc! {"
13736            one.second_completion
13737            two s
13738            three <s|>
13739            additional edit
13740        "},
13741        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13742        true,
13743        counter.clone(),
13744        &mut cx,
13745    )
13746    .await;
13747    cx.condition(|editor, _| editor.context_menu_visible())
13748        .await;
13749    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13750
13751    cx.simulate_keystroke("i");
13752
13753    handle_completion_request(
13754        indoc! {"
13755            one.second_completion
13756            two si
13757            three <si|>
13758            additional edit
13759        "},
13760        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13761        true,
13762        counter.clone(),
13763        &mut cx,
13764    )
13765    .await;
13766    cx.condition(|editor, _| editor.context_menu_visible())
13767        .await;
13768    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13769
13770    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13771        editor
13772            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13773            .unwrap()
13774    });
13775    cx.assert_editor_state(indoc! {"
13776        one.second_completion
13777        two sixth_completionˇ
13778        three sixth_completionˇ
13779        additional edit
13780    "});
13781
13782    apply_additional_edits.await.unwrap();
13783
13784    update_test_language_settings(&mut cx, |settings| {
13785        settings.defaults.show_completions_on_input = Some(false);
13786    });
13787    cx.set_state("editorˇ");
13788    cx.simulate_keystroke(".");
13789    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13790    cx.simulate_keystrokes("c l o");
13791    cx.assert_editor_state("editor.cloˇ");
13792    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13793    cx.update_editor(|editor, window, cx| {
13794        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13795    });
13796    handle_completion_request(
13797        "editor.<clo|>",
13798        vec!["close", "clobber"],
13799        true,
13800        counter.clone(),
13801        &mut cx,
13802    )
13803    .await;
13804    cx.condition(|editor, _| editor.context_menu_visible())
13805        .await;
13806    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
13807
13808    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13809        editor
13810            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13811            .unwrap()
13812    });
13813    cx.assert_editor_state("editor.clobberˇ");
13814    handle_resolve_completion_request(&mut cx, None).await;
13815    apply_additional_edits.await.unwrap();
13816}
13817
13818#[gpui::test]
13819async fn test_completion_reuse(cx: &mut TestAppContext) {
13820    init_test(cx, |_| {});
13821
13822    let mut cx = EditorLspTestContext::new_rust(
13823        lsp::ServerCapabilities {
13824            completion_provider: Some(lsp::CompletionOptions {
13825                trigger_characters: Some(vec![".".to_string()]),
13826                ..Default::default()
13827            }),
13828            ..Default::default()
13829        },
13830        cx,
13831    )
13832    .await;
13833
13834    let counter = Arc::new(AtomicUsize::new(0));
13835    cx.set_state("objˇ");
13836    cx.simulate_keystroke(".");
13837
13838    // Initial completion request returns complete results
13839    let is_incomplete = false;
13840    handle_completion_request(
13841        "obj.|<>",
13842        vec!["a", "ab", "abc"],
13843        is_incomplete,
13844        counter.clone(),
13845        &mut cx,
13846    )
13847    .await;
13848    cx.run_until_parked();
13849    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13850    cx.assert_editor_state("obj.ˇ");
13851    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13852
13853    // Type "a" - filters existing completions
13854    cx.simulate_keystroke("a");
13855    cx.run_until_parked();
13856    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13857    cx.assert_editor_state("obj.aˇ");
13858    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13859
13860    // Type "b" - filters existing completions
13861    cx.simulate_keystroke("b");
13862    cx.run_until_parked();
13863    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13864    cx.assert_editor_state("obj.abˇ");
13865    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13866
13867    // Type "c" - filters existing completions
13868    cx.simulate_keystroke("c");
13869    cx.run_until_parked();
13870    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13871    cx.assert_editor_state("obj.abcˇ");
13872    check_displayed_completions(vec!["abc"], &mut cx);
13873
13874    // Backspace to delete "c" - filters existing completions
13875    cx.update_editor(|editor, window, cx| {
13876        editor.backspace(&Backspace, window, cx);
13877    });
13878    cx.run_until_parked();
13879    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13880    cx.assert_editor_state("obj.abˇ");
13881    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13882
13883    // Moving cursor to the left dismisses menu.
13884    cx.update_editor(|editor, window, cx| {
13885        editor.move_left(&MoveLeft, window, cx);
13886    });
13887    cx.run_until_parked();
13888    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13889    cx.assert_editor_state("obj.aˇb");
13890    cx.update_editor(|editor, _, _| {
13891        assert_eq!(editor.context_menu_visible(), false);
13892    });
13893
13894    // Type "b" - new request
13895    cx.simulate_keystroke("b");
13896    let is_incomplete = false;
13897    handle_completion_request(
13898        "obj.<ab|>a",
13899        vec!["ab", "abc"],
13900        is_incomplete,
13901        counter.clone(),
13902        &mut cx,
13903    )
13904    .await;
13905    cx.run_until_parked();
13906    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13907    cx.assert_editor_state("obj.abˇb");
13908    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13909
13910    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
13911    cx.update_editor(|editor, window, cx| {
13912        editor.backspace(&Backspace, window, cx);
13913    });
13914    let is_incomplete = false;
13915    handle_completion_request(
13916        "obj.<a|>b",
13917        vec!["a", "ab", "abc"],
13918        is_incomplete,
13919        counter.clone(),
13920        &mut cx,
13921    )
13922    .await;
13923    cx.run_until_parked();
13924    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13925    cx.assert_editor_state("obj.aˇb");
13926    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13927
13928    // Backspace to delete "a" - dismisses menu.
13929    cx.update_editor(|editor, window, cx| {
13930        editor.backspace(&Backspace, window, cx);
13931    });
13932    cx.run_until_parked();
13933    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13934    cx.assert_editor_state("obj.ˇb");
13935    cx.update_editor(|editor, _, _| {
13936        assert_eq!(editor.context_menu_visible(), false);
13937    });
13938}
13939
13940#[gpui::test]
13941async fn test_word_completion(cx: &mut TestAppContext) {
13942    let lsp_fetch_timeout_ms = 10;
13943    init_test(cx, |language_settings| {
13944        language_settings.defaults.completions = Some(CompletionSettings {
13945            words: WordsCompletionMode::Fallback,
13946            words_min_length: 0,
13947            lsp: true,
13948            lsp_fetch_timeout_ms: 10,
13949            lsp_insert_mode: LspInsertMode::Insert,
13950        });
13951    });
13952
13953    let mut cx = EditorLspTestContext::new_rust(
13954        lsp::ServerCapabilities {
13955            completion_provider: Some(lsp::CompletionOptions {
13956                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13957                ..lsp::CompletionOptions::default()
13958            }),
13959            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13960            ..lsp::ServerCapabilities::default()
13961        },
13962        cx,
13963    )
13964    .await;
13965
13966    let throttle_completions = Arc::new(AtomicBool::new(false));
13967
13968    let lsp_throttle_completions = throttle_completions.clone();
13969    let _completion_requests_handler =
13970        cx.lsp
13971            .server
13972            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
13973                let lsp_throttle_completions = lsp_throttle_completions.clone();
13974                let cx = cx.clone();
13975                async move {
13976                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
13977                        cx.background_executor()
13978                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
13979                            .await;
13980                    }
13981                    Ok(Some(lsp::CompletionResponse::Array(vec![
13982                        lsp::CompletionItem {
13983                            label: "first".into(),
13984                            ..lsp::CompletionItem::default()
13985                        },
13986                        lsp::CompletionItem {
13987                            label: "last".into(),
13988                            ..lsp::CompletionItem::default()
13989                        },
13990                    ])))
13991                }
13992            });
13993
13994    cx.set_state(indoc! {"
13995        oneˇ
13996        two
13997        three
13998    "});
13999    cx.simulate_keystroke(".");
14000    cx.executor().run_until_parked();
14001    cx.condition(|editor, _| editor.context_menu_visible())
14002        .await;
14003    cx.update_editor(|editor, window, cx| {
14004        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14005        {
14006            assert_eq!(
14007                completion_menu_entries(menu),
14008                &["first", "last"],
14009                "When LSP server is fast to reply, no fallback word completions are used"
14010            );
14011        } else {
14012            panic!("expected completion menu to be open");
14013        }
14014        editor.cancel(&Cancel, window, cx);
14015    });
14016    cx.executor().run_until_parked();
14017    cx.condition(|editor, _| !editor.context_menu_visible())
14018        .await;
14019
14020    throttle_completions.store(true, atomic::Ordering::Release);
14021    cx.simulate_keystroke(".");
14022    cx.executor()
14023        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14024    cx.executor().run_until_parked();
14025    cx.condition(|editor, _| editor.context_menu_visible())
14026        .await;
14027    cx.update_editor(|editor, _, _| {
14028        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14029        {
14030            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14031                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14032        } else {
14033            panic!("expected completion menu to be open");
14034        }
14035    });
14036}
14037
14038#[gpui::test]
14039async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14040    init_test(cx, |language_settings| {
14041        language_settings.defaults.completions = Some(CompletionSettings {
14042            words: WordsCompletionMode::Enabled,
14043            words_min_length: 0,
14044            lsp: true,
14045            lsp_fetch_timeout_ms: 0,
14046            lsp_insert_mode: LspInsertMode::Insert,
14047        });
14048    });
14049
14050    let mut cx = EditorLspTestContext::new_rust(
14051        lsp::ServerCapabilities {
14052            completion_provider: Some(lsp::CompletionOptions {
14053                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14054                ..lsp::CompletionOptions::default()
14055            }),
14056            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14057            ..lsp::ServerCapabilities::default()
14058        },
14059        cx,
14060    )
14061    .await;
14062
14063    let _completion_requests_handler =
14064        cx.lsp
14065            .server
14066            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14067                Ok(Some(lsp::CompletionResponse::Array(vec![
14068                    lsp::CompletionItem {
14069                        label: "first".into(),
14070                        ..lsp::CompletionItem::default()
14071                    },
14072                    lsp::CompletionItem {
14073                        label: "last".into(),
14074                        ..lsp::CompletionItem::default()
14075                    },
14076                ])))
14077            });
14078
14079    cx.set_state(indoc! {"ˇ
14080        first
14081        last
14082        second
14083    "});
14084    cx.simulate_keystroke(".");
14085    cx.executor().run_until_parked();
14086    cx.condition(|editor, _| editor.context_menu_visible())
14087        .await;
14088    cx.update_editor(|editor, _, _| {
14089        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14090        {
14091            assert_eq!(
14092                completion_menu_entries(menu),
14093                &["first", "last", "second"],
14094                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14095            );
14096        } else {
14097            panic!("expected completion menu to be open");
14098        }
14099    });
14100}
14101
14102#[gpui::test]
14103async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14104    init_test(cx, |language_settings| {
14105        language_settings.defaults.completions = Some(CompletionSettings {
14106            words: WordsCompletionMode::Disabled,
14107            words_min_length: 0,
14108            lsp: true,
14109            lsp_fetch_timeout_ms: 0,
14110            lsp_insert_mode: LspInsertMode::Insert,
14111        });
14112    });
14113
14114    let mut cx = EditorLspTestContext::new_rust(
14115        lsp::ServerCapabilities {
14116            completion_provider: Some(lsp::CompletionOptions {
14117                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14118                ..lsp::CompletionOptions::default()
14119            }),
14120            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14121            ..lsp::ServerCapabilities::default()
14122        },
14123        cx,
14124    )
14125    .await;
14126
14127    let _completion_requests_handler =
14128        cx.lsp
14129            .server
14130            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14131                panic!("LSP completions should not be queried when dealing with word completions")
14132            });
14133
14134    cx.set_state(indoc! {"ˇ
14135        first
14136        last
14137        second
14138    "});
14139    cx.update_editor(|editor, window, cx| {
14140        editor.show_word_completions(&ShowWordCompletions, window, cx);
14141    });
14142    cx.executor().run_until_parked();
14143    cx.condition(|editor, _| editor.context_menu_visible())
14144        .await;
14145    cx.update_editor(|editor, _, _| {
14146        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14147        {
14148            assert_eq!(
14149                completion_menu_entries(menu),
14150                &["first", "last", "second"],
14151                "`ShowWordCompletions` action should show word completions"
14152            );
14153        } else {
14154            panic!("expected completion menu to be open");
14155        }
14156    });
14157
14158    cx.simulate_keystroke("l");
14159    cx.executor().run_until_parked();
14160    cx.condition(|editor, _| editor.context_menu_visible())
14161        .await;
14162    cx.update_editor(|editor, _, _| {
14163        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14164        {
14165            assert_eq!(
14166                completion_menu_entries(menu),
14167                &["last"],
14168                "After showing word completions, further editing should filter them and not query the LSP"
14169            );
14170        } else {
14171            panic!("expected completion menu to be open");
14172        }
14173    });
14174}
14175
14176#[gpui::test]
14177async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14178    init_test(cx, |language_settings| {
14179        language_settings.defaults.completions = Some(CompletionSettings {
14180            words: WordsCompletionMode::Fallback,
14181            words_min_length: 0,
14182            lsp: false,
14183            lsp_fetch_timeout_ms: 0,
14184            lsp_insert_mode: LspInsertMode::Insert,
14185        });
14186    });
14187
14188    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14189
14190    cx.set_state(indoc! {"ˇ
14191        0_usize
14192        let
14193        33
14194        4.5f32
14195    "});
14196    cx.update_editor(|editor, window, cx| {
14197        editor.show_completions(&ShowCompletions::default(), window, cx);
14198    });
14199    cx.executor().run_until_parked();
14200    cx.condition(|editor, _| editor.context_menu_visible())
14201        .await;
14202    cx.update_editor(|editor, window, cx| {
14203        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14204        {
14205            assert_eq!(
14206                completion_menu_entries(menu),
14207                &["let"],
14208                "With no digits in the completion query, no digits should be in the word completions"
14209            );
14210        } else {
14211            panic!("expected completion menu to be open");
14212        }
14213        editor.cancel(&Cancel, window, cx);
14214    });
14215
14216    cx.set_state(indoc! {"14217        0_usize
14218        let
14219        3
14220        33.35f32
14221    "});
14222    cx.update_editor(|editor, window, cx| {
14223        editor.show_completions(&ShowCompletions::default(), window, cx);
14224    });
14225    cx.executor().run_until_parked();
14226    cx.condition(|editor, _| editor.context_menu_visible())
14227        .await;
14228    cx.update_editor(|editor, _, _| {
14229        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14230        {
14231            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14232                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14233        } else {
14234            panic!("expected completion menu to be open");
14235        }
14236    });
14237}
14238
14239#[gpui::test]
14240async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14241    init_test(cx, |language_settings| {
14242        language_settings.defaults.completions = Some(CompletionSettings {
14243            words: WordsCompletionMode::Enabled,
14244            words_min_length: 3,
14245            lsp: true,
14246            lsp_fetch_timeout_ms: 0,
14247            lsp_insert_mode: LspInsertMode::Insert,
14248        });
14249    });
14250
14251    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14252    cx.set_state(indoc! {"ˇ
14253        wow
14254        wowen
14255        wowser
14256    "});
14257    cx.simulate_keystroke("w");
14258    cx.executor().run_until_parked();
14259    cx.update_editor(|editor, _, _| {
14260        if editor.context_menu.borrow_mut().is_some() {
14261            panic!(
14262                "expected completion menu to be hidden, as words completion threshold is not met"
14263            );
14264        }
14265    });
14266
14267    cx.simulate_keystroke("o");
14268    cx.executor().run_until_parked();
14269    cx.update_editor(|editor, _, _| {
14270        if editor.context_menu.borrow_mut().is_some() {
14271            panic!(
14272                "expected completion menu to be hidden, as words completion threshold is not met still"
14273            );
14274        }
14275    });
14276
14277    cx.simulate_keystroke("w");
14278    cx.executor().run_until_parked();
14279    cx.update_editor(|editor, _, _| {
14280        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14281        {
14282            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14283        } else {
14284            panic!("expected completion menu to be open after the word completions threshold is met");
14285        }
14286    });
14287}
14288
14289fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14290    let position = || lsp::Position {
14291        line: params.text_document_position.position.line,
14292        character: params.text_document_position.position.character,
14293    };
14294    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14295        range: lsp::Range {
14296            start: position(),
14297            end: position(),
14298        },
14299        new_text: text.to_string(),
14300    }))
14301}
14302
14303#[gpui::test]
14304async fn test_multiline_completion(cx: &mut TestAppContext) {
14305    init_test(cx, |_| {});
14306
14307    let fs = FakeFs::new(cx.executor());
14308    fs.insert_tree(
14309        path!("/a"),
14310        json!({
14311            "main.ts": "a",
14312        }),
14313    )
14314    .await;
14315
14316    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14317    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14318    let typescript_language = Arc::new(Language::new(
14319        LanguageConfig {
14320            name: "TypeScript".into(),
14321            matcher: LanguageMatcher {
14322                path_suffixes: vec!["ts".to_string()],
14323                ..LanguageMatcher::default()
14324            },
14325            line_comments: vec!["// ".into()],
14326            ..LanguageConfig::default()
14327        },
14328        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14329    ));
14330    language_registry.add(typescript_language.clone());
14331    let mut fake_servers = language_registry.register_fake_lsp(
14332        "TypeScript",
14333        FakeLspAdapter {
14334            capabilities: lsp::ServerCapabilities {
14335                completion_provider: Some(lsp::CompletionOptions {
14336                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14337                    ..lsp::CompletionOptions::default()
14338                }),
14339                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14340                ..lsp::ServerCapabilities::default()
14341            },
14342            // Emulate vtsls label generation
14343            label_for_completion: Some(Box::new(|item, _| {
14344                let text = if let Some(description) = item
14345                    .label_details
14346                    .as_ref()
14347                    .and_then(|label_details| label_details.description.as_ref())
14348                {
14349                    format!("{} {}", item.label, description)
14350                } else if let Some(detail) = &item.detail {
14351                    format!("{} {}", item.label, detail)
14352                } else {
14353                    item.label.clone()
14354                };
14355                let len = text.len();
14356                Some(language::CodeLabel {
14357                    text,
14358                    runs: Vec::new(),
14359                    filter_range: 0..len,
14360                })
14361            })),
14362            ..FakeLspAdapter::default()
14363        },
14364    );
14365    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14366    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14367    let worktree_id = workspace
14368        .update(cx, |workspace, _window, cx| {
14369            workspace.project().update(cx, |project, cx| {
14370                project.worktrees(cx).next().unwrap().read(cx).id()
14371            })
14372        })
14373        .unwrap();
14374    let _buffer = project
14375        .update(cx, |project, cx| {
14376            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14377        })
14378        .await
14379        .unwrap();
14380    let editor = workspace
14381        .update(cx, |workspace, window, cx| {
14382            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14383        })
14384        .unwrap()
14385        .await
14386        .unwrap()
14387        .downcast::<Editor>()
14388        .unwrap();
14389    let fake_server = fake_servers.next().await.unwrap();
14390
14391    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14392    let multiline_label_2 = "a\nb\nc\n";
14393    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14394    let multiline_description = "d\ne\nf\n";
14395    let multiline_detail_2 = "g\nh\ni\n";
14396
14397    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14398        move |params, _| async move {
14399            Ok(Some(lsp::CompletionResponse::Array(vec![
14400                lsp::CompletionItem {
14401                    label: multiline_label.to_string(),
14402                    text_edit: gen_text_edit(&params, "new_text_1"),
14403                    ..lsp::CompletionItem::default()
14404                },
14405                lsp::CompletionItem {
14406                    label: "single line label 1".to_string(),
14407                    detail: Some(multiline_detail.to_string()),
14408                    text_edit: gen_text_edit(&params, "new_text_2"),
14409                    ..lsp::CompletionItem::default()
14410                },
14411                lsp::CompletionItem {
14412                    label: "single line label 2".to_string(),
14413                    label_details: Some(lsp::CompletionItemLabelDetails {
14414                        description: Some(multiline_description.to_string()),
14415                        detail: None,
14416                    }),
14417                    text_edit: gen_text_edit(&params, "new_text_2"),
14418                    ..lsp::CompletionItem::default()
14419                },
14420                lsp::CompletionItem {
14421                    label: multiline_label_2.to_string(),
14422                    detail: Some(multiline_detail_2.to_string()),
14423                    text_edit: gen_text_edit(&params, "new_text_3"),
14424                    ..lsp::CompletionItem::default()
14425                },
14426                lsp::CompletionItem {
14427                    label: "Label with many     spaces and \t but without newlines".to_string(),
14428                    detail: Some(
14429                        "Details with many     spaces and \t but without newlines".to_string(),
14430                    ),
14431                    text_edit: gen_text_edit(&params, "new_text_4"),
14432                    ..lsp::CompletionItem::default()
14433                },
14434            ])))
14435        },
14436    );
14437
14438    editor.update_in(cx, |editor, window, cx| {
14439        cx.focus_self(window);
14440        editor.move_to_end(&MoveToEnd, window, cx);
14441        editor.handle_input(".", window, cx);
14442    });
14443    cx.run_until_parked();
14444    completion_handle.next().await.unwrap();
14445
14446    editor.update(cx, |editor, _| {
14447        assert!(editor.context_menu_visible());
14448        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14449        {
14450            let completion_labels = menu
14451                .completions
14452                .borrow()
14453                .iter()
14454                .map(|c| c.label.text.clone())
14455                .collect::<Vec<_>>();
14456            assert_eq!(
14457                completion_labels,
14458                &[
14459                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14460                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14461                    "single line label 2 d e f ",
14462                    "a b c g h i ",
14463                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14464                ],
14465                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14466            );
14467
14468            for completion in menu
14469                .completions
14470                .borrow()
14471                .iter() {
14472                    assert_eq!(
14473                        completion.label.filter_range,
14474                        0..completion.label.text.len(),
14475                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14476                    );
14477                }
14478        } else {
14479            panic!("expected completion menu to be open");
14480        }
14481    });
14482}
14483
14484#[gpui::test]
14485async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14486    init_test(cx, |_| {});
14487    let mut cx = EditorLspTestContext::new_rust(
14488        lsp::ServerCapabilities {
14489            completion_provider: Some(lsp::CompletionOptions {
14490                trigger_characters: Some(vec![".".to_string()]),
14491                ..Default::default()
14492            }),
14493            ..Default::default()
14494        },
14495        cx,
14496    )
14497    .await;
14498    cx.lsp
14499        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14500            Ok(Some(lsp::CompletionResponse::Array(vec![
14501                lsp::CompletionItem {
14502                    label: "first".into(),
14503                    ..Default::default()
14504                },
14505                lsp::CompletionItem {
14506                    label: "last".into(),
14507                    ..Default::default()
14508                },
14509            ])))
14510        });
14511    cx.set_state("variableˇ");
14512    cx.simulate_keystroke(".");
14513    cx.executor().run_until_parked();
14514
14515    cx.update_editor(|editor, _, _| {
14516        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14517        {
14518            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14519        } else {
14520            panic!("expected completion menu to be open");
14521        }
14522    });
14523
14524    cx.update_editor(|editor, window, cx| {
14525        editor.move_page_down(&MovePageDown::default(), window, cx);
14526        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14527        {
14528            assert!(
14529                menu.selected_item == 1,
14530                "expected PageDown to select the last item from the context menu"
14531            );
14532        } else {
14533            panic!("expected completion menu to stay open after PageDown");
14534        }
14535    });
14536
14537    cx.update_editor(|editor, window, cx| {
14538        editor.move_page_up(&MovePageUp::default(), window, cx);
14539        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14540        {
14541            assert!(
14542                menu.selected_item == 0,
14543                "expected PageUp to select the first item from the context menu"
14544            );
14545        } else {
14546            panic!("expected completion menu to stay open after PageUp");
14547        }
14548    });
14549}
14550
14551#[gpui::test]
14552async fn test_as_is_completions(cx: &mut TestAppContext) {
14553    init_test(cx, |_| {});
14554    let mut cx = EditorLspTestContext::new_rust(
14555        lsp::ServerCapabilities {
14556            completion_provider: Some(lsp::CompletionOptions {
14557                ..Default::default()
14558            }),
14559            ..Default::default()
14560        },
14561        cx,
14562    )
14563    .await;
14564    cx.lsp
14565        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14566            Ok(Some(lsp::CompletionResponse::Array(vec![
14567                lsp::CompletionItem {
14568                    label: "unsafe".into(),
14569                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14570                        range: lsp::Range {
14571                            start: lsp::Position {
14572                                line: 1,
14573                                character: 2,
14574                            },
14575                            end: lsp::Position {
14576                                line: 1,
14577                                character: 3,
14578                            },
14579                        },
14580                        new_text: "unsafe".to_string(),
14581                    })),
14582                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14583                    ..Default::default()
14584                },
14585            ])))
14586        });
14587    cx.set_state("fn a() {}\n");
14588    cx.executor().run_until_parked();
14589    cx.update_editor(|editor, window, cx| {
14590        editor.show_completions(
14591            &ShowCompletions {
14592                trigger: Some("\n".into()),
14593            },
14594            window,
14595            cx,
14596        );
14597    });
14598    cx.executor().run_until_parked();
14599
14600    cx.update_editor(|editor, window, cx| {
14601        editor.confirm_completion(&Default::default(), window, cx)
14602    });
14603    cx.executor().run_until_parked();
14604    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14605}
14606
14607#[gpui::test]
14608async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14609    init_test(cx, |_| {});
14610    let language =
14611        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14612    let mut cx = EditorLspTestContext::new(
14613        language,
14614        lsp::ServerCapabilities {
14615            completion_provider: Some(lsp::CompletionOptions {
14616                ..lsp::CompletionOptions::default()
14617            }),
14618            ..lsp::ServerCapabilities::default()
14619        },
14620        cx,
14621    )
14622    .await;
14623
14624    cx.set_state(
14625        "#ifndef BAR_H
14626#define BAR_H
14627
14628#include <stdbool.h>
14629
14630int fn_branch(bool do_branch1, bool do_branch2);
14631
14632#endif // BAR_H
14633ˇ",
14634    );
14635    cx.executor().run_until_parked();
14636    cx.update_editor(|editor, window, cx| {
14637        editor.handle_input("#", window, cx);
14638    });
14639    cx.executor().run_until_parked();
14640    cx.update_editor(|editor, window, cx| {
14641        editor.handle_input("i", window, cx);
14642    });
14643    cx.executor().run_until_parked();
14644    cx.update_editor(|editor, window, cx| {
14645        editor.handle_input("n", window, cx);
14646    });
14647    cx.executor().run_until_parked();
14648    cx.assert_editor_state(
14649        "#ifndef BAR_H
14650#define BAR_H
14651
14652#include <stdbool.h>
14653
14654int fn_branch(bool do_branch1, bool do_branch2);
14655
14656#endif // BAR_H
14657#inˇ",
14658    );
14659
14660    cx.lsp
14661        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14662            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14663                is_incomplete: false,
14664                item_defaults: None,
14665                items: vec![lsp::CompletionItem {
14666                    kind: Some(lsp::CompletionItemKind::SNIPPET),
14667                    label_details: Some(lsp::CompletionItemLabelDetails {
14668                        detail: Some("header".to_string()),
14669                        description: None,
14670                    }),
14671                    label: " include".to_string(),
14672                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14673                        range: lsp::Range {
14674                            start: lsp::Position {
14675                                line: 8,
14676                                character: 1,
14677                            },
14678                            end: lsp::Position {
14679                                line: 8,
14680                                character: 1,
14681                            },
14682                        },
14683                        new_text: "include \"$0\"".to_string(),
14684                    })),
14685                    sort_text: Some("40b67681include".to_string()),
14686                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14687                    filter_text: Some("include".to_string()),
14688                    insert_text: Some("include \"$0\"".to_string()),
14689                    ..lsp::CompletionItem::default()
14690                }],
14691            })))
14692        });
14693    cx.update_editor(|editor, window, cx| {
14694        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14695    });
14696    cx.executor().run_until_parked();
14697    cx.update_editor(|editor, window, cx| {
14698        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14699    });
14700    cx.executor().run_until_parked();
14701    cx.assert_editor_state(
14702        "#ifndef BAR_H
14703#define BAR_H
14704
14705#include <stdbool.h>
14706
14707int fn_branch(bool do_branch1, bool do_branch2);
14708
14709#endif // BAR_H
14710#include \"ˇ\"",
14711    );
14712
14713    cx.lsp
14714        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14715            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14716                is_incomplete: true,
14717                item_defaults: None,
14718                items: vec![lsp::CompletionItem {
14719                    kind: Some(lsp::CompletionItemKind::FILE),
14720                    label: "AGL/".to_string(),
14721                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14722                        range: lsp::Range {
14723                            start: lsp::Position {
14724                                line: 8,
14725                                character: 10,
14726                            },
14727                            end: lsp::Position {
14728                                line: 8,
14729                                character: 11,
14730                            },
14731                        },
14732                        new_text: "AGL/".to_string(),
14733                    })),
14734                    sort_text: Some("40b67681AGL/".to_string()),
14735                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14736                    filter_text: Some("AGL/".to_string()),
14737                    insert_text: Some("AGL/".to_string()),
14738                    ..lsp::CompletionItem::default()
14739                }],
14740            })))
14741        });
14742    cx.update_editor(|editor, window, cx| {
14743        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14744    });
14745    cx.executor().run_until_parked();
14746    cx.update_editor(|editor, window, cx| {
14747        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14748    });
14749    cx.executor().run_until_parked();
14750    cx.assert_editor_state(
14751        r##"#ifndef BAR_H
14752#define BAR_H
14753
14754#include <stdbool.h>
14755
14756int fn_branch(bool do_branch1, bool do_branch2);
14757
14758#endif // BAR_H
14759#include "AGL/ˇ"##,
14760    );
14761
14762    cx.update_editor(|editor, window, cx| {
14763        editor.handle_input("\"", window, cx);
14764    });
14765    cx.executor().run_until_parked();
14766    cx.assert_editor_state(
14767        r##"#ifndef BAR_H
14768#define BAR_H
14769
14770#include <stdbool.h>
14771
14772int fn_branch(bool do_branch1, bool do_branch2);
14773
14774#endif // BAR_H
14775#include "AGL/"ˇ"##,
14776    );
14777}
14778
14779#[gpui::test]
14780async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
14781    init_test(cx, |_| {});
14782
14783    let mut cx = EditorLspTestContext::new_rust(
14784        lsp::ServerCapabilities {
14785            completion_provider: Some(lsp::CompletionOptions {
14786                trigger_characters: Some(vec![".".to_string()]),
14787                resolve_provider: Some(true),
14788                ..Default::default()
14789            }),
14790            ..Default::default()
14791        },
14792        cx,
14793    )
14794    .await;
14795
14796    cx.set_state("fn main() { let a = 2ˇ; }");
14797    cx.simulate_keystroke(".");
14798    let completion_item = lsp::CompletionItem {
14799        label: "Some".into(),
14800        kind: Some(lsp::CompletionItemKind::SNIPPET),
14801        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
14802        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
14803            kind: lsp::MarkupKind::Markdown,
14804            value: "```rust\nSome(2)\n```".to_string(),
14805        })),
14806        deprecated: Some(false),
14807        sort_text: Some("Some".to_string()),
14808        filter_text: Some("Some".to_string()),
14809        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14810        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14811            range: lsp::Range {
14812                start: lsp::Position {
14813                    line: 0,
14814                    character: 22,
14815                },
14816                end: lsp::Position {
14817                    line: 0,
14818                    character: 22,
14819                },
14820            },
14821            new_text: "Some(2)".to_string(),
14822        })),
14823        additional_text_edits: Some(vec![lsp::TextEdit {
14824            range: lsp::Range {
14825                start: lsp::Position {
14826                    line: 0,
14827                    character: 20,
14828                },
14829                end: lsp::Position {
14830                    line: 0,
14831                    character: 22,
14832                },
14833            },
14834            new_text: "".to_string(),
14835        }]),
14836        ..Default::default()
14837    };
14838
14839    let closure_completion_item = completion_item.clone();
14840    let counter = Arc::new(AtomicUsize::new(0));
14841    let counter_clone = counter.clone();
14842    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14843        let task_completion_item = closure_completion_item.clone();
14844        counter_clone.fetch_add(1, atomic::Ordering::Release);
14845        async move {
14846            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14847                is_incomplete: true,
14848                item_defaults: None,
14849                items: vec![task_completion_item],
14850            })))
14851        }
14852    });
14853
14854    cx.condition(|editor, _| editor.context_menu_visible())
14855        .await;
14856    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
14857    assert!(request.next().await.is_some());
14858    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14859
14860    cx.simulate_keystrokes("S o m");
14861    cx.condition(|editor, _| editor.context_menu_visible())
14862        .await;
14863    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
14864    assert!(request.next().await.is_some());
14865    assert!(request.next().await.is_some());
14866    assert!(request.next().await.is_some());
14867    request.close();
14868    assert!(request.next().await.is_none());
14869    assert_eq!(
14870        counter.load(atomic::Ordering::Acquire),
14871        4,
14872        "With the completions menu open, only one LSP request should happen per input"
14873    );
14874}
14875
14876#[gpui::test]
14877async fn test_toggle_comment(cx: &mut TestAppContext) {
14878    init_test(cx, |_| {});
14879    let mut cx = EditorTestContext::new(cx).await;
14880    let language = Arc::new(Language::new(
14881        LanguageConfig {
14882            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14883            ..Default::default()
14884        },
14885        Some(tree_sitter_rust::LANGUAGE.into()),
14886    ));
14887    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14888
14889    // If multiple selections intersect a line, the line is only toggled once.
14890    cx.set_state(indoc! {"
14891        fn a() {
14892            «//b();
14893            ˇ»// «c();
14894            //ˇ»  d();
14895        }
14896    "});
14897
14898    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14899
14900    cx.assert_editor_state(indoc! {"
14901        fn a() {
14902            «b();
14903            c();
14904            ˇ» d();
14905        }
14906    "});
14907
14908    // The comment prefix is inserted at the same column for every line in a
14909    // selection.
14910    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14911
14912    cx.assert_editor_state(indoc! {"
14913        fn a() {
14914            // «b();
14915            // c();
14916            ˇ»//  d();
14917        }
14918    "});
14919
14920    // If a selection ends at the beginning of a line, that line is not toggled.
14921    cx.set_selections_state(indoc! {"
14922        fn a() {
14923            // b();
14924            «// c();
14925        ˇ»    //  d();
14926        }
14927    "});
14928
14929    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14930
14931    cx.assert_editor_state(indoc! {"
14932        fn a() {
14933            // b();
14934            «c();
14935        ˇ»    //  d();
14936        }
14937    "});
14938
14939    // If a selection span a single line and is empty, the line is toggled.
14940    cx.set_state(indoc! {"
14941        fn a() {
14942            a();
14943            b();
14944        ˇ
14945        }
14946    "});
14947
14948    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14949
14950    cx.assert_editor_state(indoc! {"
14951        fn a() {
14952            a();
14953            b();
14954        //•ˇ
14955        }
14956    "});
14957
14958    // If a selection span multiple lines, empty lines are not toggled.
14959    cx.set_state(indoc! {"
14960        fn a() {
14961            «a();
14962
14963            c();ˇ»
14964        }
14965    "});
14966
14967    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14968
14969    cx.assert_editor_state(indoc! {"
14970        fn a() {
14971            // «a();
14972
14973            // c();ˇ»
14974        }
14975    "});
14976
14977    // If a selection includes multiple comment prefixes, all lines are uncommented.
14978    cx.set_state(indoc! {"
14979        fn a() {
14980            «// a();
14981            /// b();
14982            //! c();ˇ»
14983        }
14984    "});
14985
14986    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14987
14988    cx.assert_editor_state(indoc! {"
14989        fn a() {
14990            «a();
14991            b();
14992            c();ˇ»
14993        }
14994    "});
14995}
14996
14997#[gpui::test]
14998async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
14999    init_test(cx, |_| {});
15000    let mut cx = EditorTestContext::new(cx).await;
15001    let language = Arc::new(Language::new(
15002        LanguageConfig {
15003            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15004            ..Default::default()
15005        },
15006        Some(tree_sitter_rust::LANGUAGE.into()),
15007    ));
15008    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15009
15010    let toggle_comments = &ToggleComments {
15011        advance_downwards: false,
15012        ignore_indent: true,
15013    };
15014
15015    // If multiple selections intersect a line, the line is only toggled once.
15016    cx.set_state(indoc! {"
15017        fn a() {
15018        //    «b();
15019        //    c();
15020        //    ˇ» d();
15021        }
15022    "});
15023
15024    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15025
15026    cx.assert_editor_state(indoc! {"
15027        fn a() {
15028            «b();
15029            c();
15030            ˇ» d();
15031        }
15032    "});
15033
15034    // The comment prefix is inserted at the beginning of each line
15035    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15036
15037    cx.assert_editor_state(indoc! {"
15038        fn a() {
15039        //    «b();
15040        //    c();
15041        //    ˇ» d();
15042        }
15043    "});
15044
15045    // If a selection ends at the beginning of a line, that line is not toggled.
15046    cx.set_selections_state(indoc! {"
15047        fn a() {
15048        //    b();
15049        //    «c();
15050        ˇ»//     d();
15051        }
15052    "});
15053
15054    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15055
15056    cx.assert_editor_state(indoc! {"
15057        fn a() {
15058        //    b();
15059            «c();
15060        ˇ»//     d();
15061        }
15062    "});
15063
15064    // If a selection span a single line and is empty, the line is toggled.
15065    cx.set_state(indoc! {"
15066        fn a() {
15067            a();
15068            b();
15069        ˇ
15070        }
15071    "});
15072
15073    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15074
15075    cx.assert_editor_state(indoc! {"
15076        fn a() {
15077            a();
15078            b();
15079        //ˇ
15080        }
15081    "});
15082
15083    // If a selection span multiple lines, empty lines are not toggled.
15084    cx.set_state(indoc! {"
15085        fn a() {
15086            «a();
15087
15088            c();ˇ»
15089        }
15090    "});
15091
15092    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15093
15094    cx.assert_editor_state(indoc! {"
15095        fn a() {
15096        //    «a();
15097
15098        //    c();ˇ»
15099        }
15100    "});
15101
15102    // If a selection includes multiple comment prefixes, all lines are uncommented.
15103    cx.set_state(indoc! {"
15104        fn a() {
15105        //    «a();
15106        ///    b();
15107        //!    c();ˇ»
15108        }
15109    "});
15110
15111    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15112
15113    cx.assert_editor_state(indoc! {"
15114        fn a() {
15115            «a();
15116            b();
15117            c();ˇ»
15118        }
15119    "});
15120}
15121
15122#[gpui::test]
15123async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15124    init_test(cx, |_| {});
15125
15126    let language = Arc::new(Language::new(
15127        LanguageConfig {
15128            line_comments: vec!["// ".into()],
15129            ..Default::default()
15130        },
15131        Some(tree_sitter_rust::LANGUAGE.into()),
15132    ));
15133
15134    let mut cx = EditorTestContext::new(cx).await;
15135
15136    cx.language_registry().add(language.clone());
15137    cx.update_buffer(|buffer, cx| {
15138        buffer.set_language(Some(language), cx);
15139    });
15140
15141    let toggle_comments = &ToggleComments {
15142        advance_downwards: true,
15143        ignore_indent: false,
15144    };
15145
15146    // Single cursor on one line -> advance
15147    // Cursor moves horizontally 3 characters as well on non-blank line
15148    cx.set_state(indoc!(
15149        "fn a() {
15150             ˇdog();
15151             cat();
15152        }"
15153    ));
15154    cx.update_editor(|editor, window, cx| {
15155        editor.toggle_comments(toggle_comments, window, cx);
15156    });
15157    cx.assert_editor_state(indoc!(
15158        "fn a() {
15159             // dog();
15160             catˇ();
15161        }"
15162    ));
15163
15164    // Single selection on one line -> don't advance
15165    cx.set_state(indoc!(
15166        "fn a() {
15167             «dog()ˇ»;
15168             cat();
15169        }"
15170    ));
15171    cx.update_editor(|editor, window, cx| {
15172        editor.toggle_comments(toggle_comments, window, cx);
15173    });
15174    cx.assert_editor_state(indoc!(
15175        "fn a() {
15176             // «dog()ˇ»;
15177             cat();
15178        }"
15179    ));
15180
15181    // Multiple cursors on one line -> advance
15182    cx.set_state(indoc!(
15183        "fn a() {
15184             ˇdˇog();
15185             cat();
15186        }"
15187    ));
15188    cx.update_editor(|editor, window, cx| {
15189        editor.toggle_comments(toggle_comments, window, cx);
15190    });
15191    cx.assert_editor_state(indoc!(
15192        "fn a() {
15193             // dog();
15194             catˇ(ˇ);
15195        }"
15196    ));
15197
15198    // Multiple cursors on one line, with selection -> don't advance
15199    cx.set_state(indoc!(
15200        "fn a() {
15201             ˇdˇog«()ˇ»;
15202             cat();
15203        }"
15204    ));
15205    cx.update_editor(|editor, window, cx| {
15206        editor.toggle_comments(toggle_comments, window, cx);
15207    });
15208    cx.assert_editor_state(indoc!(
15209        "fn a() {
15210             // ˇdˇog«()ˇ»;
15211             cat();
15212        }"
15213    ));
15214
15215    // Single cursor on one line -> advance
15216    // Cursor moves to column 0 on blank line
15217    cx.set_state(indoc!(
15218        "fn a() {
15219             ˇdog();
15220
15221             cat();
15222        }"
15223    ));
15224    cx.update_editor(|editor, window, cx| {
15225        editor.toggle_comments(toggle_comments, window, cx);
15226    });
15227    cx.assert_editor_state(indoc!(
15228        "fn a() {
15229             // dog();
15230        ˇ
15231             cat();
15232        }"
15233    ));
15234
15235    // Single cursor on one line -> advance
15236    // Cursor starts and ends at column 0
15237    cx.set_state(indoc!(
15238        "fn a() {
15239         ˇ    dog();
15240             cat();
15241        }"
15242    ));
15243    cx.update_editor(|editor, window, cx| {
15244        editor.toggle_comments(toggle_comments, window, cx);
15245    });
15246    cx.assert_editor_state(indoc!(
15247        "fn a() {
15248             // dog();
15249         ˇ    cat();
15250        }"
15251    ));
15252}
15253
15254#[gpui::test]
15255async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15256    init_test(cx, |_| {});
15257
15258    let mut cx = EditorTestContext::new(cx).await;
15259
15260    let html_language = Arc::new(
15261        Language::new(
15262            LanguageConfig {
15263                name: "HTML".into(),
15264                block_comment: Some(BlockCommentConfig {
15265                    start: "<!-- ".into(),
15266                    prefix: "".into(),
15267                    end: " -->".into(),
15268                    tab_size: 0,
15269                }),
15270                ..Default::default()
15271            },
15272            Some(tree_sitter_html::LANGUAGE.into()),
15273        )
15274        .with_injection_query(
15275            r#"
15276            (script_element
15277                (raw_text) @injection.content
15278                (#set! injection.language "javascript"))
15279            "#,
15280        )
15281        .unwrap(),
15282    );
15283
15284    let javascript_language = Arc::new(Language::new(
15285        LanguageConfig {
15286            name: "JavaScript".into(),
15287            line_comments: vec!["// ".into()],
15288            ..Default::default()
15289        },
15290        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15291    ));
15292
15293    cx.language_registry().add(html_language.clone());
15294    cx.language_registry().add(javascript_language);
15295    cx.update_buffer(|buffer, cx| {
15296        buffer.set_language(Some(html_language), cx);
15297    });
15298
15299    // Toggle comments for empty selections
15300    cx.set_state(
15301        &r#"
15302            <p>A</p>ˇ
15303            <p>B</p>ˇ
15304            <p>C</p>ˇ
15305        "#
15306        .unindent(),
15307    );
15308    cx.update_editor(|editor, window, cx| {
15309        editor.toggle_comments(&ToggleComments::default(), window, cx)
15310    });
15311    cx.assert_editor_state(
15312        &r#"
15313            <!-- <p>A</p>ˇ -->
15314            <!-- <p>B</p>ˇ -->
15315            <!-- <p>C</p>ˇ -->
15316        "#
15317        .unindent(),
15318    );
15319    cx.update_editor(|editor, window, cx| {
15320        editor.toggle_comments(&ToggleComments::default(), window, cx)
15321    });
15322    cx.assert_editor_state(
15323        &r#"
15324            <p>A</p>ˇ
15325            <p>B</p>ˇ
15326            <p>C</p>ˇ
15327        "#
15328        .unindent(),
15329    );
15330
15331    // Toggle comments for mixture of empty and non-empty selections, where
15332    // multiple selections occupy a given line.
15333    cx.set_state(
15334        &r#"
15335            <p>A«</p>
15336            <p>ˇ»B</p>ˇ
15337            <p>C«</p>
15338            <p>ˇ»D</p>ˇ
15339        "#
15340        .unindent(),
15341    );
15342
15343    cx.update_editor(|editor, window, cx| {
15344        editor.toggle_comments(&ToggleComments::default(), window, cx)
15345    });
15346    cx.assert_editor_state(
15347        &r#"
15348            <!-- <p>A«</p>
15349            <p>ˇ»B</p>ˇ -->
15350            <!-- <p>C«</p>
15351            <p>ˇ»D</p>ˇ -->
15352        "#
15353        .unindent(),
15354    );
15355    cx.update_editor(|editor, window, cx| {
15356        editor.toggle_comments(&ToggleComments::default(), window, cx)
15357    });
15358    cx.assert_editor_state(
15359        &r#"
15360            <p>A«</p>
15361            <p>ˇ»B</p>ˇ
15362            <p>C«</p>
15363            <p>ˇ»D</p>ˇ
15364        "#
15365        .unindent(),
15366    );
15367
15368    // Toggle comments when different languages are active for different
15369    // selections.
15370    cx.set_state(
15371        &r#"
15372            ˇ<script>
15373                ˇvar x = new Y();
15374            ˇ</script>
15375        "#
15376        .unindent(),
15377    );
15378    cx.executor().run_until_parked();
15379    cx.update_editor(|editor, window, cx| {
15380        editor.toggle_comments(&ToggleComments::default(), window, cx)
15381    });
15382    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15383    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15384    cx.assert_editor_state(
15385        &r#"
15386            <!-- ˇ<script> -->
15387                // ˇvar x = new Y();
15388            <!-- ˇ</script> -->
15389        "#
15390        .unindent(),
15391    );
15392}
15393
15394#[gpui::test]
15395fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15396    init_test(cx, |_| {});
15397
15398    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15399    let multibuffer = cx.new(|cx| {
15400        let mut multibuffer = MultiBuffer::new(ReadWrite);
15401        multibuffer.push_excerpts(
15402            buffer.clone(),
15403            [
15404                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15405                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15406            ],
15407            cx,
15408        );
15409        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15410        multibuffer
15411    });
15412
15413    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15414    editor.update_in(cx, |editor, window, cx| {
15415        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15417            s.select_ranges([
15418                Point::new(0, 0)..Point::new(0, 0),
15419                Point::new(1, 0)..Point::new(1, 0),
15420            ])
15421        });
15422
15423        editor.handle_input("X", window, cx);
15424        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15425        assert_eq!(
15426            editor.selections.ranges(cx),
15427            [
15428                Point::new(0, 1)..Point::new(0, 1),
15429                Point::new(1, 1)..Point::new(1, 1),
15430            ]
15431        );
15432
15433        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15434        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15435            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15436        });
15437        editor.backspace(&Default::default(), window, cx);
15438        assert_eq!(editor.text(cx), "Xa\nbbb");
15439        assert_eq!(
15440            editor.selections.ranges(cx),
15441            [Point::new(1, 0)..Point::new(1, 0)]
15442        );
15443
15444        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15445            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15446        });
15447        editor.backspace(&Default::default(), window, cx);
15448        assert_eq!(editor.text(cx), "X\nbb");
15449        assert_eq!(
15450            editor.selections.ranges(cx),
15451            [Point::new(0, 1)..Point::new(0, 1)]
15452        );
15453    });
15454}
15455
15456#[gpui::test]
15457fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15458    init_test(cx, |_| {});
15459
15460    let markers = vec![('[', ']').into(), ('(', ')').into()];
15461    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15462        indoc! {"
15463            [aaaa
15464            (bbbb]
15465            cccc)",
15466        },
15467        markers.clone(),
15468    );
15469    let excerpt_ranges = markers.into_iter().map(|marker| {
15470        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15471        ExcerptRange::new(context)
15472    });
15473    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15474    let multibuffer = cx.new(|cx| {
15475        let mut multibuffer = MultiBuffer::new(ReadWrite);
15476        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15477        multibuffer
15478    });
15479
15480    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15481    editor.update_in(cx, |editor, window, cx| {
15482        let (expected_text, selection_ranges) = marked_text_ranges(
15483            indoc! {"
15484                aaaa
15485                bˇbbb
15486                bˇbbˇb
15487                cccc"
15488            },
15489            true,
15490        );
15491        assert_eq!(editor.text(cx), expected_text);
15492        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15493            s.select_ranges(selection_ranges)
15494        });
15495
15496        editor.handle_input("X", window, cx);
15497
15498        let (expected_text, expected_selections) = marked_text_ranges(
15499            indoc! {"
15500                aaaa
15501                bXˇbbXb
15502                bXˇbbXˇb
15503                cccc"
15504            },
15505            false,
15506        );
15507        assert_eq!(editor.text(cx), expected_text);
15508        assert_eq!(editor.selections.ranges(cx), expected_selections);
15509
15510        editor.newline(&Newline, window, cx);
15511        let (expected_text, expected_selections) = marked_text_ranges(
15512            indoc! {"
15513                aaaa
15514                bX
15515                ˇbbX
15516                b
15517                bX
15518                ˇbbX
15519                ˇb
15520                cccc"
15521            },
15522            false,
15523        );
15524        assert_eq!(editor.text(cx), expected_text);
15525        assert_eq!(editor.selections.ranges(cx), expected_selections);
15526    });
15527}
15528
15529#[gpui::test]
15530fn test_refresh_selections(cx: &mut TestAppContext) {
15531    init_test(cx, |_| {});
15532
15533    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15534    let mut excerpt1_id = None;
15535    let multibuffer = cx.new(|cx| {
15536        let mut multibuffer = MultiBuffer::new(ReadWrite);
15537        excerpt1_id = multibuffer
15538            .push_excerpts(
15539                buffer.clone(),
15540                [
15541                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15542                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15543                ],
15544                cx,
15545            )
15546            .into_iter()
15547            .next();
15548        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15549        multibuffer
15550    });
15551
15552    let editor = cx.add_window(|window, cx| {
15553        let mut editor = build_editor(multibuffer.clone(), window, cx);
15554        let snapshot = editor.snapshot(window, cx);
15555        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15556            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15557        });
15558        editor.begin_selection(
15559            Point::new(2, 1).to_display_point(&snapshot),
15560            true,
15561            1,
15562            window,
15563            cx,
15564        );
15565        assert_eq!(
15566            editor.selections.ranges(cx),
15567            [
15568                Point::new(1, 3)..Point::new(1, 3),
15569                Point::new(2, 1)..Point::new(2, 1),
15570            ]
15571        );
15572        editor
15573    });
15574
15575    // Refreshing selections is a no-op when excerpts haven't changed.
15576    _ = editor.update(cx, |editor, window, cx| {
15577        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15578        assert_eq!(
15579            editor.selections.ranges(cx),
15580            [
15581                Point::new(1, 3)..Point::new(1, 3),
15582                Point::new(2, 1)..Point::new(2, 1),
15583            ]
15584        );
15585    });
15586
15587    multibuffer.update(cx, |multibuffer, cx| {
15588        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15589    });
15590    _ = editor.update(cx, |editor, window, cx| {
15591        // Removing an excerpt causes the first selection to become degenerate.
15592        assert_eq!(
15593            editor.selections.ranges(cx),
15594            [
15595                Point::new(0, 0)..Point::new(0, 0),
15596                Point::new(0, 1)..Point::new(0, 1)
15597            ]
15598        );
15599
15600        // Refreshing selections will relocate the first selection to the original buffer
15601        // location.
15602        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15603        assert_eq!(
15604            editor.selections.ranges(cx),
15605            [
15606                Point::new(0, 1)..Point::new(0, 1),
15607                Point::new(0, 3)..Point::new(0, 3)
15608            ]
15609        );
15610        assert!(editor.selections.pending_anchor().is_some());
15611    });
15612}
15613
15614#[gpui::test]
15615fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15616    init_test(cx, |_| {});
15617
15618    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15619    let mut excerpt1_id = None;
15620    let multibuffer = cx.new(|cx| {
15621        let mut multibuffer = MultiBuffer::new(ReadWrite);
15622        excerpt1_id = multibuffer
15623            .push_excerpts(
15624                buffer.clone(),
15625                [
15626                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15627                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15628                ],
15629                cx,
15630            )
15631            .into_iter()
15632            .next();
15633        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15634        multibuffer
15635    });
15636
15637    let editor = cx.add_window(|window, cx| {
15638        let mut editor = build_editor(multibuffer.clone(), window, cx);
15639        let snapshot = editor.snapshot(window, cx);
15640        editor.begin_selection(
15641            Point::new(1, 3).to_display_point(&snapshot),
15642            false,
15643            1,
15644            window,
15645            cx,
15646        );
15647        assert_eq!(
15648            editor.selections.ranges(cx),
15649            [Point::new(1, 3)..Point::new(1, 3)]
15650        );
15651        editor
15652    });
15653
15654    multibuffer.update(cx, |multibuffer, cx| {
15655        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15656    });
15657    _ = editor.update(cx, |editor, window, cx| {
15658        assert_eq!(
15659            editor.selections.ranges(cx),
15660            [Point::new(0, 0)..Point::new(0, 0)]
15661        );
15662
15663        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
15664        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15665        assert_eq!(
15666            editor.selections.ranges(cx),
15667            [Point::new(0, 3)..Point::new(0, 3)]
15668        );
15669        assert!(editor.selections.pending_anchor().is_some());
15670    });
15671}
15672
15673#[gpui::test]
15674async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
15675    init_test(cx, |_| {});
15676
15677    let language = Arc::new(
15678        Language::new(
15679            LanguageConfig {
15680                brackets: BracketPairConfig {
15681                    pairs: vec![
15682                        BracketPair {
15683                            start: "{".to_string(),
15684                            end: "}".to_string(),
15685                            close: true,
15686                            surround: true,
15687                            newline: true,
15688                        },
15689                        BracketPair {
15690                            start: "/* ".to_string(),
15691                            end: " */".to_string(),
15692                            close: true,
15693                            surround: true,
15694                            newline: true,
15695                        },
15696                    ],
15697                    ..Default::default()
15698                },
15699                ..Default::default()
15700            },
15701            Some(tree_sitter_rust::LANGUAGE.into()),
15702        )
15703        .with_indents_query("")
15704        .unwrap(),
15705    );
15706
15707    let text = concat!(
15708        "{   }\n",     //
15709        "  x\n",       //
15710        "  /*   */\n", //
15711        "x\n",         //
15712        "{{} }\n",     //
15713    );
15714
15715    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15716    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15717    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15718    editor
15719        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
15720        .await;
15721
15722    editor.update_in(cx, |editor, window, cx| {
15723        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15724            s.select_display_ranges([
15725                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
15726                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
15727                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
15728            ])
15729        });
15730        editor.newline(&Newline, window, cx);
15731
15732        assert_eq!(
15733            editor.buffer().read(cx).read(cx).text(),
15734            concat!(
15735                "{ \n",    // Suppress rustfmt
15736                "\n",      //
15737                "}\n",     //
15738                "  x\n",   //
15739                "  /* \n", //
15740                "  \n",    //
15741                "  */\n",  //
15742                "x\n",     //
15743                "{{} \n",  //
15744                "}\n",     //
15745            )
15746        );
15747    });
15748}
15749
15750#[gpui::test]
15751fn test_highlighted_ranges(cx: &mut TestAppContext) {
15752    init_test(cx, |_| {});
15753
15754    let editor = cx.add_window(|window, cx| {
15755        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
15756        build_editor(buffer, window, cx)
15757    });
15758
15759    _ = editor.update(cx, |editor, window, cx| {
15760        struct Type1;
15761        struct Type2;
15762
15763        let buffer = editor.buffer.read(cx).snapshot(cx);
15764
15765        let anchor_range =
15766            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
15767
15768        editor.highlight_background::<Type1>(
15769            &[
15770                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
15771                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
15772                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
15773                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
15774            ],
15775            |_| Hsla::red(),
15776            cx,
15777        );
15778        editor.highlight_background::<Type2>(
15779            &[
15780                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
15781                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
15782                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
15783                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
15784            ],
15785            |_| Hsla::green(),
15786            cx,
15787        );
15788
15789        let snapshot = editor.snapshot(window, cx);
15790        let highlighted_ranges = editor.sorted_background_highlights_in_range(
15791            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
15792            &snapshot,
15793            cx.theme(),
15794        );
15795        assert_eq!(
15796            highlighted_ranges,
15797            &[
15798                (
15799                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
15800                    Hsla::green(),
15801                ),
15802                (
15803                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
15804                    Hsla::red(),
15805                ),
15806                (
15807                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
15808                    Hsla::green(),
15809                ),
15810                (
15811                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
15812                    Hsla::red(),
15813                ),
15814            ]
15815        );
15816        assert_eq!(
15817            editor.sorted_background_highlights_in_range(
15818                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
15819                &snapshot,
15820                cx.theme(),
15821            ),
15822            &[(
15823                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
15824                Hsla::red(),
15825            )]
15826        );
15827    });
15828}
15829
15830#[gpui::test]
15831async fn test_following(cx: &mut TestAppContext) {
15832    init_test(cx, |_| {});
15833
15834    let fs = FakeFs::new(cx.executor());
15835    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
15836
15837    let buffer = project.update(cx, |project, cx| {
15838        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
15839        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
15840    });
15841    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
15842    let follower = cx.update(|cx| {
15843        cx.open_window(
15844            WindowOptions {
15845                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
15846                    gpui::Point::new(px(0.), px(0.)),
15847                    gpui::Point::new(px(10.), px(80.)),
15848                ))),
15849                ..Default::default()
15850            },
15851            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
15852        )
15853        .unwrap()
15854    });
15855
15856    let is_still_following = Rc::new(RefCell::new(true));
15857    let follower_edit_event_count = Rc::new(RefCell::new(0));
15858    let pending_update = Rc::new(RefCell::new(None));
15859    let leader_entity = leader.root(cx).unwrap();
15860    let follower_entity = follower.root(cx).unwrap();
15861    _ = follower.update(cx, {
15862        let update = pending_update.clone();
15863        let is_still_following = is_still_following.clone();
15864        let follower_edit_event_count = follower_edit_event_count.clone();
15865        |_, window, cx| {
15866            cx.subscribe_in(
15867                &leader_entity,
15868                window,
15869                move |_, leader, event, window, cx| {
15870                    leader.read(cx).add_event_to_update_proto(
15871                        event,
15872                        &mut update.borrow_mut(),
15873                        window,
15874                        cx,
15875                    );
15876                },
15877            )
15878            .detach();
15879
15880            cx.subscribe_in(
15881                &follower_entity,
15882                window,
15883                move |_, _, event: &EditorEvent, _window, _cx| {
15884                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
15885                        *is_still_following.borrow_mut() = false;
15886                    }
15887
15888                    if let EditorEvent::BufferEdited = event {
15889                        *follower_edit_event_count.borrow_mut() += 1;
15890                    }
15891                },
15892            )
15893            .detach();
15894        }
15895    });
15896
15897    // Update the selections only
15898    _ = leader.update(cx, |leader, window, cx| {
15899        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15900            s.select_ranges([1..1])
15901        });
15902    });
15903    follower
15904        .update(cx, |follower, window, cx| {
15905            follower.apply_update_proto(
15906                &project,
15907                pending_update.borrow_mut().take().unwrap(),
15908                window,
15909                cx,
15910            )
15911        })
15912        .unwrap()
15913        .await
15914        .unwrap();
15915    _ = follower.update(cx, |follower, _, cx| {
15916        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
15917    });
15918    assert!(*is_still_following.borrow());
15919    assert_eq!(*follower_edit_event_count.borrow(), 0);
15920
15921    // Update the scroll position only
15922    _ = leader.update(cx, |leader, window, cx| {
15923        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15924    });
15925    follower
15926        .update(cx, |follower, window, cx| {
15927            follower.apply_update_proto(
15928                &project,
15929                pending_update.borrow_mut().take().unwrap(),
15930                window,
15931                cx,
15932            )
15933        })
15934        .unwrap()
15935        .await
15936        .unwrap();
15937    assert_eq!(
15938        follower
15939            .update(cx, |follower, _, cx| follower.scroll_position(cx))
15940            .unwrap(),
15941        gpui::Point::new(1.5, 3.5)
15942    );
15943    assert!(*is_still_following.borrow());
15944    assert_eq!(*follower_edit_event_count.borrow(), 0);
15945
15946    // Update the selections and scroll position. The follower's scroll position is updated
15947    // via autoscroll, not via the leader's exact scroll position.
15948    _ = leader.update(cx, |leader, window, cx| {
15949        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15950            s.select_ranges([0..0])
15951        });
15952        leader.request_autoscroll(Autoscroll::newest(), cx);
15953        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15954    });
15955    follower
15956        .update(cx, |follower, window, cx| {
15957            follower.apply_update_proto(
15958                &project,
15959                pending_update.borrow_mut().take().unwrap(),
15960                window,
15961                cx,
15962            )
15963        })
15964        .unwrap()
15965        .await
15966        .unwrap();
15967    _ = follower.update(cx, |follower, _, cx| {
15968        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
15969        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
15970    });
15971    assert!(*is_still_following.borrow());
15972
15973    // Creating a pending selection that precedes another selection
15974    _ = leader.update(cx, |leader, window, cx| {
15975        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15976            s.select_ranges([1..1])
15977        });
15978        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
15979    });
15980    follower
15981        .update(cx, |follower, window, cx| {
15982            follower.apply_update_proto(
15983                &project,
15984                pending_update.borrow_mut().take().unwrap(),
15985                window,
15986                cx,
15987            )
15988        })
15989        .unwrap()
15990        .await
15991        .unwrap();
15992    _ = follower.update(cx, |follower, _, cx| {
15993        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
15994    });
15995    assert!(*is_still_following.borrow());
15996
15997    // Extend the pending selection so that it surrounds another selection
15998    _ = leader.update(cx, |leader, window, cx| {
15999        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16000    });
16001    follower
16002        .update(cx, |follower, window, cx| {
16003            follower.apply_update_proto(
16004                &project,
16005                pending_update.borrow_mut().take().unwrap(),
16006                window,
16007                cx,
16008            )
16009        })
16010        .unwrap()
16011        .await
16012        .unwrap();
16013    _ = follower.update(cx, |follower, _, cx| {
16014        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16015    });
16016
16017    // Scrolling locally breaks the follow
16018    _ = follower.update(cx, |follower, window, cx| {
16019        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16020        follower.set_scroll_anchor(
16021            ScrollAnchor {
16022                anchor: top_anchor,
16023                offset: gpui::Point::new(0.0, 0.5),
16024            },
16025            window,
16026            cx,
16027        );
16028    });
16029    assert!(!(*is_still_following.borrow()));
16030}
16031
16032#[gpui::test]
16033async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16034    init_test(cx, |_| {});
16035
16036    let fs = FakeFs::new(cx.executor());
16037    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16038    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16039    let pane = workspace
16040        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16041        .unwrap();
16042
16043    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16044
16045    let leader = pane.update_in(cx, |_, window, cx| {
16046        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16047        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16048    });
16049
16050    // Start following the editor when it has no excerpts.
16051    let mut state_message =
16052        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16053    let workspace_entity = workspace.root(cx).unwrap();
16054    let follower_1 = cx
16055        .update_window(*workspace.deref(), |_, window, cx| {
16056            Editor::from_state_proto(
16057                workspace_entity,
16058                ViewId {
16059                    creator: CollaboratorId::PeerId(PeerId::default()),
16060                    id: 0,
16061                },
16062                &mut state_message,
16063                window,
16064                cx,
16065            )
16066        })
16067        .unwrap()
16068        .unwrap()
16069        .await
16070        .unwrap();
16071
16072    let update_message = Rc::new(RefCell::new(None));
16073    follower_1.update_in(cx, {
16074        let update = update_message.clone();
16075        |_, window, cx| {
16076            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16077                leader.read(cx).add_event_to_update_proto(
16078                    event,
16079                    &mut update.borrow_mut(),
16080                    window,
16081                    cx,
16082                );
16083            })
16084            .detach();
16085        }
16086    });
16087
16088    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16089        (
16090            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
16091            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
16092        )
16093    });
16094
16095    // Insert some excerpts.
16096    leader.update(cx, |leader, cx| {
16097        leader.buffer.update(cx, |multibuffer, cx| {
16098            multibuffer.set_excerpts_for_path(
16099                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
16100                buffer_1.clone(),
16101                vec![
16102                    Point::row_range(0..3),
16103                    Point::row_range(1..6),
16104                    Point::row_range(12..15),
16105                ],
16106                0,
16107                cx,
16108            );
16109            multibuffer.set_excerpts_for_path(
16110                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
16111                buffer_2.clone(),
16112                vec![Point::row_range(0..6), Point::row_range(8..12)],
16113                0,
16114                cx,
16115            );
16116        });
16117    });
16118
16119    // Apply the update of adding the excerpts.
16120    follower_1
16121        .update_in(cx, |follower, window, cx| {
16122            follower.apply_update_proto(
16123                &project,
16124                update_message.borrow().clone().unwrap(),
16125                window,
16126                cx,
16127            )
16128        })
16129        .await
16130        .unwrap();
16131    assert_eq!(
16132        follower_1.update(cx, |editor, cx| editor.text(cx)),
16133        leader.update(cx, |editor, cx| editor.text(cx))
16134    );
16135    update_message.borrow_mut().take();
16136
16137    // Start following separately after it already has excerpts.
16138    let mut state_message =
16139        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16140    let workspace_entity = workspace.root(cx).unwrap();
16141    let follower_2 = cx
16142        .update_window(*workspace.deref(), |_, window, cx| {
16143            Editor::from_state_proto(
16144                workspace_entity,
16145                ViewId {
16146                    creator: CollaboratorId::PeerId(PeerId::default()),
16147                    id: 0,
16148                },
16149                &mut state_message,
16150                window,
16151                cx,
16152            )
16153        })
16154        .unwrap()
16155        .unwrap()
16156        .await
16157        .unwrap();
16158    assert_eq!(
16159        follower_2.update(cx, |editor, cx| editor.text(cx)),
16160        leader.update(cx, |editor, cx| editor.text(cx))
16161    );
16162
16163    // Remove some excerpts.
16164    leader.update(cx, |leader, cx| {
16165        leader.buffer.update(cx, |multibuffer, cx| {
16166            let excerpt_ids = multibuffer.excerpt_ids();
16167            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16168            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16169        });
16170    });
16171
16172    // Apply the update of removing the excerpts.
16173    follower_1
16174        .update_in(cx, |follower, window, cx| {
16175            follower.apply_update_proto(
16176                &project,
16177                update_message.borrow().clone().unwrap(),
16178                window,
16179                cx,
16180            )
16181        })
16182        .await
16183        .unwrap();
16184    follower_2
16185        .update_in(cx, |follower, window, cx| {
16186            follower.apply_update_proto(
16187                &project,
16188                update_message.borrow().clone().unwrap(),
16189                window,
16190                cx,
16191            )
16192        })
16193        .await
16194        .unwrap();
16195    update_message.borrow_mut().take();
16196    assert_eq!(
16197        follower_1.update(cx, |editor, cx| editor.text(cx)),
16198        leader.update(cx, |editor, cx| editor.text(cx))
16199    );
16200}
16201
16202#[gpui::test]
16203async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16204    init_test(cx, |_| {});
16205
16206    let mut cx = EditorTestContext::new(cx).await;
16207    let lsp_store =
16208        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16209
16210    cx.set_state(indoc! {"
16211        ˇfn func(abc def: i32) -> u32 {
16212        }
16213    "});
16214
16215    cx.update(|_, cx| {
16216        lsp_store.update(cx, |lsp_store, cx| {
16217            lsp_store
16218                .update_diagnostics(
16219                    LanguageServerId(0),
16220                    lsp::PublishDiagnosticsParams {
16221                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16222                        version: None,
16223                        diagnostics: vec![
16224                            lsp::Diagnostic {
16225                                range: lsp::Range::new(
16226                                    lsp::Position::new(0, 11),
16227                                    lsp::Position::new(0, 12),
16228                                ),
16229                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16230                                ..Default::default()
16231                            },
16232                            lsp::Diagnostic {
16233                                range: lsp::Range::new(
16234                                    lsp::Position::new(0, 12),
16235                                    lsp::Position::new(0, 15),
16236                                ),
16237                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16238                                ..Default::default()
16239                            },
16240                            lsp::Diagnostic {
16241                                range: lsp::Range::new(
16242                                    lsp::Position::new(0, 25),
16243                                    lsp::Position::new(0, 28),
16244                                ),
16245                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16246                                ..Default::default()
16247                            },
16248                        ],
16249                    },
16250                    None,
16251                    DiagnosticSourceKind::Pushed,
16252                    &[],
16253                    cx,
16254                )
16255                .unwrap()
16256        });
16257    });
16258
16259    executor.run_until_parked();
16260
16261    cx.update_editor(|editor, window, cx| {
16262        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16263    });
16264
16265    cx.assert_editor_state(indoc! {"
16266        fn func(abc def: i32) -> ˇu32 {
16267        }
16268    "});
16269
16270    cx.update_editor(|editor, window, cx| {
16271        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16272    });
16273
16274    cx.assert_editor_state(indoc! {"
16275        fn func(abc ˇdef: i32) -> u32 {
16276        }
16277    "});
16278
16279    cx.update_editor(|editor, window, cx| {
16280        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16281    });
16282
16283    cx.assert_editor_state(indoc! {"
16284        fn func(abcˇ def: i32) -> u32 {
16285        }
16286    "});
16287
16288    cx.update_editor(|editor, window, cx| {
16289        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16290    });
16291
16292    cx.assert_editor_state(indoc! {"
16293        fn func(abc def: i32) -> ˇu32 {
16294        }
16295    "});
16296}
16297
16298#[gpui::test]
16299async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16300    init_test(cx, |_| {});
16301
16302    let mut cx = EditorTestContext::new(cx).await;
16303
16304    let diff_base = r#"
16305        use some::mod;
16306
16307        const A: u32 = 42;
16308
16309        fn main() {
16310            println!("hello");
16311
16312            println!("world");
16313        }
16314        "#
16315    .unindent();
16316
16317    // Edits are modified, removed, modified, added
16318    cx.set_state(
16319        &r#"
16320        use some::modified;
16321
16322        ˇ
16323        fn main() {
16324            println!("hello there");
16325
16326            println!("around the");
16327            println!("world");
16328        }
16329        "#
16330        .unindent(),
16331    );
16332
16333    cx.set_head_text(&diff_base);
16334    executor.run_until_parked();
16335
16336    cx.update_editor(|editor, window, cx| {
16337        //Wrap around the bottom of the buffer
16338        for _ in 0..3 {
16339            editor.go_to_next_hunk(&GoToHunk, window, cx);
16340        }
16341    });
16342
16343    cx.assert_editor_state(
16344        &r#"
16345        ˇuse some::modified;
16346
16347
16348        fn main() {
16349            println!("hello there");
16350
16351            println!("around the");
16352            println!("world");
16353        }
16354        "#
16355        .unindent(),
16356    );
16357
16358    cx.update_editor(|editor, window, cx| {
16359        //Wrap around the top of the buffer
16360        for _ in 0..2 {
16361            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16362        }
16363    });
16364
16365    cx.assert_editor_state(
16366        &r#"
16367        use some::modified;
16368
16369
16370        fn main() {
16371        ˇ    println!("hello there");
16372
16373            println!("around the");
16374            println!("world");
16375        }
16376        "#
16377        .unindent(),
16378    );
16379
16380    cx.update_editor(|editor, window, cx| {
16381        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16382    });
16383
16384    cx.assert_editor_state(
16385        &r#"
16386        use some::modified;
16387
16388        ˇ
16389        fn main() {
16390            println!("hello there");
16391
16392            println!("around the");
16393            println!("world");
16394        }
16395        "#
16396        .unindent(),
16397    );
16398
16399    cx.update_editor(|editor, window, cx| {
16400        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16401    });
16402
16403    cx.assert_editor_state(
16404        &r#"
16405        ˇuse some::modified;
16406
16407
16408        fn main() {
16409            println!("hello there");
16410
16411            println!("around the");
16412            println!("world");
16413        }
16414        "#
16415        .unindent(),
16416    );
16417
16418    cx.update_editor(|editor, window, cx| {
16419        for _ in 0..2 {
16420            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16421        }
16422    });
16423
16424    cx.assert_editor_state(
16425        &r#"
16426        use some::modified;
16427
16428
16429        fn main() {
16430        ˇ    println!("hello there");
16431
16432            println!("around the");
16433            println!("world");
16434        }
16435        "#
16436        .unindent(),
16437    );
16438
16439    cx.update_editor(|editor, window, cx| {
16440        editor.fold(&Fold, window, cx);
16441    });
16442
16443    cx.update_editor(|editor, window, cx| {
16444        editor.go_to_next_hunk(&GoToHunk, window, cx);
16445    });
16446
16447    cx.assert_editor_state(
16448        &r#"
16449        ˇuse some::modified;
16450
16451
16452        fn main() {
16453            println!("hello there");
16454
16455            println!("around the");
16456            println!("world");
16457        }
16458        "#
16459        .unindent(),
16460    );
16461}
16462
16463#[test]
16464fn test_split_words() {
16465    fn split(text: &str) -> Vec<&str> {
16466        split_words(text).collect()
16467    }
16468
16469    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16470    assert_eq!(split("hello_world"), &["hello_", "world"]);
16471    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16472    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16473    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16474    assert_eq!(split("helloworld"), &["helloworld"]);
16475
16476    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16477}
16478
16479#[gpui::test]
16480async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16481    init_test(cx, |_| {});
16482
16483    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16484    let mut assert = |before, after| {
16485        let _state_context = cx.set_state(before);
16486        cx.run_until_parked();
16487        cx.update_editor(|editor, window, cx| {
16488            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16489        });
16490        cx.run_until_parked();
16491        cx.assert_editor_state(after);
16492    };
16493
16494    // Outside bracket jumps to outside of matching bracket
16495    assert("console.logˇ(var);", "console.log(var)ˇ;");
16496    assert("console.log(var)ˇ;", "console.logˇ(var);");
16497
16498    // Inside bracket jumps to inside of matching bracket
16499    assert("console.log(ˇvar);", "console.log(varˇ);");
16500    assert("console.log(varˇ);", "console.log(ˇvar);");
16501
16502    // When outside a bracket and inside, favor jumping to the inside bracket
16503    assert(
16504        "console.log('foo', [1, 2, 3]ˇ);",
16505        "console.log(ˇ'foo', [1, 2, 3]);",
16506    );
16507    assert(
16508        "console.log(ˇ'foo', [1, 2, 3]);",
16509        "console.log('foo', [1, 2, 3]ˇ);",
16510    );
16511
16512    // Bias forward if two options are equally likely
16513    assert(
16514        "let result = curried_fun()ˇ();",
16515        "let result = curried_fun()()ˇ;",
16516    );
16517
16518    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16519    assert(
16520        indoc! {"
16521            function test() {
16522                console.log('test')ˇ
16523            }"},
16524        indoc! {"
16525            function test() {
16526                console.logˇ('test')
16527            }"},
16528    );
16529}
16530
16531#[gpui::test]
16532async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16533    init_test(cx, |_| {});
16534
16535    let fs = FakeFs::new(cx.executor());
16536    fs.insert_tree(
16537        path!("/a"),
16538        json!({
16539            "main.rs": "fn main() { let a = 5; }",
16540            "other.rs": "// Test file",
16541        }),
16542    )
16543    .await;
16544    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16545
16546    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16547    language_registry.add(Arc::new(Language::new(
16548        LanguageConfig {
16549            name: "Rust".into(),
16550            matcher: LanguageMatcher {
16551                path_suffixes: vec!["rs".to_string()],
16552                ..Default::default()
16553            },
16554            brackets: BracketPairConfig {
16555                pairs: vec![BracketPair {
16556                    start: "{".to_string(),
16557                    end: "}".to_string(),
16558                    close: true,
16559                    surround: true,
16560                    newline: true,
16561                }],
16562                disabled_scopes_by_bracket_ix: Vec::new(),
16563            },
16564            ..Default::default()
16565        },
16566        Some(tree_sitter_rust::LANGUAGE.into()),
16567    )));
16568    let mut fake_servers = language_registry.register_fake_lsp(
16569        "Rust",
16570        FakeLspAdapter {
16571            capabilities: lsp::ServerCapabilities {
16572                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16573                    first_trigger_character: "{".to_string(),
16574                    more_trigger_character: None,
16575                }),
16576                ..Default::default()
16577            },
16578            ..Default::default()
16579        },
16580    );
16581
16582    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16583
16584    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16585
16586    let worktree_id = workspace
16587        .update(cx, |workspace, _, cx| {
16588            workspace.project().update(cx, |project, cx| {
16589                project.worktrees(cx).next().unwrap().read(cx).id()
16590            })
16591        })
16592        .unwrap();
16593
16594    let buffer = project
16595        .update(cx, |project, cx| {
16596            project.open_local_buffer(path!("/a/main.rs"), cx)
16597        })
16598        .await
16599        .unwrap();
16600    let editor_handle = workspace
16601        .update(cx, |workspace, window, cx| {
16602            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16603        })
16604        .unwrap()
16605        .await
16606        .unwrap()
16607        .downcast::<Editor>()
16608        .unwrap();
16609
16610    cx.executor().start_waiting();
16611    let fake_server = fake_servers.next().await.unwrap();
16612
16613    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16614        |params, _| async move {
16615            assert_eq!(
16616                params.text_document_position.text_document.uri,
16617                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16618            );
16619            assert_eq!(
16620                params.text_document_position.position,
16621                lsp::Position::new(0, 21),
16622            );
16623
16624            Ok(Some(vec![lsp::TextEdit {
16625                new_text: "]".to_string(),
16626                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16627            }]))
16628        },
16629    );
16630
16631    editor_handle.update_in(cx, |editor, window, cx| {
16632        window.focus(&editor.focus_handle(cx));
16633        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16634            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16635        });
16636        editor.handle_input("{", window, cx);
16637    });
16638
16639    cx.executor().run_until_parked();
16640
16641    buffer.update(cx, |buffer, _| {
16642        assert_eq!(
16643            buffer.text(),
16644            "fn main() { let a = {5}; }",
16645            "No extra braces from on type formatting should appear in the buffer"
16646        )
16647    });
16648}
16649
16650#[gpui::test(iterations = 20, seeds(31))]
16651async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
16652    init_test(cx, |_| {});
16653
16654    let mut cx = EditorLspTestContext::new_rust(
16655        lsp::ServerCapabilities {
16656            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16657                first_trigger_character: ".".to_string(),
16658                more_trigger_character: None,
16659            }),
16660            ..Default::default()
16661        },
16662        cx,
16663    )
16664    .await;
16665
16666    cx.update_buffer(|buffer, _| {
16667        // This causes autoindent to be async.
16668        buffer.set_sync_parse_timeout(Duration::ZERO)
16669    });
16670
16671    cx.set_state("fn c() {\n    d()ˇ\n}\n");
16672    cx.simulate_keystroke("\n");
16673    cx.run_until_parked();
16674
16675    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
16676    let mut request =
16677        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
16678            let buffer_cloned = buffer_cloned.clone();
16679            async move {
16680                buffer_cloned.update(&mut cx, |buffer, _| {
16681                    assert_eq!(
16682                        buffer.text(),
16683                        "fn c() {\n    d()\n        .\n}\n",
16684                        "OnTypeFormatting should triggered after autoindent applied"
16685                    )
16686                })?;
16687
16688                Ok(Some(vec![]))
16689            }
16690        });
16691
16692    cx.simulate_keystroke(".");
16693    cx.run_until_parked();
16694
16695    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
16696    assert!(request.next().await.is_some());
16697    request.close();
16698    assert!(request.next().await.is_none());
16699}
16700
16701#[gpui::test]
16702async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
16703    init_test(cx, |_| {});
16704
16705    let fs = FakeFs::new(cx.executor());
16706    fs.insert_tree(
16707        path!("/a"),
16708        json!({
16709            "main.rs": "fn main() { let a = 5; }",
16710            "other.rs": "// Test file",
16711        }),
16712    )
16713    .await;
16714
16715    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16716
16717    let server_restarts = Arc::new(AtomicUsize::new(0));
16718    let closure_restarts = Arc::clone(&server_restarts);
16719    let language_server_name = "test language server";
16720    let language_name: LanguageName = "Rust".into();
16721
16722    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16723    language_registry.add(Arc::new(Language::new(
16724        LanguageConfig {
16725            name: language_name.clone(),
16726            matcher: LanguageMatcher {
16727                path_suffixes: vec!["rs".to_string()],
16728                ..Default::default()
16729            },
16730            ..Default::default()
16731        },
16732        Some(tree_sitter_rust::LANGUAGE.into()),
16733    )));
16734    let mut fake_servers = language_registry.register_fake_lsp(
16735        "Rust",
16736        FakeLspAdapter {
16737            name: language_server_name,
16738            initialization_options: Some(json!({
16739                "testOptionValue": true
16740            })),
16741            initializer: Some(Box::new(move |fake_server| {
16742                let task_restarts = Arc::clone(&closure_restarts);
16743                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
16744                    task_restarts.fetch_add(1, atomic::Ordering::Release);
16745                    futures::future::ready(Ok(()))
16746                });
16747            })),
16748            ..Default::default()
16749        },
16750    );
16751
16752    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16753    let _buffer = project
16754        .update(cx, |project, cx| {
16755            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
16756        })
16757        .await
16758        .unwrap();
16759    let _fake_server = fake_servers.next().await.unwrap();
16760    update_test_language_settings(cx, |language_settings| {
16761        language_settings.languages.0.insert(
16762            language_name.clone(),
16763            LanguageSettingsContent {
16764                tab_size: NonZeroU32::new(8),
16765                ..Default::default()
16766            },
16767        );
16768    });
16769    cx.executor().run_until_parked();
16770    assert_eq!(
16771        server_restarts.load(atomic::Ordering::Acquire),
16772        0,
16773        "Should not restart LSP server on an unrelated change"
16774    );
16775
16776    update_test_project_settings(cx, |project_settings| {
16777        project_settings.lsp.insert(
16778            "Some other server name".into(),
16779            LspSettings {
16780                binary: None,
16781                settings: None,
16782                initialization_options: Some(json!({
16783                    "some other init value": false
16784                })),
16785                enable_lsp_tasks: false,
16786                fetch: None,
16787            },
16788        );
16789    });
16790    cx.executor().run_until_parked();
16791    assert_eq!(
16792        server_restarts.load(atomic::Ordering::Acquire),
16793        0,
16794        "Should not restart LSP server on an unrelated LSP settings change"
16795    );
16796
16797    update_test_project_settings(cx, |project_settings| {
16798        project_settings.lsp.insert(
16799            language_server_name.into(),
16800            LspSettings {
16801                binary: None,
16802                settings: None,
16803                initialization_options: Some(json!({
16804                    "anotherInitValue": false
16805                })),
16806                enable_lsp_tasks: false,
16807                fetch: None,
16808            },
16809        );
16810    });
16811    cx.executor().run_until_parked();
16812    assert_eq!(
16813        server_restarts.load(atomic::Ordering::Acquire),
16814        1,
16815        "Should restart LSP server on a related LSP settings change"
16816    );
16817
16818    update_test_project_settings(cx, |project_settings| {
16819        project_settings.lsp.insert(
16820            language_server_name.into(),
16821            LspSettings {
16822                binary: None,
16823                settings: None,
16824                initialization_options: Some(json!({
16825                    "anotherInitValue": false
16826                })),
16827                enable_lsp_tasks: false,
16828                fetch: None,
16829            },
16830        );
16831    });
16832    cx.executor().run_until_parked();
16833    assert_eq!(
16834        server_restarts.load(atomic::Ordering::Acquire),
16835        1,
16836        "Should not restart LSP server on a related LSP settings change that is the same"
16837    );
16838
16839    update_test_project_settings(cx, |project_settings| {
16840        project_settings.lsp.insert(
16841            language_server_name.into(),
16842            LspSettings {
16843                binary: None,
16844                settings: None,
16845                initialization_options: None,
16846                enable_lsp_tasks: false,
16847                fetch: None,
16848            },
16849        );
16850    });
16851    cx.executor().run_until_parked();
16852    assert_eq!(
16853        server_restarts.load(atomic::Ordering::Acquire),
16854        2,
16855        "Should restart LSP server on another related LSP settings change"
16856    );
16857}
16858
16859#[gpui::test]
16860async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
16861    init_test(cx, |_| {});
16862
16863    let mut cx = EditorLspTestContext::new_rust(
16864        lsp::ServerCapabilities {
16865            completion_provider: Some(lsp::CompletionOptions {
16866                trigger_characters: Some(vec![".".to_string()]),
16867                resolve_provider: Some(true),
16868                ..Default::default()
16869            }),
16870            ..Default::default()
16871        },
16872        cx,
16873    )
16874    .await;
16875
16876    cx.set_state("fn main() { let a = 2ˇ; }");
16877    cx.simulate_keystroke(".");
16878    let completion_item = lsp::CompletionItem {
16879        label: "some".into(),
16880        kind: Some(lsp::CompletionItemKind::SNIPPET),
16881        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16882        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16883            kind: lsp::MarkupKind::Markdown,
16884            value: "```rust\nSome(2)\n```".to_string(),
16885        })),
16886        deprecated: Some(false),
16887        sort_text: Some("fffffff2".to_string()),
16888        filter_text: Some("some".to_string()),
16889        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16890        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16891            range: lsp::Range {
16892                start: lsp::Position {
16893                    line: 0,
16894                    character: 22,
16895                },
16896                end: lsp::Position {
16897                    line: 0,
16898                    character: 22,
16899                },
16900            },
16901            new_text: "Some(2)".to_string(),
16902        })),
16903        additional_text_edits: Some(vec![lsp::TextEdit {
16904            range: lsp::Range {
16905                start: lsp::Position {
16906                    line: 0,
16907                    character: 20,
16908                },
16909                end: lsp::Position {
16910                    line: 0,
16911                    character: 22,
16912                },
16913            },
16914            new_text: "".to_string(),
16915        }]),
16916        ..Default::default()
16917    };
16918
16919    let closure_completion_item = completion_item.clone();
16920    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16921        let task_completion_item = closure_completion_item.clone();
16922        async move {
16923            Ok(Some(lsp::CompletionResponse::Array(vec![
16924                task_completion_item,
16925            ])))
16926        }
16927    });
16928
16929    request.next().await;
16930
16931    cx.condition(|editor, _| editor.context_menu_visible())
16932        .await;
16933    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16934        editor
16935            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16936            .unwrap()
16937    });
16938    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
16939
16940    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16941        let task_completion_item = completion_item.clone();
16942        async move { Ok(task_completion_item) }
16943    })
16944    .next()
16945    .await
16946    .unwrap();
16947    apply_additional_edits.await.unwrap();
16948    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
16949}
16950
16951#[gpui::test]
16952async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
16953    init_test(cx, |_| {});
16954
16955    let mut cx = EditorLspTestContext::new_rust(
16956        lsp::ServerCapabilities {
16957            completion_provider: Some(lsp::CompletionOptions {
16958                trigger_characters: Some(vec![".".to_string()]),
16959                resolve_provider: Some(true),
16960                ..Default::default()
16961            }),
16962            ..Default::default()
16963        },
16964        cx,
16965    )
16966    .await;
16967
16968    cx.set_state("fn main() { let a = 2ˇ; }");
16969    cx.simulate_keystroke(".");
16970
16971    let item1 = lsp::CompletionItem {
16972        label: "method id()".to_string(),
16973        filter_text: Some("id".to_string()),
16974        detail: None,
16975        documentation: None,
16976        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16977            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16978            new_text: ".id".to_string(),
16979        })),
16980        ..lsp::CompletionItem::default()
16981    };
16982
16983    let item2 = lsp::CompletionItem {
16984        label: "other".to_string(),
16985        filter_text: Some("other".to_string()),
16986        detail: None,
16987        documentation: None,
16988        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16989            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16990            new_text: ".other".to_string(),
16991        })),
16992        ..lsp::CompletionItem::default()
16993    };
16994
16995    let item1 = item1.clone();
16996    cx.set_request_handler::<lsp::request::Completion, _, _>({
16997        let item1 = item1.clone();
16998        move |_, _, _| {
16999            let item1 = item1.clone();
17000            let item2 = item2.clone();
17001            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17002        }
17003    })
17004    .next()
17005    .await;
17006
17007    cx.condition(|editor, _| editor.context_menu_visible())
17008        .await;
17009    cx.update_editor(|editor, _, _| {
17010        let context_menu = editor.context_menu.borrow_mut();
17011        let context_menu = context_menu
17012            .as_ref()
17013            .expect("Should have the context menu deployed");
17014        match context_menu {
17015            CodeContextMenu::Completions(completions_menu) => {
17016                let completions = completions_menu.completions.borrow_mut();
17017                assert_eq!(
17018                    completions
17019                        .iter()
17020                        .map(|completion| &completion.label.text)
17021                        .collect::<Vec<_>>(),
17022                    vec!["method id()", "other"]
17023                )
17024            }
17025            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17026        }
17027    });
17028
17029    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17030        let item1 = item1.clone();
17031        move |_, item_to_resolve, _| {
17032            let item1 = item1.clone();
17033            async move {
17034                if item1 == item_to_resolve {
17035                    Ok(lsp::CompletionItem {
17036                        label: "method id()".to_string(),
17037                        filter_text: Some("id".to_string()),
17038                        detail: Some("Now resolved!".to_string()),
17039                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17040                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17041                            range: lsp::Range::new(
17042                                lsp::Position::new(0, 22),
17043                                lsp::Position::new(0, 22),
17044                            ),
17045                            new_text: ".id".to_string(),
17046                        })),
17047                        ..lsp::CompletionItem::default()
17048                    })
17049                } else {
17050                    Ok(item_to_resolve)
17051                }
17052            }
17053        }
17054    })
17055    .next()
17056    .await
17057    .unwrap();
17058    cx.run_until_parked();
17059
17060    cx.update_editor(|editor, window, cx| {
17061        editor.context_menu_next(&Default::default(), window, cx);
17062    });
17063
17064    cx.update_editor(|editor, _, _| {
17065        let context_menu = editor.context_menu.borrow_mut();
17066        let context_menu = context_menu
17067            .as_ref()
17068            .expect("Should have the context menu deployed");
17069        match context_menu {
17070            CodeContextMenu::Completions(completions_menu) => {
17071                let completions = completions_menu.completions.borrow_mut();
17072                assert_eq!(
17073                    completions
17074                        .iter()
17075                        .map(|completion| &completion.label.text)
17076                        .collect::<Vec<_>>(),
17077                    vec!["method id() Now resolved!", "other"],
17078                    "Should update first completion label, but not second as the filter text did not match."
17079                );
17080            }
17081            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17082        }
17083    });
17084}
17085
17086#[gpui::test]
17087async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17088    init_test(cx, |_| {});
17089    let mut cx = EditorLspTestContext::new_rust(
17090        lsp::ServerCapabilities {
17091            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17092            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17093            completion_provider: Some(lsp::CompletionOptions {
17094                resolve_provider: Some(true),
17095                ..Default::default()
17096            }),
17097            ..Default::default()
17098        },
17099        cx,
17100    )
17101    .await;
17102    cx.set_state(indoc! {"
17103        struct TestStruct {
17104            field: i32
17105        }
17106
17107        fn mainˇ() {
17108            let unused_var = 42;
17109            let test_struct = TestStruct { field: 42 };
17110        }
17111    "});
17112    let symbol_range = cx.lsp_range(indoc! {"
17113        struct TestStruct {
17114            field: i32
17115        }
17116
17117        «fn main»() {
17118            let unused_var = 42;
17119            let test_struct = TestStruct { field: 42 };
17120        }
17121    "});
17122    let mut hover_requests =
17123        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17124            Ok(Some(lsp::Hover {
17125                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17126                    kind: lsp::MarkupKind::Markdown,
17127                    value: "Function documentation".to_string(),
17128                }),
17129                range: Some(symbol_range),
17130            }))
17131        });
17132
17133    // Case 1: Test that code action menu hide hover popover
17134    cx.dispatch_action(Hover);
17135    hover_requests.next().await;
17136    cx.condition(|editor, _| editor.hover_state.visible()).await;
17137    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17138        move |_, _, _| async move {
17139            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17140                lsp::CodeAction {
17141                    title: "Remove unused variable".to_string(),
17142                    kind: Some(CodeActionKind::QUICKFIX),
17143                    edit: Some(lsp::WorkspaceEdit {
17144                        changes: Some(
17145                            [(
17146                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17147                                vec![lsp::TextEdit {
17148                                    range: lsp::Range::new(
17149                                        lsp::Position::new(5, 4),
17150                                        lsp::Position::new(5, 27),
17151                                    ),
17152                                    new_text: "".to_string(),
17153                                }],
17154                            )]
17155                            .into_iter()
17156                            .collect(),
17157                        ),
17158                        ..Default::default()
17159                    }),
17160                    ..Default::default()
17161                },
17162            )]))
17163        },
17164    );
17165    cx.update_editor(|editor, window, cx| {
17166        editor.toggle_code_actions(
17167            &ToggleCodeActions {
17168                deployed_from: None,
17169                quick_launch: false,
17170            },
17171            window,
17172            cx,
17173        );
17174    });
17175    code_action_requests.next().await;
17176    cx.run_until_parked();
17177    cx.condition(|editor, _| editor.context_menu_visible())
17178        .await;
17179    cx.update_editor(|editor, _, _| {
17180        assert!(
17181            !editor.hover_state.visible(),
17182            "Hover popover should be hidden when code action menu is shown"
17183        );
17184        // Hide code actions
17185        editor.context_menu.take();
17186    });
17187
17188    // Case 2: Test that code completions hide hover popover
17189    cx.dispatch_action(Hover);
17190    hover_requests.next().await;
17191    cx.condition(|editor, _| editor.hover_state.visible()).await;
17192    let counter = Arc::new(AtomicUsize::new(0));
17193    let mut completion_requests =
17194        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17195            let counter = counter.clone();
17196            async move {
17197                counter.fetch_add(1, atomic::Ordering::Release);
17198                Ok(Some(lsp::CompletionResponse::Array(vec![
17199                    lsp::CompletionItem {
17200                        label: "main".into(),
17201                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17202                        detail: Some("() -> ()".to_string()),
17203                        ..Default::default()
17204                    },
17205                    lsp::CompletionItem {
17206                        label: "TestStruct".into(),
17207                        kind: Some(lsp::CompletionItemKind::STRUCT),
17208                        detail: Some("struct TestStruct".to_string()),
17209                        ..Default::default()
17210                    },
17211                ])))
17212            }
17213        });
17214    cx.update_editor(|editor, window, cx| {
17215        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17216    });
17217    completion_requests.next().await;
17218    cx.condition(|editor, _| editor.context_menu_visible())
17219        .await;
17220    cx.update_editor(|editor, _, _| {
17221        assert!(
17222            !editor.hover_state.visible(),
17223            "Hover popover should be hidden when completion menu is shown"
17224        );
17225    });
17226}
17227
17228#[gpui::test]
17229async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17230    init_test(cx, |_| {});
17231
17232    let mut cx = EditorLspTestContext::new_rust(
17233        lsp::ServerCapabilities {
17234            completion_provider: Some(lsp::CompletionOptions {
17235                trigger_characters: Some(vec![".".to_string()]),
17236                resolve_provider: Some(true),
17237                ..Default::default()
17238            }),
17239            ..Default::default()
17240        },
17241        cx,
17242    )
17243    .await;
17244
17245    cx.set_state("fn main() { let a = 2ˇ; }");
17246    cx.simulate_keystroke(".");
17247
17248    let unresolved_item_1 = lsp::CompletionItem {
17249        label: "id".to_string(),
17250        filter_text: Some("id".to_string()),
17251        detail: None,
17252        documentation: None,
17253        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17254            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17255            new_text: ".id".to_string(),
17256        })),
17257        ..lsp::CompletionItem::default()
17258    };
17259    let resolved_item_1 = lsp::CompletionItem {
17260        additional_text_edits: Some(vec![lsp::TextEdit {
17261            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17262            new_text: "!!".to_string(),
17263        }]),
17264        ..unresolved_item_1.clone()
17265    };
17266    let unresolved_item_2 = lsp::CompletionItem {
17267        label: "other".to_string(),
17268        filter_text: Some("other".to_string()),
17269        detail: None,
17270        documentation: None,
17271        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17272            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17273            new_text: ".other".to_string(),
17274        })),
17275        ..lsp::CompletionItem::default()
17276    };
17277    let resolved_item_2 = lsp::CompletionItem {
17278        additional_text_edits: Some(vec![lsp::TextEdit {
17279            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17280            new_text: "??".to_string(),
17281        }]),
17282        ..unresolved_item_2.clone()
17283    };
17284
17285    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17286    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17287    cx.lsp
17288        .server
17289        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17290            let unresolved_item_1 = unresolved_item_1.clone();
17291            let resolved_item_1 = resolved_item_1.clone();
17292            let unresolved_item_2 = unresolved_item_2.clone();
17293            let resolved_item_2 = resolved_item_2.clone();
17294            let resolve_requests_1 = resolve_requests_1.clone();
17295            let resolve_requests_2 = resolve_requests_2.clone();
17296            move |unresolved_request, _| {
17297                let unresolved_item_1 = unresolved_item_1.clone();
17298                let resolved_item_1 = resolved_item_1.clone();
17299                let unresolved_item_2 = unresolved_item_2.clone();
17300                let resolved_item_2 = resolved_item_2.clone();
17301                let resolve_requests_1 = resolve_requests_1.clone();
17302                let resolve_requests_2 = resolve_requests_2.clone();
17303                async move {
17304                    if unresolved_request == unresolved_item_1 {
17305                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17306                        Ok(resolved_item_1.clone())
17307                    } else if unresolved_request == unresolved_item_2 {
17308                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17309                        Ok(resolved_item_2.clone())
17310                    } else {
17311                        panic!("Unexpected completion item {unresolved_request:?}")
17312                    }
17313                }
17314            }
17315        })
17316        .detach();
17317
17318    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17319        let unresolved_item_1 = unresolved_item_1.clone();
17320        let unresolved_item_2 = unresolved_item_2.clone();
17321        async move {
17322            Ok(Some(lsp::CompletionResponse::Array(vec![
17323                unresolved_item_1,
17324                unresolved_item_2,
17325            ])))
17326        }
17327    })
17328    .next()
17329    .await;
17330
17331    cx.condition(|editor, _| editor.context_menu_visible())
17332        .await;
17333    cx.update_editor(|editor, _, _| {
17334        let context_menu = editor.context_menu.borrow_mut();
17335        let context_menu = context_menu
17336            .as_ref()
17337            .expect("Should have the context menu deployed");
17338        match context_menu {
17339            CodeContextMenu::Completions(completions_menu) => {
17340                let completions = completions_menu.completions.borrow_mut();
17341                assert_eq!(
17342                    completions
17343                        .iter()
17344                        .map(|completion| &completion.label.text)
17345                        .collect::<Vec<_>>(),
17346                    vec!["id", "other"]
17347                )
17348            }
17349            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17350        }
17351    });
17352    cx.run_until_parked();
17353
17354    cx.update_editor(|editor, window, cx| {
17355        editor.context_menu_next(&ContextMenuNext, window, cx);
17356    });
17357    cx.run_until_parked();
17358    cx.update_editor(|editor, window, cx| {
17359        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17360    });
17361    cx.run_until_parked();
17362    cx.update_editor(|editor, window, cx| {
17363        editor.context_menu_next(&ContextMenuNext, window, cx);
17364    });
17365    cx.run_until_parked();
17366    cx.update_editor(|editor, window, cx| {
17367        editor
17368            .compose_completion(&ComposeCompletion::default(), window, cx)
17369            .expect("No task returned")
17370    })
17371    .await
17372    .expect("Completion failed");
17373    cx.run_until_parked();
17374
17375    cx.update_editor(|editor, _, cx| {
17376        assert_eq!(
17377            resolve_requests_1.load(atomic::Ordering::Acquire),
17378            1,
17379            "Should always resolve once despite multiple selections"
17380        );
17381        assert_eq!(
17382            resolve_requests_2.load(atomic::Ordering::Acquire),
17383            1,
17384            "Should always resolve once after multiple selections and applying the completion"
17385        );
17386        assert_eq!(
17387            editor.text(cx),
17388            "fn main() { let a = ??.other; }",
17389            "Should use resolved data when applying the completion"
17390        );
17391    });
17392}
17393
17394#[gpui::test]
17395async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17396    init_test(cx, |_| {});
17397
17398    let item_0 = lsp::CompletionItem {
17399        label: "abs".into(),
17400        insert_text: Some("abs".into()),
17401        data: Some(json!({ "very": "special"})),
17402        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17403        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17404            lsp::InsertReplaceEdit {
17405                new_text: "abs".to_string(),
17406                insert: lsp::Range::default(),
17407                replace: lsp::Range::default(),
17408            },
17409        )),
17410        ..lsp::CompletionItem::default()
17411    };
17412    let items = iter::once(item_0.clone())
17413        .chain((11..51).map(|i| lsp::CompletionItem {
17414            label: format!("item_{}", i),
17415            insert_text: Some(format!("item_{}", i)),
17416            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17417            ..lsp::CompletionItem::default()
17418        }))
17419        .collect::<Vec<_>>();
17420
17421    let default_commit_characters = vec!["?".to_string()];
17422    let default_data = json!({ "default": "data"});
17423    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17424    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17425    let default_edit_range = lsp::Range {
17426        start: lsp::Position {
17427            line: 0,
17428            character: 5,
17429        },
17430        end: lsp::Position {
17431            line: 0,
17432            character: 5,
17433        },
17434    };
17435
17436    let mut cx = EditorLspTestContext::new_rust(
17437        lsp::ServerCapabilities {
17438            completion_provider: Some(lsp::CompletionOptions {
17439                trigger_characters: Some(vec![".".to_string()]),
17440                resolve_provider: Some(true),
17441                ..Default::default()
17442            }),
17443            ..Default::default()
17444        },
17445        cx,
17446    )
17447    .await;
17448
17449    cx.set_state("fn main() { let a = 2ˇ; }");
17450    cx.simulate_keystroke(".");
17451
17452    let completion_data = default_data.clone();
17453    let completion_characters = default_commit_characters.clone();
17454    let completion_items = items.clone();
17455    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17456        let default_data = completion_data.clone();
17457        let default_commit_characters = completion_characters.clone();
17458        let items = completion_items.clone();
17459        async move {
17460            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17461                items,
17462                item_defaults: Some(lsp::CompletionListItemDefaults {
17463                    data: Some(default_data.clone()),
17464                    commit_characters: Some(default_commit_characters.clone()),
17465                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17466                        default_edit_range,
17467                    )),
17468                    insert_text_format: Some(default_insert_text_format),
17469                    insert_text_mode: Some(default_insert_text_mode),
17470                }),
17471                ..lsp::CompletionList::default()
17472            })))
17473        }
17474    })
17475    .next()
17476    .await;
17477
17478    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17479    cx.lsp
17480        .server
17481        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17482            let closure_resolved_items = resolved_items.clone();
17483            move |item_to_resolve, _| {
17484                let closure_resolved_items = closure_resolved_items.clone();
17485                async move {
17486                    closure_resolved_items.lock().push(item_to_resolve.clone());
17487                    Ok(item_to_resolve)
17488                }
17489            }
17490        })
17491        .detach();
17492
17493    cx.condition(|editor, _| editor.context_menu_visible())
17494        .await;
17495    cx.run_until_parked();
17496    cx.update_editor(|editor, _, _| {
17497        let menu = editor.context_menu.borrow_mut();
17498        match menu.as_ref().expect("should have the completions menu") {
17499            CodeContextMenu::Completions(completions_menu) => {
17500                assert_eq!(
17501                    completions_menu
17502                        .entries
17503                        .borrow()
17504                        .iter()
17505                        .map(|mat| mat.string.clone())
17506                        .collect::<Vec<String>>(),
17507                    items
17508                        .iter()
17509                        .map(|completion| completion.label.clone())
17510                        .collect::<Vec<String>>()
17511                );
17512            }
17513            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17514        }
17515    });
17516    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17517    // with 4 from the end.
17518    assert_eq!(
17519        *resolved_items.lock(),
17520        [&items[0..16], &items[items.len() - 4..items.len()]]
17521            .concat()
17522            .iter()
17523            .cloned()
17524            .map(|mut item| {
17525                if item.data.is_none() {
17526                    item.data = Some(default_data.clone());
17527                }
17528                item
17529            })
17530            .collect::<Vec<lsp::CompletionItem>>(),
17531        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17532    );
17533    resolved_items.lock().clear();
17534
17535    cx.update_editor(|editor, window, cx| {
17536        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17537    });
17538    cx.run_until_parked();
17539    // Completions that have already been resolved are skipped.
17540    assert_eq!(
17541        *resolved_items.lock(),
17542        items[items.len() - 17..items.len() - 4]
17543            .iter()
17544            .cloned()
17545            .map(|mut item| {
17546                if item.data.is_none() {
17547                    item.data = Some(default_data.clone());
17548                }
17549                item
17550            })
17551            .collect::<Vec<lsp::CompletionItem>>()
17552    );
17553    resolved_items.lock().clear();
17554}
17555
17556#[gpui::test]
17557async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17558    init_test(cx, |_| {});
17559
17560    let mut cx = EditorLspTestContext::new(
17561        Language::new(
17562            LanguageConfig {
17563                matcher: LanguageMatcher {
17564                    path_suffixes: vec!["jsx".into()],
17565                    ..Default::default()
17566                },
17567                overrides: [(
17568                    "element".into(),
17569                    LanguageConfigOverride {
17570                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17571                        ..Default::default()
17572                    },
17573                )]
17574                .into_iter()
17575                .collect(),
17576                ..Default::default()
17577            },
17578            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17579        )
17580        .with_override_query("(jsx_self_closing_element) @element")
17581        .unwrap(),
17582        lsp::ServerCapabilities {
17583            completion_provider: Some(lsp::CompletionOptions {
17584                trigger_characters: Some(vec![":".to_string()]),
17585                ..Default::default()
17586            }),
17587            ..Default::default()
17588        },
17589        cx,
17590    )
17591    .await;
17592
17593    cx.lsp
17594        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17595            Ok(Some(lsp::CompletionResponse::Array(vec![
17596                lsp::CompletionItem {
17597                    label: "bg-blue".into(),
17598                    ..Default::default()
17599                },
17600                lsp::CompletionItem {
17601                    label: "bg-red".into(),
17602                    ..Default::default()
17603                },
17604                lsp::CompletionItem {
17605                    label: "bg-yellow".into(),
17606                    ..Default::default()
17607                },
17608            ])))
17609        });
17610
17611    cx.set_state(r#"<p class="bgˇ" />"#);
17612
17613    // Trigger completion when typing a dash, because the dash is an extra
17614    // word character in the 'element' scope, which contains the cursor.
17615    cx.simulate_keystroke("-");
17616    cx.executor().run_until_parked();
17617    cx.update_editor(|editor, _, _| {
17618        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17619        {
17620            assert_eq!(
17621                completion_menu_entries(menu),
17622                &["bg-blue", "bg-red", "bg-yellow"]
17623            );
17624        } else {
17625            panic!("expected completion menu to be open");
17626        }
17627    });
17628
17629    cx.simulate_keystroke("l");
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!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17635        } else {
17636            panic!("expected completion menu to be open");
17637        }
17638    });
17639
17640    // When filtering completions, consider the character after the '-' to
17641    // be the start of a subword.
17642    cx.set_state(r#"<p class="yelˇ" />"#);
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-yellow"]);
17649        } else {
17650            panic!("expected completion menu to be open");
17651        }
17652    });
17653}
17654
17655fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
17656    let entries = menu.entries.borrow();
17657    entries.iter().map(|mat| mat.string.clone()).collect()
17658}
17659
17660#[gpui::test]
17661async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
17662    init_test(cx, |settings| {
17663        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
17664            Formatter::Prettier,
17665        )))
17666    });
17667
17668    let fs = FakeFs::new(cx.executor());
17669    fs.insert_file(path!("/file.ts"), Default::default()).await;
17670
17671    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
17672    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17673
17674    language_registry.add(Arc::new(Language::new(
17675        LanguageConfig {
17676            name: "TypeScript".into(),
17677            matcher: LanguageMatcher {
17678                path_suffixes: vec!["ts".to_string()],
17679                ..Default::default()
17680            },
17681            ..Default::default()
17682        },
17683        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17684    )));
17685    update_test_language_settings(cx, |settings| {
17686        settings.defaults.prettier = Some(PrettierSettings {
17687            allowed: true,
17688            ..PrettierSettings::default()
17689        });
17690    });
17691
17692    let test_plugin = "test_plugin";
17693    let _ = language_registry.register_fake_lsp(
17694        "TypeScript",
17695        FakeLspAdapter {
17696            prettier_plugins: vec![test_plugin],
17697            ..Default::default()
17698        },
17699    );
17700
17701    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
17702    let buffer = project
17703        .update(cx, |project, cx| {
17704            project.open_local_buffer(path!("/file.ts"), cx)
17705        })
17706        .await
17707        .unwrap();
17708
17709    let buffer_text = "one\ntwo\nthree\n";
17710    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17711    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17712    editor.update_in(cx, |editor, window, cx| {
17713        editor.set_text(buffer_text, window, cx)
17714    });
17715
17716    editor
17717        .update_in(cx, |editor, window, cx| {
17718            editor.perform_format(
17719                project.clone(),
17720                FormatTrigger::Manual,
17721                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
17722                window,
17723                cx,
17724            )
17725        })
17726        .unwrap()
17727        .await;
17728    assert_eq!(
17729        editor.update(cx, |editor, cx| editor.text(cx)),
17730        buffer_text.to_string() + prettier_format_suffix,
17731        "Test prettier formatting was not applied to the original buffer text",
17732    );
17733
17734    update_test_language_settings(cx, |settings| {
17735        settings.defaults.formatter = Some(SelectedFormatter::Auto)
17736    });
17737    let format = editor.update_in(cx, |editor, window, cx| {
17738        editor.perform_format(
17739            project.clone(),
17740            FormatTrigger::Manual,
17741            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
17742            window,
17743            cx,
17744        )
17745    });
17746    format.await.unwrap();
17747    assert_eq!(
17748        editor.update(cx, |editor, cx| editor.text(cx)),
17749        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
17750        "Autoformatting (via test prettier) was not applied to the original buffer text",
17751    );
17752}
17753
17754#[gpui::test]
17755async fn test_addition_reverts(cx: &mut TestAppContext) {
17756    init_test(cx, |_| {});
17757    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17758    let base_text = indoc! {r#"
17759        struct Row;
17760        struct Row1;
17761        struct Row2;
17762
17763        struct Row4;
17764        struct Row5;
17765        struct Row6;
17766
17767        struct Row8;
17768        struct Row9;
17769        struct Row10;"#};
17770
17771    // When addition hunks are not adjacent to carets, no hunk revert is performed
17772    assert_hunk_revert(
17773        indoc! {r#"struct Row;
17774                   struct Row1;
17775                   struct Row1.1;
17776                   struct Row1.2;
17777                   struct Row2;ˇ
17778
17779                   struct Row4;
17780                   struct Row5;
17781                   struct Row6;
17782
17783                   struct Row8;
17784                   ˇstruct Row9;
17785                   struct Row9.1;
17786                   struct Row9.2;
17787                   struct Row9.3;
17788                   struct Row10;"#},
17789        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
17790        indoc! {r#"struct Row;
17791                   struct Row1;
17792                   struct Row1.1;
17793                   struct Row1.2;
17794                   struct Row2;ˇ
17795
17796                   struct Row4;
17797                   struct Row5;
17798                   struct Row6;
17799
17800                   struct Row8;
17801                   ˇstruct Row9;
17802                   struct Row9.1;
17803                   struct Row9.2;
17804                   struct Row9.3;
17805                   struct Row10;"#},
17806        base_text,
17807        &mut cx,
17808    );
17809    // Same for selections
17810    assert_hunk_revert(
17811        indoc! {r#"struct Row;
17812                   struct Row1;
17813                   struct Row2;
17814                   struct Row2.1;
17815                   struct Row2.2;
17816                   «ˇ
17817                   struct Row4;
17818                   struct» Row5;
17819                   «struct Row6;
17820                   ˇ»
17821                   struct Row9.1;
17822                   struct Row9.2;
17823                   struct Row9.3;
17824                   struct Row8;
17825                   struct Row9;
17826                   struct Row10;"#},
17827        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
17828        indoc! {r#"struct Row;
17829                   struct Row1;
17830                   struct Row2;
17831                   struct Row2.1;
17832                   struct Row2.2;
17833                   «ˇ
17834                   struct Row4;
17835                   struct» Row5;
17836                   «struct Row6;
17837                   ˇ»
17838                   struct Row9.1;
17839                   struct Row9.2;
17840                   struct Row9.3;
17841                   struct Row8;
17842                   struct Row9;
17843                   struct Row10;"#},
17844        base_text,
17845        &mut cx,
17846    );
17847
17848    // When carets and selections intersect the addition hunks, those are reverted.
17849    // Adjacent carets got merged.
17850    assert_hunk_revert(
17851        indoc! {r#"struct Row;
17852                   ˇ// something on the top
17853                   struct Row1;
17854                   struct Row2;
17855                   struct Roˇw3.1;
17856                   struct Row2.2;
17857                   struct Row2.3;ˇ
17858
17859                   struct Row4;
17860                   struct ˇRow5.1;
17861                   struct Row5.2;
17862                   struct «Rowˇ»5.3;
17863                   struct Row5;
17864                   struct Row6;
17865                   ˇ
17866                   struct Row9.1;
17867                   struct «Rowˇ»9.2;
17868                   struct «ˇRow»9.3;
17869                   struct Row8;
17870                   struct Row9;
17871                   «ˇ// something on bottom»
17872                   struct Row10;"#},
17873        vec![
17874            DiffHunkStatusKind::Added,
17875            DiffHunkStatusKind::Added,
17876            DiffHunkStatusKind::Added,
17877            DiffHunkStatusKind::Added,
17878            DiffHunkStatusKind::Added,
17879        ],
17880        indoc! {r#"struct Row;
17881                   ˇstruct Row1;
17882                   struct Row2;
17883                   ˇ
17884                   struct Row4;
17885                   ˇstruct Row5;
17886                   struct Row6;
17887                   ˇ
17888                   ˇstruct Row8;
17889                   struct Row9;
17890                   ˇstruct Row10;"#},
17891        base_text,
17892        &mut cx,
17893    );
17894}
17895
17896#[gpui::test]
17897async fn test_modification_reverts(cx: &mut TestAppContext) {
17898    init_test(cx, |_| {});
17899    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17900    let base_text = indoc! {r#"
17901        struct Row;
17902        struct Row1;
17903        struct Row2;
17904
17905        struct Row4;
17906        struct Row5;
17907        struct Row6;
17908
17909        struct Row8;
17910        struct Row9;
17911        struct Row10;"#};
17912
17913    // Modification hunks behave the same as the addition ones.
17914    assert_hunk_revert(
17915        indoc! {r#"struct Row;
17916                   struct Row1;
17917                   struct Row33;
17918                   ˇ
17919                   struct Row4;
17920                   struct Row5;
17921                   struct Row6;
17922                   ˇ
17923                   struct Row99;
17924                   struct Row9;
17925                   struct Row10;"#},
17926        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17927        indoc! {r#"struct Row;
17928                   struct Row1;
17929                   struct Row33;
17930                   ˇ
17931                   struct Row4;
17932                   struct Row5;
17933                   struct Row6;
17934                   ˇ
17935                   struct Row99;
17936                   struct Row9;
17937                   struct Row10;"#},
17938        base_text,
17939        &mut cx,
17940    );
17941    assert_hunk_revert(
17942        indoc! {r#"struct Row;
17943                   struct Row1;
17944                   struct Row33;
17945                   «ˇ
17946                   struct Row4;
17947                   struct» Row5;
17948                   «struct Row6;
17949                   ˇ»
17950                   struct Row99;
17951                   struct Row9;
17952                   struct Row10;"#},
17953        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17954        indoc! {r#"struct Row;
17955                   struct Row1;
17956                   struct Row33;
17957                   «ˇ
17958                   struct Row4;
17959                   struct» Row5;
17960                   «struct Row6;
17961                   ˇ»
17962                   struct Row99;
17963                   struct Row9;
17964                   struct Row10;"#},
17965        base_text,
17966        &mut cx,
17967    );
17968
17969    assert_hunk_revert(
17970        indoc! {r#"ˇstruct Row1.1;
17971                   struct Row1;
17972                   «ˇstr»uct Row22;
17973
17974                   struct ˇRow44;
17975                   struct Row5;
17976                   struct «Rˇ»ow66;ˇ
17977
17978                   «struˇ»ct Row88;
17979                   struct Row9;
17980                   struct Row1011;ˇ"#},
17981        vec![
17982            DiffHunkStatusKind::Modified,
17983            DiffHunkStatusKind::Modified,
17984            DiffHunkStatusKind::Modified,
17985            DiffHunkStatusKind::Modified,
17986            DiffHunkStatusKind::Modified,
17987            DiffHunkStatusKind::Modified,
17988        ],
17989        indoc! {r#"struct Row;
17990                   ˇstruct Row1;
17991                   struct Row2;
17992                   ˇ
17993                   struct Row4;
17994                   ˇstruct Row5;
17995                   struct Row6;
17996                   ˇ
17997                   struct Row8;
17998                   ˇstruct Row9;
17999                   struct Row10;ˇ"#},
18000        base_text,
18001        &mut cx,
18002    );
18003}
18004
18005#[gpui::test]
18006async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18007    init_test(cx, |_| {});
18008    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18009    let base_text = indoc! {r#"
18010        one
18011
18012        two
18013        three
18014        "#};
18015
18016    cx.set_head_text(base_text);
18017    cx.set_state("\nˇ\n");
18018    cx.executor().run_until_parked();
18019    cx.update_editor(|editor, _window, cx| {
18020        editor.expand_selected_diff_hunks(cx);
18021    });
18022    cx.executor().run_until_parked();
18023    cx.update_editor(|editor, window, cx| {
18024        editor.backspace(&Default::default(), window, cx);
18025    });
18026    cx.run_until_parked();
18027    cx.assert_state_with_diff(
18028        indoc! {r#"
18029
18030        - two
18031        - threeˇ
18032        +
18033        "#}
18034        .to_string(),
18035    );
18036}
18037
18038#[gpui::test]
18039async fn test_deletion_reverts(cx: &mut TestAppContext) {
18040    init_test(cx, |_| {});
18041    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18042    let base_text = indoc! {r#"struct Row;
18043struct Row1;
18044struct Row2;
18045
18046struct Row4;
18047struct Row5;
18048struct Row6;
18049
18050struct Row8;
18051struct Row9;
18052struct Row10;"#};
18053
18054    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18055    assert_hunk_revert(
18056        indoc! {r#"struct Row;
18057                   struct Row2;
18058
18059                   ˇstruct Row4;
18060                   struct Row5;
18061                   struct Row6;
18062                   ˇ
18063                   struct Row8;
18064                   struct Row10;"#},
18065        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18066        indoc! {r#"struct Row;
18067                   struct Row2;
18068
18069                   ˇstruct Row4;
18070                   struct Row5;
18071                   struct Row6;
18072                   ˇ
18073                   struct Row8;
18074                   struct Row10;"#},
18075        base_text,
18076        &mut cx,
18077    );
18078    assert_hunk_revert(
18079        indoc! {r#"struct Row;
18080                   struct Row2;
18081
18082                   «ˇstruct Row4;
18083                   struct» Row5;
18084                   «struct Row6;
18085                   ˇ»
18086                   struct Row8;
18087                   struct Row10;"#},
18088        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18089        indoc! {r#"struct Row;
18090                   struct Row2;
18091
18092                   «ˇstruct Row4;
18093                   struct» Row5;
18094                   «struct Row6;
18095                   ˇ»
18096                   struct Row8;
18097                   struct Row10;"#},
18098        base_text,
18099        &mut cx,
18100    );
18101
18102    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18103    assert_hunk_revert(
18104        indoc! {r#"struct Row;
18105                   ˇstruct Row2;
18106
18107                   struct Row4;
18108                   struct Row5;
18109                   struct Row6;
18110
18111                   struct Row8;ˇ
18112                   struct Row10;"#},
18113        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18114        indoc! {r#"struct Row;
18115                   struct Row1;
18116                   ˇstruct Row2;
18117
18118                   struct Row4;
18119                   struct Row5;
18120                   struct Row6;
18121
18122                   struct Row8;ˇ
18123                   struct Row9;
18124                   struct Row10;"#},
18125        base_text,
18126        &mut cx,
18127    );
18128    assert_hunk_revert(
18129        indoc! {r#"struct Row;
18130                   struct Row2«ˇ;
18131                   struct Row4;
18132                   struct» Row5;
18133                   «struct Row6;
18134
18135                   struct Row8;ˇ»
18136                   struct Row10;"#},
18137        vec![
18138            DiffHunkStatusKind::Deleted,
18139            DiffHunkStatusKind::Deleted,
18140            DiffHunkStatusKind::Deleted,
18141        ],
18142        indoc! {r#"struct Row;
18143                   struct Row1;
18144                   struct Row2«ˇ;
18145
18146                   struct Row4;
18147                   struct» Row5;
18148                   «struct Row6;
18149
18150                   struct Row8;ˇ»
18151                   struct Row9;
18152                   struct Row10;"#},
18153        base_text,
18154        &mut cx,
18155    );
18156}
18157
18158#[gpui::test]
18159async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18160    init_test(cx, |_| {});
18161
18162    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18163    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18164    let base_text_3 =
18165        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18166
18167    let text_1 = edit_first_char_of_every_line(base_text_1);
18168    let text_2 = edit_first_char_of_every_line(base_text_2);
18169    let text_3 = edit_first_char_of_every_line(base_text_3);
18170
18171    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18172    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18173    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18174
18175    let multibuffer = cx.new(|cx| {
18176        let mut multibuffer = MultiBuffer::new(ReadWrite);
18177        multibuffer.push_excerpts(
18178            buffer_1.clone(),
18179            [
18180                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18181                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18182                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18183            ],
18184            cx,
18185        );
18186        multibuffer.push_excerpts(
18187            buffer_2.clone(),
18188            [
18189                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18190                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18191                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18192            ],
18193            cx,
18194        );
18195        multibuffer.push_excerpts(
18196            buffer_3.clone(),
18197            [
18198                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18199                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18200                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18201            ],
18202            cx,
18203        );
18204        multibuffer
18205    });
18206
18207    let fs = FakeFs::new(cx.executor());
18208    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18209    let (editor, cx) = cx
18210        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18211    editor.update_in(cx, |editor, _window, cx| {
18212        for (buffer, diff_base) in [
18213            (buffer_1.clone(), base_text_1),
18214            (buffer_2.clone(), base_text_2),
18215            (buffer_3.clone(), base_text_3),
18216        ] {
18217            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18218            editor
18219                .buffer
18220                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18221        }
18222    });
18223    cx.executor().run_until_parked();
18224
18225    editor.update_in(cx, |editor, window, cx| {
18226        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}");
18227        editor.select_all(&SelectAll, window, cx);
18228        editor.git_restore(&Default::default(), window, cx);
18229    });
18230    cx.executor().run_until_parked();
18231
18232    // When all ranges are selected, all buffer hunks are reverted.
18233    editor.update(cx, |editor, cx| {
18234        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");
18235    });
18236    buffer_1.update(cx, |buffer, _| {
18237        assert_eq!(buffer.text(), base_text_1);
18238    });
18239    buffer_2.update(cx, |buffer, _| {
18240        assert_eq!(buffer.text(), base_text_2);
18241    });
18242    buffer_3.update(cx, |buffer, _| {
18243        assert_eq!(buffer.text(), base_text_3);
18244    });
18245
18246    editor.update_in(cx, |editor, window, cx| {
18247        editor.undo(&Default::default(), window, cx);
18248    });
18249
18250    editor.update_in(cx, |editor, window, cx| {
18251        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18252            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18253        });
18254        editor.git_restore(&Default::default(), window, cx);
18255    });
18256
18257    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18258    // but not affect buffer_2 and its related excerpts.
18259    editor.update(cx, |editor, cx| {
18260        assert_eq!(
18261            editor.text(cx),
18262            "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}"
18263        );
18264    });
18265    buffer_1.update(cx, |buffer, _| {
18266        assert_eq!(buffer.text(), base_text_1);
18267    });
18268    buffer_2.update(cx, |buffer, _| {
18269        assert_eq!(
18270            buffer.text(),
18271            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18272        );
18273    });
18274    buffer_3.update(cx, |buffer, _| {
18275        assert_eq!(
18276            buffer.text(),
18277            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18278        );
18279    });
18280
18281    fn edit_first_char_of_every_line(text: &str) -> String {
18282        text.split('\n')
18283            .map(|line| format!("X{}", &line[1..]))
18284            .collect::<Vec<_>>()
18285            .join("\n")
18286    }
18287}
18288
18289#[gpui::test]
18290async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18291    init_test(cx, |_| {});
18292
18293    let cols = 4;
18294    let rows = 10;
18295    let sample_text_1 = sample_text(rows, cols, 'a');
18296    assert_eq!(
18297        sample_text_1,
18298        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18299    );
18300    let sample_text_2 = sample_text(rows, cols, 'l');
18301    assert_eq!(
18302        sample_text_2,
18303        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18304    );
18305    let sample_text_3 = sample_text(rows, cols, 'v');
18306    assert_eq!(
18307        sample_text_3,
18308        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18309    );
18310
18311    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18312    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18313    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18314
18315    let multi_buffer = cx.new(|cx| {
18316        let mut multibuffer = MultiBuffer::new(ReadWrite);
18317        multibuffer.push_excerpts(
18318            buffer_1.clone(),
18319            [
18320                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18321                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18322                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18323            ],
18324            cx,
18325        );
18326        multibuffer.push_excerpts(
18327            buffer_2.clone(),
18328            [
18329                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18330                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18331                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18332            ],
18333            cx,
18334        );
18335        multibuffer.push_excerpts(
18336            buffer_3.clone(),
18337            [
18338                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18339                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18340                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18341            ],
18342            cx,
18343        );
18344        multibuffer
18345    });
18346
18347    let fs = FakeFs::new(cx.executor());
18348    fs.insert_tree(
18349        "/a",
18350        json!({
18351            "main.rs": sample_text_1,
18352            "other.rs": sample_text_2,
18353            "lib.rs": sample_text_3,
18354        }),
18355    )
18356    .await;
18357    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18358    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18359    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18360    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18361        Editor::new(
18362            EditorMode::full(),
18363            multi_buffer,
18364            Some(project.clone()),
18365            window,
18366            cx,
18367        )
18368    });
18369    let multibuffer_item_id = workspace
18370        .update(cx, |workspace, window, cx| {
18371            assert!(
18372                workspace.active_item(cx).is_none(),
18373                "active item should be None before the first item is added"
18374            );
18375            workspace.add_item_to_active_pane(
18376                Box::new(multi_buffer_editor.clone()),
18377                None,
18378                true,
18379                window,
18380                cx,
18381            );
18382            let active_item = workspace
18383                .active_item(cx)
18384                .expect("should have an active item after adding the multi buffer");
18385            assert!(
18386                !active_item.is_singleton(cx),
18387                "A multi buffer was expected to active after adding"
18388            );
18389            active_item.item_id()
18390        })
18391        .unwrap();
18392    cx.executor().run_until_parked();
18393
18394    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18395        editor.change_selections(
18396            SelectionEffects::scroll(Autoscroll::Next),
18397            window,
18398            cx,
18399            |s| s.select_ranges(Some(1..2)),
18400        );
18401        editor.open_excerpts(&OpenExcerpts, window, cx);
18402    });
18403    cx.executor().run_until_parked();
18404    let first_item_id = workspace
18405        .update(cx, |workspace, window, cx| {
18406            let active_item = workspace
18407                .active_item(cx)
18408                .expect("should have an active item after navigating into the 1st buffer");
18409            let first_item_id = active_item.item_id();
18410            assert_ne!(
18411                first_item_id, multibuffer_item_id,
18412                "Should navigate into the 1st buffer and activate it"
18413            );
18414            assert!(
18415                active_item.is_singleton(cx),
18416                "New active item should be a singleton buffer"
18417            );
18418            assert_eq!(
18419                active_item
18420                    .act_as::<Editor>(cx)
18421                    .expect("should have navigated into an editor for the 1st buffer")
18422                    .read(cx)
18423                    .text(cx),
18424                sample_text_1
18425            );
18426
18427            workspace
18428                .go_back(workspace.active_pane().downgrade(), window, cx)
18429                .detach_and_log_err(cx);
18430
18431            first_item_id
18432        })
18433        .unwrap();
18434    cx.executor().run_until_parked();
18435    workspace
18436        .update(cx, |workspace, _, cx| {
18437            let active_item = workspace
18438                .active_item(cx)
18439                .expect("should have an active item after navigating back");
18440            assert_eq!(
18441                active_item.item_id(),
18442                multibuffer_item_id,
18443                "Should navigate back to the multi buffer"
18444            );
18445            assert!(!active_item.is_singleton(cx));
18446        })
18447        .unwrap();
18448
18449    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18450        editor.change_selections(
18451            SelectionEffects::scroll(Autoscroll::Next),
18452            window,
18453            cx,
18454            |s| s.select_ranges(Some(39..40)),
18455        );
18456        editor.open_excerpts(&OpenExcerpts, window, cx);
18457    });
18458    cx.executor().run_until_parked();
18459    let second_item_id = workspace
18460        .update(cx, |workspace, window, cx| {
18461            let active_item = workspace
18462                .active_item(cx)
18463                .expect("should have an active item after navigating into the 2nd buffer");
18464            let second_item_id = active_item.item_id();
18465            assert_ne!(
18466                second_item_id, multibuffer_item_id,
18467                "Should navigate away from the multibuffer"
18468            );
18469            assert_ne!(
18470                second_item_id, first_item_id,
18471                "Should navigate into the 2nd buffer and activate it"
18472            );
18473            assert!(
18474                active_item.is_singleton(cx),
18475                "New active item should be a singleton buffer"
18476            );
18477            assert_eq!(
18478                active_item
18479                    .act_as::<Editor>(cx)
18480                    .expect("should have navigated into an editor")
18481                    .read(cx)
18482                    .text(cx),
18483                sample_text_2
18484            );
18485
18486            workspace
18487                .go_back(workspace.active_pane().downgrade(), window, cx)
18488                .detach_and_log_err(cx);
18489
18490            second_item_id
18491        })
18492        .unwrap();
18493    cx.executor().run_until_parked();
18494    workspace
18495        .update(cx, |workspace, _, cx| {
18496            let active_item = workspace
18497                .active_item(cx)
18498                .expect("should have an active item after navigating back from the 2nd buffer");
18499            assert_eq!(
18500                active_item.item_id(),
18501                multibuffer_item_id,
18502                "Should navigate back from the 2nd buffer to the multi buffer"
18503            );
18504            assert!(!active_item.is_singleton(cx));
18505        })
18506        .unwrap();
18507
18508    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18509        editor.change_selections(
18510            SelectionEffects::scroll(Autoscroll::Next),
18511            window,
18512            cx,
18513            |s| s.select_ranges(Some(70..70)),
18514        );
18515        editor.open_excerpts(&OpenExcerpts, window, cx);
18516    });
18517    cx.executor().run_until_parked();
18518    workspace
18519        .update(cx, |workspace, window, cx| {
18520            let active_item = workspace
18521                .active_item(cx)
18522                .expect("should have an active item after navigating into the 3rd buffer");
18523            let third_item_id = active_item.item_id();
18524            assert_ne!(
18525                third_item_id, multibuffer_item_id,
18526                "Should navigate into the 3rd buffer and activate it"
18527            );
18528            assert_ne!(third_item_id, first_item_id);
18529            assert_ne!(third_item_id, second_item_id);
18530            assert!(
18531                active_item.is_singleton(cx),
18532                "New active item should be a singleton buffer"
18533            );
18534            assert_eq!(
18535                active_item
18536                    .act_as::<Editor>(cx)
18537                    .expect("should have navigated into an editor")
18538                    .read(cx)
18539                    .text(cx),
18540                sample_text_3
18541            );
18542
18543            workspace
18544                .go_back(workspace.active_pane().downgrade(), window, cx)
18545                .detach_and_log_err(cx);
18546        })
18547        .unwrap();
18548    cx.executor().run_until_parked();
18549    workspace
18550        .update(cx, |workspace, _, cx| {
18551            let active_item = workspace
18552                .active_item(cx)
18553                .expect("should have an active item after navigating back from the 3rd buffer");
18554            assert_eq!(
18555                active_item.item_id(),
18556                multibuffer_item_id,
18557                "Should navigate back from the 3rd buffer to the multi buffer"
18558            );
18559            assert!(!active_item.is_singleton(cx));
18560        })
18561        .unwrap();
18562}
18563
18564#[gpui::test]
18565async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18566    init_test(cx, |_| {});
18567
18568    let mut cx = EditorTestContext::new(cx).await;
18569
18570    let diff_base = r#"
18571        use some::mod;
18572
18573        const A: u32 = 42;
18574
18575        fn main() {
18576            println!("hello");
18577
18578            println!("world");
18579        }
18580        "#
18581    .unindent();
18582
18583    cx.set_state(
18584        &r#"
18585        use some::modified;
18586
18587        ˇ
18588        fn main() {
18589            println!("hello there");
18590
18591            println!("around the");
18592            println!("world");
18593        }
18594        "#
18595        .unindent(),
18596    );
18597
18598    cx.set_head_text(&diff_base);
18599    executor.run_until_parked();
18600
18601    cx.update_editor(|editor, window, cx| {
18602        editor.go_to_next_hunk(&GoToHunk, window, cx);
18603        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18604    });
18605    executor.run_until_parked();
18606    cx.assert_state_with_diff(
18607        r#"
18608          use some::modified;
18609
18610
18611          fn main() {
18612        -     println!("hello");
18613        + ˇ    println!("hello there");
18614
18615              println!("around the");
18616              println!("world");
18617          }
18618        "#
18619        .unindent(),
18620    );
18621
18622    cx.update_editor(|editor, window, cx| {
18623        for _ in 0..2 {
18624            editor.go_to_next_hunk(&GoToHunk, window, cx);
18625            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18626        }
18627    });
18628    executor.run_until_parked();
18629    cx.assert_state_with_diff(
18630        r#"
18631        - use some::mod;
18632        + ˇuse some::modified;
18633
18634
18635          fn main() {
18636        -     println!("hello");
18637        +     println!("hello there");
18638
18639        +     println!("around the");
18640              println!("world");
18641          }
18642        "#
18643        .unindent(),
18644    );
18645
18646    cx.update_editor(|editor, window, cx| {
18647        editor.go_to_next_hunk(&GoToHunk, window, cx);
18648        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18649    });
18650    executor.run_until_parked();
18651    cx.assert_state_with_diff(
18652        r#"
18653        - use some::mod;
18654        + use some::modified;
18655
18656        - const A: u32 = 42;
18657          ˇ
18658          fn main() {
18659        -     println!("hello");
18660        +     println!("hello there");
18661
18662        +     println!("around the");
18663              println!("world");
18664          }
18665        "#
18666        .unindent(),
18667    );
18668
18669    cx.update_editor(|editor, window, cx| {
18670        editor.cancel(&Cancel, window, cx);
18671    });
18672
18673    cx.assert_state_with_diff(
18674        r#"
18675          use some::modified;
18676
18677          ˇ
18678          fn main() {
18679              println!("hello there");
18680
18681              println!("around the");
18682              println!("world");
18683          }
18684        "#
18685        .unindent(),
18686    );
18687}
18688
18689#[gpui::test]
18690async fn test_diff_base_change_with_expanded_diff_hunks(
18691    executor: BackgroundExecutor,
18692    cx: &mut TestAppContext,
18693) {
18694    init_test(cx, |_| {});
18695
18696    let mut cx = EditorTestContext::new(cx).await;
18697
18698    let diff_base = r#"
18699        use some::mod1;
18700        use some::mod2;
18701
18702        const A: u32 = 42;
18703        const B: u32 = 42;
18704        const C: u32 = 42;
18705
18706        fn main() {
18707            println!("hello");
18708
18709            println!("world");
18710        }
18711        "#
18712    .unindent();
18713
18714    cx.set_state(
18715        &r#"
18716        use some::mod2;
18717
18718        const A: u32 = 42;
18719        const C: u32 = 42;
18720
18721        fn main(ˇ) {
18722            //println!("hello");
18723
18724            println!("world");
18725            //
18726            //
18727        }
18728        "#
18729        .unindent(),
18730    );
18731
18732    cx.set_head_text(&diff_base);
18733    executor.run_until_parked();
18734
18735    cx.update_editor(|editor, window, cx| {
18736        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18737    });
18738    executor.run_until_parked();
18739    cx.assert_state_with_diff(
18740        r#"
18741        - use some::mod1;
18742          use some::mod2;
18743
18744          const A: u32 = 42;
18745        - const B: u32 = 42;
18746          const C: u32 = 42;
18747
18748          fn main(ˇ) {
18749        -     println!("hello");
18750        +     //println!("hello");
18751
18752              println!("world");
18753        +     //
18754        +     //
18755          }
18756        "#
18757        .unindent(),
18758    );
18759
18760    cx.set_head_text("new diff base!");
18761    executor.run_until_parked();
18762    cx.assert_state_with_diff(
18763        r#"
18764        - new diff base!
18765        + use some::mod2;
18766        +
18767        + const A: u32 = 42;
18768        + const C: u32 = 42;
18769        +
18770        + fn main(ˇ) {
18771        +     //println!("hello");
18772        +
18773        +     println!("world");
18774        +     //
18775        +     //
18776        + }
18777        "#
18778        .unindent(),
18779    );
18780}
18781
18782#[gpui::test]
18783async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
18784    init_test(cx, |_| {});
18785
18786    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
18787    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
18788    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
18789    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
18790    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
18791    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
18792
18793    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
18794    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
18795    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
18796
18797    let multi_buffer = cx.new(|cx| {
18798        let mut multibuffer = MultiBuffer::new(ReadWrite);
18799        multibuffer.push_excerpts(
18800            buffer_1.clone(),
18801            [
18802                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18803                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18804                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18805            ],
18806            cx,
18807        );
18808        multibuffer.push_excerpts(
18809            buffer_2.clone(),
18810            [
18811                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18812                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18813                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18814            ],
18815            cx,
18816        );
18817        multibuffer.push_excerpts(
18818            buffer_3.clone(),
18819            [
18820                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18821                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18822                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18823            ],
18824            cx,
18825        );
18826        multibuffer
18827    });
18828
18829    let editor =
18830        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18831    editor
18832        .update(cx, |editor, _window, cx| {
18833            for (buffer, diff_base) in [
18834                (buffer_1.clone(), file_1_old),
18835                (buffer_2.clone(), file_2_old),
18836                (buffer_3.clone(), file_3_old),
18837            ] {
18838                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18839                editor
18840                    .buffer
18841                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18842            }
18843        })
18844        .unwrap();
18845
18846    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18847    cx.run_until_parked();
18848
18849    cx.assert_editor_state(
18850        &"
18851            ˇaaa
18852            ccc
18853            ddd
18854
18855            ggg
18856            hhh
18857
18858
18859            lll
18860            mmm
18861            NNN
18862
18863            qqq
18864            rrr
18865
18866            uuu
18867            111
18868            222
18869            333
18870
18871            666
18872            777
18873
18874            000
18875            !!!"
18876        .unindent(),
18877    );
18878
18879    cx.update_editor(|editor, window, cx| {
18880        editor.select_all(&SelectAll, window, cx);
18881        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18882    });
18883    cx.executor().run_until_parked();
18884
18885    cx.assert_state_with_diff(
18886        "
18887            «aaa
18888          - bbb
18889            ccc
18890            ddd
18891
18892            ggg
18893            hhh
18894
18895
18896            lll
18897            mmm
18898          - nnn
18899          + NNN
18900
18901            qqq
18902            rrr
18903
18904            uuu
18905            111
18906            222
18907            333
18908
18909          + 666
18910            777
18911
18912            000
18913            !!!ˇ»"
18914            .unindent(),
18915    );
18916}
18917
18918#[gpui::test]
18919async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
18920    init_test(cx, |_| {});
18921
18922    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
18923    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
18924
18925    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
18926    let multi_buffer = cx.new(|cx| {
18927        let mut multibuffer = MultiBuffer::new(ReadWrite);
18928        multibuffer.push_excerpts(
18929            buffer.clone(),
18930            [
18931                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
18932                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
18933                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
18934            ],
18935            cx,
18936        );
18937        multibuffer
18938    });
18939
18940    let editor =
18941        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18942    editor
18943        .update(cx, |editor, _window, cx| {
18944            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
18945            editor
18946                .buffer
18947                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
18948        })
18949        .unwrap();
18950
18951    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18952    cx.run_until_parked();
18953
18954    cx.update_editor(|editor, window, cx| {
18955        editor.expand_all_diff_hunks(&Default::default(), window, cx)
18956    });
18957    cx.executor().run_until_parked();
18958
18959    // When the start of a hunk coincides with the start of its excerpt,
18960    // the hunk is expanded. When the start of a a hunk is earlier than
18961    // the start of its excerpt, the hunk is not expanded.
18962    cx.assert_state_with_diff(
18963        "
18964            ˇaaa
18965          - bbb
18966          + BBB
18967
18968          - ddd
18969          - eee
18970          + DDD
18971          + EEE
18972            fff
18973
18974            iii
18975        "
18976        .unindent(),
18977    );
18978}
18979
18980#[gpui::test]
18981async fn test_edits_around_expanded_insertion_hunks(
18982    executor: BackgroundExecutor,
18983    cx: &mut TestAppContext,
18984) {
18985    init_test(cx, |_| {});
18986
18987    let mut cx = EditorTestContext::new(cx).await;
18988
18989    let diff_base = r#"
18990        use some::mod1;
18991        use some::mod2;
18992
18993        const A: u32 = 42;
18994
18995        fn main() {
18996            println!("hello");
18997
18998            println!("world");
18999        }
19000        "#
19001    .unindent();
19002    executor.run_until_parked();
19003    cx.set_state(
19004        &r#"
19005        use some::mod1;
19006        use some::mod2;
19007
19008        const A: u32 = 42;
19009        const B: u32 = 42;
19010        const C: u32 = 42;
19011        ˇ
19012
19013        fn main() {
19014            println!("hello");
19015
19016            println!("world");
19017        }
19018        "#
19019        .unindent(),
19020    );
19021
19022    cx.set_head_text(&diff_base);
19023    executor.run_until_parked();
19024
19025    cx.update_editor(|editor, window, cx| {
19026        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19027    });
19028    executor.run_until_parked();
19029
19030    cx.assert_state_with_diff(
19031        r#"
19032        use some::mod1;
19033        use some::mod2;
19034
19035        const A: u32 = 42;
19036      + const B: u32 = 42;
19037      + const C: u32 = 42;
19038      + ˇ
19039
19040        fn main() {
19041            println!("hello");
19042
19043            println!("world");
19044        }
19045      "#
19046        .unindent(),
19047    );
19048
19049    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19050    executor.run_until_parked();
19051
19052    cx.assert_state_with_diff(
19053        r#"
19054        use some::mod1;
19055        use some::mod2;
19056
19057        const A: u32 = 42;
19058      + const B: u32 = 42;
19059      + const C: u32 = 42;
19060      + const D: u32 = 42;
19061      + ˇ
19062
19063        fn main() {
19064            println!("hello");
19065
19066            println!("world");
19067        }
19068      "#
19069        .unindent(),
19070    );
19071
19072    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19073    executor.run_until_parked();
19074
19075    cx.assert_state_with_diff(
19076        r#"
19077        use some::mod1;
19078        use some::mod2;
19079
19080        const A: u32 = 42;
19081      + const B: u32 = 42;
19082      + const C: u32 = 42;
19083      + const D: u32 = 42;
19084      + const E: u32 = 42;
19085      + ˇ
19086
19087        fn main() {
19088            println!("hello");
19089
19090            println!("world");
19091        }
19092      "#
19093        .unindent(),
19094    );
19095
19096    cx.update_editor(|editor, window, cx| {
19097        editor.delete_line(&DeleteLine, window, cx);
19098    });
19099    executor.run_until_parked();
19100
19101    cx.assert_state_with_diff(
19102        r#"
19103        use some::mod1;
19104        use some::mod2;
19105
19106        const A: u32 = 42;
19107      + const B: u32 = 42;
19108      + const C: u32 = 42;
19109      + const D: u32 = 42;
19110      + const E: u32 = 42;
19111        ˇ
19112        fn main() {
19113            println!("hello");
19114
19115            println!("world");
19116        }
19117      "#
19118        .unindent(),
19119    );
19120
19121    cx.update_editor(|editor, window, cx| {
19122        editor.move_up(&MoveUp, window, cx);
19123        editor.delete_line(&DeleteLine, window, cx);
19124        editor.move_up(&MoveUp, window, cx);
19125        editor.delete_line(&DeleteLine, window, cx);
19126        editor.move_up(&MoveUp, window, cx);
19127        editor.delete_line(&DeleteLine, window, cx);
19128    });
19129    executor.run_until_parked();
19130    cx.assert_state_with_diff(
19131        r#"
19132        use some::mod1;
19133        use some::mod2;
19134
19135        const A: u32 = 42;
19136      + const B: u32 = 42;
19137        ˇ
19138        fn main() {
19139            println!("hello");
19140
19141            println!("world");
19142        }
19143      "#
19144        .unindent(),
19145    );
19146
19147    cx.update_editor(|editor, window, cx| {
19148        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19149        editor.delete_line(&DeleteLine, window, cx);
19150    });
19151    executor.run_until_parked();
19152    cx.assert_state_with_diff(
19153        r#"
19154        ˇ
19155        fn main() {
19156            println!("hello");
19157
19158            println!("world");
19159        }
19160      "#
19161        .unindent(),
19162    );
19163}
19164
19165#[gpui::test]
19166async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19167    init_test(cx, |_| {});
19168
19169    let mut cx = EditorTestContext::new(cx).await;
19170    cx.set_head_text(indoc! { "
19171        one
19172        two
19173        three
19174        four
19175        five
19176        "
19177    });
19178    cx.set_state(indoc! { "
19179        one
19180        ˇthree
19181        five
19182    "});
19183    cx.run_until_parked();
19184    cx.update_editor(|editor, window, cx| {
19185        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19186    });
19187    cx.assert_state_with_diff(
19188        indoc! { "
19189        one
19190      - two
19191        ˇthree
19192      - four
19193        five
19194    "}
19195        .to_string(),
19196    );
19197    cx.update_editor(|editor, window, cx| {
19198        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19199    });
19200
19201    cx.assert_state_with_diff(
19202        indoc! { "
19203        one
19204        ˇthree
19205        five
19206    "}
19207        .to_string(),
19208    );
19209
19210    cx.set_state(indoc! { "
19211        one
19212        ˇTWO
19213        three
19214        four
19215        five
19216    "});
19217    cx.run_until_parked();
19218    cx.update_editor(|editor, window, cx| {
19219        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19220    });
19221
19222    cx.assert_state_with_diff(
19223        indoc! { "
19224            one
19225          - two
19226          + ˇTWO
19227            three
19228            four
19229            five
19230        "}
19231        .to_string(),
19232    );
19233    cx.update_editor(|editor, window, cx| {
19234        editor.move_up(&Default::default(), window, cx);
19235        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19236    });
19237    cx.assert_state_with_diff(
19238        indoc! { "
19239            one
19240            ˇTWO
19241            three
19242            four
19243            five
19244        "}
19245        .to_string(),
19246    );
19247}
19248
19249#[gpui::test]
19250async fn test_edits_around_expanded_deletion_hunks(
19251    executor: BackgroundExecutor,
19252    cx: &mut TestAppContext,
19253) {
19254    init_test(cx, |_| {});
19255
19256    let mut cx = EditorTestContext::new(cx).await;
19257
19258    let diff_base = r#"
19259        use some::mod1;
19260        use some::mod2;
19261
19262        const A: u32 = 42;
19263        const B: u32 = 42;
19264        const C: u32 = 42;
19265
19266
19267        fn main() {
19268            println!("hello");
19269
19270            println!("world");
19271        }
19272    "#
19273    .unindent();
19274    executor.run_until_parked();
19275    cx.set_state(
19276        &r#"
19277        use some::mod1;
19278        use some::mod2;
19279
19280        ˇconst B: u32 = 42;
19281        const C: u32 = 42;
19282
19283
19284        fn main() {
19285            println!("hello");
19286
19287            println!("world");
19288        }
19289        "#
19290        .unindent(),
19291    );
19292
19293    cx.set_head_text(&diff_base);
19294    executor.run_until_parked();
19295
19296    cx.update_editor(|editor, window, cx| {
19297        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19298    });
19299    executor.run_until_parked();
19300
19301    cx.assert_state_with_diff(
19302        r#"
19303        use some::mod1;
19304        use some::mod2;
19305
19306      - const A: u32 = 42;
19307        ˇconst B: u32 = 42;
19308        const C: u32 = 42;
19309
19310
19311        fn main() {
19312            println!("hello");
19313
19314            println!("world");
19315        }
19316      "#
19317        .unindent(),
19318    );
19319
19320    cx.update_editor(|editor, window, cx| {
19321        editor.delete_line(&DeleteLine, window, cx);
19322    });
19323    executor.run_until_parked();
19324    cx.assert_state_with_diff(
19325        r#"
19326        use some::mod1;
19327        use some::mod2;
19328
19329      - const A: u32 = 42;
19330      - const B: u32 = 42;
19331        ˇconst C: u32 = 42;
19332
19333
19334        fn main() {
19335            println!("hello");
19336
19337            println!("world");
19338        }
19339      "#
19340        .unindent(),
19341    );
19342
19343    cx.update_editor(|editor, window, cx| {
19344        editor.delete_line(&DeleteLine, window, cx);
19345    });
19346    executor.run_until_parked();
19347    cx.assert_state_with_diff(
19348        r#"
19349        use some::mod1;
19350        use some::mod2;
19351
19352      - const A: u32 = 42;
19353      - const B: u32 = 42;
19354      - const C: u32 = 42;
19355        ˇ
19356
19357        fn main() {
19358            println!("hello");
19359
19360            println!("world");
19361        }
19362      "#
19363        .unindent(),
19364    );
19365
19366    cx.update_editor(|editor, window, cx| {
19367        editor.handle_input("replacement", window, cx);
19368    });
19369    executor.run_until_parked();
19370    cx.assert_state_with_diff(
19371        r#"
19372        use some::mod1;
19373        use some::mod2;
19374
19375      - const A: u32 = 42;
19376      - const B: u32 = 42;
19377      - const C: u32 = 42;
19378      -
19379      + replacementˇ
19380
19381        fn main() {
19382            println!("hello");
19383
19384            println!("world");
19385        }
19386      "#
19387        .unindent(),
19388    );
19389}
19390
19391#[gpui::test]
19392async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19393    init_test(cx, |_| {});
19394
19395    let mut cx = EditorTestContext::new(cx).await;
19396
19397    let base_text = r#"
19398        one
19399        two
19400        three
19401        four
19402        five
19403    "#
19404    .unindent();
19405    executor.run_until_parked();
19406    cx.set_state(
19407        &r#"
19408        one
19409        two
19410        fˇour
19411        five
19412        "#
19413        .unindent(),
19414    );
19415
19416    cx.set_head_text(&base_text);
19417    executor.run_until_parked();
19418
19419    cx.update_editor(|editor, window, cx| {
19420        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19421    });
19422    executor.run_until_parked();
19423
19424    cx.assert_state_with_diff(
19425        r#"
19426          one
19427          two
19428        - three
19429          fˇour
19430          five
19431        "#
19432        .unindent(),
19433    );
19434
19435    cx.update_editor(|editor, window, cx| {
19436        editor.backspace(&Backspace, window, cx);
19437        editor.backspace(&Backspace, window, cx);
19438    });
19439    executor.run_until_parked();
19440    cx.assert_state_with_diff(
19441        r#"
19442          one
19443          two
19444        - threeˇ
19445        - four
19446        + our
19447          five
19448        "#
19449        .unindent(),
19450    );
19451}
19452
19453#[gpui::test]
19454async fn test_edit_after_expanded_modification_hunk(
19455    executor: BackgroundExecutor,
19456    cx: &mut TestAppContext,
19457) {
19458    init_test(cx, |_| {});
19459
19460    let mut cx = EditorTestContext::new(cx).await;
19461
19462    let diff_base = r#"
19463        use some::mod1;
19464        use some::mod2;
19465
19466        const A: u32 = 42;
19467        const B: u32 = 42;
19468        const C: u32 = 42;
19469        const D: u32 = 42;
19470
19471
19472        fn main() {
19473            println!("hello");
19474
19475            println!("world");
19476        }"#
19477    .unindent();
19478
19479    cx.set_state(
19480        &r#"
19481        use some::mod1;
19482        use some::mod2;
19483
19484        const A: u32 = 42;
19485        const B: u32 = 42;
19486        const C: u32 = 43ˇ
19487        const D: u32 = 42;
19488
19489
19490        fn main() {
19491            println!("hello");
19492
19493            println!("world");
19494        }"#
19495        .unindent(),
19496    );
19497
19498    cx.set_head_text(&diff_base);
19499    executor.run_until_parked();
19500    cx.update_editor(|editor, window, cx| {
19501        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19502    });
19503    executor.run_until_parked();
19504
19505    cx.assert_state_with_diff(
19506        r#"
19507        use some::mod1;
19508        use some::mod2;
19509
19510        const A: u32 = 42;
19511        const B: u32 = 42;
19512      - const C: u32 = 42;
19513      + const C: u32 = 43ˇ
19514        const D: u32 = 42;
19515
19516
19517        fn main() {
19518            println!("hello");
19519
19520            println!("world");
19521        }"#
19522        .unindent(),
19523    );
19524
19525    cx.update_editor(|editor, window, cx| {
19526        editor.handle_input("\nnew_line\n", window, cx);
19527    });
19528    executor.run_until_parked();
19529
19530    cx.assert_state_with_diff(
19531        r#"
19532        use some::mod1;
19533        use some::mod2;
19534
19535        const A: u32 = 42;
19536        const B: u32 = 42;
19537      - const C: u32 = 42;
19538      + const C: u32 = 43
19539      + new_line
19540      + ˇ
19541        const D: u32 = 42;
19542
19543
19544        fn main() {
19545            println!("hello");
19546
19547            println!("world");
19548        }"#
19549        .unindent(),
19550    );
19551}
19552
19553#[gpui::test]
19554async fn test_stage_and_unstage_added_file_hunk(
19555    executor: BackgroundExecutor,
19556    cx: &mut TestAppContext,
19557) {
19558    init_test(cx, |_| {});
19559
19560    let mut cx = EditorTestContext::new(cx).await;
19561    cx.update_editor(|editor, _, cx| {
19562        editor.set_expand_all_diff_hunks(cx);
19563    });
19564
19565    let working_copy = r#"
19566            ˇfn main() {
19567                println!("hello, world!");
19568            }
19569        "#
19570    .unindent();
19571
19572    cx.set_state(&working_copy);
19573    executor.run_until_parked();
19574
19575    cx.assert_state_with_diff(
19576        r#"
19577            + ˇfn main() {
19578            +     println!("hello, world!");
19579            + }
19580        "#
19581        .unindent(),
19582    );
19583    cx.assert_index_text(None);
19584
19585    cx.update_editor(|editor, window, cx| {
19586        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19587    });
19588    executor.run_until_parked();
19589    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19590    cx.assert_state_with_diff(
19591        r#"
19592            + ˇfn main() {
19593            +     println!("hello, world!");
19594            + }
19595        "#
19596        .unindent(),
19597    );
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(None);
19604}
19605
19606async fn setup_indent_guides_editor(
19607    text: &str,
19608    cx: &mut TestAppContext,
19609) -> (BufferId, EditorTestContext) {
19610    init_test(cx, |_| {});
19611
19612    let mut cx = EditorTestContext::new(cx).await;
19613
19614    let buffer_id = cx.update_editor(|editor, window, cx| {
19615        editor.set_text(text, window, cx);
19616        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19617
19618        buffer_ids[0]
19619    });
19620
19621    (buffer_id, cx)
19622}
19623
19624fn assert_indent_guides(
19625    range: Range<u32>,
19626    expected: Vec<IndentGuide>,
19627    active_indices: Option<Vec<usize>>,
19628    cx: &mut EditorTestContext,
19629) {
19630    let indent_guides = cx.update_editor(|editor, window, cx| {
19631        let snapshot = editor.snapshot(window, cx).display_snapshot;
19632        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19633            editor,
19634            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19635            true,
19636            &snapshot,
19637            cx,
19638        );
19639
19640        indent_guides.sort_by(|a, b| {
19641            a.depth.cmp(&b.depth).then(
19642                a.start_row
19643                    .cmp(&b.start_row)
19644                    .then(a.end_row.cmp(&b.end_row)),
19645            )
19646        });
19647        indent_guides
19648    });
19649
19650    if let Some(expected) = active_indices {
19651        let active_indices = cx.update_editor(|editor, window, cx| {
19652            let snapshot = editor.snapshot(window, cx).display_snapshot;
19653            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
19654        });
19655
19656        assert_eq!(
19657            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
19658            expected,
19659            "Active indent guide indices do not match"
19660        );
19661    }
19662
19663    assert_eq!(indent_guides, expected, "Indent guides do not match");
19664}
19665
19666fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
19667    IndentGuide {
19668        buffer_id,
19669        start_row: MultiBufferRow(start_row),
19670        end_row: MultiBufferRow(end_row),
19671        depth,
19672        tab_size: 4,
19673        settings: IndentGuideSettings {
19674            enabled: true,
19675            line_width: 1,
19676            active_line_width: 1,
19677            ..Default::default()
19678        },
19679    }
19680}
19681
19682#[gpui::test]
19683async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
19684    let (buffer_id, mut cx) = setup_indent_guides_editor(
19685        &"
19686        fn main() {
19687            let a = 1;
19688        }"
19689        .unindent(),
19690        cx,
19691    )
19692    .await;
19693
19694    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19695}
19696
19697#[gpui::test]
19698async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
19699    let (buffer_id, mut cx) = setup_indent_guides_editor(
19700        &"
19701        fn main() {
19702            let a = 1;
19703            let b = 2;
19704        }"
19705        .unindent(),
19706        cx,
19707    )
19708    .await;
19709
19710    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
19711}
19712
19713#[gpui::test]
19714async fn test_indent_guide_nested(cx: &mut TestAppContext) {
19715    let (buffer_id, mut cx) = setup_indent_guides_editor(
19716        &"
19717        fn main() {
19718            let a = 1;
19719            if a == 3 {
19720                let b = 2;
19721            } else {
19722                let c = 3;
19723            }
19724        }"
19725        .unindent(),
19726        cx,
19727    )
19728    .await;
19729
19730    assert_indent_guides(
19731        0..8,
19732        vec![
19733            indent_guide(buffer_id, 1, 6, 0),
19734            indent_guide(buffer_id, 3, 3, 1),
19735            indent_guide(buffer_id, 5, 5, 1),
19736        ],
19737        None,
19738        &mut cx,
19739    );
19740}
19741
19742#[gpui::test]
19743async fn test_indent_guide_tab(cx: &mut TestAppContext) {
19744    let (buffer_id, mut cx) = setup_indent_guides_editor(
19745        &"
19746        fn main() {
19747            let a = 1;
19748                let b = 2;
19749            let c = 3;
19750        }"
19751        .unindent(),
19752        cx,
19753    )
19754    .await;
19755
19756    assert_indent_guides(
19757        0..5,
19758        vec![
19759            indent_guide(buffer_id, 1, 3, 0),
19760            indent_guide(buffer_id, 2, 2, 1),
19761        ],
19762        None,
19763        &mut cx,
19764    );
19765}
19766
19767#[gpui::test]
19768async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
19769    let (buffer_id, mut cx) = setup_indent_guides_editor(
19770        &"
19771        fn main() {
19772            let a = 1;
19773
19774            let c = 3;
19775        }"
19776        .unindent(),
19777        cx,
19778    )
19779    .await;
19780
19781    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
19782}
19783
19784#[gpui::test]
19785async fn test_indent_guide_complex(cx: &mut TestAppContext) {
19786    let (buffer_id, mut cx) = setup_indent_guides_editor(
19787        &"
19788        fn main() {
19789            let a = 1;
19790
19791            let c = 3;
19792
19793            if a == 3 {
19794                let b = 2;
19795            } else {
19796                let c = 3;
19797            }
19798        }"
19799        .unindent(),
19800        cx,
19801    )
19802    .await;
19803
19804    assert_indent_guides(
19805        0..11,
19806        vec![
19807            indent_guide(buffer_id, 1, 9, 0),
19808            indent_guide(buffer_id, 6, 6, 1),
19809            indent_guide(buffer_id, 8, 8, 1),
19810        ],
19811        None,
19812        &mut cx,
19813    );
19814}
19815
19816#[gpui::test]
19817async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
19818    let (buffer_id, mut cx) = setup_indent_guides_editor(
19819        &"
19820        fn main() {
19821            let a = 1;
19822
19823            let c = 3;
19824
19825            if a == 3 {
19826                let b = 2;
19827            } else {
19828                let c = 3;
19829            }
19830        }"
19831        .unindent(),
19832        cx,
19833    )
19834    .await;
19835
19836    assert_indent_guides(
19837        1..11,
19838        vec![
19839            indent_guide(buffer_id, 1, 9, 0),
19840            indent_guide(buffer_id, 6, 6, 1),
19841            indent_guide(buffer_id, 8, 8, 1),
19842        ],
19843        None,
19844        &mut cx,
19845    );
19846}
19847
19848#[gpui::test]
19849async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
19850    let (buffer_id, mut cx) = setup_indent_guides_editor(
19851        &"
19852        fn main() {
19853            let a = 1;
19854
19855            let c = 3;
19856
19857            if a == 3 {
19858                let b = 2;
19859            } else {
19860                let c = 3;
19861            }
19862        }"
19863        .unindent(),
19864        cx,
19865    )
19866    .await;
19867
19868    assert_indent_guides(
19869        1..10,
19870        vec![
19871            indent_guide(buffer_id, 1, 9, 0),
19872            indent_guide(buffer_id, 6, 6, 1),
19873            indent_guide(buffer_id, 8, 8, 1),
19874        ],
19875        None,
19876        &mut cx,
19877    );
19878}
19879
19880#[gpui::test]
19881async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
19882    let (buffer_id, mut cx) = setup_indent_guides_editor(
19883        &"
19884        fn main() {
19885            if a {
19886                b(
19887                    c,
19888                    d,
19889                )
19890            } else {
19891                e(
19892                    f
19893                )
19894            }
19895        }"
19896        .unindent(),
19897        cx,
19898    )
19899    .await;
19900
19901    assert_indent_guides(
19902        0..11,
19903        vec![
19904            indent_guide(buffer_id, 1, 10, 0),
19905            indent_guide(buffer_id, 2, 5, 1),
19906            indent_guide(buffer_id, 7, 9, 1),
19907            indent_guide(buffer_id, 3, 4, 2),
19908            indent_guide(buffer_id, 8, 8, 2),
19909        ],
19910        None,
19911        &mut cx,
19912    );
19913
19914    cx.update_editor(|editor, window, cx| {
19915        editor.fold_at(MultiBufferRow(2), window, cx);
19916        assert_eq!(
19917            editor.display_text(cx),
19918            "
19919            fn main() {
19920                if a {
19921                    b(⋯
19922                    )
19923                } else {
19924                    e(
19925                        f
19926                    )
19927                }
19928            }"
19929            .unindent()
19930        );
19931    });
19932
19933    assert_indent_guides(
19934        0..11,
19935        vec![
19936            indent_guide(buffer_id, 1, 10, 0),
19937            indent_guide(buffer_id, 2, 5, 1),
19938            indent_guide(buffer_id, 7, 9, 1),
19939            indent_guide(buffer_id, 8, 8, 2),
19940        ],
19941        None,
19942        &mut cx,
19943    );
19944}
19945
19946#[gpui::test]
19947async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
19948    let (buffer_id, mut cx) = setup_indent_guides_editor(
19949        &"
19950        block1
19951            block2
19952                block3
19953                    block4
19954            block2
19955        block1
19956        block1"
19957            .unindent(),
19958        cx,
19959    )
19960    .await;
19961
19962    assert_indent_guides(
19963        1..10,
19964        vec![
19965            indent_guide(buffer_id, 1, 4, 0),
19966            indent_guide(buffer_id, 2, 3, 1),
19967            indent_guide(buffer_id, 3, 3, 2),
19968        ],
19969        None,
19970        &mut cx,
19971    );
19972}
19973
19974#[gpui::test]
19975async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
19976    let (buffer_id, mut cx) = setup_indent_guides_editor(
19977        &"
19978        block1
19979            block2
19980                block3
19981
19982        block1
19983        block1"
19984            .unindent(),
19985        cx,
19986    )
19987    .await;
19988
19989    assert_indent_guides(
19990        0..6,
19991        vec![
19992            indent_guide(buffer_id, 1, 2, 0),
19993            indent_guide(buffer_id, 2, 2, 1),
19994        ],
19995        None,
19996        &mut cx,
19997    );
19998}
19999
20000#[gpui::test]
20001async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20002    let (buffer_id, mut cx) = setup_indent_guides_editor(
20003        &"
20004        function component() {
20005        \treturn (
20006        \t\t\t
20007        \t\t<div>
20008        \t\t\t<abc></abc>
20009        \t\t</div>
20010        \t)
20011        }"
20012        .unindent(),
20013        cx,
20014    )
20015    .await;
20016
20017    assert_indent_guides(
20018        0..8,
20019        vec![
20020            indent_guide(buffer_id, 1, 6, 0),
20021            indent_guide(buffer_id, 2, 5, 1),
20022            indent_guide(buffer_id, 4, 4, 2),
20023        ],
20024        None,
20025        &mut cx,
20026    );
20027}
20028
20029#[gpui::test]
20030async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20031    let (buffer_id, mut cx) = setup_indent_guides_editor(
20032        &"
20033        function component() {
20034        \treturn (
20035        \t
20036        \t\t<div>
20037        \t\t\t<abc></abc>
20038        \t\t</div>
20039        \t)
20040        }"
20041        .unindent(),
20042        cx,
20043    )
20044    .await;
20045
20046    assert_indent_guides(
20047        0..8,
20048        vec![
20049            indent_guide(buffer_id, 1, 6, 0),
20050            indent_guide(buffer_id, 2, 5, 1),
20051            indent_guide(buffer_id, 4, 4, 2),
20052        ],
20053        None,
20054        &mut cx,
20055    );
20056}
20057
20058#[gpui::test]
20059async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20060    let (buffer_id, mut cx) = setup_indent_guides_editor(
20061        &"
20062        block1
20063
20064
20065
20066            block2
20067        "
20068        .unindent(),
20069        cx,
20070    )
20071    .await;
20072
20073    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20074}
20075
20076#[gpui::test]
20077async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20078    let (buffer_id, mut cx) = setup_indent_guides_editor(
20079        &"
20080        def a:
20081        \tb = 3
20082        \tif True:
20083        \t\tc = 4
20084        \t\td = 5
20085        \tprint(b)
20086        "
20087        .unindent(),
20088        cx,
20089    )
20090    .await;
20091
20092    assert_indent_guides(
20093        0..6,
20094        vec![
20095            indent_guide(buffer_id, 1, 5, 0),
20096            indent_guide(buffer_id, 3, 4, 1),
20097        ],
20098        None,
20099        &mut cx,
20100    );
20101}
20102
20103#[gpui::test]
20104async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20105    let (buffer_id, mut cx) = setup_indent_guides_editor(
20106        &"
20107    fn main() {
20108        let a = 1;
20109    }"
20110        .unindent(),
20111        cx,
20112    )
20113    .await;
20114
20115    cx.update_editor(|editor, window, cx| {
20116        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20117            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20118        });
20119    });
20120
20121    assert_indent_guides(
20122        0..3,
20123        vec![indent_guide(buffer_id, 1, 1, 0)],
20124        Some(vec![0]),
20125        &mut cx,
20126    );
20127}
20128
20129#[gpui::test]
20130async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20131    let (buffer_id, mut cx) = setup_indent_guides_editor(
20132        &"
20133    fn main() {
20134        if 1 == 2 {
20135            let a = 1;
20136        }
20137    }"
20138        .unindent(),
20139        cx,
20140    )
20141    .await;
20142
20143    cx.update_editor(|editor, window, cx| {
20144        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20145            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20146        });
20147    });
20148
20149    assert_indent_guides(
20150        0..4,
20151        vec![
20152            indent_guide(buffer_id, 1, 3, 0),
20153            indent_guide(buffer_id, 2, 2, 1),
20154        ],
20155        Some(vec![1]),
20156        &mut cx,
20157    );
20158
20159    cx.update_editor(|editor, window, cx| {
20160        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20161            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20162        });
20163    });
20164
20165    assert_indent_guides(
20166        0..4,
20167        vec![
20168            indent_guide(buffer_id, 1, 3, 0),
20169            indent_guide(buffer_id, 2, 2, 1),
20170        ],
20171        Some(vec![1]),
20172        &mut cx,
20173    );
20174
20175    cx.update_editor(|editor, window, cx| {
20176        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20177            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20178        });
20179    });
20180
20181    assert_indent_guides(
20182        0..4,
20183        vec![
20184            indent_guide(buffer_id, 1, 3, 0),
20185            indent_guide(buffer_id, 2, 2, 1),
20186        ],
20187        Some(vec![0]),
20188        &mut cx,
20189    );
20190}
20191
20192#[gpui::test]
20193async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20194    let (buffer_id, mut cx) = setup_indent_guides_editor(
20195        &"
20196    fn main() {
20197        let a = 1;
20198
20199        let b = 2;
20200    }"
20201        .unindent(),
20202        cx,
20203    )
20204    .await;
20205
20206    cx.update_editor(|editor, window, cx| {
20207        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20208            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20209        });
20210    });
20211
20212    assert_indent_guides(
20213        0..5,
20214        vec![indent_guide(buffer_id, 1, 3, 0)],
20215        Some(vec![0]),
20216        &mut cx,
20217    );
20218}
20219
20220#[gpui::test]
20221async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20222    let (buffer_id, mut cx) = setup_indent_guides_editor(
20223        &"
20224    def m:
20225        a = 1
20226        pass"
20227            .unindent(),
20228        cx,
20229    )
20230    .await;
20231
20232    cx.update_editor(|editor, window, cx| {
20233        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20234            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20235        });
20236    });
20237
20238    assert_indent_guides(
20239        0..3,
20240        vec![indent_guide(buffer_id, 1, 2, 0)],
20241        Some(vec![0]),
20242        &mut cx,
20243    );
20244}
20245
20246#[gpui::test]
20247async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20248    init_test(cx, |_| {});
20249    let mut cx = EditorTestContext::new(cx).await;
20250    let text = indoc! {
20251        "
20252        impl A {
20253            fn b() {
20254                0;
20255                3;
20256                5;
20257                6;
20258                7;
20259            }
20260        }
20261        "
20262    };
20263    let base_text = indoc! {
20264        "
20265        impl A {
20266            fn b() {
20267                0;
20268                1;
20269                2;
20270                3;
20271                4;
20272            }
20273            fn c() {
20274                5;
20275                6;
20276                7;
20277            }
20278        }
20279        "
20280    };
20281
20282    cx.update_editor(|editor, window, cx| {
20283        editor.set_text(text, window, cx);
20284
20285        editor.buffer().update(cx, |multibuffer, cx| {
20286            let buffer = multibuffer.as_singleton().unwrap();
20287            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20288
20289            multibuffer.set_all_diff_hunks_expanded(cx);
20290            multibuffer.add_diff(diff, cx);
20291
20292            buffer.read(cx).remote_id()
20293        })
20294    });
20295    cx.run_until_parked();
20296
20297    cx.assert_state_with_diff(
20298        indoc! { "
20299          impl A {
20300              fn b() {
20301                  0;
20302        -         1;
20303        -         2;
20304                  3;
20305        -         4;
20306        -     }
20307        -     fn c() {
20308                  5;
20309                  6;
20310                  7;
20311              }
20312          }
20313          ˇ"
20314        }
20315        .to_string(),
20316    );
20317
20318    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20319        editor
20320            .snapshot(window, cx)
20321            .buffer_snapshot
20322            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20323            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20324            .collect::<Vec<_>>()
20325    });
20326    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20327    assert_eq!(
20328        actual_guides,
20329        vec![
20330            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20331            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20332            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20333        ]
20334    );
20335}
20336
20337#[gpui::test]
20338async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20339    init_test(cx, |_| {});
20340    let mut cx = EditorTestContext::new(cx).await;
20341
20342    let diff_base = r#"
20343        a
20344        b
20345        c
20346        "#
20347    .unindent();
20348
20349    cx.set_state(
20350        &r#"
20351        ˇA
20352        b
20353        C
20354        "#
20355        .unindent(),
20356    );
20357    cx.set_head_text(&diff_base);
20358    cx.update_editor(|editor, window, cx| {
20359        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20360    });
20361    executor.run_until_parked();
20362
20363    let both_hunks_expanded = r#"
20364        - a
20365        + ˇA
20366          b
20367        - c
20368        + C
20369        "#
20370    .unindent();
20371
20372    cx.assert_state_with_diff(both_hunks_expanded.clone());
20373
20374    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20375        let snapshot = editor.snapshot(window, cx);
20376        let hunks = editor
20377            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20378            .collect::<Vec<_>>();
20379        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20380        let buffer_id = hunks[0].buffer_id;
20381        hunks
20382            .into_iter()
20383            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20384            .collect::<Vec<_>>()
20385    });
20386    assert_eq!(hunk_ranges.len(), 2);
20387
20388    cx.update_editor(|editor, _, cx| {
20389        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20390    });
20391    executor.run_until_parked();
20392
20393    let second_hunk_expanded = r#"
20394          ˇA
20395          b
20396        - c
20397        + C
20398        "#
20399    .unindent();
20400
20401    cx.assert_state_with_diff(second_hunk_expanded);
20402
20403    cx.update_editor(|editor, _, cx| {
20404        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20405    });
20406    executor.run_until_parked();
20407
20408    cx.assert_state_with_diff(both_hunks_expanded.clone());
20409
20410    cx.update_editor(|editor, _, cx| {
20411        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20412    });
20413    executor.run_until_parked();
20414
20415    let first_hunk_expanded = r#"
20416        - a
20417        + ˇA
20418          b
20419          C
20420        "#
20421    .unindent();
20422
20423    cx.assert_state_with_diff(first_hunk_expanded);
20424
20425    cx.update_editor(|editor, _, cx| {
20426        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20427    });
20428    executor.run_until_parked();
20429
20430    cx.assert_state_with_diff(both_hunks_expanded);
20431
20432    cx.set_state(
20433        &r#"
20434        ˇA
20435        b
20436        "#
20437        .unindent(),
20438    );
20439    cx.run_until_parked();
20440
20441    // TODO this cursor position seems bad
20442    cx.assert_state_with_diff(
20443        r#"
20444        - ˇa
20445        + A
20446          b
20447        "#
20448        .unindent(),
20449    );
20450
20451    cx.update_editor(|editor, window, cx| {
20452        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20453    });
20454
20455    cx.assert_state_with_diff(
20456        r#"
20457            - ˇa
20458            + A
20459              b
20460            - c
20461            "#
20462        .unindent(),
20463    );
20464
20465    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20466        let snapshot = editor.snapshot(window, cx);
20467        let hunks = editor
20468            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20469            .collect::<Vec<_>>();
20470        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20471        let buffer_id = hunks[0].buffer_id;
20472        hunks
20473            .into_iter()
20474            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20475            .collect::<Vec<_>>()
20476    });
20477    assert_eq!(hunk_ranges.len(), 2);
20478
20479    cx.update_editor(|editor, _, cx| {
20480        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20481    });
20482    executor.run_until_parked();
20483
20484    cx.assert_state_with_diff(
20485        r#"
20486        - ˇa
20487        + A
20488          b
20489        "#
20490        .unindent(),
20491    );
20492}
20493
20494#[gpui::test]
20495async fn test_toggle_deletion_hunk_at_start_of_file(
20496    executor: BackgroundExecutor,
20497    cx: &mut TestAppContext,
20498) {
20499    init_test(cx, |_| {});
20500    let mut cx = EditorTestContext::new(cx).await;
20501
20502    let diff_base = r#"
20503        a
20504        b
20505        c
20506        "#
20507    .unindent();
20508
20509    cx.set_state(
20510        &r#"
20511        ˇb
20512        c
20513        "#
20514        .unindent(),
20515    );
20516    cx.set_head_text(&diff_base);
20517    cx.update_editor(|editor, window, cx| {
20518        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20519    });
20520    executor.run_until_parked();
20521
20522    let hunk_expanded = r#"
20523        - a
20524          ˇb
20525          c
20526        "#
20527    .unindent();
20528
20529    cx.assert_state_with_diff(hunk_expanded.clone());
20530
20531    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20532        let snapshot = editor.snapshot(window, cx);
20533        let hunks = editor
20534            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20535            .collect::<Vec<_>>();
20536        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20537        let buffer_id = hunks[0].buffer_id;
20538        hunks
20539            .into_iter()
20540            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20541            .collect::<Vec<_>>()
20542    });
20543    assert_eq!(hunk_ranges.len(), 1);
20544
20545    cx.update_editor(|editor, _, cx| {
20546        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20547    });
20548    executor.run_until_parked();
20549
20550    let hunk_collapsed = r#"
20551          ˇb
20552          c
20553        "#
20554    .unindent();
20555
20556    cx.assert_state_with_diff(hunk_collapsed);
20557
20558    cx.update_editor(|editor, _, cx| {
20559        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20560    });
20561    executor.run_until_parked();
20562
20563    cx.assert_state_with_diff(hunk_expanded);
20564}
20565
20566#[gpui::test]
20567async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20568    init_test(cx, |_| {});
20569
20570    let fs = FakeFs::new(cx.executor());
20571    fs.insert_tree(
20572        path!("/test"),
20573        json!({
20574            ".git": {},
20575            "file-1": "ONE\n",
20576            "file-2": "TWO\n",
20577            "file-3": "THREE\n",
20578        }),
20579    )
20580    .await;
20581
20582    fs.set_head_for_repo(
20583        path!("/test/.git").as_ref(),
20584        &[
20585            ("file-1".into(), "one\n".into()),
20586            ("file-2".into(), "two\n".into()),
20587            ("file-3".into(), "three\n".into()),
20588        ],
20589        "deadbeef",
20590    );
20591
20592    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20593    let mut buffers = vec![];
20594    for i in 1..=3 {
20595        let buffer = project
20596            .update(cx, |project, cx| {
20597                let path = format!(path!("/test/file-{}"), i);
20598                project.open_local_buffer(path, cx)
20599            })
20600            .await
20601            .unwrap();
20602        buffers.push(buffer);
20603    }
20604
20605    let multibuffer = cx.new(|cx| {
20606        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20607        multibuffer.set_all_diff_hunks_expanded(cx);
20608        for buffer in &buffers {
20609            let snapshot = buffer.read(cx).snapshot();
20610            multibuffer.set_excerpts_for_path(
20611                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20612                buffer.clone(),
20613                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20614                2,
20615                cx,
20616            );
20617        }
20618        multibuffer
20619    });
20620
20621    let editor = cx.add_window(|window, cx| {
20622        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20623    });
20624    cx.run_until_parked();
20625
20626    let snapshot = editor
20627        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20628        .unwrap();
20629    let hunks = snapshot
20630        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20631        .map(|hunk| match hunk {
20632            DisplayDiffHunk::Unfolded {
20633                display_row_range, ..
20634            } => display_row_range,
20635            DisplayDiffHunk::Folded { .. } => unreachable!(),
20636        })
20637        .collect::<Vec<_>>();
20638    assert_eq!(
20639        hunks,
20640        [
20641            DisplayRow(2)..DisplayRow(4),
20642            DisplayRow(7)..DisplayRow(9),
20643            DisplayRow(12)..DisplayRow(14),
20644        ]
20645    );
20646}
20647
20648#[gpui::test]
20649async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
20650    init_test(cx, |_| {});
20651
20652    let mut cx = EditorTestContext::new(cx).await;
20653    cx.set_head_text(indoc! { "
20654        one
20655        two
20656        three
20657        four
20658        five
20659        "
20660    });
20661    cx.set_index_text(indoc! { "
20662        one
20663        two
20664        three
20665        four
20666        five
20667        "
20668    });
20669    cx.set_state(indoc! {"
20670        one
20671        TWO
20672        ˇTHREE
20673        FOUR
20674        five
20675    "});
20676    cx.run_until_parked();
20677    cx.update_editor(|editor, window, cx| {
20678        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20679    });
20680    cx.run_until_parked();
20681    cx.assert_index_text(Some(indoc! {"
20682        one
20683        TWO
20684        THREE
20685        FOUR
20686        five
20687    "}));
20688    cx.set_state(indoc! { "
20689        one
20690        TWO
20691        ˇTHREE-HUNDRED
20692        FOUR
20693        five
20694    "});
20695    cx.run_until_parked();
20696    cx.update_editor(|editor, window, cx| {
20697        let snapshot = editor.snapshot(window, cx);
20698        let hunks = editor
20699            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20700            .collect::<Vec<_>>();
20701        assert_eq!(hunks.len(), 1);
20702        assert_eq!(
20703            hunks[0].status(),
20704            DiffHunkStatus {
20705                kind: DiffHunkStatusKind::Modified,
20706                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
20707            }
20708        );
20709
20710        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20711    });
20712    cx.run_until_parked();
20713    cx.assert_index_text(Some(indoc! {"
20714        one
20715        TWO
20716        THREE-HUNDRED
20717        FOUR
20718        five
20719    "}));
20720}
20721
20722#[gpui::test]
20723fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
20724    init_test(cx, |_| {});
20725
20726    let editor = cx.add_window(|window, cx| {
20727        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
20728        build_editor(buffer, window, cx)
20729    });
20730
20731    let render_args = Arc::new(Mutex::new(None));
20732    let snapshot = editor
20733        .update(cx, |editor, window, cx| {
20734            let snapshot = editor.buffer().read(cx).snapshot(cx);
20735            let range =
20736                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
20737
20738            struct RenderArgs {
20739                row: MultiBufferRow,
20740                folded: bool,
20741                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
20742            }
20743
20744            let crease = Crease::inline(
20745                range,
20746                FoldPlaceholder::test(),
20747                {
20748                    let toggle_callback = render_args.clone();
20749                    move |row, folded, callback, _window, _cx| {
20750                        *toggle_callback.lock() = Some(RenderArgs {
20751                            row,
20752                            folded,
20753                            callback,
20754                        });
20755                        div()
20756                    }
20757                },
20758                |_row, _folded, _window, _cx| div(),
20759            );
20760
20761            editor.insert_creases(Some(crease), cx);
20762            let snapshot = editor.snapshot(window, cx);
20763            let _div =
20764                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
20765            snapshot
20766        })
20767        .unwrap();
20768
20769    let render_args = render_args.lock().take().unwrap();
20770    assert_eq!(render_args.row, MultiBufferRow(1));
20771    assert!(!render_args.folded);
20772    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
20773
20774    cx.update_window(*editor, |_, window, cx| {
20775        (render_args.callback)(true, window, cx)
20776    })
20777    .unwrap();
20778    let snapshot = editor
20779        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20780        .unwrap();
20781    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
20782
20783    cx.update_window(*editor, |_, window, cx| {
20784        (render_args.callback)(false, window, cx)
20785    })
20786    .unwrap();
20787    let snapshot = editor
20788        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20789        .unwrap();
20790    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
20791}
20792
20793#[gpui::test]
20794async fn test_input_text(cx: &mut TestAppContext) {
20795    init_test(cx, |_| {});
20796    let mut cx = EditorTestContext::new(cx).await;
20797
20798    cx.set_state(
20799        &r#"ˇone
20800        two
20801
20802        three
20803        fourˇ
20804        five
20805
20806        siˇx"#
20807            .unindent(),
20808    );
20809
20810    cx.dispatch_action(HandleInput(String::new()));
20811    cx.assert_editor_state(
20812        &r#"ˇone
20813        two
20814
20815        three
20816        fourˇ
20817        five
20818
20819        siˇx"#
20820            .unindent(),
20821    );
20822
20823    cx.dispatch_action(HandleInput("AAAA".to_string()));
20824    cx.assert_editor_state(
20825        &r#"AAAAˇone
20826        two
20827
20828        three
20829        fourAAAAˇ
20830        five
20831
20832        siAAAAˇx"#
20833            .unindent(),
20834    );
20835}
20836
20837#[gpui::test]
20838async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
20839    init_test(cx, |_| {});
20840
20841    let mut cx = EditorTestContext::new(cx).await;
20842    cx.set_state(
20843        r#"let foo = 1;
20844let foo = 2;
20845let foo = 3;
20846let fooˇ = 4;
20847let foo = 5;
20848let foo = 6;
20849let foo = 7;
20850let foo = 8;
20851let foo = 9;
20852let foo = 10;
20853let foo = 11;
20854let foo = 12;
20855let foo = 13;
20856let foo = 14;
20857let foo = 15;"#,
20858    );
20859
20860    cx.update_editor(|e, window, cx| {
20861        assert_eq!(
20862            e.next_scroll_position,
20863            NextScrollCursorCenterTopBottom::Center,
20864            "Default next scroll direction is center",
20865        );
20866
20867        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20868        assert_eq!(
20869            e.next_scroll_position,
20870            NextScrollCursorCenterTopBottom::Top,
20871            "After center, next scroll direction should be top",
20872        );
20873
20874        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20875        assert_eq!(
20876            e.next_scroll_position,
20877            NextScrollCursorCenterTopBottom::Bottom,
20878            "After top, next scroll direction should be bottom",
20879        );
20880
20881        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20882        assert_eq!(
20883            e.next_scroll_position,
20884            NextScrollCursorCenterTopBottom::Center,
20885            "After bottom, scrolling should start over",
20886        );
20887
20888        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20889        assert_eq!(
20890            e.next_scroll_position,
20891            NextScrollCursorCenterTopBottom::Top,
20892            "Scrolling continues if retriggered fast enough"
20893        );
20894    });
20895
20896    cx.executor()
20897        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
20898    cx.executor().run_until_parked();
20899    cx.update_editor(|e, _, _| {
20900        assert_eq!(
20901            e.next_scroll_position,
20902            NextScrollCursorCenterTopBottom::Center,
20903            "If scrolling is not triggered fast enough, it should reset"
20904        );
20905    });
20906}
20907
20908#[gpui::test]
20909async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
20910    init_test(cx, |_| {});
20911    let mut cx = EditorLspTestContext::new_rust(
20912        lsp::ServerCapabilities {
20913            definition_provider: Some(lsp::OneOf::Left(true)),
20914            references_provider: Some(lsp::OneOf::Left(true)),
20915            ..lsp::ServerCapabilities::default()
20916        },
20917        cx,
20918    )
20919    .await;
20920
20921    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
20922        let go_to_definition = cx
20923            .lsp
20924            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20925                move |params, _| async move {
20926                    if empty_go_to_definition {
20927                        Ok(None)
20928                    } else {
20929                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
20930                            uri: params.text_document_position_params.text_document.uri,
20931                            range: lsp::Range::new(
20932                                lsp::Position::new(4, 3),
20933                                lsp::Position::new(4, 6),
20934                            ),
20935                        })))
20936                    }
20937                },
20938            );
20939        let references = cx
20940            .lsp
20941            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
20942                Ok(Some(vec![lsp::Location {
20943                    uri: params.text_document_position.text_document.uri,
20944                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
20945                }]))
20946            });
20947        (go_to_definition, references)
20948    };
20949
20950    cx.set_state(
20951        &r#"fn one() {
20952            let mut a = ˇtwo();
20953        }
20954
20955        fn two() {}"#
20956            .unindent(),
20957    );
20958    set_up_lsp_handlers(false, &mut cx);
20959    let navigated = cx
20960        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20961        .await
20962        .expect("Failed to navigate to definition");
20963    assert_eq!(
20964        navigated,
20965        Navigated::Yes,
20966        "Should have navigated to definition from the GetDefinition response"
20967    );
20968    cx.assert_editor_state(
20969        &r#"fn one() {
20970            let mut a = two();
20971        }
20972
20973        fn «twoˇ»() {}"#
20974            .unindent(),
20975    );
20976
20977    let editors = cx.update_workspace(|workspace, _, cx| {
20978        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20979    });
20980    cx.update_editor(|_, _, test_editor_cx| {
20981        assert_eq!(
20982            editors.len(),
20983            1,
20984            "Initially, only one, test, editor should be open in the workspace"
20985        );
20986        assert_eq!(
20987            test_editor_cx.entity(),
20988            editors.last().expect("Asserted len is 1").clone()
20989        );
20990    });
20991
20992    set_up_lsp_handlers(true, &mut cx);
20993    let navigated = cx
20994        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20995        .await
20996        .expect("Failed to navigate to lookup references");
20997    assert_eq!(
20998        navigated,
20999        Navigated::Yes,
21000        "Should have navigated to references as a fallback after empty GoToDefinition response"
21001    );
21002    // We should not change the selections in the existing file,
21003    // if opening another milti buffer with the references
21004    cx.assert_editor_state(
21005        &r#"fn one() {
21006            let mut a = two();
21007        }
21008
21009        fn «twoˇ»() {}"#
21010            .unindent(),
21011    );
21012    let editors = cx.update_workspace(|workspace, _, cx| {
21013        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21014    });
21015    cx.update_editor(|_, _, test_editor_cx| {
21016        assert_eq!(
21017            editors.len(),
21018            2,
21019            "After falling back to references search, we open a new editor with the results"
21020        );
21021        let references_fallback_text = editors
21022            .into_iter()
21023            .find(|new_editor| *new_editor != test_editor_cx.entity())
21024            .expect("Should have one non-test editor now")
21025            .read(test_editor_cx)
21026            .text(test_editor_cx);
21027        assert_eq!(
21028            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21029            "Should use the range from the references response and not the GoToDefinition one"
21030        );
21031    });
21032}
21033
21034#[gpui::test]
21035async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21036    init_test(cx, |_| {});
21037    cx.update(|cx| {
21038        let mut editor_settings = EditorSettings::get_global(cx).clone();
21039        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21040        EditorSettings::override_global(editor_settings, cx);
21041    });
21042    let mut cx = EditorLspTestContext::new_rust(
21043        lsp::ServerCapabilities {
21044            definition_provider: Some(lsp::OneOf::Left(true)),
21045            references_provider: Some(lsp::OneOf::Left(true)),
21046            ..lsp::ServerCapabilities::default()
21047        },
21048        cx,
21049    )
21050    .await;
21051    let original_state = r#"fn one() {
21052        let mut a = ˇtwo();
21053    }
21054
21055    fn two() {}"#
21056        .unindent();
21057    cx.set_state(&original_state);
21058
21059    let mut go_to_definition = cx
21060        .lsp
21061        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21062            move |_, _| async move { Ok(None) },
21063        );
21064    let _references = cx
21065        .lsp
21066        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21067            panic!("Should not call for references with no go to definition fallback")
21068        });
21069
21070    let navigated = cx
21071        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21072        .await
21073        .expect("Failed to navigate to lookup references");
21074    go_to_definition
21075        .next()
21076        .await
21077        .expect("Should have called the go_to_definition handler");
21078
21079    assert_eq!(
21080        navigated,
21081        Navigated::No,
21082        "Should have navigated to references as a fallback after empty GoToDefinition response"
21083    );
21084    cx.assert_editor_state(&original_state);
21085    let editors = cx.update_workspace(|workspace, _, cx| {
21086        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21087    });
21088    cx.update_editor(|_, _, _| {
21089        assert_eq!(
21090            editors.len(),
21091            1,
21092            "After unsuccessful fallback, no other editor should have been opened"
21093        );
21094    });
21095}
21096
21097#[gpui::test]
21098async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21099    init_test(cx, |_| {});
21100
21101    let language = Arc::new(Language::new(
21102        LanguageConfig::default(),
21103        Some(tree_sitter_rust::LANGUAGE.into()),
21104    ));
21105
21106    let text = r#"
21107        #[cfg(test)]
21108        mod tests() {
21109            #[test]
21110            fn runnable_1() {
21111                let a = 1;
21112            }
21113
21114            #[test]
21115            fn runnable_2() {
21116                let a = 1;
21117                let b = 2;
21118            }
21119        }
21120    "#
21121    .unindent();
21122
21123    let fs = FakeFs::new(cx.executor());
21124    fs.insert_file("/file.rs", Default::default()).await;
21125
21126    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21127    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21128    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21129    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21130    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21131
21132    let editor = cx.new_window_entity(|window, cx| {
21133        Editor::new(
21134            EditorMode::full(),
21135            multi_buffer,
21136            Some(project.clone()),
21137            window,
21138            cx,
21139        )
21140    });
21141
21142    editor.update_in(cx, |editor, window, cx| {
21143        let snapshot = editor.buffer().read(cx).snapshot(cx);
21144        editor.tasks.insert(
21145            (buffer.read(cx).remote_id(), 3),
21146            RunnableTasks {
21147                templates: vec![],
21148                offset: snapshot.anchor_before(43),
21149                column: 0,
21150                extra_variables: HashMap::default(),
21151                context_range: BufferOffset(43)..BufferOffset(85),
21152            },
21153        );
21154        editor.tasks.insert(
21155            (buffer.read(cx).remote_id(), 8),
21156            RunnableTasks {
21157                templates: vec![],
21158                offset: snapshot.anchor_before(86),
21159                column: 0,
21160                extra_variables: HashMap::default(),
21161                context_range: BufferOffset(86)..BufferOffset(191),
21162            },
21163        );
21164
21165        // Test finding task when cursor is inside function body
21166        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21167            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21168        });
21169        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21170        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21171
21172        // Test finding task when cursor is on function name
21173        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21174            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21175        });
21176        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21177        assert_eq!(row, 8, "Should find task when cursor is on function name");
21178    });
21179}
21180
21181#[gpui::test]
21182async fn test_folding_buffers(cx: &mut TestAppContext) {
21183    init_test(cx, |_| {});
21184
21185    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21186    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21187    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21188
21189    let fs = FakeFs::new(cx.executor());
21190    fs.insert_tree(
21191        path!("/a"),
21192        json!({
21193            "first.rs": sample_text_1,
21194            "second.rs": sample_text_2,
21195            "third.rs": sample_text_3,
21196        }),
21197    )
21198    .await;
21199    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21200    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21201    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21202    let worktree = project.update(cx, |project, cx| {
21203        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21204        assert_eq!(worktrees.len(), 1);
21205        worktrees.pop().unwrap()
21206    });
21207    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21208
21209    let buffer_1 = project
21210        .update(cx, |project, cx| {
21211            project.open_buffer((worktree_id, "first.rs"), cx)
21212        })
21213        .await
21214        .unwrap();
21215    let buffer_2 = project
21216        .update(cx, |project, cx| {
21217            project.open_buffer((worktree_id, "second.rs"), cx)
21218        })
21219        .await
21220        .unwrap();
21221    let buffer_3 = project
21222        .update(cx, |project, cx| {
21223            project.open_buffer((worktree_id, "third.rs"), cx)
21224        })
21225        .await
21226        .unwrap();
21227
21228    let multi_buffer = cx.new(|cx| {
21229        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21230        multi_buffer.push_excerpts(
21231            buffer_1.clone(),
21232            [
21233                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21234                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21235                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21236            ],
21237            cx,
21238        );
21239        multi_buffer.push_excerpts(
21240            buffer_2.clone(),
21241            [
21242                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21243                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21244                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21245            ],
21246            cx,
21247        );
21248        multi_buffer.push_excerpts(
21249            buffer_3.clone(),
21250            [
21251                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21252                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21253                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21254            ],
21255            cx,
21256        );
21257        multi_buffer
21258    });
21259    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21260        Editor::new(
21261            EditorMode::full(),
21262            multi_buffer.clone(),
21263            Some(project.clone()),
21264            window,
21265            cx,
21266        )
21267    });
21268
21269    assert_eq!(
21270        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21271        "\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",
21272    );
21273
21274    multi_buffer_editor.update(cx, |editor, cx| {
21275        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21276    });
21277    assert_eq!(
21278        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21279        "\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",
21280        "After folding the first buffer, its text should not be displayed"
21281    );
21282
21283    multi_buffer_editor.update(cx, |editor, cx| {
21284        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21285    });
21286    assert_eq!(
21287        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21288        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21289        "After folding the second buffer, its text should not be displayed"
21290    );
21291
21292    multi_buffer_editor.update(cx, |editor, cx| {
21293        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21294    });
21295    assert_eq!(
21296        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21297        "\n\n\n\n\n",
21298        "After folding the third buffer, its text should not be displayed"
21299    );
21300
21301    // Emulate selection inside the fold logic, that should work
21302    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21303        editor
21304            .snapshot(window, cx)
21305            .next_line_boundary(Point::new(0, 4));
21306    });
21307
21308    multi_buffer_editor.update(cx, |editor, cx| {
21309        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21310    });
21311    assert_eq!(
21312        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21313        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21314        "After unfolding the second buffer, its text should be displayed"
21315    );
21316
21317    // Typing inside of buffer 1 causes that buffer to be unfolded.
21318    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21319        assert_eq!(
21320            multi_buffer
21321                .read(cx)
21322                .snapshot(cx)
21323                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21324                .collect::<String>(),
21325            "bbbb"
21326        );
21327        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21328            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21329        });
21330        editor.handle_input("B", window, cx);
21331    });
21332
21333    assert_eq!(
21334        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21335        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21336        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21337    );
21338
21339    multi_buffer_editor.update(cx, |editor, cx| {
21340        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21341    });
21342    assert_eq!(
21343        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21344        "\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",
21345        "After unfolding the all buffers, all original text should be displayed"
21346    );
21347}
21348
21349#[gpui::test]
21350async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21351    init_test(cx, |_| {});
21352
21353    let sample_text_1 = "1111\n2222\n3333".to_string();
21354    let sample_text_2 = "4444\n5555\n6666".to_string();
21355    let sample_text_3 = "7777\n8888\n9999".to_string();
21356
21357    let fs = FakeFs::new(cx.executor());
21358    fs.insert_tree(
21359        path!("/a"),
21360        json!({
21361            "first.rs": sample_text_1,
21362            "second.rs": sample_text_2,
21363            "third.rs": sample_text_3,
21364        }),
21365    )
21366    .await;
21367    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21368    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21369    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21370    let worktree = project.update(cx, |project, cx| {
21371        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21372        assert_eq!(worktrees.len(), 1);
21373        worktrees.pop().unwrap()
21374    });
21375    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21376
21377    let buffer_1 = project
21378        .update(cx, |project, cx| {
21379            project.open_buffer((worktree_id, "first.rs"), cx)
21380        })
21381        .await
21382        .unwrap();
21383    let buffer_2 = project
21384        .update(cx, |project, cx| {
21385            project.open_buffer((worktree_id, "second.rs"), cx)
21386        })
21387        .await
21388        .unwrap();
21389    let buffer_3 = project
21390        .update(cx, |project, cx| {
21391            project.open_buffer((worktree_id, "third.rs"), cx)
21392        })
21393        .await
21394        .unwrap();
21395
21396    let multi_buffer = cx.new(|cx| {
21397        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21398        multi_buffer.push_excerpts(
21399            buffer_1.clone(),
21400            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21401            cx,
21402        );
21403        multi_buffer.push_excerpts(
21404            buffer_2.clone(),
21405            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21406            cx,
21407        );
21408        multi_buffer.push_excerpts(
21409            buffer_3.clone(),
21410            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21411            cx,
21412        );
21413        multi_buffer
21414    });
21415
21416    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21417        Editor::new(
21418            EditorMode::full(),
21419            multi_buffer,
21420            Some(project.clone()),
21421            window,
21422            cx,
21423        )
21424    });
21425
21426    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21427    assert_eq!(
21428        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21429        full_text,
21430    );
21431
21432    multi_buffer_editor.update(cx, |editor, cx| {
21433        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21434    });
21435    assert_eq!(
21436        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21437        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21438        "After folding the first buffer, its text should not be displayed"
21439    );
21440
21441    multi_buffer_editor.update(cx, |editor, cx| {
21442        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21443    });
21444
21445    assert_eq!(
21446        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21447        "\n\n\n\n\n\n7777\n8888\n9999",
21448        "After folding the second buffer, its text should not be displayed"
21449    );
21450
21451    multi_buffer_editor.update(cx, |editor, cx| {
21452        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21453    });
21454    assert_eq!(
21455        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21456        "\n\n\n\n\n",
21457        "After folding the third buffer, its text should not be displayed"
21458    );
21459
21460    multi_buffer_editor.update(cx, |editor, cx| {
21461        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21462    });
21463    assert_eq!(
21464        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21465        "\n\n\n\n4444\n5555\n6666\n\n",
21466        "After unfolding the second buffer, its text should be displayed"
21467    );
21468
21469    multi_buffer_editor.update(cx, |editor, cx| {
21470        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21471    });
21472    assert_eq!(
21473        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21474        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21475        "After unfolding the first buffer, its text should be displayed"
21476    );
21477
21478    multi_buffer_editor.update(cx, |editor, cx| {
21479        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21480    });
21481    assert_eq!(
21482        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21483        full_text,
21484        "After unfolding all buffers, all original text should be displayed"
21485    );
21486}
21487
21488#[gpui::test]
21489async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21490    init_test(cx, |_| {});
21491
21492    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21493
21494    let fs = FakeFs::new(cx.executor());
21495    fs.insert_tree(
21496        path!("/a"),
21497        json!({
21498            "main.rs": sample_text,
21499        }),
21500    )
21501    .await;
21502    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21503    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21504    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21505    let worktree = project.update(cx, |project, cx| {
21506        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21507        assert_eq!(worktrees.len(), 1);
21508        worktrees.pop().unwrap()
21509    });
21510    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21511
21512    let buffer_1 = project
21513        .update(cx, |project, cx| {
21514            project.open_buffer((worktree_id, "main.rs"), cx)
21515        })
21516        .await
21517        .unwrap();
21518
21519    let multi_buffer = cx.new(|cx| {
21520        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21521        multi_buffer.push_excerpts(
21522            buffer_1.clone(),
21523            [ExcerptRange::new(
21524                Point::new(0, 0)
21525                    ..Point::new(
21526                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
21527                        0,
21528                    ),
21529            )],
21530            cx,
21531        );
21532        multi_buffer
21533    });
21534    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21535        Editor::new(
21536            EditorMode::full(),
21537            multi_buffer,
21538            Some(project.clone()),
21539            window,
21540            cx,
21541        )
21542    });
21543
21544    let selection_range = Point::new(1, 0)..Point::new(2, 0);
21545    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21546        enum TestHighlight {}
21547        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
21548        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
21549        editor.highlight_text::<TestHighlight>(
21550            vec![highlight_range.clone()],
21551            HighlightStyle::color(Hsla::green()),
21552            cx,
21553        );
21554        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21555            s.select_ranges(Some(highlight_range))
21556        });
21557    });
21558
21559    let full_text = format!("\n\n{sample_text}");
21560    assert_eq!(
21561        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21562        full_text,
21563    );
21564}
21565
21566#[gpui::test]
21567async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
21568    init_test(cx, |_| {});
21569    cx.update(|cx| {
21570        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
21571            "keymaps/default-linux.json",
21572            cx,
21573        )
21574        .unwrap();
21575        cx.bind_keys(default_key_bindings);
21576    });
21577
21578    let (editor, cx) = cx.add_window_view(|window, cx| {
21579        let multi_buffer = MultiBuffer::build_multi(
21580            [
21581                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
21582                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
21583                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
21584                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
21585            ],
21586            cx,
21587        );
21588        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
21589
21590        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
21591        // fold all but the second buffer, so that we test navigating between two
21592        // adjacent folded buffers, as well as folded buffers at the start and
21593        // end the multibuffer
21594        editor.fold_buffer(buffer_ids[0], cx);
21595        editor.fold_buffer(buffer_ids[2], cx);
21596        editor.fold_buffer(buffer_ids[3], cx);
21597
21598        editor
21599    });
21600    cx.simulate_resize(size(px(1000.), px(1000.)));
21601
21602    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
21603    cx.assert_excerpts_with_selections(indoc! {"
21604        [EXCERPT]
21605        ˇ[FOLDED]
21606        [EXCERPT]
21607        a1
21608        b1
21609        [EXCERPT]
21610        [FOLDED]
21611        [EXCERPT]
21612        [FOLDED]
21613        "
21614    });
21615    cx.simulate_keystroke("down");
21616    cx.assert_excerpts_with_selections(indoc! {"
21617        [EXCERPT]
21618        [FOLDED]
21619        [EXCERPT]
21620        ˇa1
21621        b1
21622        [EXCERPT]
21623        [FOLDED]
21624        [EXCERPT]
21625        [FOLDED]
21626        "
21627    });
21628    cx.simulate_keystroke("down");
21629    cx.assert_excerpts_with_selections(indoc! {"
21630        [EXCERPT]
21631        [FOLDED]
21632        [EXCERPT]
21633        a1
21634        ˇb1
21635        [EXCERPT]
21636        [FOLDED]
21637        [EXCERPT]
21638        [FOLDED]
21639        "
21640    });
21641    cx.simulate_keystroke("down");
21642    cx.assert_excerpts_with_selections(indoc! {"
21643        [EXCERPT]
21644        [FOLDED]
21645        [EXCERPT]
21646        a1
21647        b1
21648        ˇ[EXCERPT]
21649        [FOLDED]
21650        [EXCERPT]
21651        [FOLDED]
21652        "
21653    });
21654    cx.simulate_keystroke("down");
21655    cx.assert_excerpts_with_selections(indoc! {"
21656        [EXCERPT]
21657        [FOLDED]
21658        [EXCERPT]
21659        a1
21660        b1
21661        [EXCERPT]
21662        ˇ[FOLDED]
21663        [EXCERPT]
21664        [FOLDED]
21665        "
21666    });
21667    for _ in 0..5 {
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    }
21682
21683    cx.simulate_keystroke("up");
21684    cx.assert_excerpts_with_selections(indoc! {"
21685        [EXCERPT]
21686        [FOLDED]
21687        [EXCERPT]
21688        a1
21689        b1
21690        [EXCERPT]
21691        ˇ[FOLDED]
21692        [EXCERPT]
21693        [FOLDED]
21694        "
21695    });
21696    cx.simulate_keystroke("up");
21697    cx.assert_excerpts_with_selections(indoc! {"
21698        [EXCERPT]
21699        [FOLDED]
21700        [EXCERPT]
21701        a1
21702        b1
21703        ˇ[EXCERPT]
21704        [FOLDED]
21705        [EXCERPT]
21706        [FOLDED]
21707        "
21708    });
21709    cx.simulate_keystroke("up");
21710    cx.assert_excerpts_with_selections(indoc! {"
21711        [EXCERPT]
21712        [FOLDED]
21713        [EXCERPT]
21714        a1
21715        ˇb1
21716        [EXCERPT]
21717        [FOLDED]
21718        [EXCERPT]
21719        [FOLDED]
21720        "
21721    });
21722    cx.simulate_keystroke("up");
21723    cx.assert_excerpts_with_selections(indoc! {"
21724        [EXCERPT]
21725        [FOLDED]
21726        [EXCERPT]
21727        ˇa1
21728        b1
21729        [EXCERPT]
21730        [FOLDED]
21731        [EXCERPT]
21732        [FOLDED]
21733        "
21734    });
21735    for _ in 0..5 {
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    }
21750}
21751
21752#[gpui::test]
21753async fn test_edit_prediction_text(cx: &mut TestAppContext) {
21754    init_test(cx, |_| {});
21755
21756    // Simple insertion
21757    assert_highlighted_edits(
21758        "Hello, world!",
21759        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
21760        true,
21761        cx,
21762        |highlighted_edits, cx| {
21763            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
21764            assert_eq!(highlighted_edits.highlights.len(), 1);
21765            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
21766            assert_eq!(
21767                highlighted_edits.highlights[0].1.background_color,
21768                Some(cx.theme().status().created_background)
21769            );
21770        },
21771    )
21772    .await;
21773
21774    // Replacement
21775    assert_highlighted_edits(
21776        "This is a test.",
21777        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
21778        false,
21779        cx,
21780        |highlighted_edits, cx| {
21781            assert_eq!(highlighted_edits.text, "That is a test.");
21782            assert_eq!(highlighted_edits.highlights.len(), 1);
21783            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
21784            assert_eq!(
21785                highlighted_edits.highlights[0].1.background_color,
21786                Some(cx.theme().status().created_background)
21787            );
21788        },
21789    )
21790    .await;
21791
21792    // Multiple edits
21793    assert_highlighted_edits(
21794        "Hello, world!",
21795        vec![
21796            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
21797            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
21798        ],
21799        false,
21800        cx,
21801        |highlighted_edits, cx| {
21802            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
21803            assert_eq!(highlighted_edits.highlights.len(), 2);
21804            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
21805            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
21806            assert_eq!(
21807                highlighted_edits.highlights[0].1.background_color,
21808                Some(cx.theme().status().created_background)
21809            );
21810            assert_eq!(
21811                highlighted_edits.highlights[1].1.background_color,
21812                Some(cx.theme().status().created_background)
21813            );
21814        },
21815    )
21816    .await;
21817
21818    // Multiple lines with edits
21819    assert_highlighted_edits(
21820        "First line\nSecond line\nThird line\nFourth line",
21821        vec![
21822            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
21823            (
21824                Point::new(2, 0)..Point::new(2, 10),
21825                "New third line".to_string(),
21826            ),
21827            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
21828        ],
21829        false,
21830        cx,
21831        |highlighted_edits, cx| {
21832            assert_eq!(
21833                highlighted_edits.text,
21834                "Second modified\nNew third line\nFourth updated line"
21835            );
21836            assert_eq!(highlighted_edits.highlights.len(), 3);
21837            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
21838            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
21839            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
21840            for highlight in &highlighted_edits.highlights {
21841                assert_eq!(
21842                    highlight.1.background_color,
21843                    Some(cx.theme().status().created_background)
21844                );
21845            }
21846        },
21847    )
21848    .await;
21849}
21850
21851#[gpui::test]
21852async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
21853    init_test(cx, |_| {});
21854
21855    // Deletion
21856    assert_highlighted_edits(
21857        "Hello, world!",
21858        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
21859        true,
21860        cx,
21861        |highlighted_edits, cx| {
21862            assert_eq!(highlighted_edits.text, "Hello, world!");
21863            assert_eq!(highlighted_edits.highlights.len(), 1);
21864            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
21865            assert_eq!(
21866                highlighted_edits.highlights[0].1.background_color,
21867                Some(cx.theme().status().deleted_background)
21868            );
21869        },
21870    )
21871    .await;
21872
21873    // Insertion
21874    assert_highlighted_edits(
21875        "Hello, world!",
21876        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
21877        true,
21878        cx,
21879        |highlighted_edits, cx| {
21880            assert_eq!(highlighted_edits.highlights.len(), 1);
21881            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
21882            assert_eq!(
21883                highlighted_edits.highlights[0].1.background_color,
21884                Some(cx.theme().status().created_background)
21885            );
21886        },
21887    )
21888    .await;
21889}
21890
21891async fn assert_highlighted_edits(
21892    text: &str,
21893    edits: Vec<(Range<Point>, String)>,
21894    include_deletions: bool,
21895    cx: &mut TestAppContext,
21896    assertion_fn: impl Fn(HighlightedText, &App),
21897) {
21898    let window = cx.add_window(|window, cx| {
21899        let buffer = MultiBuffer::build_simple(text, cx);
21900        Editor::new(EditorMode::full(), buffer, None, window, cx)
21901    });
21902    let cx = &mut VisualTestContext::from_window(*window, cx);
21903
21904    let (buffer, snapshot) = window
21905        .update(cx, |editor, _window, cx| {
21906            (
21907                editor.buffer().clone(),
21908                editor.buffer().read(cx).snapshot(cx),
21909            )
21910        })
21911        .unwrap();
21912
21913    let edits = edits
21914        .into_iter()
21915        .map(|(range, edit)| {
21916            (
21917                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
21918                edit,
21919            )
21920        })
21921        .collect::<Vec<_>>();
21922
21923    let text_anchor_edits = edits
21924        .clone()
21925        .into_iter()
21926        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
21927        .collect::<Vec<_>>();
21928
21929    let edit_preview = window
21930        .update(cx, |_, _window, cx| {
21931            buffer
21932                .read(cx)
21933                .as_singleton()
21934                .unwrap()
21935                .read(cx)
21936                .preview_edits(text_anchor_edits.into(), cx)
21937        })
21938        .unwrap()
21939        .await;
21940
21941    cx.update(|_window, cx| {
21942        let highlighted_edits = edit_prediction_edit_text(
21943            snapshot.as_singleton().unwrap().2,
21944            &edits,
21945            &edit_preview,
21946            include_deletions,
21947            cx,
21948        );
21949        assertion_fn(highlighted_edits, cx)
21950    });
21951}
21952
21953#[track_caller]
21954fn assert_breakpoint(
21955    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
21956    path: &Arc<Path>,
21957    expected: Vec<(u32, Breakpoint)>,
21958) {
21959    if expected.is_empty() {
21960        assert!(!breakpoints.contains_key(path), "{}", path.display());
21961    } else {
21962        let mut breakpoint = breakpoints
21963            .get(path)
21964            .unwrap()
21965            .iter()
21966            .map(|breakpoint| {
21967                (
21968                    breakpoint.row,
21969                    Breakpoint {
21970                        message: breakpoint.message.clone(),
21971                        state: breakpoint.state,
21972                        condition: breakpoint.condition.clone(),
21973                        hit_condition: breakpoint.hit_condition.clone(),
21974                    },
21975                )
21976            })
21977            .collect::<Vec<_>>();
21978
21979        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
21980
21981        assert_eq!(expected, breakpoint);
21982    }
21983}
21984
21985fn add_log_breakpoint_at_cursor(
21986    editor: &mut Editor,
21987    log_message: &str,
21988    window: &mut Window,
21989    cx: &mut Context<Editor>,
21990) {
21991    let (anchor, bp) = editor
21992        .breakpoints_at_cursors(window, cx)
21993        .first()
21994        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
21995        .unwrap_or_else(|| {
21996            let cursor_position: Point = editor.selections.newest(cx).head();
21997
21998            let breakpoint_position = editor
21999                .snapshot(window, cx)
22000                .display_snapshot
22001                .buffer_snapshot
22002                .anchor_before(Point::new(cursor_position.row, 0));
22003
22004            (breakpoint_position, Breakpoint::new_log(log_message))
22005        });
22006
22007    editor.edit_breakpoint_at_anchor(
22008        anchor,
22009        bp,
22010        BreakpointEditAction::EditLogMessage(log_message.into()),
22011        cx,
22012    );
22013}
22014
22015#[gpui::test]
22016async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22017    init_test(cx, |_| {});
22018
22019    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22020    let fs = FakeFs::new(cx.executor());
22021    fs.insert_tree(
22022        path!("/a"),
22023        json!({
22024            "main.rs": sample_text,
22025        }),
22026    )
22027    .await;
22028    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22029    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22030    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22031
22032    let fs = FakeFs::new(cx.executor());
22033    fs.insert_tree(
22034        path!("/a"),
22035        json!({
22036            "main.rs": sample_text,
22037        }),
22038    )
22039    .await;
22040    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22041    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22042    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22043    let worktree_id = workspace
22044        .update(cx, |workspace, _window, cx| {
22045            workspace.project().update(cx, |project, cx| {
22046                project.worktrees(cx).next().unwrap().read(cx).id()
22047            })
22048        })
22049        .unwrap();
22050
22051    let buffer = project
22052        .update(cx, |project, cx| {
22053            project.open_buffer((worktree_id, "main.rs"), cx)
22054        })
22055        .await
22056        .unwrap();
22057
22058    let (editor, cx) = cx.add_window_view(|window, cx| {
22059        Editor::new(
22060            EditorMode::full(),
22061            MultiBuffer::build_from_buffer(buffer, cx),
22062            Some(project.clone()),
22063            window,
22064            cx,
22065        )
22066    });
22067
22068    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22069    let abs_path = project.read_with(cx, |project, cx| {
22070        project
22071            .absolute_path(&project_path, cx)
22072            .map(Arc::from)
22073            .unwrap()
22074    });
22075
22076    // assert we can add breakpoint on the first line
22077    editor.update_in(cx, |editor, window, cx| {
22078        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22079        editor.move_to_end(&MoveToEnd, window, cx);
22080        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22081    });
22082
22083    let breakpoints = editor.update(cx, |editor, cx| {
22084        editor
22085            .breakpoint_store()
22086            .as_ref()
22087            .unwrap()
22088            .read(cx)
22089            .all_source_breakpoints(cx)
22090    });
22091
22092    assert_eq!(1, breakpoints.len());
22093    assert_breakpoint(
22094        &breakpoints,
22095        &abs_path,
22096        vec![
22097            (0, Breakpoint::new_standard()),
22098            (3, Breakpoint::new_standard()),
22099        ],
22100    );
22101
22102    editor.update_in(cx, |editor, window, cx| {
22103        editor.move_to_beginning(&MoveToBeginning, window, cx);
22104        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22105    });
22106
22107    let breakpoints = editor.update(cx, |editor, cx| {
22108        editor
22109            .breakpoint_store()
22110            .as_ref()
22111            .unwrap()
22112            .read(cx)
22113            .all_source_breakpoints(cx)
22114    });
22115
22116    assert_eq!(1, breakpoints.len());
22117    assert_breakpoint(
22118        &breakpoints,
22119        &abs_path,
22120        vec![(3, Breakpoint::new_standard())],
22121    );
22122
22123    editor.update_in(cx, |editor, window, cx| {
22124        editor.move_to_end(&MoveToEnd, window, cx);
22125        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22126    });
22127
22128    let breakpoints = editor.update(cx, |editor, cx| {
22129        editor
22130            .breakpoint_store()
22131            .as_ref()
22132            .unwrap()
22133            .read(cx)
22134            .all_source_breakpoints(cx)
22135    });
22136
22137    assert_eq!(0, breakpoints.len());
22138    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22139}
22140
22141#[gpui::test]
22142async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22143    init_test(cx, |_| {});
22144
22145    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22146
22147    let fs = FakeFs::new(cx.executor());
22148    fs.insert_tree(
22149        path!("/a"),
22150        json!({
22151            "main.rs": sample_text,
22152        }),
22153    )
22154    .await;
22155    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22156    let (workspace, cx) =
22157        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22158
22159    let worktree_id = workspace.update(cx, |workspace, cx| {
22160        workspace.project().update(cx, |project, cx| {
22161            project.worktrees(cx).next().unwrap().read(cx).id()
22162        })
22163    });
22164
22165    let buffer = project
22166        .update(cx, |project, cx| {
22167            project.open_buffer((worktree_id, "main.rs"), cx)
22168        })
22169        .await
22170        .unwrap();
22171
22172    let (editor, cx) = cx.add_window_view(|window, cx| {
22173        Editor::new(
22174            EditorMode::full(),
22175            MultiBuffer::build_from_buffer(buffer, cx),
22176            Some(project.clone()),
22177            window,
22178            cx,
22179        )
22180    });
22181
22182    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22183    let abs_path = project.read_with(cx, |project, cx| {
22184        project
22185            .absolute_path(&project_path, cx)
22186            .map(Arc::from)
22187            .unwrap()
22188    });
22189
22190    editor.update_in(cx, |editor, window, cx| {
22191        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22192    });
22193
22194    let breakpoints = editor.update(cx, |editor, cx| {
22195        editor
22196            .breakpoint_store()
22197            .as_ref()
22198            .unwrap()
22199            .read(cx)
22200            .all_source_breakpoints(cx)
22201    });
22202
22203    assert_breakpoint(
22204        &breakpoints,
22205        &abs_path,
22206        vec![(0, Breakpoint::new_log("hello world"))],
22207    );
22208
22209    // Removing a log message from a log breakpoint should remove it
22210    editor.update_in(cx, |editor, window, cx| {
22211        add_log_breakpoint_at_cursor(editor, "", window, cx);
22212    });
22213
22214    let breakpoints = editor.update(cx, |editor, cx| {
22215        editor
22216            .breakpoint_store()
22217            .as_ref()
22218            .unwrap()
22219            .read(cx)
22220            .all_source_breakpoints(cx)
22221    });
22222
22223    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22224
22225    editor.update_in(cx, |editor, window, cx| {
22226        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22227        editor.move_to_end(&MoveToEnd, window, cx);
22228        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22229        // Not adding a log message to a standard breakpoint shouldn't remove it
22230        add_log_breakpoint_at_cursor(editor, "", window, cx);
22231    });
22232
22233    let breakpoints = editor.update(cx, |editor, cx| {
22234        editor
22235            .breakpoint_store()
22236            .as_ref()
22237            .unwrap()
22238            .read(cx)
22239            .all_source_breakpoints(cx)
22240    });
22241
22242    assert_breakpoint(
22243        &breakpoints,
22244        &abs_path,
22245        vec![
22246            (0, Breakpoint::new_standard()),
22247            (3, Breakpoint::new_standard()),
22248        ],
22249    );
22250
22251    editor.update_in(cx, |editor, window, cx| {
22252        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22253    });
22254
22255    let breakpoints = editor.update(cx, |editor, cx| {
22256        editor
22257            .breakpoint_store()
22258            .as_ref()
22259            .unwrap()
22260            .read(cx)
22261            .all_source_breakpoints(cx)
22262    });
22263
22264    assert_breakpoint(
22265        &breakpoints,
22266        &abs_path,
22267        vec![
22268            (0, Breakpoint::new_standard()),
22269            (3, Breakpoint::new_log("hello world")),
22270        ],
22271    );
22272
22273    editor.update_in(cx, |editor, window, cx| {
22274        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22275    });
22276
22277    let breakpoints = editor.update(cx, |editor, cx| {
22278        editor
22279            .breakpoint_store()
22280            .as_ref()
22281            .unwrap()
22282            .read(cx)
22283            .all_source_breakpoints(cx)
22284    });
22285
22286    assert_breakpoint(
22287        &breakpoints,
22288        &abs_path,
22289        vec![
22290            (0, Breakpoint::new_standard()),
22291            (3, Breakpoint::new_log("hello Earth!!")),
22292        ],
22293    );
22294}
22295
22296/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22297/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22298/// or when breakpoints were placed out of order. This tests for a regression too
22299#[gpui::test]
22300async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22301    init_test(cx, |_| {});
22302
22303    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22304    let fs = FakeFs::new(cx.executor());
22305    fs.insert_tree(
22306        path!("/a"),
22307        json!({
22308            "main.rs": sample_text,
22309        }),
22310    )
22311    .await;
22312    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22313    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22314    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22315
22316    let fs = FakeFs::new(cx.executor());
22317    fs.insert_tree(
22318        path!("/a"),
22319        json!({
22320            "main.rs": sample_text,
22321        }),
22322    )
22323    .await;
22324    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22325    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22326    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22327    let worktree_id = workspace
22328        .update(cx, |workspace, _window, cx| {
22329            workspace.project().update(cx, |project, cx| {
22330                project.worktrees(cx).next().unwrap().read(cx).id()
22331            })
22332        })
22333        .unwrap();
22334
22335    let buffer = project
22336        .update(cx, |project, cx| {
22337            project.open_buffer((worktree_id, "main.rs"), cx)
22338        })
22339        .await
22340        .unwrap();
22341
22342    let (editor, cx) = cx.add_window_view(|window, cx| {
22343        Editor::new(
22344            EditorMode::full(),
22345            MultiBuffer::build_from_buffer(buffer, cx),
22346            Some(project.clone()),
22347            window,
22348            cx,
22349        )
22350    });
22351
22352    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22353    let abs_path = project.read_with(cx, |project, cx| {
22354        project
22355            .absolute_path(&project_path, cx)
22356            .map(Arc::from)
22357            .unwrap()
22358    });
22359
22360    // assert we can add breakpoint on the first line
22361    editor.update_in(cx, |editor, window, cx| {
22362        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22363        editor.move_to_end(&MoveToEnd, window, cx);
22364        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22365        editor.move_up(&MoveUp, window, cx);
22366        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22367    });
22368
22369    let breakpoints = editor.update(cx, |editor, cx| {
22370        editor
22371            .breakpoint_store()
22372            .as_ref()
22373            .unwrap()
22374            .read(cx)
22375            .all_source_breakpoints(cx)
22376    });
22377
22378    assert_eq!(1, breakpoints.len());
22379    assert_breakpoint(
22380        &breakpoints,
22381        &abs_path,
22382        vec![
22383            (0, Breakpoint::new_standard()),
22384            (2, Breakpoint::new_standard()),
22385            (3, Breakpoint::new_standard()),
22386        ],
22387    );
22388
22389    editor.update_in(cx, |editor, window, cx| {
22390        editor.move_to_beginning(&MoveToBeginning, window, cx);
22391        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22392        editor.move_to_end(&MoveToEnd, window, cx);
22393        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22394        // Disabling a breakpoint that doesn't exist should do nothing
22395        editor.move_up(&MoveUp, window, cx);
22396        editor.move_up(&MoveUp, window, cx);
22397        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22398    });
22399
22400    let breakpoints = editor.update(cx, |editor, cx| {
22401        editor
22402            .breakpoint_store()
22403            .as_ref()
22404            .unwrap()
22405            .read(cx)
22406            .all_source_breakpoints(cx)
22407    });
22408
22409    let disable_breakpoint = {
22410        let mut bp = Breakpoint::new_standard();
22411        bp.state = BreakpointState::Disabled;
22412        bp
22413    };
22414
22415    assert_eq!(1, breakpoints.len());
22416    assert_breakpoint(
22417        &breakpoints,
22418        &abs_path,
22419        vec![
22420            (0, disable_breakpoint.clone()),
22421            (2, Breakpoint::new_standard()),
22422            (3, disable_breakpoint.clone()),
22423        ],
22424    );
22425
22426    editor.update_in(cx, |editor, window, cx| {
22427        editor.move_to_beginning(&MoveToBeginning, window, cx);
22428        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22429        editor.move_to_end(&MoveToEnd, window, cx);
22430        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22431        editor.move_up(&MoveUp, window, cx);
22432        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22433    });
22434
22435    let breakpoints = editor.update(cx, |editor, cx| {
22436        editor
22437            .breakpoint_store()
22438            .as_ref()
22439            .unwrap()
22440            .read(cx)
22441            .all_source_breakpoints(cx)
22442    });
22443
22444    assert_eq!(1, breakpoints.len());
22445    assert_breakpoint(
22446        &breakpoints,
22447        &abs_path,
22448        vec![
22449            (0, Breakpoint::new_standard()),
22450            (2, disable_breakpoint),
22451            (3, Breakpoint::new_standard()),
22452        ],
22453    );
22454}
22455
22456#[gpui::test]
22457async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22458    init_test(cx, |_| {});
22459    let capabilities = lsp::ServerCapabilities {
22460        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22461            prepare_provider: Some(true),
22462            work_done_progress_options: Default::default(),
22463        })),
22464        ..Default::default()
22465    };
22466    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22467
22468    cx.set_state(indoc! {"
22469        struct Fˇoo {}
22470    "});
22471
22472    cx.update_editor(|editor, _, cx| {
22473        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22474        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22475        editor.highlight_background::<DocumentHighlightRead>(
22476            &[highlight_range],
22477            |theme| theme.colors().editor_document_highlight_read_background,
22478            cx,
22479        );
22480    });
22481
22482    let mut prepare_rename_handler = cx
22483        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22484            move |_, _, _| async move {
22485                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22486                    start: lsp::Position {
22487                        line: 0,
22488                        character: 7,
22489                    },
22490                    end: lsp::Position {
22491                        line: 0,
22492                        character: 10,
22493                    },
22494                })))
22495            },
22496        );
22497    let prepare_rename_task = cx
22498        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22499        .expect("Prepare rename was not started");
22500    prepare_rename_handler.next().await.unwrap();
22501    prepare_rename_task.await.expect("Prepare rename failed");
22502
22503    let mut rename_handler =
22504        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22505            let edit = lsp::TextEdit {
22506                range: lsp::Range {
22507                    start: lsp::Position {
22508                        line: 0,
22509                        character: 7,
22510                    },
22511                    end: lsp::Position {
22512                        line: 0,
22513                        character: 10,
22514                    },
22515                },
22516                new_text: "FooRenamed".to_string(),
22517            };
22518            Ok(Some(lsp::WorkspaceEdit::new(
22519                // Specify the same edit twice
22520                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
22521            )))
22522        });
22523    let rename_task = cx
22524        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22525        .expect("Confirm rename was not started");
22526    rename_handler.next().await.unwrap();
22527    rename_task.await.expect("Confirm rename failed");
22528    cx.run_until_parked();
22529
22530    // Despite two edits, only one is actually applied as those are identical
22531    cx.assert_editor_state(indoc! {"
22532        struct FooRenamedˇ {}
22533    "});
22534}
22535
22536#[gpui::test]
22537async fn test_rename_without_prepare(cx: &mut TestAppContext) {
22538    init_test(cx, |_| {});
22539    // These capabilities indicate that the server does not support prepare rename.
22540    let capabilities = lsp::ServerCapabilities {
22541        rename_provider: Some(lsp::OneOf::Left(true)),
22542        ..Default::default()
22543    };
22544    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22545
22546    cx.set_state(indoc! {"
22547        struct Fˇoo {}
22548    "});
22549
22550    cx.update_editor(|editor, _window, cx| {
22551        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22552        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22553        editor.highlight_background::<DocumentHighlightRead>(
22554            &[highlight_range],
22555            |theme| theme.colors().editor_document_highlight_read_background,
22556            cx,
22557        );
22558    });
22559
22560    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22561        .expect("Prepare rename was not started")
22562        .await
22563        .expect("Prepare rename failed");
22564
22565    let mut rename_handler =
22566        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22567            let edit = lsp::TextEdit {
22568                range: lsp::Range {
22569                    start: lsp::Position {
22570                        line: 0,
22571                        character: 7,
22572                    },
22573                    end: lsp::Position {
22574                        line: 0,
22575                        character: 10,
22576                    },
22577                },
22578                new_text: "FooRenamed".to_string(),
22579            };
22580            Ok(Some(lsp::WorkspaceEdit::new(
22581                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
22582            )))
22583        });
22584    let rename_task = cx
22585        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22586        .expect("Confirm rename was not started");
22587    rename_handler.next().await.unwrap();
22588    rename_task.await.expect("Confirm rename failed");
22589    cx.run_until_parked();
22590
22591    // Correct range is renamed, as `surrounding_word` is used to find it.
22592    cx.assert_editor_state(indoc! {"
22593        struct FooRenamedˇ {}
22594    "});
22595}
22596
22597#[gpui::test]
22598async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
22599    init_test(cx, |_| {});
22600    let mut cx = EditorTestContext::new(cx).await;
22601
22602    let language = Arc::new(
22603        Language::new(
22604            LanguageConfig::default(),
22605            Some(tree_sitter_html::LANGUAGE.into()),
22606        )
22607        .with_brackets_query(
22608            r#"
22609            ("<" @open "/>" @close)
22610            ("</" @open ">" @close)
22611            ("<" @open ">" @close)
22612            ("\"" @open "\"" @close)
22613            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
22614        "#,
22615        )
22616        .unwrap(),
22617    );
22618    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22619
22620    cx.set_state(indoc! {"
22621        <span>ˇ</span>
22622    "});
22623    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22624    cx.assert_editor_state(indoc! {"
22625        <span>
22626        ˇ
22627        </span>
22628    "});
22629
22630    cx.set_state(indoc! {"
22631        <span><span></span>ˇ</span>
22632    "});
22633    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22634    cx.assert_editor_state(indoc! {"
22635        <span><span></span>
22636        ˇ</span>
22637    "});
22638
22639    cx.set_state(indoc! {"
22640        <span>ˇ
22641        </span>
22642    "});
22643    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22644    cx.assert_editor_state(indoc! {"
22645        <span>
22646        ˇ
22647        </span>
22648    "});
22649}
22650
22651#[gpui::test(iterations = 10)]
22652async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
22653    init_test(cx, |_| {});
22654
22655    let fs = FakeFs::new(cx.executor());
22656    fs.insert_tree(
22657        path!("/dir"),
22658        json!({
22659            "a.ts": "a",
22660        }),
22661    )
22662    .await;
22663
22664    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
22665    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22666    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22667
22668    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22669    language_registry.add(Arc::new(Language::new(
22670        LanguageConfig {
22671            name: "TypeScript".into(),
22672            matcher: LanguageMatcher {
22673                path_suffixes: vec!["ts".to_string()],
22674                ..Default::default()
22675            },
22676            ..Default::default()
22677        },
22678        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
22679    )));
22680    let mut fake_language_servers = language_registry.register_fake_lsp(
22681        "TypeScript",
22682        FakeLspAdapter {
22683            capabilities: lsp::ServerCapabilities {
22684                code_lens_provider: Some(lsp::CodeLensOptions {
22685                    resolve_provider: Some(true),
22686                }),
22687                execute_command_provider: Some(lsp::ExecuteCommandOptions {
22688                    commands: vec!["_the/command".to_string()],
22689                    ..lsp::ExecuteCommandOptions::default()
22690                }),
22691                ..lsp::ServerCapabilities::default()
22692            },
22693            ..FakeLspAdapter::default()
22694        },
22695    );
22696
22697    let editor = workspace
22698        .update(cx, |workspace, window, cx| {
22699            workspace.open_abs_path(
22700                PathBuf::from(path!("/dir/a.ts")),
22701                OpenOptions::default(),
22702                window,
22703                cx,
22704            )
22705        })
22706        .unwrap()
22707        .await
22708        .unwrap()
22709        .downcast::<Editor>()
22710        .unwrap();
22711    cx.executor().run_until_parked();
22712
22713    let fake_server = fake_language_servers.next().await.unwrap();
22714
22715    let buffer = editor.update(cx, |editor, cx| {
22716        editor
22717            .buffer()
22718            .read(cx)
22719            .as_singleton()
22720            .expect("have opened a single file by path")
22721    });
22722
22723    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
22724    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
22725    drop(buffer_snapshot);
22726    let actions = cx
22727        .update_window(*workspace, |_, window, cx| {
22728            project.code_actions(&buffer, anchor..anchor, window, cx)
22729        })
22730        .unwrap();
22731
22732    fake_server
22733        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
22734            Ok(Some(vec![
22735                lsp::CodeLens {
22736                    range: lsp::Range::default(),
22737                    command: Some(lsp::Command {
22738                        title: "Code lens command".to_owned(),
22739                        command: "_the/command".to_owned(),
22740                        arguments: None,
22741                    }),
22742                    data: None,
22743                },
22744                lsp::CodeLens {
22745                    range: lsp::Range::default(),
22746                    command: Some(lsp::Command {
22747                        title: "Command not in capabilities".to_owned(),
22748                        command: "not in capabilities".to_owned(),
22749                        arguments: None,
22750                    }),
22751                    data: None,
22752                },
22753                lsp::CodeLens {
22754                    range: lsp::Range {
22755                        start: lsp::Position {
22756                            line: 1,
22757                            character: 1,
22758                        },
22759                        end: lsp::Position {
22760                            line: 1,
22761                            character: 1,
22762                        },
22763                    },
22764                    command: Some(lsp::Command {
22765                        title: "Command not in range".to_owned(),
22766                        command: "_the/command".to_owned(),
22767                        arguments: None,
22768                    }),
22769                    data: None,
22770                },
22771            ]))
22772        })
22773        .next()
22774        .await;
22775
22776    let actions = actions.await.unwrap();
22777    assert_eq!(
22778        actions.len(),
22779        1,
22780        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
22781    );
22782    let action = actions[0].clone();
22783    let apply = project.update(cx, |project, cx| {
22784        project.apply_code_action(buffer.clone(), action, true, cx)
22785    });
22786
22787    // Resolving the code action does not populate its edits. In absence of
22788    // edits, we must execute the given command.
22789    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
22790        |mut lens, _| async move {
22791            let lens_command = lens.command.as_mut().expect("should have a command");
22792            assert_eq!(lens_command.title, "Code lens command");
22793            lens_command.arguments = Some(vec![json!("the-argument")]);
22794            Ok(lens)
22795        },
22796    );
22797
22798    // While executing the command, the language server sends the editor
22799    // a `workspaceEdit` request.
22800    fake_server
22801        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
22802            let fake = fake_server.clone();
22803            move |params, _| {
22804                assert_eq!(params.command, "_the/command");
22805                let fake = fake.clone();
22806                async move {
22807                    fake.server
22808                        .request::<lsp::request::ApplyWorkspaceEdit>(
22809                            lsp::ApplyWorkspaceEditParams {
22810                                label: None,
22811                                edit: lsp::WorkspaceEdit {
22812                                    changes: Some(
22813                                        [(
22814                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
22815                                            vec![lsp::TextEdit {
22816                                                range: lsp::Range::new(
22817                                                    lsp::Position::new(0, 0),
22818                                                    lsp::Position::new(0, 0),
22819                                                ),
22820                                                new_text: "X".into(),
22821                                            }],
22822                                        )]
22823                                        .into_iter()
22824                                        .collect(),
22825                                    ),
22826                                    ..lsp::WorkspaceEdit::default()
22827                                },
22828                            },
22829                        )
22830                        .await
22831                        .into_response()
22832                        .unwrap();
22833                    Ok(Some(json!(null)))
22834                }
22835            }
22836        })
22837        .next()
22838        .await;
22839
22840    // Applying the code lens command returns a project transaction containing the edits
22841    // sent by the language server in its `workspaceEdit` request.
22842    let transaction = apply.await.unwrap();
22843    assert!(transaction.0.contains_key(&buffer));
22844    buffer.update(cx, |buffer, cx| {
22845        assert_eq!(buffer.text(), "Xa");
22846        buffer.undo(cx);
22847        assert_eq!(buffer.text(), "a");
22848    });
22849
22850    let actions_after_edits = cx
22851        .update_window(*workspace, |_, window, cx| {
22852            project.code_actions(&buffer, anchor..anchor, window, cx)
22853        })
22854        .unwrap()
22855        .await
22856        .unwrap();
22857    assert_eq!(
22858        actions, actions_after_edits,
22859        "For the same selection, same code lens actions should be returned"
22860    );
22861
22862    let _responses =
22863        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
22864            panic!("No more code lens requests are expected");
22865        });
22866    editor.update_in(cx, |editor, window, cx| {
22867        editor.select_all(&SelectAll, window, cx);
22868    });
22869    cx.executor().run_until_parked();
22870    let new_actions = cx
22871        .update_window(*workspace, |_, window, cx| {
22872            project.code_actions(&buffer, anchor..anchor, window, cx)
22873        })
22874        .unwrap()
22875        .await
22876        .unwrap();
22877    assert_eq!(
22878        actions, new_actions,
22879        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
22880    );
22881}
22882
22883#[gpui::test]
22884async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
22885    init_test(cx, |_| {});
22886
22887    let fs = FakeFs::new(cx.executor());
22888    let main_text = r#"fn main() {
22889println!("1");
22890println!("2");
22891println!("3");
22892println!("4");
22893println!("5");
22894}"#;
22895    let lib_text = "mod foo {}";
22896    fs.insert_tree(
22897        path!("/a"),
22898        json!({
22899            "lib.rs": lib_text,
22900            "main.rs": main_text,
22901        }),
22902    )
22903    .await;
22904
22905    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22906    let (workspace, cx) =
22907        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22908    let worktree_id = workspace.update(cx, |workspace, cx| {
22909        workspace.project().update(cx, |project, cx| {
22910            project.worktrees(cx).next().unwrap().read(cx).id()
22911        })
22912    });
22913
22914    let expected_ranges = vec![
22915        Point::new(0, 0)..Point::new(0, 0),
22916        Point::new(1, 0)..Point::new(1, 1),
22917        Point::new(2, 0)..Point::new(2, 2),
22918        Point::new(3, 0)..Point::new(3, 3),
22919    ];
22920
22921    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22922    let editor_1 = workspace
22923        .update_in(cx, |workspace, window, cx| {
22924            workspace.open_path(
22925                (worktree_id, "main.rs"),
22926                Some(pane_1.downgrade()),
22927                true,
22928                window,
22929                cx,
22930            )
22931        })
22932        .unwrap()
22933        .await
22934        .downcast::<Editor>()
22935        .unwrap();
22936    pane_1.update(cx, |pane, cx| {
22937        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22938        open_editor.update(cx, |editor, cx| {
22939            assert_eq!(
22940                editor.display_text(cx),
22941                main_text,
22942                "Original main.rs text on initial open",
22943            );
22944            assert_eq!(
22945                editor
22946                    .selections
22947                    .all::<Point>(cx)
22948                    .into_iter()
22949                    .map(|s| s.range())
22950                    .collect::<Vec<_>>(),
22951                vec![Point::zero()..Point::zero()],
22952                "Default selections on initial open",
22953            );
22954        })
22955    });
22956    editor_1.update_in(cx, |editor, window, cx| {
22957        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22958            s.select_ranges(expected_ranges.clone());
22959        });
22960    });
22961
22962    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
22963        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
22964    });
22965    let editor_2 = workspace
22966        .update_in(cx, |workspace, window, cx| {
22967            workspace.open_path(
22968                (worktree_id, "main.rs"),
22969                Some(pane_2.downgrade()),
22970                true,
22971                window,
22972                cx,
22973            )
22974        })
22975        .unwrap()
22976        .await
22977        .downcast::<Editor>()
22978        .unwrap();
22979    pane_2.update(cx, |pane, cx| {
22980        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22981        open_editor.update(cx, |editor, cx| {
22982            assert_eq!(
22983                editor.display_text(cx),
22984                main_text,
22985                "Original main.rs text on initial open in another panel",
22986            );
22987            assert_eq!(
22988                editor
22989                    .selections
22990                    .all::<Point>(cx)
22991                    .into_iter()
22992                    .map(|s| s.range())
22993                    .collect::<Vec<_>>(),
22994                vec![Point::zero()..Point::zero()],
22995                "Default selections on initial open in another panel",
22996            );
22997        })
22998    });
22999
23000    editor_2.update_in(cx, |editor, window, cx| {
23001        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23002    });
23003
23004    let _other_editor_1 = workspace
23005        .update_in(cx, |workspace, window, cx| {
23006            workspace.open_path(
23007                (worktree_id, "lib.rs"),
23008                Some(pane_1.downgrade()),
23009                true,
23010                window,
23011                cx,
23012            )
23013        })
23014        .unwrap()
23015        .await
23016        .downcast::<Editor>()
23017        .unwrap();
23018    pane_1
23019        .update_in(cx, |pane, window, cx| {
23020            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23021        })
23022        .await
23023        .unwrap();
23024    drop(editor_1);
23025    pane_1.update(cx, |pane, cx| {
23026        pane.active_item()
23027            .unwrap()
23028            .downcast::<Editor>()
23029            .unwrap()
23030            .update(cx, |editor, cx| {
23031                assert_eq!(
23032                    editor.display_text(cx),
23033                    lib_text,
23034                    "Other file should be open and active",
23035                );
23036            });
23037        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23038    });
23039
23040    let _other_editor_2 = workspace
23041        .update_in(cx, |workspace, window, cx| {
23042            workspace.open_path(
23043                (worktree_id, "lib.rs"),
23044                Some(pane_2.downgrade()),
23045                true,
23046                window,
23047                cx,
23048            )
23049        })
23050        .unwrap()
23051        .await
23052        .downcast::<Editor>()
23053        .unwrap();
23054    pane_2
23055        .update_in(cx, |pane, window, cx| {
23056            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23057        })
23058        .await
23059        .unwrap();
23060    drop(editor_2);
23061    pane_2.update(cx, |pane, cx| {
23062        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23063        open_editor.update(cx, |editor, cx| {
23064            assert_eq!(
23065                editor.display_text(cx),
23066                lib_text,
23067                "Other file should be open and active in another panel too",
23068            );
23069        });
23070        assert_eq!(
23071            pane.items().count(),
23072            1,
23073            "No other editors should be open in another pane",
23074        );
23075    });
23076
23077    let _editor_1_reopened = workspace
23078        .update_in(cx, |workspace, window, cx| {
23079            workspace.open_path(
23080                (worktree_id, "main.rs"),
23081                Some(pane_1.downgrade()),
23082                true,
23083                window,
23084                cx,
23085            )
23086        })
23087        .unwrap()
23088        .await
23089        .downcast::<Editor>()
23090        .unwrap();
23091    let _editor_2_reopened = workspace
23092        .update_in(cx, |workspace, window, cx| {
23093            workspace.open_path(
23094                (worktree_id, "main.rs"),
23095                Some(pane_2.downgrade()),
23096                true,
23097                window,
23098                cx,
23099            )
23100        })
23101        .unwrap()
23102        .await
23103        .downcast::<Editor>()
23104        .unwrap();
23105    pane_1.update(cx, |pane, cx| {
23106        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23107        open_editor.update(cx, |editor, cx| {
23108            assert_eq!(
23109                editor.display_text(cx),
23110                main_text,
23111                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23112            );
23113            assert_eq!(
23114                editor
23115                    .selections
23116                    .all::<Point>(cx)
23117                    .into_iter()
23118                    .map(|s| s.range())
23119                    .collect::<Vec<_>>(),
23120                expected_ranges,
23121                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23122            );
23123        })
23124    });
23125    pane_2.update(cx, |pane, cx| {
23126        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23127        open_editor.update(cx, |editor, cx| {
23128            assert_eq!(
23129                editor.display_text(cx),
23130                r#"fn main() {
23131⋯rintln!("1");
23132⋯intln!("2");
23133⋯ntln!("3");
23134println!("4");
23135println!("5");
23136}"#,
23137                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23138            );
23139            assert_eq!(
23140                editor
23141                    .selections
23142                    .all::<Point>(cx)
23143                    .into_iter()
23144                    .map(|s| s.range())
23145                    .collect::<Vec<_>>(),
23146                vec![Point::zero()..Point::zero()],
23147                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23148            );
23149        })
23150    });
23151}
23152
23153#[gpui::test]
23154async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23155    init_test(cx, |_| {});
23156
23157    let fs = FakeFs::new(cx.executor());
23158    let main_text = r#"fn main() {
23159println!("1");
23160println!("2");
23161println!("3");
23162println!("4");
23163println!("5");
23164}"#;
23165    let lib_text = "mod foo {}";
23166    fs.insert_tree(
23167        path!("/a"),
23168        json!({
23169            "lib.rs": lib_text,
23170            "main.rs": main_text,
23171        }),
23172    )
23173    .await;
23174
23175    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23176    let (workspace, cx) =
23177        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23178    let worktree_id = workspace.update(cx, |workspace, cx| {
23179        workspace.project().update(cx, |project, cx| {
23180            project.worktrees(cx).next().unwrap().read(cx).id()
23181        })
23182    });
23183
23184    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23185    let editor = workspace
23186        .update_in(cx, |workspace, window, cx| {
23187            workspace.open_path(
23188                (worktree_id, "main.rs"),
23189                Some(pane.downgrade()),
23190                true,
23191                window,
23192                cx,
23193            )
23194        })
23195        .unwrap()
23196        .await
23197        .downcast::<Editor>()
23198        .unwrap();
23199    pane.update(cx, |pane, cx| {
23200        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23201        open_editor.update(cx, |editor, cx| {
23202            assert_eq!(
23203                editor.display_text(cx),
23204                main_text,
23205                "Original main.rs text on initial open",
23206            );
23207        })
23208    });
23209    editor.update_in(cx, |editor, window, cx| {
23210        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23211    });
23212
23213    cx.update_global(|store: &mut SettingsStore, cx| {
23214        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23215            s.restore_on_file_reopen = Some(false);
23216        });
23217    });
23218    editor.update_in(cx, |editor, window, cx| {
23219        editor.fold_ranges(
23220            vec![
23221                Point::new(1, 0)..Point::new(1, 1),
23222                Point::new(2, 0)..Point::new(2, 2),
23223                Point::new(3, 0)..Point::new(3, 3),
23224            ],
23225            false,
23226            window,
23227            cx,
23228        );
23229    });
23230    pane.update_in(cx, |pane, window, cx| {
23231        pane.close_all_items(&CloseAllItems::default(), window, cx)
23232    })
23233    .await
23234    .unwrap();
23235    pane.update(cx, |pane, _| {
23236        assert!(pane.active_item().is_none());
23237    });
23238    cx.update_global(|store: &mut SettingsStore, cx| {
23239        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23240            s.restore_on_file_reopen = Some(true);
23241        });
23242    });
23243
23244    let _editor_reopened = workspace
23245        .update_in(cx, |workspace, window, cx| {
23246            workspace.open_path(
23247                (worktree_id, "main.rs"),
23248                Some(pane.downgrade()),
23249                true,
23250                window,
23251                cx,
23252            )
23253        })
23254        .unwrap()
23255        .await
23256        .downcast::<Editor>()
23257        .unwrap();
23258    pane.update(cx, |pane, cx| {
23259        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23260        open_editor.update(cx, |editor, cx| {
23261            assert_eq!(
23262                editor.display_text(cx),
23263                main_text,
23264                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23265            );
23266        })
23267    });
23268}
23269
23270#[gpui::test]
23271async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23272    struct EmptyModalView {
23273        focus_handle: gpui::FocusHandle,
23274    }
23275    impl EventEmitter<DismissEvent> for EmptyModalView {}
23276    impl Render for EmptyModalView {
23277        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23278            div()
23279        }
23280    }
23281    impl Focusable for EmptyModalView {
23282        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23283            self.focus_handle.clone()
23284        }
23285    }
23286    impl workspace::ModalView for EmptyModalView {}
23287    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23288        EmptyModalView {
23289            focus_handle: cx.focus_handle(),
23290        }
23291    }
23292
23293    init_test(cx, |_| {});
23294
23295    let fs = FakeFs::new(cx.executor());
23296    let project = Project::test(fs, [], cx).await;
23297    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23298    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23299    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23300    let editor = cx.new_window_entity(|window, cx| {
23301        Editor::new(
23302            EditorMode::full(),
23303            buffer,
23304            Some(project.clone()),
23305            window,
23306            cx,
23307        )
23308    });
23309    workspace
23310        .update(cx, |workspace, window, cx| {
23311            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23312        })
23313        .unwrap();
23314    editor.update_in(cx, |editor, window, cx| {
23315        editor.open_context_menu(&OpenContextMenu, window, cx);
23316        assert!(editor.mouse_context_menu.is_some());
23317    });
23318    workspace
23319        .update(cx, |workspace, window, cx| {
23320            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23321        })
23322        .unwrap();
23323    cx.read(|cx| {
23324        assert!(editor.read(cx).mouse_context_menu.is_none());
23325    });
23326}
23327
23328#[gpui::test]
23329async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23330    init_test(cx, |_| {});
23331
23332    let fs = FakeFs::new(cx.executor());
23333    fs.insert_file(path!("/file.html"), Default::default())
23334        .await;
23335
23336    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23337
23338    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23339    let html_language = Arc::new(Language::new(
23340        LanguageConfig {
23341            name: "HTML".into(),
23342            matcher: LanguageMatcher {
23343                path_suffixes: vec!["html".to_string()],
23344                ..LanguageMatcher::default()
23345            },
23346            brackets: BracketPairConfig {
23347                pairs: vec![BracketPair {
23348                    start: "<".into(),
23349                    end: ">".into(),
23350                    close: true,
23351                    ..Default::default()
23352                }],
23353                ..Default::default()
23354            },
23355            ..Default::default()
23356        },
23357        Some(tree_sitter_html::LANGUAGE.into()),
23358    ));
23359    language_registry.add(html_language);
23360    let mut fake_servers = language_registry.register_fake_lsp(
23361        "HTML",
23362        FakeLspAdapter {
23363            capabilities: lsp::ServerCapabilities {
23364                completion_provider: Some(lsp::CompletionOptions {
23365                    resolve_provider: Some(true),
23366                    ..Default::default()
23367                }),
23368                ..Default::default()
23369            },
23370            ..Default::default()
23371        },
23372    );
23373
23374    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23375    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23376
23377    let worktree_id = workspace
23378        .update(cx, |workspace, _window, cx| {
23379            workspace.project().update(cx, |project, cx| {
23380                project.worktrees(cx).next().unwrap().read(cx).id()
23381            })
23382        })
23383        .unwrap();
23384    project
23385        .update(cx, |project, cx| {
23386            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23387        })
23388        .await
23389        .unwrap();
23390    let editor = workspace
23391        .update(cx, |workspace, window, cx| {
23392            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23393        })
23394        .unwrap()
23395        .await
23396        .unwrap()
23397        .downcast::<Editor>()
23398        .unwrap();
23399
23400    let fake_server = fake_servers.next().await.unwrap();
23401    editor.update_in(cx, |editor, window, cx| {
23402        editor.set_text("<ad></ad>", window, cx);
23403        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23404            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23405        });
23406        let Some((buffer, _)) = editor
23407            .buffer
23408            .read(cx)
23409            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23410        else {
23411            panic!("Failed to get buffer for selection position");
23412        };
23413        let buffer = buffer.read(cx);
23414        let buffer_id = buffer.remote_id();
23415        let opening_range =
23416            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
23417        let closing_range =
23418            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
23419        let mut linked_ranges = HashMap::default();
23420        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23421        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23422    });
23423    let mut completion_handle =
23424        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23425            Ok(Some(lsp::CompletionResponse::Array(vec![
23426                lsp::CompletionItem {
23427                    label: "head".to_string(),
23428                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23429                        lsp::InsertReplaceEdit {
23430                            new_text: "head".to_string(),
23431                            insert: lsp::Range::new(
23432                                lsp::Position::new(0, 1),
23433                                lsp::Position::new(0, 3),
23434                            ),
23435                            replace: lsp::Range::new(
23436                                lsp::Position::new(0, 1),
23437                                lsp::Position::new(0, 3),
23438                            ),
23439                        },
23440                    )),
23441                    ..Default::default()
23442                },
23443            ])))
23444        });
23445    editor.update_in(cx, |editor, window, cx| {
23446        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23447    });
23448    cx.run_until_parked();
23449    completion_handle.next().await.unwrap();
23450    editor.update(cx, |editor, _| {
23451        assert!(
23452            editor.context_menu_visible(),
23453            "Completion menu should be visible"
23454        );
23455    });
23456    editor.update_in(cx, |editor, window, cx| {
23457        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23458    });
23459    cx.executor().run_until_parked();
23460    editor.update(cx, |editor, cx| {
23461        assert_eq!(editor.text(cx), "<head></head>");
23462    });
23463}
23464
23465#[gpui::test]
23466async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
23467    init_test(cx, |_| {});
23468
23469    let fs = FakeFs::new(cx.executor());
23470    fs.insert_tree(
23471        path!("/root"),
23472        json!({
23473            "a": {
23474                "main.rs": "fn main() {}",
23475            },
23476            "foo": {
23477                "bar": {
23478                    "external_file.rs": "pub mod external {}",
23479                }
23480            }
23481        }),
23482    )
23483    .await;
23484
23485    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
23486    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23487    language_registry.add(rust_lang());
23488    let _fake_servers = language_registry.register_fake_lsp(
23489        "Rust",
23490        FakeLspAdapter {
23491            ..FakeLspAdapter::default()
23492        },
23493    );
23494    let (workspace, cx) =
23495        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23496    let worktree_id = workspace.update(cx, |workspace, cx| {
23497        workspace.project().update(cx, |project, cx| {
23498            project.worktrees(cx).next().unwrap().read(cx).id()
23499        })
23500    });
23501
23502    let assert_language_servers_count =
23503        |expected: usize, context: &str, cx: &mut VisualTestContext| {
23504            project.update(cx, |project, cx| {
23505                let current = project
23506                    .lsp_store()
23507                    .read(cx)
23508                    .as_local()
23509                    .unwrap()
23510                    .language_servers
23511                    .len();
23512                assert_eq!(expected, current, "{context}");
23513            });
23514        };
23515
23516    assert_language_servers_count(
23517        0,
23518        "No servers should be running before any file is open",
23519        cx,
23520    );
23521    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23522    let main_editor = workspace
23523        .update_in(cx, |workspace, window, cx| {
23524            workspace.open_path(
23525                (worktree_id, "main.rs"),
23526                Some(pane.downgrade()),
23527                true,
23528                window,
23529                cx,
23530            )
23531        })
23532        .unwrap()
23533        .await
23534        .downcast::<Editor>()
23535        .unwrap();
23536    pane.update(cx, |pane, cx| {
23537        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23538        open_editor.update(cx, |editor, cx| {
23539            assert_eq!(
23540                editor.display_text(cx),
23541                "fn main() {}",
23542                "Original main.rs text on initial open",
23543            );
23544        });
23545        assert_eq!(open_editor, main_editor);
23546    });
23547    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
23548
23549    let external_editor = workspace
23550        .update_in(cx, |workspace, window, cx| {
23551            workspace.open_abs_path(
23552                PathBuf::from("/root/foo/bar/external_file.rs"),
23553                OpenOptions::default(),
23554                window,
23555                cx,
23556            )
23557        })
23558        .await
23559        .expect("opening external file")
23560        .downcast::<Editor>()
23561        .expect("downcasted external file's open element to editor");
23562    pane.update(cx, |pane, cx| {
23563        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23564        open_editor.update(cx, |editor, cx| {
23565            assert_eq!(
23566                editor.display_text(cx),
23567                "pub mod external {}",
23568                "External file is open now",
23569            );
23570        });
23571        assert_eq!(open_editor, external_editor);
23572    });
23573    assert_language_servers_count(
23574        1,
23575        "Second, external, *.rs file should join the existing server",
23576        cx,
23577    );
23578
23579    pane.update_in(cx, |pane, window, cx| {
23580        pane.close_active_item(&CloseActiveItem::default(), window, cx)
23581    })
23582    .await
23583    .unwrap();
23584    pane.update_in(cx, |pane, window, cx| {
23585        pane.navigate_backward(&Default::default(), window, cx);
23586    });
23587    cx.run_until_parked();
23588    pane.update(cx, |pane, cx| {
23589        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23590        open_editor.update(cx, |editor, cx| {
23591            assert_eq!(
23592                editor.display_text(cx),
23593                "pub mod external {}",
23594                "External file is open now",
23595            );
23596        });
23597    });
23598    assert_language_servers_count(
23599        1,
23600        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
23601        cx,
23602    );
23603
23604    cx.update(|_, cx| {
23605        workspace::reload(cx);
23606    });
23607    assert_language_servers_count(
23608        1,
23609        "After reloading the worktree with local and external files opened, only one project should be started",
23610        cx,
23611    );
23612}
23613
23614#[gpui::test]
23615async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
23616    init_test(cx, |_| {});
23617
23618    let mut cx = EditorTestContext::new(cx).await;
23619    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23620    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23621
23622    // test cursor move to start of each line on tab
23623    // for `if`, `elif`, `else`, `while`, `with` and `for`
23624    cx.set_state(indoc! {"
23625        def main():
23626        ˇ    for item in items:
23627        ˇ        while item.active:
23628        ˇ            if item.value > 10:
23629        ˇ                continue
23630        ˇ            elif item.value < 0:
23631        ˇ                break
23632        ˇ            else:
23633        ˇ                with item.context() as ctx:
23634        ˇ                    yield count
23635        ˇ        else:
23636        ˇ            log('while else')
23637        ˇ    else:
23638        ˇ        log('for else')
23639    "});
23640    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23641    cx.assert_editor_state(indoc! {"
23642        def main():
23643            ˇfor item in items:
23644                ˇwhile item.active:
23645                    ˇif item.value > 10:
23646                        ˇcontinue
23647                    ˇelif item.value < 0:
23648                        ˇbreak
23649                    ˇelse:
23650                        ˇwith item.context() as ctx:
23651                            ˇyield count
23652                ˇelse:
23653                    ˇlog('while else')
23654            ˇelse:
23655                ˇlog('for else')
23656    "});
23657    // test relative indent is preserved when tab
23658    // for `if`, `elif`, `else`, `while`, `with` and `for`
23659    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23660    cx.assert_editor_state(indoc! {"
23661        def main():
23662                ˇfor item in items:
23663                    ˇwhile item.active:
23664                        ˇif item.value > 10:
23665                            ˇcontinue
23666                        ˇelif item.value < 0:
23667                            ˇbreak
23668                        ˇelse:
23669                            ˇwith item.context() as ctx:
23670                                ˇyield count
23671                    ˇelse:
23672                        ˇlog('while else')
23673                ˇelse:
23674                    ˇlog('for else')
23675    "});
23676
23677    // test cursor move to start of each line on tab
23678    // for `try`, `except`, `else`, `finally`, `match` and `def`
23679    cx.set_state(indoc! {"
23680        def main():
23681        ˇ    try:
23682        ˇ        fetch()
23683        ˇ    except ValueError:
23684        ˇ        handle_error()
23685        ˇ    else:
23686        ˇ        match value:
23687        ˇ            case _:
23688        ˇ    finally:
23689        ˇ        def status():
23690        ˇ            return 0
23691    "});
23692    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23693    cx.assert_editor_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    // test relative indent is preserved when tab
23707    // for `try`, `except`, `else`, `finally`, `match` and `def`
23708    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23709    cx.assert_editor_state(indoc! {"
23710        def main():
23711                ˇtry:
23712                    ˇfetch()
23713                ˇexcept ValueError:
23714                    ˇhandle_error()
23715                ˇelse:
23716                    ˇmatch value:
23717                        ˇcase _:
23718                ˇfinally:
23719                    ˇdef status():
23720                        ˇreturn 0
23721    "});
23722}
23723
23724#[gpui::test]
23725async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
23726    init_test(cx, |_| {});
23727
23728    let mut cx = EditorTestContext::new(cx).await;
23729    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23730    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23731
23732    // test `else` auto outdents when typed inside `if` block
23733    cx.set_state(indoc! {"
23734        def main():
23735            if i == 2:
23736                return
23737                ˇ
23738    "});
23739    cx.update_editor(|editor, window, cx| {
23740        editor.handle_input("else:", window, cx);
23741    });
23742    cx.assert_editor_state(indoc! {"
23743        def main():
23744            if i == 2:
23745                return
23746            else:ˇ
23747    "});
23748
23749    // test `except` auto outdents when typed inside `try` block
23750    cx.set_state(indoc! {"
23751        def main():
23752            try:
23753                i = 2
23754                ˇ
23755    "});
23756    cx.update_editor(|editor, window, cx| {
23757        editor.handle_input("except:", window, cx);
23758    });
23759    cx.assert_editor_state(indoc! {"
23760        def main():
23761            try:
23762                i = 2
23763            except:ˇ
23764    "});
23765
23766    // test `else` auto outdents when typed inside `except` block
23767    cx.set_state(indoc! {"
23768        def main():
23769            try:
23770                i = 2
23771            except:
23772                j = 2
23773                ˇ
23774    "});
23775    cx.update_editor(|editor, window, cx| {
23776        editor.handle_input("else:", window, cx);
23777    });
23778    cx.assert_editor_state(indoc! {"
23779        def main():
23780            try:
23781                i = 2
23782            except:
23783                j = 2
23784            else:ˇ
23785    "});
23786
23787    // test `finally` auto outdents when typed inside `else` block
23788    cx.set_state(indoc! {"
23789        def main():
23790            try:
23791                i = 2
23792            except:
23793                j = 2
23794            else:
23795                k = 2
23796                ˇ
23797    "});
23798    cx.update_editor(|editor, window, cx| {
23799        editor.handle_input("finally:", window, cx);
23800    });
23801    cx.assert_editor_state(indoc! {"
23802        def main():
23803            try:
23804                i = 2
23805            except:
23806                j = 2
23807            else:
23808                k = 2
23809            finally:ˇ
23810    "});
23811
23812    // test `else` does not outdents when typed inside `except` block right after for block
23813    cx.set_state(indoc! {"
23814        def main():
23815            try:
23816                i = 2
23817            except:
23818                for i in range(n):
23819                    pass
23820                ˇ
23821    "});
23822    cx.update_editor(|editor, window, cx| {
23823        editor.handle_input("else:", window, cx);
23824    });
23825    cx.assert_editor_state(indoc! {"
23826        def main():
23827            try:
23828                i = 2
23829            except:
23830                for i in range(n):
23831                    pass
23832                else:ˇ
23833    "});
23834
23835    // test `finally` auto outdents when typed inside `else` block right after for block
23836    cx.set_state(indoc! {"
23837        def main():
23838            try:
23839                i = 2
23840            except:
23841                j = 2
23842            else:
23843                for i in range(n):
23844                    pass
23845                ˇ
23846    "});
23847    cx.update_editor(|editor, window, cx| {
23848        editor.handle_input("finally:", window, cx);
23849    });
23850    cx.assert_editor_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            finally:ˇ
23860    "});
23861
23862    // test `except` outdents to inner "try" block
23863    cx.set_state(indoc! {"
23864        def main():
23865            try:
23866                i = 2
23867                if i == 2:
23868                    try:
23869                        i = 3
23870                        ˇ
23871    "});
23872    cx.update_editor(|editor, window, cx| {
23873        editor.handle_input("except:", window, cx);
23874    });
23875    cx.assert_editor_state(indoc! {"
23876        def main():
23877            try:
23878                i = 2
23879                if i == 2:
23880                    try:
23881                        i = 3
23882                    except:ˇ
23883    "});
23884
23885    // test `except` outdents to outer "try" block
23886    cx.set_state(indoc! {"
23887        def main():
23888            try:
23889                i = 2
23890                if i == 2:
23891                    try:
23892                        i = 3
23893                ˇ
23894    "});
23895    cx.update_editor(|editor, window, cx| {
23896        editor.handle_input("except:", window, cx);
23897    });
23898    cx.assert_editor_state(indoc! {"
23899        def main():
23900            try:
23901                i = 2
23902                if i == 2:
23903                    try:
23904                        i = 3
23905            except:ˇ
23906    "});
23907
23908    // test `else` stays at correct indent when typed after `for` block
23909    cx.set_state(indoc! {"
23910        def main():
23911            for i in range(10):
23912                if i == 3:
23913                    break
23914            ˇ
23915    "});
23916    cx.update_editor(|editor, window, cx| {
23917        editor.handle_input("else:", window, cx);
23918    });
23919    cx.assert_editor_state(indoc! {"
23920        def main():
23921            for i in range(10):
23922                if i == 3:
23923                    break
23924            else:ˇ
23925    "});
23926
23927    // test does not outdent on typing after line with square brackets
23928    cx.set_state(indoc! {"
23929        def f() -> list[str]:
23930            ˇ
23931    "});
23932    cx.update_editor(|editor, window, cx| {
23933        editor.handle_input("a", window, cx);
23934    });
23935    cx.assert_editor_state(indoc! {"
23936        def f() -> list[str]:
2393723938    "});
23939
23940    // test does not outdent on typing : after case keyword
23941    cx.set_state(indoc! {"
23942        match 1:
23943            caseˇ
23944    "});
23945    cx.update_editor(|editor, window, cx| {
23946        editor.handle_input(":", window, cx);
23947    });
23948    cx.assert_editor_state(indoc! {"
23949        match 1:
23950            case:ˇ
23951    "});
23952}
23953
23954#[gpui::test]
23955async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
23956    init_test(cx, |_| {});
23957    update_test_language_settings(cx, |settings| {
23958        settings.defaults.extend_comment_on_newline = Some(false);
23959    });
23960    let mut cx = EditorTestContext::new(cx).await;
23961    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23962    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23963
23964    // test correct indent after newline on comment
23965    cx.set_state(indoc! {"
23966        # COMMENT:ˇ
23967    "});
23968    cx.update_editor(|editor, window, cx| {
23969        editor.newline(&Newline, window, cx);
23970    });
23971    cx.assert_editor_state(indoc! {"
23972        # COMMENT:
23973        ˇ
23974    "});
23975
23976    // test correct indent after newline in brackets
23977    cx.set_state(indoc! {"
23978        {ˇ}
23979    "});
23980    cx.update_editor(|editor, window, cx| {
23981        editor.newline(&Newline, window, cx);
23982    });
23983    cx.run_until_parked();
23984    cx.assert_editor_state(indoc! {"
23985        {
23986            ˇ
23987        }
23988    "});
23989
23990    cx.set_state(indoc! {"
23991        (ˇ)
23992    "});
23993    cx.update_editor(|editor, window, cx| {
23994        editor.newline(&Newline, window, cx);
23995    });
23996    cx.run_until_parked();
23997    cx.assert_editor_state(indoc! {"
23998        (
23999            ˇ
24000        )
24001    "});
24002
24003    // do not indent after empty lists or dictionaries
24004    cx.set_state(indoc! {"
24005        a = []ˇ
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        a = []
24013        ˇ
24014    "});
24015}
24016
24017#[gpui::test]
24018async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24019    init_test(cx, |_| {});
24020
24021    let mut cx = EditorTestContext::new(cx).await;
24022    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24023    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24024
24025    // test cursor move to start of each line on tab
24026    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24027    cx.set_state(indoc! {"
24028        function main() {
24029        ˇ    for item in $items; do
24030        ˇ        while [ -n \"$item\" ]; do
24031        ˇ            if [ \"$value\" -gt 10 ]; then
24032        ˇ                continue
24033        ˇ            elif [ \"$value\" -lt 0 ]; then
24034        ˇ                break
24035        ˇ            else
24036        ˇ                echo \"$item\"
24037        ˇ            fi
24038        ˇ        done
24039        ˇ    done
24040        ˇ}
24041    "});
24042    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24043    cx.assert_editor_state(indoc! {"
24044        function main() {
24045            ˇfor item in $items; do
24046                ˇwhile [ -n \"$item\" ]; do
24047                    ˇif [ \"$value\" -gt 10 ]; then
24048                        ˇcontinue
24049                    ˇelif [ \"$value\" -lt 0 ]; then
24050                        ˇbreak
24051                    ˇelse
24052                        ˇecho \"$item\"
24053                    ˇfi
24054                ˇdone
24055            ˇdone
24056        ˇ}
24057    "});
24058    // test relative indent is preserved when tab
24059    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24060    cx.assert_editor_state(indoc! {"
24061        function main() {
24062                ˇfor item in $items; do
24063                    ˇwhile [ -n \"$item\" ]; do
24064                        ˇif [ \"$value\" -gt 10 ]; then
24065                            ˇcontinue
24066                        ˇelif [ \"$value\" -lt 0 ]; then
24067                            ˇbreak
24068                        ˇelse
24069                            ˇecho \"$item\"
24070                        ˇfi
24071                    ˇdone
24072                ˇdone
24073            ˇ}
24074    "});
24075
24076    // test cursor move to start of each line on tab
24077    // for `case` statement with patterns
24078    cx.set_state(indoc! {"
24079        function handle() {
24080        ˇ    case \"$1\" in
24081        ˇ        start)
24082        ˇ            echo \"a\"
24083        ˇ            ;;
24084        ˇ        stop)
24085        ˇ            echo \"b\"
24086        ˇ            ;;
24087        ˇ        *)
24088        ˇ            echo \"c\"
24089        ˇ            ;;
24090        ˇ    esac
24091        ˇ}
24092    "});
24093    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24094    cx.assert_editor_state(indoc! {"
24095        function handle() {
24096            ˇcase \"$1\" in
24097                ˇstart)
24098                    ˇecho \"a\"
24099                    ˇ;;
24100                ˇstop)
24101                    ˇecho \"b\"
24102                    ˇ;;
24103                ˇ*)
24104                    ˇecho \"c\"
24105                    ˇ;;
24106            ˇesac
24107        ˇ}
24108    "});
24109}
24110
24111#[gpui::test]
24112async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24113    init_test(cx, |_| {});
24114
24115    let mut cx = EditorTestContext::new(cx).await;
24116    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24117    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24118
24119    // test indents on comment insert
24120    cx.set_state(indoc! {"
24121        function main() {
24122        ˇ    for item in $items; do
24123        ˇ        while [ -n \"$item\" ]; do
24124        ˇ            if [ \"$value\" -gt 10 ]; then
24125        ˇ                continue
24126        ˇ            elif [ \"$value\" -lt 0 ]; then
24127        ˇ                break
24128        ˇ            else
24129        ˇ                echo \"$item\"
24130        ˇ            fi
24131        ˇ        done
24132        ˇ    done
24133        ˇ}
24134    "});
24135    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24136    cx.assert_editor_state(indoc! {"
24137        function main() {
24138        #ˇ    for item in $items; do
24139        #ˇ        while [ -n \"$item\" ]; do
24140        #ˇ            if [ \"$value\" -gt 10 ]; then
24141        #ˇ                continue
24142        #ˇ            elif [ \"$value\" -lt 0 ]; then
24143        #ˇ                break
24144        #ˇ            else
24145        #ˇ                echo \"$item\"
24146        #ˇ            fi
24147        #ˇ        done
24148        #ˇ    done
24149        #ˇ}
24150    "});
24151}
24152
24153#[gpui::test]
24154async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24155    init_test(cx, |_| {});
24156
24157    let mut cx = EditorTestContext::new(cx).await;
24158    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24159    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24160
24161    // test `else` auto outdents when typed inside `if` block
24162    cx.set_state(indoc! {"
24163        if [ \"$1\" = \"test\" ]; then
24164            echo \"foo bar\"
24165            ˇ
24166    "});
24167    cx.update_editor(|editor, window, cx| {
24168        editor.handle_input("else", window, cx);
24169    });
24170    cx.assert_editor_state(indoc! {"
24171        if [ \"$1\" = \"test\" ]; then
24172            echo \"foo bar\"
24173        elseˇ
24174    "});
24175
24176    // test `elif` auto outdents when typed inside `if` block
24177    cx.set_state(indoc! {"
24178        if [ \"$1\" = \"test\" ]; then
24179            echo \"foo bar\"
24180            ˇ
24181    "});
24182    cx.update_editor(|editor, window, cx| {
24183        editor.handle_input("elif", window, cx);
24184    });
24185    cx.assert_editor_state(indoc! {"
24186        if [ \"$1\" = \"test\" ]; then
24187            echo \"foo bar\"
24188        elifˇ
24189    "});
24190
24191    // test `fi` auto outdents when typed inside `else` block
24192    cx.set_state(indoc! {"
24193        if [ \"$1\" = \"test\" ]; then
24194            echo \"foo bar\"
24195        else
24196            echo \"bar baz\"
24197            ˇ
24198    "});
24199    cx.update_editor(|editor, window, cx| {
24200        editor.handle_input("fi", window, cx);
24201    });
24202    cx.assert_editor_state(indoc! {"
24203        if [ \"$1\" = \"test\" ]; then
24204            echo \"foo bar\"
24205        else
24206            echo \"bar baz\"
24207        fiˇ
24208    "});
24209
24210    // test `done` auto outdents when typed inside `while` block
24211    cx.set_state(indoc! {"
24212        while read line; do
24213            echo \"$line\"
24214            ˇ
24215    "});
24216    cx.update_editor(|editor, window, cx| {
24217        editor.handle_input("done", window, cx);
24218    });
24219    cx.assert_editor_state(indoc! {"
24220        while read line; do
24221            echo \"$line\"
24222        doneˇ
24223    "});
24224
24225    // test `done` auto outdents when typed inside `for` block
24226    cx.set_state(indoc! {"
24227        for file in *.txt; do
24228            cat \"$file\"
24229            ˇ
24230    "});
24231    cx.update_editor(|editor, window, cx| {
24232        editor.handle_input("done", window, cx);
24233    });
24234    cx.assert_editor_state(indoc! {"
24235        for file in *.txt; do
24236            cat \"$file\"
24237        doneˇ
24238    "});
24239
24240    // test `esac` auto outdents when typed inside `case` block
24241    cx.set_state(indoc! {"
24242        case \"$1\" in
24243            start)
24244                echo \"foo bar\"
24245                ;;
24246            stop)
24247                echo \"bar baz\"
24248                ;;
24249            ˇ
24250    "});
24251    cx.update_editor(|editor, window, cx| {
24252        editor.handle_input("esac", window, cx);
24253    });
24254    cx.assert_editor_state(indoc! {"
24255        case \"$1\" in
24256            start)
24257                echo \"foo bar\"
24258                ;;
24259            stop)
24260                echo \"bar baz\"
24261                ;;
24262        esacˇ
24263    "});
24264
24265    // test `*)` auto outdents when typed inside `case` block
24266    cx.set_state(indoc! {"
24267        case \"$1\" in
24268            start)
24269                echo \"foo bar\"
24270                ;;
24271                ˇ
24272    "});
24273    cx.update_editor(|editor, window, cx| {
24274        editor.handle_input("*)", window, cx);
24275    });
24276    cx.assert_editor_state(indoc! {"
24277        case \"$1\" in
24278            start)
24279                echo \"foo bar\"
24280                ;;
24281            *)ˇ
24282    "});
24283
24284    // test `fi` outdents to correct level with nested if blocks
24285    cx.set_state(indoc! {"
24286        if [ \"$1\" = \"test\" ]; then
24287            echo \"outer if\"
24288            if [ \"$2\" = \"debug\" ]; then
24289                echo \"inner if\"
24290                ˇ
24291    "});
24292    cx.update_editor(|editor, window, cx| {
24293        editor.handle_input("fi", window, cx);
24294    });
24295    cx.assert_editor_state(indoc! {"
24296        if [ \"$1\" = \"test\" ]; then
24297            echo \"outer if\"
24298            if [ \"$2\" = \"debug\" ]; then
24299                echo \"inner if\"
24300            fiˇ
24301    "});
24302}
24303
24304#[gpui::test]
24305async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24306    init_test(cx, |_| {});
24307    update_test_language_settings(cx, |settings| {
24308        settings.defaults.extend_comment_on_newline = Some(false);
24309    });
24310    let mut cx = EditorTestContext::new(cx).await;
24311    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24312    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24313
24314    // test correct indent after newline on comment
24315    cx.set_state(indoc! {"
24316        # COMMENT:ˇ
24317    "});
24318    cx.update_editor(|editor, window, cx| {
24319        editor.newline(&Newline, window, cx);
24320    });
24321    cx.assert_editor_state(indoc! {"
24322        # COMMENT:
24323        ˇ
24324    "});
24325
24326    // test correct indent after newline after `then`
24327    cx.set_state(indoc! {"
24328
24329        if [ \"$1\" = \"test\" ]; thenˇ
24330    "});
24331    cx.update_editor(|editor, window, cx| {
24332        editor.newline(&Newline, window, cx);
24333    });
24334    cx.run_until_parked();
24335    cx.assert_editor_state(indoc! {"
24336
24337        if [ \"$1\" = \"test\" ]; then
24338            ˇ
24339    "});
24340
24341    // test correct indent after newline after `else`
24342    cx.set_state(indoc! {"
24343        if [ \"$1\" = \"test\" ]; then
24344        elseˇ
24345    "});
24346    cx.update_editor(|editor, window, cx| {
24347        editor.newline(&Newline, window, cx);
24348    });
24349    cx.run_until_parked();
24350    cx.assert_editor_state(indoc! {"
24351        if [ \"$1\" = \"test\" ]; then
24352        else
24353            ˇ
24354    "});
24355
24356    // test correct indent after newline after `elif`
24357    cx.set_state(indoc! {"
24358        if [ \"$1\" = \"test\" ]; then
24359        elifˇ
24360    "});
24361    cx.update_editor(|editor, window, cx| {
24362        editor.newline(&Newline, window, cx);
24363    });
24364    cx.run_until_parked();
24365    cx.assert_editor_state(indoc! {"
24366        if [ \"$1\" = \"test\" ]; then
24367        elif
24368            ˇ
24369    "});
24370
24371    // test correct indent after newline after `do`
24372    cx.set_state(indoc! {"
24373        for file in *.txt; doˇ
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        for file in *.txt; do
24381            ˇ
24382    "});
24383
24384    // test correct indent after newline after case pattern
24385    cx.set_state(indoc! {"
24386        case \"$1\" in
24387            start)ˇ
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        case \"$1\" in
24395            start)
24396                ˇ
24397    "});
24398
24399    // test correct indent after newline after case pattern
24400    cx.set_state(indoc! {"
24401        case \"$1\" in
24402            start)
24403                ;;
24404            *)ˇ
24405    "});
24406    cx.update_editor(|editor, window, cx| {
24407        editor.newline(&Newline, window, cx);
24408    });
24409    cx.run_until_parked();
24410    cx.assert_editor_state(indoc! {"
24411        case \"$1\" in
24412            start)
24413                ;;
24414            *)
24415                ˇ
24416    "});
24417
24418    // test correct indent after newline after function opening brace
24419    cx.set_state(indoc! {"
24420        function test() {ˇ}
24421    "});
24422    cx.update_editor(|editor, window, cx| {
24423        editor.newline(&Newline, window, cx);
24424    });
24425    cx.run_until_parked();
24426    cx.assert_editor_state(indoc! {"
24427        function test() {
24428            ˇ
24429        }
24430    "});
24431
24432    // test no extra indent after semicolon on same line
24433    cx.set_state(indoc! {"
24434        echo \"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        echo \"test\";
24442        ˇ
24443    "});
24444}
24445
24446fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24447    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24448    point..point
24449}
24450
24451#[track_caller]
24452fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24453    let (text, ranges) = marked_text_ranges(marked_text, true);
24454    assert_eq!(editor.text(cx), text);
24455    assert_eq!(
24456        editor.selections.ranges(cx),
24457        ranges,
24458        "Assert selections are {}",
24459        marked_text
24460    );
24461}
24462
24463pub fn handle_signature_help_request(
24464    cx: &mut EditorLspTestContext,
24465    mocked_response: lsp::SignatureHelp,
24466) -> impl Future<Output = ()> + use<> {
24467    let mut request =
24468        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
24469            let mocked_response = mocked_response.clone();
24470            async move { Ok(Some(mocked_response)) }
24471        });
24472
24473    async move {
24474        request.next().await;
24475    }
24476}
24477
24478#[track_caller]
24479pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
24480    cx.update_editor(|editor, _, _| {
24481        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
24482            let entries = menu.entries.borrow();
24483            let entries = entries
24484                .iter()
24485                .map(|entry| entry.string.as_str())
24486                .collect::<Vec<_>>();
24487            assert_eq!(entries, expected);
24488        } else {
24489            panic!("Expected completions menu");
24490        }
24491    });
24492}
24493
24494/// Handle completion request passing a marked string specifying where the completion
24495/// should be triggered from using '|' character, what range should be replaced, and what completions
24496/// should be returned using '<' and '>' to delimit the range.
24497///
24498/// Also see `handle_completion_request_with_insert_and_replace`.
24499#[track_caller]
24500pub fn handle_completion_request(
24501    marked_string: &str,
24502    completions: Vec<&'static str>,
24503    is_incomplete: bool,
24504    counter: Arc<AtomicUsize>,
24505    cx: &mut EditorLspTestContext,
24506) -> impl Future<Output = ()> {
24507    let complete_from_marker: TextRangeMarker = '|'.into();
24508    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24509    let (_, mut marked_ranges) = marked_text_ranges_by(
24510        marked_string,
24511        vec![complete_from_marker.clone(), replace_range_marker.clone()],
24512    );
24513
24514    let complete_from_position =
24515        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24516    let replace_range =
24517        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24518
24519    let mut request =
24520        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24521            let completions = completions.clone();
24522            counter.fetch_add(1, atomic::Ordering::Release);
24523            async move {
24524                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24525                assert_eq!(
24526                    params.text_document_position.position,
24527                    complete_from_position
24528                );
24529                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
24530                    is_incomplete,
24531                    item_defaults: None,
24532                    items: completions
24533                        .iter()
24534                        .map(|completion_text| lsp::CompletionItem {
24535                            label: completion_text.to_string(),
24536                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
24537                                range: replace_range,
24538                                new_text: completion_text.to_string(),
24539                            })),
24540                            ..Default::default()
24541                        })
24542                        .collect(),
24543                })))
24544            }
24545        });
24546
24547    async move {
24548        request.next().await;
24549    }
24550}
24551
24552/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
24553/// given instead, which also contains an `insert` range.
24554///
24555/// This function uses markers to define ranges:
24556/// - `|` marks the cursor position
24557/// - `<>` marks the replace range
24558/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
24559pub fn handle_completion_request_with_insert_and_replace(
24560    cx: &mut EditorLspTestContext,
24561    marked_string: &str,
24562    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
24563    counter: Arc<AtomicUsize>,
24564) -> impl Future<Output = ()> {
24565    let complete_from_marker: TextRangeMarker = '|'.into();
24566    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24567    let insert_range_marker: TextRangeMarker = ('{', '}').into();
24568
24569    let (_, mut marked_ranges) = marked_text_ranges_by(
24570        marked_string,
24571        vec![
24572            complete_from_marker.clone(),
24573            replace_range_marker.clone(),
24574            insert_range_marker.clone(),
24575        ],
24576    );
24577
24578    let complete_from_position =
24579        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24580    let replace_range =
24581        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24582
24583    let insert_range = match marked_ranges.remove(&insert_range_marker) {
24584        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
24585        _ => lsp::Range {
24586            start: replace_range.start,
24587            end: complete_from_position,
24588        },
24589    };
24590
24591    let mut request =
24592        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24593            let completions = completions.clone();
24594            counter.fetch_add(1, atomic::Ordering::Release);
24595            async move {
24596                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24597                assert_eq!(
24598                    params.text_document_position.position, complete_from_position,
24599                    "marker `|` position doesn't match",
24600                );
24601                Ok(Some(lsp::CompletionResponse::Array(
24602                    completions
24603                        .iter()
24604                        .map(|(label, new_text)| lsp::CompletionItem {
24605                            label: label.to_string(),
24606                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24607                                lsp::InsertReplaceEdit {
24608                                    insert: insert_range,
24609                                    replace: replace_range,
24610                                    new_text: new_text.to_string(),
24611                                },
24612                            )),
24613                            ..Default::default()
24614                        })
24615                        .collect(),
24616                )))
24617            }
24618        });
24619
24620    async move {
24621        request.next().await;
24622    }
24623}
24624
24625fn handle_resolve_completion_request(
24626    cx: &mut EditorLspTestContext,
24627    edits: Option<Vec<(&'static str, &'static str)>>,
24628) -> impl Future<Output = ()> {
24629    let edits = edits.map(|edits| {
24630        edits
24631            .iter()
24632            .map(|(marked_string, new_text)| {
24633                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
24634                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
24635                lsp::TextEdit::new(replace_range, new_text.to_string())
24636            })
24637            .collect::<Vec<_>>()
24638    });
24639
24640    let mut request =
24641        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
24642            let edits = edits.clone();
24643            async move {
24644                Ok(lsp::CompletionItem {
24645                    additional_text_edits: edits,
24646                    ..Default::default()
24647                })
24648            }
24649        });
24650
24651    async move {
24652        request.next().await;
24653    }
24654}
24655
24656pub(crate) fn update_test_language_settings(
24657    cx: &mut TestAppContext,
24658    f: impl Fn(&mut AllLanguageSettingsContent),
24659) {
24660    cx.update(|cx| {
24661        SettingsStore::update_global(cx, |store, cx| {
24662            store.update_user_settings::<AllLanguageSettings>(cx, f);
24663        });
24664    });
24665}
24666
24667pub(crate) fn update_test_project_settings(
24668    cx: &mut TestAppContext,
24669    f: impl Fn(&mut ProjectSettings),
24670) {
24671    cx.update(|cx| {
24672        SettingsStore::update_global(cx, |store, cx| {
24673            store.update_user_settings::<ProjectSettings>(cx, f);
24674        });
24675    });
24676}
24677
24678pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
24679    cx.update(|cx| {
24680        assets::Assets.load_test_fonts(cx);
24681        let store = SettingsStore::test(cx);
24682        cx.set_global(store);
24683        theme::init(theme::LoadThemes::JustBase, cx);
24684        release_channel::init(SemanticVersion::default(), cx);
24685        client::init_settings(cx);
24686        language::init(cx);
24687        Project::init_settings(cx);
24688        workspace::init_settings(cx);
24689        crate::init(cx);
24690    });
24691    zlog::init_test();
24692    update_test_language_settings(cx, f);
24693}
24694
24695#[track_caller]
24696fn assert_hunk_revert(
24697    not_reverted_text_with_selections: &str,
24698    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
24699    expected_reverted_text_with_selections: &str,
24700    base_text: &str,
24701    cx: &mut EditorLspTestContext,
24702) {
24703    cx.set_state(not_reverted_text_with_selections);
24704    cx.set_head_text(base_text);
24705    cx.executor().run_until_parked();
24706
24707    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
24708        let snapshot = editor.snapshot(window, cx);
24709        let reverted_hunk_statuses = snapshot
24710            .buffer_snapshot
24711            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
24712            .map(|hunk| hunk.status().kind)
24713            .collect::<Vec<_>>();
24714
24715        editor.git_restore(&Default::default(), window, cx);
24716        reverted_hunk_statuses
24717    });
24718    cx.executor().run_until_parked();
24719    cx.assert_editor_state(expected_reverted_text_with_selections);
24720    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
24721}
24722
24723#[gpui::test(iterations = 10)]
24724async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
24725    init_test(cx, |_| {});
24726
24727    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
24728    let counter = diagnostic_requests.clone();
24729
24730    let fs = FakeFs::new(cx.executor());
24731    fs.insert_tree(
24732        path!("/a"),
24733        json!({
24734            "first.rs": "fn main() { let a = 5; }",
24735            "second.rs": "// Test file",
24736        }),
24737    )
24738    .await;
24739
24740    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24741    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24742    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24743
24744    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24745    language_registry.add(rust_lang());
24746    let mut fake_servers = language_registry.register_fake_lsp(
24747        "Rust",
24748        FakeLspAdapter {
24749            capabilities: lsp::ServerCapabilities {
24750                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
24751                    lsp::DiagnosticOptions {
24752                        identifier: None,
24753                        inter_file_dependencies: true,
24754                        workspace_diagnostics: true,
24755                        work_done_progress_options: Default::default(),
24756                    },
24757                )),
24758                ..Default::default()
24759            },
24760            ..Default::default()
24761        },
24762    );
24763
24764    let editor = workspace
24765        .update(cx, |workspace, window, cx| {
24766            workspace.open_abs_path(
24767                PathBuf::from(path!("/a/first.rs")),
24768                OpenOptions::default(),
24769                window,
24770                cx,
24771            )
24772        })
24773        .unwrap()
24774        .await
24775        .unwrap()
24776        .downcast::<Editor>()
24777        .unwrap();
24778    let fake_server = fake_servers.next().await.unwrap();
24779    let server_id = fake_server.server.server_id();
24780    let mut first_request = fake_server
24781        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
24782            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
24783            let result_id = Some(new_result_id.to_string());
24784            assert_eq!(
24785                params.text_document.uri,
24786                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
24787            );
24788            async move {
24789                Ok(lsp::DocumentDiagnosticReportResult::Report(
24790                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
24791                        related_documents: None,
24792                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
24793                            items: Vec::new(),
24794                            result_id,
24795                        },
24796                    }),
24797                ))
24798            }
24799        });
24800
24801    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
24802        project.update(cx, |project, cx| {
24803            let buffer_id = editor
24804                .read(cx)
24805                .buffer()
24806                .read(cx)
24807                .as_singleton()
24808                .expect("created a singleton buffer")
24809                .read(cx)
24810                .remote_id();
24811            let buffer_result_id = project
24812                .lsp_store()
24813                .read(cx)
24814                .result_id(server_id, buffer_id, cx);
24815            assert_eq!(expected, buffer_result_id);
24816        });
24817    };
24818
24819    ensure_result_id(None, cx);
24820    cx.executor().advance_clock(Duration::from_millis(60));
24821    cx.executor().run_until_parked();
24822    assert_eq!(
24823        diagnostic_requests.load(atomic::Ordering::Acquire),
24824        1,
24825        "Opening file should trigger diagnostic request"
24826    );
24827    first_request
24828        .next()
24829        .await
24830        .expect("should have sent the first diagnostics pull request");
24831    ensure_result_id(Some("1".to_string()), cx);
24832
24833    // Editing should trigger diagnostics
24834    editor.update_in(cx, |editor, window, cx| {
24835        editor.handle_input("2", window, cx)
24836    });
24837    cx.executor().advance_clock(Duration::from_millis(60));
24838    cx.executor().run_until_parked();
24839    assert_eq!(
24840        diagnostic_requests.load(atomic::Ordering::Acquire),
24841        2,
24842        "Editing should trigger diagnostic request"
24843    );
24844    ensure_result_id(Some("2".to_string()), cx);
24845
24846    // Moving cursor should not trigger diagnostic request
24847    editor.update_in(cx, |editor, window, cx| {
24848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24849            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
24850        });
24851    });
24852    cx.executor().advance_clock(Duration::from_millis(60));
24853    cx.executor().run_until_parked();
24854    assert_eq!(
24855        diagnostic_requests.load(atomic::Ordering::Acquire),
24856        2,
24857        "Cursor movement should not trigger diagnostic request"
24858    );
24859    ensure_result_id(Some("2".to_string()), cx);
24860    // Multiple rapid edits should be debounced
24861    for _ in 0..5 {
24862        editor.update_in(cx, |editor, window, cx| {
24863            editor.handle_input("x", window, cx)
24864        });
24865    }
24866    cx.executor().advance_clock(Duration::from_millis(60));
24867    cx.executor().run_until_parked();
24868
24869    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
24870    assert!(
24871        final_requests <= 4,
24872        "Multiple rapid edits should be debounced (got {final_requests} requests)",
24873    );
24874    ensure_result_id(Some(final_requests.to_string()), cx);
24875}
24876
24877#[gpui::test]
24878async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
24879    // Regression test for issue #11671
24880    // Previously, adding a cursor after moving multiple cursors would reset
24881    // the cursor count instead of adding to the existing cursors.
24882    init_test(cx, |_| {});
24883    let mut cx = EditorTestContext::new(cx).await;
24884
24885    // Create a simple buffer with cursor at start
24886    cx.set_state(indoc! {"
24887        ˇaaaa
24888        bbbb
24889        cccc
24890        dddd
24891        eeee
24892        ffff
24893        gggg
24894        hhhh"});
24895
24896    // Add 2 cursors below (so we have 3 total)
24897    cx.update_editor(|editor, window, cx| {
24898        editor.add_selection_below(&Default::default(), window, cx);
24899        editor.add_selection_below(&Default::default(), window, cx);
24900    });
24901
24902    // Verify we have 3 cursors
24903    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
24904    assert_eq!(
24905        initial_count, 3,
24906        "Should have 3 cursors after adding 2 below"
24907    );
24908
24909    // Move down one line
24910    cx.update_editor(|editor, window, cx| {
24911        editor.move_down(&MoveDown, window, cx);
24912    });
24913
24914    // Add another cursor below
24915    cx.update_editor(|editor, window, cx| {
24916        editor.add_selection_below(&Default::default(), window, cx);
24917    });
24918
24919    // Should now have 4 cursors (3 original + 1 new)
24920    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
24921    assert_eq!(
24922        final_count, 4,
24923        "Should have 4 cursors after moving and adding another"
24924    );
24925}
24926
24927#[gpui::test(iterations = 10)]
24928async fn test_document_colors(cx: &mut TestAppContext) {
24929    let expected_color = Rgba {
24930        r: 0.33,
24931        g: 0.33,
24932        b: 0.33,
24933        a: 0.33,
24934    };
24935
24936    init_test(cx, |_| {});
24937
24938    let fs = FakeFs::new(cx.executor());
24939    fs.insert_tree(
24940        path!("/a"),
24941        json!({
24942            "first.rs": "fn main() { let a = 5; }",
24943        }),
24944    )
24945    .await;
24946
24947    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24948    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24949    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24950
24951    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24952    language_registry.add(rust_lang());
24953    let mut fake_servers = language_registry.register_fake_lsp(
24954        "Rust",
24955        FakeLspAdapter {
24956            capabilities: lsp::ServerCapabilities {
24957                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
24958                ..lsp::ServerCapabilities::default()
24959            },
24960            name: "rust-analyzer",
24961            ..FakeLspAdapter::default()
24962        },
24963    );
24964    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
24965        "Rust",
24966        FakeLspAdapter {
24967            capabilities: lsp::ServerCapabilities {
24968                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
24969                ..lsp::ServerCapabilities::default()
24970            },
24971            name: "not-rust-analyzer",
24972            ..FakeLspAdapter::default()
24973        },
24974    );
24975
24976    let editor = workspace
24977        .update(cx, |workspace, window, cx| {
24978            workspace.open_abs_path(
24979                PathBuf::from(path!("/a/first.rs")),
24980                OpenOptions::default(),
24981                window,
24982                cx,
24983            )
24984        })
24985        .unwrap()
24986        .await
24987        .unwrap()
24988        .downcast::<Editor>()
24989        .unwrap();
24990    let fake_language_server = fake_servers.next().await.unwrap();
24991    let fake_language_server_without_capabilities =
24992        fake_servers_without_capabilities.next().await.unwrap();
24993    let requests_made = Arc::new(AtomicUsize::new(0));
24994    let closure_requests_made = Arc::clone(&requests_made);
24995    let mut color_request_handle = fake_language_server
24996        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
24997            let requests_made = Arc::clone(&closure_requests_made);
24998            async move {
24999                assert_eq!(
25000                    params.text_document.uri,
25001                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25002                );
25003                requests_made.fetch_add(1, atomic::Ordering::Release);
25004                Ok(vec![
25005                    lsp::ColorInformation {
25006                        range: lsp::Range {
25007                            start: lsp::Position {
25008                                line: 0,
25009                                character: 0,
25010                            },
25011                            end: lsp::Position {
25012                                line: 0,
25013                                character: 1,
25014                            },
25015                        },
25016                        color: lsp::Color {
25017                            red: 0.33,
25018                            green: 0.33,
25019                            blue: 0.33,
25020                            alpha: 0.33,
25021                        },
25022                    },
25023                    lsp::ColorInformation {
25024                        range: lsp::Range {
25025                            start: lsp::Position {
25026                                line: 0,
25027                                character: 0,
25028                            },
25029                            end: lsp::Position {
25030                                line: 0,
25031                                character: 1,
25032                            },
25033                        },
25034                        color: lsp::Color {
25035                            red: 0.33,
25036                            green: 0.33,
25037                            blue: 0.33,
25038                            alpha: 0.33,
25039                        },
25040                    },
25041                ])
25042            }
25043        });
25044
25045    let _handle = fake_language_server_without_capabilities
25046        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25047            panic!("Should not be called");
25048        });
25049    cx.executor().advance_clock(Duration::from_millis(100));
25050    color_request_handle.next().await.unwrap();
25051    cx.run_until_parked();
25052    assert_eq!(
25053        1,
25054        requests_made.load(atomic::Ordering::Acquire),
25055        "Should query for colors once per editor open"
25056    );
25057    editor.update_in(cx, |editor, _, cx| {
25058        assert_eq!(
25059            vec![expected_color],
25060            extract_color_inlays(editor, cx),
25061            "Should have an initial inlay"
25062        );
25063    });
25064
25065    // opening another file in a split should not influence the LSP query counter
25066    workspace
25067        .update(cx, |workspace, window, cx| {
25068            assert_eq!(
25069                workspace.panes().len(),
25070                1,
25071                "Should have one pane with one editor"
25072            );
25073            workspace.move_item_to_pane_in_direction(
25074                &MoveItemToPaneInDirection {
25075                    direction: SplitDirection::Right,
25076                    focus: false,
25077                    clone: true,
25078                },
25079                window,
25080                cx,
25081            );
25082        })
25083        .unwrap();
25084    cx.run_until_parked();
25085    workspace
25086        .update(cx, |workspace, _, cx| {
25087            let panes = workspace.panes();
25088            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25089            for pane in panes {
25090                let editor = pane
25091                    .read(cx)
25092                    .active_item()
25093                    .and_then(|item| item.downcast::<Editor>())
25094                    .expect("Should have opened an editor in each split");
25095                let editor_file = editor
25096                    .read(cx)
25097                    .buffer()
25098                    .read(cx)
25099                    .as_singleton()
25100                    .expect("test deals with singleton buffers")
25101                    .read(cx)
25102                    .file()
25103                    .expect("test buffese should have a file")
25104                    .path();
25105                assert_eq!(
25106                    editor_file.as_ref(),
25107                    Path::new("first.rs"),
25108                    "Both editors should be opened for the same file"
25109                )
25110            }
25111        })
25112        .unwrap();
25113
25114    cx.executor().advance_clock(Duration::from_millis(500));
25115    let save = editor.update_in(cx, |editor, window, cx| {
25116        editor.move_to_end(&MoveToEnd, window, cx);
25117        editor.handle_input("dirty", window, cx);
25118        editor.save(
25119            SaveOptions {
25120                format: true,
25121                autosave: true,
25122            },
25123            project.clone(),
25124            window,
25125            cx,
25126        )
25127    });
25128    save.await.unwrap();
25129
25130    color_request_handle.next().await.unwrap();
25131    cx.run_until_parked();
25132    assert_eq!(
25133        3,
25134        requests_made.load(atomic::Ordering::Acquire),
25135        "Should query for colors once per save and once per formatting after save"
25136    );
25137
25138    drop(editor);
25139    let close = workspace
25140        .update(cx, |workspace, window, cx| {
25141            workspace.active_pane().update(cx, |pane, cx| {
25142                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25143            })
25144        })
25145        .unwrap();
25146    close.await.unwrap();
25147    let close = workspace
25148        .update(cx, |workspace, window, cx| {
25149            workspace.active_pane().update(cx, |pane, cx| {
25150                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25151            })
25152        })
25153        .unwrap();
25154    close.await.unwrap();
25155    assert_eq!(
25156        3,
25157        requests_made.load(atomic::Ordering::Acquire),
25158        "After saving and closing all editors, no extra requests should be made"
25159    );
25160    workspace
25161        .update(cx, |workspace, _, cx| {
25162            assert!(
25163                workspace.active_item(cx).is_none(),
25164                "Should close all editors"
25165            )
25166        })
25167        .unwrap();
25168
25169    workspace
25170        .update(cx, |workspace, window, cx| {
25171            workspace.active_pane().update(cx, |pane, cx| {
25172                pane.navigate_backward(&Default::default(), window, cx);
25173            })
25174        })
25175        .unwrap();
25176    cx.executor().advance_clock(Duration::from_millis(100));
25177    cx.run_until_parked();
25178    let editor = workspace
25179        .update(cx, |workspace, _, cx| {
25180            workspace
25181                .active_item(cx)
25182                .expect("Should have reopened the editor again after navigating back")
25183                .downcast::<Editor>()
25184                .expect("Should be an editor")
25185        })
25186        .unwrap();
25187    color_request_handle.next().await.unwrap();
25188    assert_eq!(
25189        3,
25190        requests_made.load(atomic::Ordering::Acquire),
25191        "Cache should be reused on buffer close and reopen"
25192    );
25193    editor.update(cx, |editor, cx| {
25194        assert_eq!(
25195            vec![expected_color],
25196            extract_color_inlays(editor, cx),
25197            "Should have an initial inlay"
25198        );
25199    });
25200}
25201
25202#[gpui::test]
25203async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25204    init_test(cx, |_| {});
25205    let (editor, cx) = cx.add_window_view(Editor::single_line);
25206    editor.update_in(cx, |editor, window, cx| {
25207        editor.set_text("oops\n\nwow\n", window, cx)
25208    });
25209    cx.run_until_parked();
25210    editor.update(cx, |editor, cx| {
25211        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25212    });
25213    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25214    cx.run_until_parked();
25215    editor.update(cx, |editor, cx| {
25216        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25217    });
25218}
25219
25220#[gpui::test]
25221async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25222    init_test(cx, |_| {});
25223
25224    cx.update(|cx| {
25225        register_project_item::<Editor>(cx);
25226    });
25227
25228    let fs = FakeFs::new(cx.executor());
25229    fs.insert_tree("/root1", json!({})).await;
25230    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25231        .await;
25232
25233    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25234    let (workspace, cx) =
25235        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25236
25237    let worktree_id = project.update(cx, |project, cx| {
25238        project.worktrees(cx).next().unwrap().read(cx).id()
25239    });
25240
25241    let handle = workspace
25242        .update_in(cx, |workspace, window, cx| {
25243            let project_path = (worktree_id, "one.pdf");
25244            workspace.open_path(project_path, None, true, window, cx)
25245        })
25246        .await
25247        .unwrap();
25248
25249    assert_eq!(
25250        handle.to_any().entity_type(),
25251        TypeId::of::<InvalidBufferView>()
25252    );
25253}
25254
25255#[track_caller]
25256fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
25257    editor
25258        .all_inlays(cx)
25259        .into_iter()
25260        .filter_map(|inlay| inlay.get_color())
25261        .map(Rgba::from)
25262        .collect()
25263}