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]
 2479fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2480    init_test(cx, |_| {});
 2481
 2482    let editor = cx.add_window(|window, cx| {
 2483        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2484        build_editor(buffer, window, cx)
 2485    });
 2486
 2487    _ = editor.update(cx, |editor, window, cx| {
 2488        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2489            s.select_display_ranges([
 2490                // an empty selection - the preceding word fragment is deleted
 2491                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2492                // characters selected - they are deleted
 2493                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2494            ])
 2495        });
 2496        editor.delete_to_previous_word_start(
 2497            &DeleteToPreviousWordStart {
 2498                ignore_newlines: false,
 2499            },
 2500            window,
 2501            cx,
 2502        );
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2504    });
 2505
 2506    _ = editor.update(cx, |editor, window, cx| {
 2507        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2508            s.select_display_ranges([
 2509                // an empty selection - the following word fragment is deleted
 2510                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2511                // characters selected - they are deleted
 2512                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2513            ])
 2514        });
 2515        editor.delete_to_next_word_end(
 2516            &DeleteToNextWordEnd {
 2517                ignore_newlines: false,
 2518            },
 2519            window,
 2520            cx,
 2521        );
 2522        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2523    });
 2524}
 2525
 2526#[gpui::test]
 2527fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2528    init_test(cx, |_| {});
 2529
 2530    let editor = cx.add_window(|window, cx| {
 2531        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2532        build_editor(buffer, window, cx)
 2533    });
 2534    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2535        ignore_newlines: false,
 2536    };
 2537    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2538        ignore_newlines: true,
 2539    };
 2540
 2541    _ = editor.update(cx, |editor, window, cx| {
 2542        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2543            s.select_display_ranges([
 2544                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2545            ])
 2546        });
 2547        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2548        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2549        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2550        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2551        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2553        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2555        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2557        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2558        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2559    });
 2560}
 2561
 2562#[gpui::test]
 2563fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2564    init_test(cx, |_| {});
 2565
 2566    let editor = cx.add_window(|window, cx| {
 2567        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2568        build_editor(buffer, window, cx)
 2569    });
 2570    let del_to_next_word_end = DeleteToNextWordEnd {
 2571        ignore_newlines: false,
 2572    };
 2573    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2574        ignore_newlines: true,
 2575    };
 2576
 2577    _ = editor.update(cx, |editor, window, cx| {
 2578        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2579            s.select_display_ranges([
 2580                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2581            ])
 2582        });
 2583        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2584        assert_eq!(
 2585            editor.buffer.read(cx).read(cx).text(),
 2586            "one\n   two\nthree\n   four"
 2587        );
 2588        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2589        assert_eq!(
 2590            editor.buffer.read(cx).read(cx).text(),
 2591            "\n   two\nthree\n   four"
 2592        );
 2593        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2594        assert_eq!(
 2595            editor.buffer.read(cx).read(cx).text(),
 2596            "two\nthree\n   four"
 2597        );
 2598        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2599        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2600        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2601        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2602        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2603        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2604    });
 2605}
 2606
 2607#[gpui::test]
 2608fn test_newline(cx: &mut TestAppContext) {
 2609    init_test(cx, |_| {});
 2610
 2611    let editor = cx.add_window(|window, cx| {
 2612        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2613        build_editor(buffer, window, cx)
 2614    });
 2615
 2616    _ = editor.update(cx, |editor, window, cx| {
 2617        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2618            s.select_display_ranges([
 2619                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2620                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2621                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2622            ])
 2623        });
 2624
 2625        editor.newline(&Newline, window, cx);
 2626        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2627    });
 2628}
 2629
 2630#[gpui::test]
 2631fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2632    init_test(cx, |_| {});
 2633
 2634    let editor = cx.add_window(|window, cx| {
 2635        let buffer = MultiBuffer::build_simple(
 2636            "
 2637                a
 2638                b(
 2639                    X
 2640                )
 2641                c(
 2642                    X
 2643                )
 2644            "
 2645            .unindent()
 2646            .as_str(),
 2647            cx,
 2648        );
 2649        let mut editor = build_editor(buffer, window, cx);
 2650        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2651            s.select_ranges([
 2652                Point::new(2, 4)..Point::new(2, 5),
 2653                Point::new(5, 4)..Point::new(5, 5),
 2654            ])
 2655        });
 2656        editor
 2657    });
 2658
 2659    _ = editor.update(cx, |editor, window, cx| {
 2660        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2661        editor.buffer.update(cx, |buffer, cx| {
 2662            buffer.edit(
 2663                [
 2664                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2665                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2666                ],
 2667                None,
 2668                cx,
 2669            );
 2670            assert_eq!(
 2671                buffer.read(cx).text(),
 2672                "
 2673                    a
 2674                    b()
 2675                    c()
 2676                "
 2677                .unindent()
 2678            );
 2679        });
 2680        assert_eq!(
 2681            editor.selections.ranges(cx),
 2682            &[
 2683                Point::new(1, 2)..Point::new(1, 2),
 2684                Point::new(2, 2)..Point::new(2, 2),
 2685            ],
 2686        );
 2687
 2688        editor.newline(&Newline, window, cx);
 2689        assert_eq!(
 2690            editor.text(cx),
 2691            "
 2692                a
 2693                b(
 2694                )
 2695                c(
 2696                )
 2697            "
 2698            .unindent()
 2699        );
 2700
 2701        // The selections are moved after the inserted newlines
 2702        assert_eq!(
 2703            editor.selections.ranges(cx),
 2704            &[
 2705                Point::new(2, 0)..Point::new(2, 0),
 2706                Point::new(4, 0)..Point::new(4, 0),
 2707            ],
 2708        );
 2709    });
 2710}
 2711
 2712#[gpui::test]
 2713async fn test_newline_above(cx: &mut TestAppContext) {
 2714    init_test(cx, |settings| {
 2715        settings.defaults.tab_size = NonZeroU32::new(4)
 2716    });
 2717
 2718    let language = Arc::new(
 2719        Language::new(
 2720            LanguageConfig::default(),
 2721            Some(tree_sitter_rust::LANGUAGE.into()),
 2722        )
 2723        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2724        .unwrap(),
 2725    );
 2726
 2727    let mut cx = EditorTestContext::new(cx).await;
 2728    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2729    cx.set_state(indoc! {"
 2730        const a: ˇA = (
 2731 2732                «const_functionˇ»(ˇ),
 2733                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2734 2735        ˇ);ˇ
 2736    "});
 2737
 2738    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2739    cx.assert_editor_state(indoc! {"
 2740        ˇ
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                ˇ
 2746                const_function(),
 2747                ˇ
 2748                ˇ
 2749                ˇ
 2750                ˇ
 2751                something_else,
 2752                ˇ
 2753            )
 2754            ˇ
 2755            ˇ
 2756        );
 2757    "});
 2758}
 2759
 2760#[gpui::test]
 2761async fn test_newline_below(cx: &mut TestAppContext) {
 2762    init_test(cx, |settings| {
 2763        settings.defaults.tab_size = NonZeroU32::new(4)
 2764    });
 2765
 2766    let language = Arc::new(
 2767        Language::new(
 2768            LanguageConfig::default(),
 2769            Some(tree_sitter_rust::LANGUAGE.into()),
 2770        )
 2771        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2772        .unwrap(),
 2773    );
 2774
 2775    let mut cx = EditorTestContext::new(cx).await;
 2776    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777    cx.set_state(indoc! {"
 2778        const a: ˇA = (
 2779 2780                «const_functionˇ»(ˇ),
 2781                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2782 2783        ˇ);ˇ
 2784    "});
 2785
 2786    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2787    cx.assert_editor_state(indoc! {"
 2788        const a: A = (
 2789            ˇ
 2790            (
 2791                ˇ
 2792                const_function(),
 2793                ˇ
 2794                ˇ
 2795                something_else,
 2796                ˇ
 2797                ˇ
 2798                ˇ
 2799                ˇ
 2800            )
 2801            ˇ
 2802        );
 2803        ˇ
 2804        ˇ
 2805    "});
 2806}
 2807
 2808#[gpui::test]
 2809async fn test_newline_comments(cx: &mut TestAppContext) {
 2810    init_test(cx, |settings| {
 2811        settings.defaults.tab_size = NonZeroU32::new(4)
 2812    });
 2813
 2814    let language = Arc::new(Language::new(
 2815        LanguageConfig {
 2816            line_comments: vec!["// ".into()],
 2817            ..LanguageConfig::default()
 2818        },
 2819        None,
 2820    ));
 2821    {
 2822        let mut cx = EditorTestContext::new(cx).await;
 2823        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2824        cx.set_state(indoc! {"
 2825        // Fooˇ
 2826    "});
 2827
 2828        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2829        cx.assert_editor_state(indoc! {"
 2830        // Foo
 2831        // ˇ
 2832    "});
 2833        // Ensure that we add comment prefix when existing line contains space
 2834        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2835        cx.assert_editor_state(
 2836            indoc! {"
 2837        // Foo
 2838        //s
 2839        // ˇ
 2840    "}
 2841            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2842            .as_str(),
 2843        );
 2844        // Ensure that we add comment prefix when existing line does not contain space
 2845        cx.set_state(indoc! {"
 2846        // Foo
 2847        //ˇ
 2848    "});
 2849        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2850        cx.assert_editor_state(indoc! {"
 2851        // Foo
 2852        //
 2853        // ˇ
 2854    "});
 2855        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2856        cx.set_state(indoc! {"
 2857        ˇ// Foo
 2858    "});
 2859        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2860        cx.assert_editor_state(indoc! {"
 2861
 2862        ˇ// Foo
 2863    "});
 2864    }
 2865    // Ensure that comment continuations can be disabled.
 2866    update_test_language_settings(cx, |settings| {
 2867        settings.defaults.extend_comment_on_newline = Some(false);
 2868    });
 2869    let mut cx = EditorTestContext::new(cx).await;
 2870    cx.set_state(indoc! {"
 2871        // Fooˇ
 2872    "});
 2873    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2874    cx.assert_editor_state(indoc! {"
 2875        // Foo
 2876        ˇ
 2877    "});
 2878}
 2879
 2880#[gpui::test]
 2881async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2882    init_test(cx, |settings| {
 2883        settings.defaults.tab_size = NonZeroU32::new(4)
 2884    });
 2885
 2886    let language = Arc::new(Language::new(
 2887        LanguageConfig {
 2888            line_comments: vec!["// ".into(), "/// ".into()],
 2889            ..LanguageConfig::default()
 2890        },
 2891        None,
 2892    ));
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        //ˇ
 2898    "});
 2899        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2900        cx.assert_editor_state(indoc! {"
 2901        //
 2902        // ˇ
 2903    "});
 2904
 2905        cx.set_state(indoc! {"
 2906        ///ˇ
 2907    "});
 2908        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2909        cx.assert_editor_state(indoc! {"
 2910        ///
 2911        /// ˇ
 2912    "});
 2913    }
 2914}
 2915
 2916#[gpui::test]
 2917async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2918    init_test(cx, |settings| {
 2919        settings.defaults.tab_size = NonZeroU32::new(4)
 2920    });
 2921
 2922    let language = Arc::new(
 2923        Language::new(
 2924            LanguageConfig {
 2925                documentation_comment: Some(language::BlockCommentConfig {
 2926                    start: "/**".into(),
 2927                    end: "*/".into(),
 2928                    prefix: "* ".into(),
 2929                    tab_size: 1,
 2930                }),
 2931
 2932                ..LanguageConfig::default()
 2933            },
 2934            Some(tree_sitter_rust::LANGUAGE.into()),
 2935        )
 2936        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2937        .unwrap(),
 2938    );
 2939
 2940    {
 2941        let mut cx = EditorTestContext::new(cx).await;
 2942        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2943        cx.set_state(indoc! {"
 2944        /**ˇ
 2945    "});
 2946
 2947        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2948        cx.assert_editor_state(indoc! {"
 2949        /**
 2950         * ˇ
 2951    "});
 2952        // Ensure that if cursor is before the comment start,
 2953        // we do not actually insert a comment prefix.
 2954        cx.set_state(indoc! {"
 2955        ˇ/**
 2956    "});
 2957        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2958        cx.assert_editor_state(indoc! {"
 2959
 2960        ˇ/**
 2961    "});
 2962        // Ensure that if cursor is between it doesn't add comment prefix.
 2963        cx.set_state(indoc! {"
 2964        /*ˇ*
 2965    "});
 2966        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2967        cx.assert_editor_state(indoc! {"
 2968        /*
 2969        ˇ*
 2970    "});
 2971        // Ensure that if suffix exists on same line after cursor it adds new line.
 2972        cx.set_state(indoc! {"
 2973        /**ˇ*/
 2974    "});
 2975        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2976        cx.assert_editor_state(indoc! {"
 2977        /**
 2978         * ˇ
 2979         */
 2980    "});
 2981        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2982        cx.set_state(indoc! {"
 2983        /**ˇ */
 2984    "});
 2985        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2986        cx.assert_editor_state(indoc! {"
 2987        /**
 2988         * ˇ
 2989         */
 2990    "});
 2991        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2992        cx.set_state(indoc! {"
 2993        /** ˇ*/
 2994    "});
 2995        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2996        cx.assert_editor_state(
 2997            indoc! {"
 2998        /**s
 2999         * ˇ
 3000         */
 3001    "}
 3002            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3003            .as_str(),
 3004        );
 3005        // Ensure that delimiter space is preserved when newline on already
 3006        // spaced delimiter.
 3007        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3008        cx.assert_editor_state(
 3009            indoc! {"
 3010        /**s
 3011         *s
 3012         * ˇ
 3013         */
 3014    "}
 3015            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3016            .as_str(),
 3017        );
 3018        // Ensure that delimiter space is preserved when space is not
 3019        // on existing delimiter.
 3020        cx.set_state(indoc! {"
 3021        /**
 3022 3023         */
 3024    "});
 3025        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3026        cx.assert_editor_state(indoc! {"
 3027        /**
 3028         *
 3029         * ˇ
 3030         */
 3031    "});
 3032        // Ensure that if suffix exists on same line after cursor it
 3033        // doesn't add extra new line if prefix is not on same line.
 3034        cx.set_state(indoc! {"
 3035        /**
 3036        ˇ*/
 3037    "});
 3038        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3039        cx.assert_editor_state(indoc! {"
 3040        /**
 3041
 3042        ˇ*/
 3043    "});
 3044        // Ensure that it detects suffix after existing prefix.
 3045        cx.set_state(indoc! {"
 3046        /**ˇ/
 3047    "});
 3048        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3049        cx.assert_editor_state(indoc! {"
 3050        /**
 3051        ˇ/
 3052    "});
 3053        // Ensure that if suffix exists on same line before
 3054        // cursor it does not add comment prefix.
 3055        cx.set_state(indoc! {"
 3056        /** */ˇ
 3057    "});
 3058        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3059        cx.assert_editor_state(indoc! {"
 3060        /** */
 3061        ˇ
 3062    "});
 3063        // Ensure that if suffix exists on same line before
 3064        // cursor it does not add comment prefix.
 3065        cx.set_state(indoc! {"
 3066        /**
 3067         *
 3068         */ˇ
 3069    "});
 3070        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3071        cx.assert_editor_state(indoc! {"
 3072        /**
 3073         *
 3074         */
 3075         ˇ
 3076    "});
 3077
 3078        // Ensure that inline comment followed by code
 3079        // doesn't add comment prefix on newline
 3080        cx.set_state(indoc! {"
 3081        /** */ textˇ
 3082    "});
 3083        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3084        cx.assert_editor_state(indoc! {"
 3085        /** */ text
 3086        ˇ
 3087    "});
 3088
 3089        // Ensure that text after comment end tag
 3090        // doesn't add comment prefix on newline
 3091        cx.set_state(indoc! {"
 3092        /**
 3093         *
 3094         */ˇtext
 3095    "});
 3096        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3097        cx.assert_editor_state(indoc! {"
 3098        /**
 3099         *
 3100         */
 3101         ˇtext
 3102    "});
 3103
 3104        // Ensure if not comment block it doesn't
 3105        // add comment prefix on newline
 3106        cx.set_state(indoc! {"
 3107        * textˇ
 3108    "});
 3109        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3110        cx.assert_editor_state(indoc! {"
 3111        * text
 3112        ˇ
 3113    "});
 3114    }
 3115    // Ensure that comment continuations can be disabled.
 3116    update_test_language_settings(cx, |settings| {
 3117        settings.defaults.extend_comment_on_newline = Some(false);
 3118    });
 3119    let mut cx = EditorTestContext::new(cx).await;
 3120    cx.set_state(indoc! {"
 3121        /**ˇ
 3122    "});
 3123    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3124    cx.assert_editor_state(indoc! {"
 3125        /**
 3126        ˇ
 3127    "});
 3128}
 3129
 3130#[gpui::test]
 3131async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3132    init_test(cx, |settings| {
 3133        settings.defaults.tab_size = NonZeroU32::new(4)
 3134    });
 3135
 3136    let lua_language = Arc::new(Language::new(
 3137        LanguageConfig {
 3138            line_comments: vec!["--".into()],
 3139            block_comment: Some(language::BlockCommentConfig {
 3140                start: "--[[".into(),
 3141                prefix: "".into(),
 3142                end: "]]".into(),
 3143                tab_size: 0,
 3144            }),
 3145            ..LanguageConfig::default()
 3146        },
 3147        None,
 3148    ));
 3149
 3150    let mut cx = EditorTestContext::new(cx).await;
 3151    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3152
 3153    // Line with line comment should extend
 3154    cx.set_state(indoc! {"
 3155        --ˇ
 3156    "});
 3157    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3158    cx.assert_editor_state(indoc! {"
 3159        --
 3160        --ˇ
 3161    "});
 3162
 3163    // Line with block comment that matches line comment should not extend
 3164    cx.set_state(indoc! {"
 3165        --[[ˇ
 3166    "});
 3167    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3168    cx.assert_editor_state(indoc! {"
 3169        --[[
 3170        ˇ
 3171    "});
 3172}
 3173
 3174#[gpui::test]
 3175fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3176    init_test(cx, |_| {});
 3177
 3178    let editor = cx.add_window(|window, cx| {
 3179        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3180        let mut editor = build_editor(buffer, window, cx);
 3181        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3182            s.select_ranges([3..4, 11..12, 19..20])
 3183        });
 3184        editor
 3185    });
 3186
 3187    _ = editor.update(cx, |editor, window, cx| {
 3188        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3189        editor.buffer.update(cx, |buffer, cx| {
 3190            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3191            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3192        });
 3193        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3194
 3195        editor.insert("Z", window, cx);
 3196        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3197
 3198        // The selections are moved after the inserted characters
 3199        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3200    });
 3201}
 3202
 3203#[gpui::test]
 3204async fn test_tab(cx: &mut TestAppContext) {
 3205    init_test(cx, |settings| {
 3206        settings.defaults.tab_size = NonZeroU32::new(3)
 3207    });
 3208
 3209    let mut cx = EditorTestContext::new(cx).await;
 3210    cx.set_state(indoc! {"
 3211        ˇabˇc
 3212        ˇ🏀ˇ🏀ˇefg
 3213 3214    "});
 3215    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3216    cx.assert_editor_state(indoc! {"
 3217           ˇab ˇc
 3218           ˇ🏀  ˇ🏀  ˇefg
 3219        d  ˇ
 3220    "});
 3221
 3222    cx.set_state(indoc! {"
 3223        a
 3224        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3225    "});
 3226    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3227    cx.assert_editor_state(indoc! {"
 3228        a
 3229           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3230    "});
 3231}
 3232
 3233#[gpui::test]
 3234async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3235    init_test(cx, |_| {});
 3236
 3237    let mut cx = EditorTestContext::new(cx).await;
 3238    let language = Arc::new(
 3239        Language::new(
 3240            LanguageConfig::default(),
 3241            Some(tree_sitter_rust::LANGUAGE.into()),
 3242        )
 3243        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3244        .unwrap(),
 3245    );
 3246    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3247
 3248    // test when all cursors are not at suggested indent
 3249    // then simply move to their suggested indent location
 3250    cx.set_state(indoc! {"
 3251        const a: B = (
 3252            c(
 3253        ˇ
 3254        ˇ    )
 3255        );
 3256    "});
 3257    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3258    cx.assert_editor_state(indoc! {"
 3259        const a: B = (
 3260            c(
 3261                ˇ
 3262            ˇ)
 3263        );
 3264    "});
 3265
 3266    // test cursor already at suggested indent not moving when
 3267    // other cursors are yet to reach their suggested indents
 3268    cx.set_state(indoc! {"
 3269        ˇ
 3270        const a: B = (
 3271            c(
 3272                d(
 3273        ˇ
 3274                )
 3275        ˇ
 3276        ˇ    )
 3277        );
 3278    "});
 3279    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3280    cx.assert_editor_state(indoc! {"
 3281        ˇ
 3282        const a: B = (
 3283            c(
 3284                d(
 3285                    ˇ
 3286                )
 3287                ˇ
 3288            ˇ)
 3289        );
 3290    "});
 3291    // test when all cursors are at suggested indent then tab is inserted
 3292    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3293    cx.assert_editor_state(indoc! {"
 3294            ˇ
 3295        const a: B = (
 3296            c(
 3297                d(
 3298                        ˇ
 3299                )
 3300                    ˇ
 3301                ˇ)
 3302        );
 3303    "});
 3304
 3305    // test when current indent is less than suggested indent,
 3306    // we adjust line to match suggested indent and move cursor to it
 3307    //
 3308    // when no other cursor is at word boundary, all of them should move
 3309    cx.set_state(indoc! {"
 3310        const a: B = (
 3311            c(
 3312                d(
 3313        ˇ
 3314        ˇ   )
 3315        ˇ   )
 3316        );
 3317    "});
 3318    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3319    cx.assert_editor_state(indoc! {"
 3320        const a: B = (
 3321            c(
 3322                d(
 3323                    ˇ
 3324                ˇ)
 3325            ˇ)
 3326        );
 3327    "});
 3328
 3329    // test when current indent is less than suggested indent,
 3330    // we adjust line to match suggested indent and move cursor to it
 3331    //
 3332    // when some other cursor is at word boundary, it should not move
 3333    cx.set_state(indoc! {"
 3334        const a: B = (
 3335            c(
 3336                d(
 3337        ˇ
 3338        ˇ   )
 3339           ˇ)
 3340        );
 3341    "});
 3342    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3343    cx.assert_editor_state(indoc! {"
 3344        const a: B = (
 3345            c(
 3346                d(
 3347                    ˇ
 3348                ˇ)
 3349            ˇ)
 3350        );
 3351    "});
 3352
 3353    // test when current indent is more than suggested indent,
 3354    // we just move cursor to current indent instead of suggested indent
 3355    //
 3356    // when no other cursor is at word boundary, all of them should move
 3357    cx.set_state(indoc! {"
 3358        const a: B = (
 3359            c(
 3360                d(
 3361        ˇ
 3362        ˇ                )
 3363        ˇ   )
 3364        );
 3365    "});
 3366    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3367    cx.assert_editor_state(indoc! {"
 3368        const a: B = (
 3369            c(
 3370                d(
 3371                    ˇ
 3372                        ˇ)
 3373            ˇ)
 3374        );
 3375    "});
 3376    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3377    cx.assert_editor_state(indoc! {"
 3378        const a: B = (
 3379            c(
 3380                d(
 3381                        ˇ
 3382                            ˇ)
 3383                ˇ)
 3384        );
 3385    "});
 3386
 3387    // test when current indent is more than suggested indent,
 3388    // we just move cursor to current indent instead of suggested indent
 3389    //
 3390    // when some other cursor is at word boundary, it doesn't move
 3391    cx.set_state(indoc! {"
 3392        const a: B = (
 3393            c(
 3394                d(
 3395        ˇ
 3396        ˇ                )
 3397            ˇ)
 3398        );
 3399    "});
 3400    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3401    cx.assert_editor_state(indoc! {"
 3402        const a: B = (
 3403            c(
 3404                d(
 3405                    ˇ
 3406                        ˇ)
 3407            ˇ)
 3408        );
 3409    "});
 3410
 3411    // handle auto-indent when there are multiple cursors on the same line
 3412    cx.set_state(indoc! {"
 3413        const a: B = (
 3414            c(
 3415        ˇ    ˇ
 3416        ˇ    )
 3417        );
 3418    "});
 3419    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3420    cx.assert_editor_state(indoc! {"
 3421        const a: B = (
 3422            c(
 3423                ˇ
 3424            ˇ)
 3425        );
 3426    "});
 3427}
 3428
 3429#[gpui::test]
 3430async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3431    init_test(cx, |settings| {
 3432        settings.defaults.tab_size = NonZeroU32::new(3)
 3433    });
 3434
 3435    let mut cx = EditorTestContext::new(cx).await;
 3436    cx.set_state(indoc! {"
 3437         ˇ
 3438        \t ˇ
 3439        \t  ˇ
 3440        \t   ˇ
 3441         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3442    "});
 3443
 3444    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3445    cx.assert_editor_state(indoc! {"
 3446           ˇ
 3447        \t   ˇ
 3448        \t   ˇ
 3449        \t      ˇ
 3450         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3451    "});
 3452}
 3453
 3454#[gpui::test]
 3455async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3456    init_test(cx, |settings| {
 3457        settings.defaults.tab_size = NonZeroU32::new(4)
 3458    });
 3459
 3460    let language = Arc::new(
 3461        Language::new(
 3462            LanguageConfig::default(),
 3463            Some(tree_sitter_rust::LANGUAGE.into()),
 3464        )
 3465        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3466        .unwrap(),
 3467    );
 3468
 3469    let mut cx = EditorTestContext::new(cx).await;
 3470    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3471    cx.set_state(indoc! {"
 3472        fn a() {
 3473            if b {
 3474        \t ˇc
 3475            }
 3476        }
 3477    "});
 3478
 3479    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3480    cx.assert_editor_state(indoc! {"
 3481        fn a() {
 3482            if b {
 3483                ˇc
 3484            }
 3485        }
 3486    "});
 3487}
 3488
 3489#[gpui::test]
 3490async fn test_indent_outdent(cx: &mut TestAppContext) {
 3491    init_test(cx, |settings| {
 3492        settings.defaults.tab_size = NonZeroU32::new(4);
 3493    });
 3494
 3495    let mut cx = EditorTestContext::new(cx).await;
 3496
 3497    cx.set_state(indoc! {"
 3498          «oneˇ» «twoˇ»
 3499        three
 3500         four
 3501    "});
 3502    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3503    cx.assert_editor_state(indoc! {"
 3504            «oneˇ» «twoˇ»
 3505        three
 3506         four
 3507    "});
 3508
 3509    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3510    cx.assert_editor_state(indoc! {"
 3511        «oneˇ» «twoˇ»
 3512        three
 3513         four
 3514    "});
 3515
 3516    // select across line ending
 3517    cx.set_state(indoc! {"
 3518        one two
 3519        t«hree
 3520        ˇ» four
 3521    "});
 3522    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3523    cx.assert_editor_state(indoc! {"
 3524        one two
 3525            t«hree
 3526        ˇ» four
 3527    "});
 3528
 3529    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3530    cx.assert_editor_state(indoc! {"
 3531        one two
 3532        t«hree
 3533        ˇ» four
 3534    "});
 3535
 3536    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3537    cx.set_state(indoc! {"
 3538        one two
 3539        ˇthree
 3540            four
 3541    "});
 3542    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3543    cx.assert_editor_state(indoc! {"
 3544        one two
 3545            ˇthree
 3546            four
 3547    "});
 3548
 3549    cx.set_state(indoc! {"
 3550        one two
 3551        ˇ    three
 3552            four
 3553    "});
 3554    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3555    cx.assert_editor_state(indoc! {"
 3556        one two
 3557        ˇthree
 3558            four
 3559    "});
 3560}
 3561
 3562#[gpui::test]
 3563async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3564    // This is a regression test for issue #33761
 3565    init_test(cx, |_| {});
 3566
 3567    let mut cx = EditorTestContext::new(cx).await;
 3568    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3569    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3570
 3571    cx.set_state(
 3572        r#"ˇ#     ingress:
 3573ˇ#         api:
 3574ˇ#             enabled: false
 3575ˇ#             pathType: Prefix
 3576ˇ#           console:
 3577ˇ#               enabled: false
 3578ˇ#               pathType: Prefix
 3579"#,
 3580    );
 3581
 3582    // Press tab to indent all lines
 3583    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3584
 3585    cx.assert_editor_state(
 3586        r#"    ˇ#     ingress:
 3587    ˇ#         api:
 3588    ˇ#             enabled: false
 3589    ˇ#             pathType: Prefix
 3590    ˇ#           console:
 3591    ˇ#               enabled: false
 3592    ˇ#               pathType: Prefix
 3593"#,
 3594    );
 3595}
 3596
 3597#[gpui::test]
 3598async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3599    // This is a test to make sure our fix for issue #33761 didn't break anything
 3600    init_test(cx, |_| {});
 3601
 3602    let mut cx = EditorTestContext::new(cx).await;
 3603    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3604    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3605
 3606    cx.set_state(
 3607        r#"ˇingress:
 3608ˇ  api:
 3609ˇ    enabled: false
 3610ˇ    pathType: Prefix
 3611"#,
 3612    );
 3613
 3614    // Press tab to indent all lines
 3615    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3616
 3617    cx.assert_editor_state(
 3618        r#"ˇingress:
 3619    ˇapi:
 3620        ˇenabled: false
 3621        ˇpathType: Prefix
 3622"#,
 3623    );
 3624}
 3625
 3626#[gpui::test]
 3627async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3628    init_test(cx, |settings| {
 3629        settings.defaults.hard_tabs = Some(true);
 3630    });
 3631
 3632    let mut cx = EditorTestContext::new(cx).await;
 3633
 3634    // select two ranges on one line
 3635    cx.set_state(indoc! {"
 3636        «oneˇ» «twoˇ»
 3637        three
 3638        four
 3639    "});
 3640    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3641    cx.assert_editor_state(indoc! {"
 3642        \t«oneˇ» «twoˇ»
 3643        three
 3644        four
 3645    "});
 3646    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3647    cx.assert_editor_state(indoc! {"
 3648        \t\t«oneˇ» «twoˇ»
 3649        three
 3650        four
 3651    "});
 3652    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3653    cx.assert_editor_state(indoc! {"
 3654        \t«oneˇ» «twoˇ»
 3655        three
 3656        four
 3657    "});
 3658    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3659    cx.assert_editor_state(indoc! {"
 3660        «oneˇ» «twoˇ»
 3661        three
 3662        four
 3663    "});
 3664
 3665    // select across a line ending
 3666    cx.set_state(indoc! {"
 3667        one two
 3668        t«hree
 3669        ˇ»four
 3670    "});
 3671    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3672    cx.assert_editor_state(indoc! {"
 3673        one two
 3674        \tt«hree
 3675        ˇ»four
 3676    "});
 3677    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3678    cx.assert_editor_state(indoc! {"
 3679        one two
 3680        \t\tt«hree
 3681        ˇ»four
 3682    "});
 3683    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3684    cx.assert_editor_state(indoc! {"
 3685        one two
 3686        \tt«hree
 3687        ˇ»four
 3688    "});
 3689    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3690    cx.assert_editor_state(indoc! {"
 3691        one two
 3692        t«hree
 3693        ˇ»four
 3694    "});
 3695
 3696    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3697    cx.set_state(indoc! {"
 3698        one two
 3699        ˇthree
 3700        four
 3701    "});
 3702    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3703    cx.assert_editor_state(indoc! {"
 3704        one two
 3705        ˇthree
 3706        four
 3707    "});
 3708    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3709    cx.assert_editor_state(indoc! {"
 3710        one two
 3711        \tˇthree
 3712        four
 3713    "});
 3714    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3715    cx.assert_editor_state(indoc! {"
 3716        one two
 3717        ˇthree
 3718        four
 3719    "});
 3720}
 3721
 3722#[gpui::test]
 3723fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3724    init_test(cx, |settings| {
 3725        settings.languages.0.extend([
 3726            (
 3727                "TOML".into(),
 3728                LanguageSettingsContent {
 3729                    tab_size: NonZeroU32::new(2),
 3730                    ..Default::default()
 3731                },
 3732            ),
 3733            (
 3734                "Rust".into(),
 3735                LanguageSettingsContent {
 3736                    tab_size: NonZeroU32::new(4),
 3737                    ..Default::default()
 3738                },
 3739            ),
 3740        ]);
 3741    });
 3742
 3743    let toml_language = Arc::new(Language::new(
 3744        LanguageConfig {
 3745            name: "TOML".into(),
 3746            ..Default::default()
 3747        },
 3748        None,
 3749    ));
 3750    let rust_language = Arc::new(Language::new(
 3751        LanguageConfig {
 3752            name: "Rust".into(),
 3753            ..Default::default()
 3754        },
 3755        None,
 3756    ));
 3757
 3758    let toml_buffer =
 3759        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3760    let rust_buffer =
 3761        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3762    let multibuffer = cx.new(|cx| {
 3763        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3764        multibuffer.push_excerpts(
 3765            toml_buffer.clone(),
 3766            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3767            cx,
 3768        );
 3769        multibuffer.push_excerpts(
 3770            rust_buffer.clone(),
 3771            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3772            cx,
 3773        );
 3774        multibuffer
 3775    });
 3776
 3777    cx.add_window(|window, cx| {
 3778        let mut editor = build_editor(multibuffer, window, cx);
 3779
 3780        assert_eq!(
 3781            editor.text(cx),
 3782            indoc! {"
 3783                a = 1
 3784                b = 2
 3785
 3786                const c: usize = 3;
 3787            "}
 3788        );
 3789
 3790        select_ranges(
 3791            &mut editor,
 3792            indoc! {"
 3793                «aˇ» = 1
 3794                b = 2
 3795
 3796                «const c:ˇ» usize = 3;
 3797            "},
 3798            window,
 3799            cx,
 3800        );
 3801
 3802        editor.tab(&Tab, window, cx);
 3803        assert_text_with_selections(
 3804            &mut editor,
 3805            indoc! {"
 3806                  «aˇ» = 1
 3807                b = 2
 3808
 3809                    «const c:ˇ» usize = 3;
 3810            "},
 3811            cx,
 3812        );
 3813        editor.backtab(&Backtab, window, cx);
 3814        assert_text_with_selections(
 3815            &mut editor,
 3816            indoc! {"
 3817                «aˇ» = 1
 3818                b = 2
 3819
 3820                «const c:ˇ» usize = 3;
 3821            "},
 3822            cx,
 3823        );
 3824
 3825        editor
 3826    });
 3827}
 3828
 3829#[gpui::test]
 3830async fn test_backspace(cx: &mut TestAppContext) {
 3831    init_test(cx, |_| {});
 3832
 3833    let mut cx = EditorTestContext::new(cx).await;
 3834
 3835    // Basic backspace
 3836    cx.set_state(indoc! {"
 3837        onˇe two three
 3838        fou«rˇ» five six
 3839        seven «ˇeight nine
 3840        »ten
 3841    "});
 3842    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3843    cx.assert_editor_state(indoc! {"
 3844        oˇe two three
 3845        fouˇ five six
 3846        seven ˇten
 3847    "});
 3848
 3849    // Test backspace inside and around indents
 3850    cx.set_state(indoc! {"
 3851        zero
 3852            ˇone
 3853                ˇtwo
 3854            ˇ ˇ ˇ  three
 3855        ˇ  ˇ  four
 3856    "});
 3857    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3858    cx.assert_editor_state(indoc! {"
 3859        zero
 3860        ˇone
 3861            ˇtwo
 3862        ˇ  threeˇ  four
 3863    "});
 3864}
 3865
 3866#[gpui::test]
 3867async fn test_delete(cx: &mut TestAppContext) {
 3868    init_test(cx, |_| {});
 3869
 3870    let mut cx = EditorTestContext::new(cx).await;
 3871    cx.set_state(indoc! {"
 3872        onˇe two three
 3873        fou«rˇ» five six
 3874        seven «ˇeight nine
 3875        »ten
 3876    "});
 3877    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3878    cx.assert_editor_state(indoc! {"
 3879        onˇ two three
 3880        fouˇ five six
 3881        seven ˇten
 3882    "});
 3883}
 3884
 3885#[gpui::test]
 3886fn test_delete_line(cx: &mut TestAppContext) {
 3887    init_test(cx, |_| {});
 3888
 3889    let editor = cx.add_window(|window, cx| {
 3890        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3891        build_editor(buffer, window, cx)
 3892    });
 3893    _ = editor.update(cx, |editor, window, cx| {
 3894        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3895            s.select_display_ranges([
 3896                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3897                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3898                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3899            ])
 3900        });
 3901        editor.delete_line(&DeleteLine, window, cx);
 3902        assert_eq!(editor.display_text(cx), "ghi");
 3903        assert_eq!(
 3904            editor.selections.display_ranges(cx),
 3905            vec![
 3906                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3907                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3908            ]
 3909        );
 3910    });
 3911
 3912    let editor = cx.add_window(|window, cx| {
 3913        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3914        build_editor(buffer, window, cx)
 3915    });
 3916    _ = editor.update(cx, |editor, window, cx| {
 3917        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3918            s.select_display_ranges([
 3919                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3920            ])
 3921        });
 3922        editor.delete_line(&DeleteLine, window, cx);
 3923        assert_eq!(editor.display_text(cx), "ghi\n");
 3924        assert_eq!(
 3925            editor.selections.display_ranges(cx),
 3926            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3927        );
 3928    });
 3929}
 3930
 3931#[gpui::test]
 3932fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3933    init_test(cx, |_| {});
 3934
 3935    cx.add_window(|window, cx| {
 3936        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3937        let mut editor = build_editor(buffer.clone(), window, cx);
 3938        let buffer = buffer.read(cx).as_singleton().unwrap();
 3939
 3940        assert_eq!(
 3941            editor.selections.ranges::<Point>(cx),
 3942            &[Point::new(0, 0)..Point::new(0, 0)]
 3943        );
 3944
 3945        // When on single line, replace newline at end by space
 3946        editor.join_lines(&JoinLines, window, cx);
 3947        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3948        assert_eq!(
 3949            editor.selections.ranges::<Point>(cx),
 3950            &[Point::new(0, 3)..Point::new(0, 3)]
 3951        );
 3952
 3953        // When multiple lines are selected, remove newlines that are spanned by the selection
 3954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3955            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3956        });
 3957        editor.join_lines(&JoinLines, window, cx);
 3958        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3959        assert_eq!(
 3960            editor.selections.ranges::<Point>(cx),
 3961            &[Point::new(0, 11)..Point::new(0, 11)]
 3962        );
 3963
 3964        // Undo should be transactional
 3965        editor.undo(&Undo, window, cx);
 3966        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3967        assert_eq!(
 3968            editor.selections.ranges::<Point>(cx),
 3969            &[Point::new(0, 5)..Point::new(2, 2)]
 3970        );
 3971
 3972        // When joining an empty line don't insert a space
 3973        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3974            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3975        });
 3976        editor.join_lines(&JoinLines, window, cx);
 3977        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3978        assert_eq!(
 3979            editor.selections.ranges::<Point>(cx),
 3980            [Point::new(2, 3)..Point::new(2, 3)]
 3981        );
 3982
 3983        // We can remove trailing newlines
 3984        editor.join_lines(&JoinLines, window, cx);
 3985        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3986        assert_eq!(
 3987            editor.selections.ranges::<Point>(cx),
 3988            [Point::new(2, 3)..Point::new(2, 3)]
 3989        );
 3990
 3991        // We don't blow up on the last line
 3992        editor.join_lines(&JoinLines, window, cx);
 3993        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3994        assert_eq!(
 3995            editor.selections.ranges::<Point>(cx),
 3996            [Point::new(2, 3)..Point::new(2, 3)]
 3997        );
 3998
 3999        // reset to test indentation
 4000        editor.buffer.update(cx, |buffer, cx| {
 4001            buffer.edit(
 4002                [
 4003                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4004                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4005                ],
 4006                None,
 4007                cx,
 4008            )
 4009        });
 4010
 4011        // We remove any leading spaces
 4012        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4013        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4014            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4015        });
 4016        editor.join_lines(&JoinLines, window, cx);
 4017        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4018
 4019        // We don't insert a space for a line containing only spaces
 4020        editor.join_lines(&JoinLines, window, cx);
 4021        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4022
 4023        // We ignore any leading tabs
 4024        editor.join_lines(&JoinLines, window, cx);
 4025        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4026
 4027        editor
 4028    });
 4029}
 4030
 4031#[gpui::test]
 4032fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4033    init_test(cx, |_| {});
 4034
 4035    cx.add_window(|window, cx| {
 4036        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4037        let mut editor = build_editor(buffer.clone(), window, cx);
 4038        let buffer = buffer.read(cx).as_singleton().unwrap();
 4039
 4040        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4041            s.select_ranges([
 4042                Point::new(0, 2)..Point::new(1, 1),
 4043                Point::new(1, 2)..Point::new(1, 2),
 4044                Point::new(3, 1)..Point::new(3, 2),
 4045            ])
 4046        });
 4047
 4048        editor.join_lines(&JoinLines, window, cx);
 4049        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4050
 4051        assert_eq!(
 4052            editor.selections.ranges::<Point>(cx),
 4053            [
 4054                Point::new(0, 7)..Point::new(0, 7),
 4055                Point::new(1, 3)..Point::new(1, 3)
 4056            ]
 4057        );
 4058        editor
 4059    });
 4060}
 4061
 4062#[gpui::test]
 4063async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4064    init_test(cx, |_| {});
 4065
 4066    let mut cx = EditorTestContext::new(cx).await;
 4067
 4068    let diff_base = r#"
 4069        Line 0
 4070        Line 1
 4071        Line 2
 4072        Line 3
 4073        "#
 4074    .unindent();
 4075
 4076    cx.set_state(
 4077        &r#"
 4078        ˇLine 0
 4079        Line 1
 4080        Line 2
 4081        Line 3
 4082        "#
 4083        .unindent(),
 4084    );
 4085
 4086    cx.set_head_text(&diff_base);
 4087    executor.run_until_parked();
 4088
 4089    // Join lines
 4090    cx.update_editor(|editor, window, cx| {
 4091        editor.join_lines(&JoinLines, window, cx);
 4092    });
 4093    executor.run_until_parked();
 4094
 4095    cx.assert_editor_state(
 4096        &r#"
 4097        Line 0ˇ Line 1
 4098        Line 2
 4099        Line 3
 4100        "#
 4101        .unindent(),
 4102    );
 4103    // Join again
 4104    cx.update_editor(|editor, window, cx| {
 4105        editor.join_lines(&JoinLines, window, cx);
 4106    });
 4107    executor.run_until_parked();
 4108
 4109    cx.assert_editor_state(
 4110        &r#"
 4111        Line 0 Line 1ˇ Line 2
 4112        Line 3
 4113        "#
 4114        .unindent(),
 4115    );
 4116}
 4117
 4118#[gpui::test]
 4119async fn test_custom_newlines_cause_no_false_positive_diffs(
 4120    executor: BackgroundExecutor,
 4121    cx: &mut TestAppContext,
 4122) {
 4123    init_test(cx, |_| {});
 4124    let mut cx = EditorTestContext::new(cx).await;
 4125    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4126    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4127    executor.run_until_parked();
 4128
 4129    cx.update_editor(|editor, window, cx| {
 4130        let snapshot = editor.snapshot(window, cx);
 4131        assert_eq!(
 4132            snapshot
 4133                .buffer_snapshot
 4134                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4135                .collect::<Vec<_>>(),
 4136            Vec::new(),
 4137            "Should not have any diffs for files with custom newlines"
 4138        );
 4139    });
 4140}
 4141
 4142#[gpui::test]
 4143async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4144    init_test(cx, |_| {});
 4145
 4146    let mut cx = EditorTestContext::new(cx).await;
 4147
 4148    // Test sort_lines_case_insensitive()
 4149    cx.set_state(indoc! {"
 4150        «z
 4151        y
 4152        x
 4153        Z
 4154        Y
 4155        Xˇ»
 4156    "});
 4157    cx.update_editor(|e, window, cx| {
 4158        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4159    });
 4160    cx.assert_editor_state(indoc! {"
 4161        «x
 4162        X
 4163        y
 4164        Y
 4165        z
 4166        Zˇ»
 4167    "});
 4168
 4169    // Test sort_lines_by_length()
 4170    //
 4171    // Demonstrates:
 4172    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4173    // - sort is stable
 4174    cx.set_state(indoc! {"
 4175        «123
 4176        æ
 4177        12
 4178 4179        1
 4180        æˇ»
 4181    "});
 4182    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4183    cx.assert_editor_state(indoc! {"
 4184        «æ
 4185 4186        1
 4187        æ
 4188        12
 4189        123ˇ»
 4190    "});
 4191
 4192    // Test reverse_lines()
 4193    cx.set_state(indoc! {"
 4194        «5
 4195        4
 4196        3
 4197        2
 4198        1ˇ»
 4199    "});
 4200    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4201    cx.assert_editor_state(indoc! {"
 4202        «1
 4203        2
 4204        3
 4205        4
 4206        5ˇ»
 4207    "});
 4208
 4209    // Skip testing shuffle_line()
 4210
 4211    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4212    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4213
 4214    // Don't manipulate when cursor is on single line, but expand the selection
 4215    cx.set_state(indoc! {"
 4216        ddˇdd
 4217        ccc
 4218        bb
 4219        a
 4220    "});
 4221    cx.update_editor(|e, window, cx| {
 4222        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4223    });
 4224    cx.assert_editor_state(indoc! {"
 4225        «ddddˇ»
 4226        ccc
 4227        bb
 4228        a
 4229    "});
 4230
 4231    // Basic manipulate case
 4232    // Start selection moves to column 0
 4233    // End of selection shrinks to fit shorter line
 4234    cx.set_state(indoc! {"
 4235        dd«d
 4236        ccc
 4237        bb
 4238        aaaaaˇ»
 4239    "});
 4240    cx.update_editor(|e, window, cx| {
 4241        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4242    });
 4243    cx.assert_editor_state(indoc! {"
 4244        «aaaaa
 4245        bb
 4246        ccc
 4247        dddˇ»
 4248    "});
 4249
 4250    // Manipulate case with newlines
 4251    cx.set_state(indoc! {"
 4252        dd«d
 4253        ccc
 4254
 4255        bb
 4256        aaaaa
 4257
 4258        ˇ»
 4259    "});
 4260    cx.update_editor(|e, window, cx| {
 4261        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4262    });
 4263    cx.assert_editor_state(indoc! {"
 4264        «
 4265
 4266        aaaaa
 4267        bb
 4268        ccc
 4269        dddˇ»
 4270
 4271    "});
 4272
 4273    // Adding new line
 4274    cx.set_state(indoc! {"
 4275        aa«a
 4276        bbˇ»b
 4277    "});
 4278    cx.update_editor(|e, window, cx| {
 4279        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4280    });
 4281    cx.assert_editor_state(indoc! {"
 4282        «aaa
 4283        bbb
 4284        added_lineˇ»
 4285    "});
 4286
 4287    // Removing line
 4288    cx.set_state(indoc! {"
 4289        aa«a
 4290        bbbˇ»
 4291    "});
 4292    cx.update_editor(|e, window, cx| {
 4293        e.manipulate_immutable_lines(window, cx, |lines| {
 4294            lines.pop();
 4295        })
 4296    });
 4297    cx.assert_editor_state(indoc! {"
 4298        «aaaˇ»
 4299    "});
 4300
 4301    // Removing all lines
 4302    cx.set_state(indoc! {"
 4303        aa«a
 4304        bbbˇ»
 4305    "});
 4306    cx.update_editor(|e, window, cx| {
 4307        e.manipulate_immutable_lines(window, cx, |lines| {
 4308            lines.drain(..);
 4309        })
 4310    });
 4311    cx.assert_editor_state(indoc! {"
 4312        ˇ
 4313    "});
 4314}
 4315
 4316#[gpui::test]
 4317async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4318    init_test(cx, |_| {});
 4319
 4320    let mut cx = EditorTestContext::new(cx).await;
 4321
 4322    // Consider continuous selection as single selection
 4323    cx.set_state(indoc! {"
 4324        Aaa«aa
 4325        cˇ»c«c
 4326        bb
 4327        aaaˇ»aa
 4328    "});
 4329    cx.update_editor(|e, window, cx| {
 4330        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4331    });
 4332    cx.assert_editor_state(indoc! {"
 4333        «Aaaaa
 4334        ccc
 4335        bb
 4336        aaaaaˇ»
 4337    "});
 4338
 4339    cx.set_state(indoc! {"
 4340        Aaa«aa
 4341        cˇ»c«c
 4342        bb
 4343        aaaˇ»aa
 4344    "});
 4345    cx.update_editor(|e, window, cx| {
 4346        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4347    });
 4348    cx.assert_editor_state(indoc! {"
 4349        «Aaaaa
 4350        ccc
 4351        bbˇ»
 4352    "});
 4353
 4354    // Consider non continuous selection as distinct dedup operations
 4355    cx.set_state(indoc! {"
 4356        «aaaaa
 4357        bb
 4358        aaaaa
 4359        aaaaaˇ»
 4360
 4361        aaa«aaˇ»
 4362    "});
 4363    cx.update_editor(|e, window, cx| {
 4364        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4365    });
 4366    cx.assert_editor_state(indoc! {"
 4367        «aaaaa
 4368        bbˇ»
 4369
 4370        «aaaaaˇ»
 4371    "});
 4372}
 4373
 4374#[gpui::test]
 4375async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4376    init_test(cx, |_| {});
 4377
 4378    let mut cx = EditorTestContext::new(cx).await;
 4379
 4380    cx.set_state(indoc! {"
 4381        «Aaa
 4382        aAa
 4383        Aaaˇ»
 4384    "});
 4385    cx.update_editor(|e, window, cx| {
 4386        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4387    });
 4388    cx.assert_editor_state(indoc! {"
 4389        «Aaa
 4390        aAaˇ»
 4391    "});
 4392
 4393    cx.set_state(indoc! {"
 4394        «Aaa
 4395        aAa
 4396        aaAˇ»
 4397    "});
 4398    cx.update_editor(|e, window, cx| {
 4399        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4400    });
 4401    cx.assert_editor_state(indoc! {"
 4402        «Aaaˇ»
 4403    "});
 4404}
 4405
 4406#[gpui::test]
 4407async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4408    init_test(cx, |_| {});
 4409
 4410    let mut cx = EditorTestContext::new(cx).await;
 4411
 4412    let js_language = Arc::new(Language::new(
 4413        LanguageConfig {
 4414            name: "JavaScript".into(),
 4415            wrap_characters: Some(language::WrapCharactersConfig {
 4416                start_prefix: "<".into(),
 4417                start_suffix: ">".into(),
 4418                end_prefix: "</".into(),
 4419                end_suffix: ">".into(),
 4420            }),
 4421            ..LanguageConfig::default()
 4422        },
 4423        None,
 4424    ));
 4425
 4426    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4427
 4428    cx.set_state(indoc! {"
 4429        «testˇ»
 4430    "});
 4431    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4432    cx.assert_editor_state(indoc! {"
 4433        <«ˇ»>test</«ˇ»>
 4434    "});
 4435
 4436    cx.set_state(indoc! {"
 4437        «test
 4438         testˇ»
 4439    "});
 4440    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4441    cx.assert_editor_state(indoc! {"
 4442        <«ˇ»>test
 4443         test</«ˇ»>
 4444    "});
 4445
 4446    cx.set_state(indoc! {"
 4447        teˇst
 4448    "});
 4449    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4450    cx.assert_editor_state(indoc! {"
 4451        te<«ˇ»></«ˇ»>st
 4452    "});
 4453}
 4454
 4455#[gpui::test]
 4456async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4457    init_test(cx, |_| {});
 4458
 4459    let mut cx = EditorTestContext::new(cx).await;
 4460
 4461    let js_language = Arc::new(Language::new(
 4462        LanguageConfig {
 4463            name: "JavaScript".into(),
 4464            wrap_characters: Some(language::WrapCharactersConfig {
 4465                start_prefix: "<".into(),
 4466                start_suffix: ">".into(),
 4467                end_prefix: "</".into(),
 4468                end_suffix: ">".into(),
 4469            }),
 4470            ..LanguageConfig::default()
 4471        },
 4472        None,
 4473    ));
 4474
 4475    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4476
 4477    cx.set_state(indoc! {"
 4478        «testˇ»
 4479        «testˇ» «testˇ»
 4480        «testˇ»
 4481    "});
 4482    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4483    cx.assert_editor_state(indoc! {"
 4484        <«ˇ»>test</«ˇ»>
 4485        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4486        <«ˇ»>test</«ˇ»>
 4487    "});
 4488
 4489    cx.set_state(indoc! {"
 4490        «test
 4491         testˇ»
 4492        «test
 4493         testˇ»
 4494    "});
 4495    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4496    cx.assert_editor_state(indoc! {"
 4497        <«ˇ»>test
 4498         test</«ˇ»>
 4499        <«ˇ»>test
 4500         test</«ˇ»>
 4501    "});
 4502}
 4503
 4504#[gpui::test]
 4505async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4506    init_test(cx, |_| {});
 4507
 4508    let mut cx = EditorTestContext::new(cx).await;
 4509
 4510    let plaintext_language = Arc::new(Language::new(
 4511        LanguageConfig {
 4512            name: "Plain Text".into(),
 4513            ..LanguageConfig::default()
 4514        },
 4515        None,
 4516    ));
 4517
 4518    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4519
 4520    cx.set_state(indoc! {"
 4521        «testˇ»
 4522    "});
 4523    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4524    cx.assert_editor_state(indoc! {"
 4525      «testˇ»
 4526    "});
 4527}
 4528
 4529#[gpui::test]
 4530async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4531    init_test(cx, |_| {});
 4532
 4533    let mut cx = EditorTestContext::new(cx).await;
 4534
 4535    // Manipulate with multiple selections on a single line
 4536    cx.set_state(indoc! {"
 4537        dd«dd
 4538        cˇ»c«c
 4539        bb
 4540        aaaˇ»aa
 4541    "});
 4542    cx.update_editor(|e, window, cx| {
 4543        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4544    });
 4545    cx.assert_editor_state(indoc! {"
 4546        «aaaaa
 4547        bb
 4548        ccc
 4549        ddddˇ»
 4550    "});
 4551
 4552    // Manipulate with multiple disjoin selections
 4553    cx.set_state(indoc! {"
 4554 4555        4
 4556        3
 4557        2
 4558        1ˇ»
 4559
 4560        dd«dd
 4561        ccc
 4562        bb
 4563        aaaˇ»aa
 4564    "});
 4565    cx.update_editor(|e, window, cx| {
 4566        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4567    });
 4568    cx.assert_editor_state(indoc! {"
 4569        «1
 4570        2
 4571        3
 4572        4
 4573        5ˇ»
 4574
 4575        «aaaaa
 4576        bb
 4577        ccc
 4578        ddddˇ»
 4579    "});
 4580
 4581    // Adding lines on each selection
 4582    cx.set_state(indoc! {"
 4583 4584        1ˇ»
 4585
 4586        bb«bb
 4587        aaaˇ»aa
 4588    "});
 4589    cx.update_editor(|e, window, cx| {
 4590        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4591    });
 4592    cx.assert_editor_state(indoc! {"
 4593        «2
 4594        1
 4595        added lineˇ»
 4596
 4597        «bbbb
 4598        aaaaa
 4599        added lineˇ»
 4600    "});
 4601
 4602    // Removing lines on each selection
 4603    cx.set_state(indoc! {"
 4604 4605        1ˇ»
 4606
 4607        bb«bb
 4608        aaaˇ»aa
 4609    "});
 4610    cx.update_editor(|e, window, cx| {
 4611        e.manipulate_immutable_lines(window, cx, |lines| {
 4612            lines.pop();
 4613        })
 4614    });
 4615    cx.assert_editor_state(indoc! {"
 4616        «2ˇ»
 4617
 4618        «bbbbˇ»
 4619    "});
 4620}
 4621
 4622#[gpui::test]
 4623async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4624    init_test(cx, |settings| {
 4625        settings.defaults.tab_size = NonZeroU32::new(3)
 4626    });
 4627
 4628    let mut cx = EditorTestContext::new(cx).await;
 4629
 4630    // MULTI SELECTION
 4631    // Ln.1 "«" tests empty lines
 4632    // Ln.9 tests just leading whitespace
 4633    cx.set_state(indoc! {"
 4634        «
 4635        abc                 // No indentationˇ»
 4636        «\tabc              // 1 tabˇ»
 4637        \t\tabc «      ˇ»   // 2 tabs
 4638        \t ab«c             // Tab followed by space
 4639         \tabc              // Space followed by tab (3 spaces should be the result)
 4640        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4641           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4642        \t
 4643        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4644    "});
 4645    cx.update_editor(|e, window, cx| {
 4646        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4647    });
 4648    cx.assert_editor_state(
 4649        indoc! {"
 4650            «
 4651            abc                 // No indentation
 4652               abc              // 1 tab
 4653                  abc          // 2 tabs
 4654                abc             // Tab followed by space
 4655               abc              // Space followed by tab (3 spaces should be the result)
 4656                           abc   // Mixed indentation (tab conversion depends on the column)
 4657               abc         // Already space indented
 4658               ·
 4659               abc\tdef          // Only the leading tab is manipulatedˇ»
 4660        "}
 4661        .replace("·", "")
 4662        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4663    );
 4664
 4665    // Test on just a few lines, the others should remain unchanged
 4666    // Only lines (3, 5, 10, 11) should change
 4667    cx.set_state(
 4668        indoc! {"
 4669            ·
 4670            abc                 // No indentation
 4671            \tabcˇ               // 1 tab
 4672            \t\tabc             // 2 tabs
 4673            \t abcˇ              // Tab followed by space
 4674             \tabc              // Space followed by tab (3 spaces should be the result)
 4675            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4676               abc              // Already space indented
 4677            «\t
 4678            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4679        "}
 4680        .replace("·", "")
 4681        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4682    );
 4683    cx.update_editor(|e, window, cx| {
 4684        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4685    });
 4686    cx.assert_editor_state(
 4687        indoc! {"
 4688            ·
 4689            abc                 // No indentation
 4690            «   abc               // 1 tabˇ»
 4691            \t\tabc             // 2 tabs
 4692            «    abc              // Tab followed by spaceˇ»
 4693             \tabc              // Space followed by tab (3 spaces should be the result)
 4694            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4695               abc              // Already space indented
 4696            «   ·
 4697               abc\tdef          // Only the leading tab is manipulatedˇ»
 4698        "}
 4699        .replace("·", "")
 4700        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4701    );
 4702
 4703    // SINGLE SELECTION
 4704    // Ln.1 "«" tests empty lines
 4705    // Ln.9 tests just leading whitespace
 4706    cx.set_state(indoc! {"
 4707        «
 4708        abc                 // No indentation
 4709        \tabc               // 1 tab
 4710        \t\tabc             // 2 tabs
 4711        \t abc              // Tab followed by space
 4712         \tabc              // Space followed by tab (3 spaces should be the result)
 4713        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4714           abc              // Already space indented
 4715        \t
 4716        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4717    "});
 4718    cx.update_editor(|e, window, cx| {
 4719        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4720    });
 4721    cx.assert_editor_state(
 4722        indoc! {"
 4723            «
 4724            abc                 // No indentation
 4725               abc               // 1 tab
 4726                  abc             // 2 tabs
 4727                abc              // Tab followed by space
 4728               abc              // Space followed by tab (3 spaces should be the result)
 4729                           abc   // Mixed indentation (tab conversion depends on the column)
 4730               abc              // Already space indented
 4731               ·
 4732               abc\tdef          // Only the leading tab is manipulatedˇ»
 4733        "}
 4734        .replace("·", "")
 4735        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4736    );
 4737}
 4738
 4739#[gpui::test]
 4740async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4741    init_test(cx, |settings| {
 4742        settings.defaults.tab_size = NonZeroU32::new(3)
 4743    });
 4744
 4745    let mut cx = EditorTestContext::new(cx).await;
 4746
 4747    // MULTI SELECTION
 4748    // Ln.1 "«" tests empty lines
 4749    // Ln.11 tests just leading whitespace
 4750    cx.set_state(indoc! {"
 4751        «
 4752        abˇ»ˇc                 // No indentation
 4753         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4754          abc  «             // 2 spaces (< 3 so dont convert)
 4755           abc              // 3 spaces (convert)
 4756             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4757        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4758        «\t abc              // Tab followed by space
 4759         \tabc              // Space followed by tab (should be consumed due to tab)
 4760        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4761           \tˇ»  «\t
 4762           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4763    "});
 4764    cx.update_editor(|e, window, cx| {
 4765        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4766    });
 4767    cx.assert_editor_state(indoc! {"
 4768        «
 4769        abc                 // No indentation
 4770         abc                // 1 space (< 3 so dont convert)
 4771          abc               // 2 spaces (< 3 so dont convert)
 4772        \tabc              // 3 spaces (convert)
 4773        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4774        \t\t\tabc           // Already tab indented
 4775        \t abc              // Tab followed by space
 4776        \tabc              // Space followed by tab (should be consumed due to tab)
 4777        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4778        \t\t\t
 4779        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4780    "});
 4781
 4782    // Test on just a few lines, the other should remain unchanged
 4783    // Only lines (4, 8, 11, 12) should change
 4784    cx.set_state(
 4785        indoc! {"
 4786            ·
 4787            abc                 // No indentation
 4788             abc                // 1 space (< 3 so dont convert)
 4789              abc               // 2 spaces (< 3 so dont convert)
 4790            «   abc              // 3 spaces (convert)ˇ»
 4791                 abc            // 5 spaces (1 tab + 2 spaces)
 4792            \t\t\tabc           // Already tab indented
 4793            \t abc              // Tab followed by space
 4794             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4795               \t\t  \tabc      // Mixed indentation
 4796            \t \t  \t   \tabc   // Mixed indentation
 4797               \t  \tˇ
 4798            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4799        "}
 4800        .replace("·", "")
 4801        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4802    );
 4803    cx.update_editor(|e, window, cx| {
 4804        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4805    });
 4806    cx.assert_editor_state(
 4807        indoc! {"
 4808            ·
 4809            abc                 // No indentation
 4810             abc                // 1 space (< 3 so dont convert)
 4811              abc               // 2 spaces (< 3 so dont convert)
 4812            «\tabc              // 3 spaces (convert)ˇ»
 4813                 abc            // 5 spaces (1 tab + 2 spaces)
 4814            \t\t\tabc           // Already tab indented
 4815            \t abc              // Tab followed by space
 4816            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4817               \t\t  \tabc      // Mixed indentation
 4818            \t \t  \t   \tabc   // Mixed indentation
 4819            «\t\t\t
 4820            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4821        "}
 4822        .replace("·", "")
 4823        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4824    );
 4825
 4826    // SINGLE SELECTION
 4827    // Ln.1 "«" tests empty lines
 4828    // Ln.11 tests just leading whitespace
 4829    cx.set_state(indoc! {"
 4830        «
 4831        abc                 // No indentation
 4832         abc                // 1 space (< 3 so dont convert)
 4833          abc               // 2 spaces (< 3 so dont convert)
 4834           abc              // 3 spaces (convert)
 4835             abc            // 5 spaces (1 tab + 2 spaces)
 4836        \t\t\tabc           // Already tab indented
 4837        \t abc              // Tab followed by space
 4838         \tabc              // Space followed by tab (should be consumed due to tab)
 4839        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4840           \t  \t
 4841           abc   \t         // Only the leading spaces should be convertedˇ»
 4842    "});
 4843    cx.update_editor(|e, window, cx| {
 4844        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4845    });
 4846    cx.assert_editor_state(indoc! {"
 4847        «
 4848        abc                 // No indentation
 4849         abc                // 1 space (< 3 so dont convert)
 4850          abc               // 2 spaces (< 3 so dont convert)
 4851        \tabc              // 3 spaces (convert)
 4852        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4853        \t\t\tabc           // Already tab indented
 4854        \t abc              // Tab followed by space
 4855        \tabc              // Space followed by tab (should be consumed due to tab)
 4856        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4857        \t\t\t
 4858        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4859    "});
 4860}
 4861
 4862#[gpui::test]
 4863async fn test_toggle_case(cx: &mut TestAppContext) {
 4864    init_test(cx, |_| {});
 4865
 4866    let mut cx = EditorTestContext::new(cx).await;
 4867
 4868    // If all lower case -> upper case
 4869    cx.set_state(indoc! {"
 4870        «hello worldˇ»
 4871    "});
 4872    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4873    cx.assert_editor_state(indoc! {"
 4874        «HELLO WORLDˇ»
 4875    "});
 4876
 4877    // If all upper case -> lower case
 4878    cx.set_state(indoc! {"
 4879        «HELLO WORLDˇ»
 4880    "});
 4881    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4882    cx.assert_editor_state(indoc! {"
 4883        «hello worldˇ»
 4884    "});
 4885
 4886    // If any upper case characters are identified -> lower case
 4887    // This matches JetBrains IDEs
 4888    cx.set_state(indoc! {"
 4889        «hEllo worldˇ»
 4890    "});
 4891    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4892    cx.assert_editor_state(indoc! {"
 4893        «hello worldˇ»
 4894    "});
 4895}
 4896
 4897#[gpui::test]
 4898async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 4899    init_test(cx, |_| {});
 4900
 4901    let mut cx = EditorTestContext::new(cx).await;
 4902
 4903    cx.set_state(indoc! {"
 4904        «implement-windows-supportˇ»
 4905    "});
 4906    cx.update_editor(|e, window, cx| {
 4907        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 4908    });
 4909    cx.assert_editor_state(indoc! {"
 4910        «Implement windows supportˇ»
 4911    "});
 4912}
 4913
 4914#[gpui::test]
 4915async fn test_manipulate_text(cx: &mut TestAppContext) {
 4916    init_test(cx, |_| {});
 4917
 4918    let mut cx = EditorTestContext::new(cx).await;
 4919
 4920    // Test convert_to_upper_case()
 4921    cx.set_state(indoc! {"
 4922        «hello worldˇ»
 4923    "});
 4924    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4925    cx.assert_editor_state(indoc! {"
 4926        «HELLO WORLDˇ»
 4927    "});
 4928
 4929    // Test convert_to_lower_case()
 4930    cx.set_state(indoc! {"
 4931        «HELLO WORLDˇ»
 4932    "});
 4933    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4934    cx.assert_editor_state(indoc! {"
 4935        «hello worldˇ»
 4936    "});
 4937
 4938    // Test multiple line, single selection case
 4939    cx.set_state(indoc! {"
 4940        «The quick brown
 4941        fox jumps over
 4942        the lazy dogˇ»
 4943    "});
 4944    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4945    cx.assert_editor_state(indoc! {"
 4946        «The Quick Brown
 4947        Fox Jumps Over
 4948        The Lazy Dogˇ»
 4949    "});
 4950
 4951    // Test multiple line, single selection case
 4952    cx.set_state(indoc! {"
 4953        «The quick brown
 4954        fox jumps over
 4955        the lazy dogˇ»
 4956    "});
 4957    cx.update_editor(|e, window, cx| {
 4958        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4959    });
 4960    cx.assert_editor_state(indoc! {"
 4961        «TheQuickBrown
 4962        FoxJumpsOver
 4963        TheLazyDogˇ»
 4964    "});
 4965
 4966    // From here on out, test more complex cases of manipulate_text()
 4967
 4968    // Test no selection case - should affect words cursors are in
 4969    // Cursor at beginning, middle, and end of word
 4970    cx.set_state(indoc! {"
 4971        ˇhello big beauˇtiful worldˇ
 4972    "});
 4973    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4974    cx.assert_editor_state(indoc! {"
 4975        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4976    "});
 4977
 4978    // Test multiple selections on a single line and across multiple lines
 4979    cx.set_state(indoc! {"
 4980        «Theˇ» quick «brown
 4981        foxˇ» jumps «overˇ»
 4982        the «lazyˇ» dog
 4983    "});
 4984    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4985    cx.assert_editor_state(indoc! {"
 4986        «THEˇ» quick «BROWN
 4987        FOXˇ» jumps «OVERˇ»
 4988        the «LAZYˇ» dog
 4989    "});
 4990
 4991    // Test case where text length grows
 4992    cx.set_state(indoc! {"
 4993        «tschüߡ»
 4994    "});
 4995    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4996    cx.assert_editor_state(indoc! {"
 4997        «TSCHÜSSˇ»
 4998    "});
 4999
 5000    // Test to make sure we don't crash when text shrinks
 5001    cx.set_state(indoc! {"
 5002        aaa_bbbˇ
 5003    "});
 5004    cx.update_editor(|e, window, cx| {
 5005        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5006    });
 5007    cx.assert_editor_state(indoc! {"
 5008        «aaaBbbˇ»
 5009    "});
 5010
 5011    // Test to make sure we all aware of the fact that each word can grow and shrink
 5012    // Final selections should be aware of this fact
 5013    cx.set_state(indoc! {"
 5014        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5015    "});
 5016    cx.update_editor(|e, window, cx| {
 5017        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5018    });
 5019    cx.assert_editor_state(indoc! {"
 5020        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5021    "});
 5022
 5023    cx.set_state(indoc! {"
 5024        «hElLo, WoRld!ˇ»
 5025    "});
 5026    cx.update_editor(|e, window, cx| {
 5027        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5028    });
 5029    cx.assert_editor_state(indoc! {"
 5030        «HeLlO, wOrLD!ˇ»
 5031    "});
 5032}
 5033
 5034#[gpui::test]
 5035fn test_duplicate_line(cx: &mut TestAppContext) {
 5036    init_test(cx, |_| {});
 5037
 5038    let editor = cx.add_window(|window, cx| {
 5039        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5040        build_editor(buffer, window, cx)
 5041    });
 5042    _ = editor.update(cx, |editor, window, cx| {
 5043        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5044            s.select_display_ranges([
 5045                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5046                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5047                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5048                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5049            ])
 5050        });
 5051        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5052        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5053        assert_eq!(
 5054            editor.selections.display_ranges(cx),
 5055            vec![
 5056                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5057                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5058                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5059                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5060            ]
 5061        );
 5062    });
 5063
 5064    let editor = cx.add_window(|window, cx| {
 5065        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5066        build_editor(buffer, window, cx)
 5067    });
 5068    _ = editor.update(cx, |editor, window, cx| {
 5069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5070            s.select_display_ranges([
 5071                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5072                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5073            ])
 5074        });
 5075        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5076        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5077        assert_eq!(
 5078            editor.selections.display_ranges(cx),
 5079            vec![
 5080                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5081                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5082            ]
 5083        );
 5084    });
 5085
 5086    // With `move_upwards` the selections stay in place, except for
 5087    // the lines inserted above them
 5088    let editor = cx.add_window(|window, cx| {
 5089        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5090        build_editor(buffer, window, cx)
 5091    });
 5092    _ = editor.update(cx, |editor, window, cx| {
 5093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5094            s.select_display_ranges([
 5095                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5096                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5097                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5098                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5099            ])
 5100        });
 5101        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5102        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5103        assert_eq!(
 5104            editor.selections.display_ranges(cx),
 5105            vec![
 5106                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5107                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5108                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5109                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5110            ]
 5111        );
 5112    });
 5113
 5114    let editor = cx.add_window(|window, cx| {
 5115        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5116        build_editor(buffer, window, cx)
 5117    });
 5118    _ = editor.update(cx, |editor, window, cx| {
 5119        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5120            s.select_display_ranges([
 5121                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5122                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5123            ])
 5124        });
 5125        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5126        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5127        assert_eq!(
 5128            editor.selections.display_ranges(cx),
 5129            vec![
 5130                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5131                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5132            ]
 5133        );
 5134    });
 5135
 5136    let editor = cx.add_window(|window, cx| {
 5137        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5138        build_editor(buffer, window, cx)
 5139    });
 5140    _ = editor.update(cx, |editor, window, cx| {
 5141        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5142            s.select_display_ranges([
 5143                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5144                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5145            ])
 5146        });
 5147        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5148        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5149        assert_eq!(
 5150            editor.selections.display_ranges(cx),
 5151            vec![
 5152                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5153                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5154            ]
 5155        );
 5156    });
 5157}
 5158
 5159#[gpui::test]
 5160fn test_move_line_up_down(cx: &mut TestAppContext) {
 5161    init_test(cx, |_| {});
 5162
 5163    let editor = cx.add_window(|window, cx| {
 5164        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5165        build_editor(buffer, window, cx)
 5166    });
 5167    _ = editor.update(cx, |editor, window, cx| {
 5168        editor.fold_creases(
 5169            vec![
 5170                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5171                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5172                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5173            ],
 5174            true,
 5175            window,
 5176            cx,
 5177        );
 5178        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5179            s.select_display_ranges([
 5180                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5181                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5182                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5183                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5184            ])
 5185        });
 5186        assert_eq!(
 5187            editor.display_text(cx),
 5188            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5189        );
 5190
 5191        editor.move_line_up(&MoveLineUp, window, cx);
 5192        assert_eq!(
 5193            editor.display_text(cx),
 5194            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5195        );
 5196        assert_eq!(
 5197            editor.selections.display_ranges(cx),
 5198            vec![
 5199                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5200                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5201                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5202                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5203            ]
 5204        );
 5205    });
 5206
 5207    _ = editor.update(cx, |editor, window, cx| {
 5208        editor.move_line_down(&MoveLineDown, window, cx);
 5209        assert_eq!(
 5210            editor.display_text(cx),
 5211            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5212        );
 5213        assert_eq!(
 5214            editor.selections.display_ranges(cx),
 5215            vec![
 5216                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5217                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5218                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5219                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5220            ]
 5221        );
 5222    });
 5223
 5224    _ = editor.update(cx, |editor, window, cx| {
 5225        editor.move_line_down(&MoveLineDown, window, cx);
 5226        assert_eq!(
 5227            editor.display_text(cx),
 5228            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5229        );
 5230        assert_eq!(
 5231            editor.selections.display_ranges(cx),
 5232            vec![
 5233                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5234                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5235                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5236                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5237            ]
 5238        );
 5239    });
 5240
 5241    _ = editor.update(cx, |editor, window, cx| {
 5242        editor.move_line_up(&MoveLineUp, window, cx);
 5243        assert_eq!(
 5244            editor.display_text(cx),
 5245            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5246        );
 5247        assert_eq!(
 5248            editor.selections.display_ranges(cx),
 5249            vec![
 5250                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5251                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5252                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5253                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5254            ]
 5255        );
 5256    });
 5257}
 5258
 5259#[gpui::test]
 5260fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5261    init_test(cx, |_| {});
 5262    let editor = cx.add_window(|window, cx| {
 5263        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5264        build_editor(buffer, window, cx)
 5265    });
 5266    _ = editor.update(cx, |editor, window, cx| {
 5267        editor.fold_creases(
 5268            vec![Crease::simple(
 5269                Point::new(6, 4)..Point::new(7, 4),
 5270                FoldPlaceholder::test(),
 5271            )],
 5272            true,
 5273            window,
 5274            cx,
 5275        );
 5276        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5277            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5278        });
 5279        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5280        editor.move_line_up(&MoveLineUp, window, cx);
 5281        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5282        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5283    });
 5284}
 5285
 5286#[gpui::test]
 5287fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5288    init_test(cx, |_| {});
 5289
 5290    let editor = cx.add_window(|window, cx| {
 5291        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5292        build_editor(buffer, window, cx)
 5293    });
 5294    _ = editor.update(cx, |editor, window, cx| {
 5295        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5296        editor.insert_blocks(
 5297            [BlockProperties {
 5298                style: BlockStyle::Fixed,
 5299                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5300                height: Some(1),
 5301                render: Arc::new(|_| div().into_any()),
 5302                priority: 0,
 5303            }],
 5304            Some(Autoscroll::fit()),
 5305            cx,
 5306        );
 5307        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5308            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5309        });
 5310        editor.move_line_down(&MoveLineDown, window, cx);
 5311    });
 5312}
 5313
 5314#[gpui::test]
 5315async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5316    init_test(cx, |_| {});
 5317
 5318    let mut cx = EditorTestContext::new(cx).await;
 5319    cx.set_state(
 5320        &"
 5321            ˇzero
 5322            one
 5323            two
 5324            three
 5325            four
 5326            five
 5327        "
 5328        .unindent(),
 5329    );
 5330
 5331    // Create a four-line block that replaces three lines of text.
 5332    cx.update_editor(|editor, window, cx| {
 5333        let snapshot = editor.snapshot(window, cx);
 5334        let snapshot = &snapshot.buffer_snapshot;
 5335        let placement = BlockPlacement::Replace(
 5336            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5337        );
 5338        editor.insert_blocks(
 5339            [BlockProperties {
 5340                placement,
 5341                height: Some(4),
 5342                style: BlockStyle::Sticky,
 5343                render: Arc::new(|_| gpui::div().into_any_element()),
 5344                priority: 0,
 5345            }],
 5346            None,
 5347            cx,
 5348        );
 5349    });
 5350
 5351    // Move down so that the cursor touches the block.
 5352    cx.update_editor(|editor, window, cx| {
 5353        editor.move_down(&Default::default(), window, cx);
 5354    });
 5355    cx.assert_editor_state(
 5356        &"
 5357            zero
 5358            «one
 5359            two
 5360            threeˇ»
 5361            four
 5362            five
 5363        "
 5364        .unindent(),
 5365    );
 5366
 5367    // Move down past the block.
 5368    cx.update_editor(|editor, window, cx| {
 5369        editor.move_down(&Default::default(), window, cx);
 5370    });
 5371    cx.assert_editor_state(
 5372        &"
 5373            zero
 5374            one
 5375            two
 5376            three
 5377            ˇfour
 5378            five
 5379        "
 5380        .unindent(),
 5381    );
 5382}
 5383
 5384#[gpui::test]
 5385fn test_transpose(cx: &mut TestAppContext) {
 5386    init_test(cx, |_| {});
 5387
 5388    _ = cx.add_window(|window, cx| {
 5389        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5390        editor.set_style(EditorStyle::default(), window, cx);
 5391        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5392            s.select_ranges([1..1])
 5393        });
 5394        editor.transpose(&Default::default(), window, cx);
 5395        assert_eq!(editor.text(cx), "bac");
 5396        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5397
 5398        editor.transpose(&Default::default(), window, cx);
 5399        assert_eq!(editor.text(cx), "bca");
 5400        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5401
 5402        editor.transpose(&Default::default(), window, cx);
 5403        assert_eq!(editor.text(cx), "bac");
 5404        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5405
 5406        editor
 5407    });
 5408
 5409    _ = cx.add_window(|window, cx| {
 5410        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5411        editor.set_style(EditorStyle::default(), window, cx);
 5412        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5413            s.select_ranges([3..3])
 5414        });
 5415        editor.transpose(&Default::default(), window, cx);
 5416        assert_eq!(editor.text(cx), "acb\nde");
 5417        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5418
 5419        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5420            s.select_ranges([4..4])
 5421        });
 5422        editor.transpose(&Default::default(), window, cx);
 5423        assert_eq!(editor.text(cx), "acbd\ne");
 5424        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5425
 5426        editor.transpose(&Default::default(), window, cx);
 5427        assert_eq!(editor.text(cx), "acbde\n");
 5428        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5429
 5430        editor.transpose(&Default::default(), window, cx);
 5431        assert_eq!(editor.text(cx), "acbd\ne");
 5432        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5433
 5434        editor
 5435    });
 5436
 5437    _ = cx.add_window(|window, cx| {
 5438        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5439        editor.set_style(EditorStyle::default(), window, cx);
 5440        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5441            s.select_ranges([1..1, 2..2, 4..4])
 5442        });
 5443        editor.transpose(&Default::default(), window, cx);
 5444        assert_eq!(editor.text(cx), "bacd\ne");
 5445        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5446
 5447        editor.transpose(&Default::default(), window, cx);
 5448        assert_eq!(editor.text(cx), "bcade\n");
 5449        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5450
 5451        editor.transpose(&Default::default(), window, cx);
 5452        assert_eq!(editor.text(cx), "bcda\ne");
 5453        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5454
 5455        editor.transpose(&Default::default(), window, cx);
 5456        assert_eq!(editor.text(cx), "bcade\n");
 5457        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5458
 5459        editor.transpose(&Default::default(), window, cx);
 5460        assert_eq!(editor.text(cx), "bcaed\n");
 5461        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5462
 5463        editor
 5464    });
 5465
 5466    _ = cx.add_window(|window, cx| {
 5467        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5468        editor.set_style(EditorStyle::default(), window, cx);
 5469        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5470            s.select_ranges([4..4])
 5471        });
 5472        editor.transpose(&Default::default(), window, cx);
 5473        assert_eq!(editor.text(cx), "🏀🍐✋");
 5474        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5475
 5476        editor.transpose(&Default::default(), window, cx);
 5477        assert_eq!(editor.text(cx), "🏀✋🍐");
 5478        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5479
 5480        editor.transpose(&Default::default(), window, cx);
 5481        assert_eq!(editor.text(cx), "🏀🍐✋");
 5482        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5483
 5484        editor
 5485    });
 5486}
 5487
 5488#[gpui::test]
 5489async fn test_rewrap(cx: &mut TestAppContext) {
 5490    init_test(cx, |settings| {
 5491        settings.languages.0.extend([
 5492            (
 5493                "Markdown".into(),
 5494                LanguageSettingsContent {
 5495                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5496                    preferred_line_length: Some(40),
 5497                    ..Default::default()
 5498                },
 5499            ),
 5500            (
 5501                "Plain Text".into(),
 5502                LanguageSettingsContent {
 5503                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5504                    preferred_line_length: Some(40),
 5505                    ..Default::default()
 5506                },
 5507            ),
 5508            (
 5509                "C++".into(),
 5510                LanguageSettingsContent {
 5511                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5512                    preferred_line_length: Some(40),
 5513                    ..Default::default()
 5514                },
 5515            ),
 5516            (
 5517                "Python".into(),
 5518                LanguageSettingsContent {
 5519                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5520                    preferred_line_length: Some(40),
 5521                    ..Default::default()
 5522                },
 5523            ),
 5524            (
 5525                "Rust".into(),
 5526                LanguageSettingsContent {
 5527                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5528                    preferred_line_length: Some(40),
 5529                    ..Default::default()
 5530                },
 5531            ),
 5532        ])
 5533    });
 5534
 5535    let mut cx = EditorTestContext::new(cx).await;
 5536
 5537    let cpp_language = Arc::new(Language::new(
 5538        LanguageConfig {
 5539            name: "C++".into(),
 5540            line_comments: vec!["// ".into()],
 5541            ..LanguageConfig::default()
 5542        },
 5543        None,
 5544    ));
 5545    let python_language = Arc::new(Language::new(
 5546        LanguageConfig {
 5547            name: "Python".into(),
 5548            line_comments: vec!["# ".into()],
 5549            ..LanguageConfig::default()
 5550        },
 5551        None,
 5552    ));
 5553    let markdown_language = Arc::new(Language::new(
 5554        LanguageConfig {
 5555            name: "Markdown".into(),
 5556            rewrap_prefixes: vec![
 5557                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5558                regex::Regex::new("[-*+]\\s+").unwrap(),
 5559            ],
 5560            ..LanguageConfig::default()
 5561        },
 5562        None,
 5563    ));
 5564    let rust_language = Arc::new(Language::new(
 5565        LanguageConfig {
 5566            name: "Rust".into(),
 5567            line_comments: vec!["// ".into(), "/// ".into()],
 5568            ..LanguageConfig::default()
 5569        },
 5570        Some(tree_sitter_rust::LANGUAGE.into()),
 5571    ));
 5572
 5573    let plaintext_language = Arc::new(Language::new(
 5574        LanguageConfig {
 5575            name: "Plain Text".into(),
 5576            ..LanguageConfig::default()
 5577        },
 5578        None,
 5579    ));
 5580
 5581    // Test basic rewrapping of a long line with a cursor
 5582    assert_rewrap(
 5583        indoc! {"
 5584            // ˇThis is a long comment that needs to be wrapped.
 5585        "},
 5586        indoc! {"
 5587            // ˇThis is a long comment that needs to
 5588            // be wrapped.
 5589        "},
 5590        cpp_language.clone(),
 5591        &mut cx,
 5592    );
 5593
 5594    // Test rewrapping a full selection
 5595    assert_rewrap(
 5596        indoc! {"
 5597            «// This selected long comment needs to be wrapped.ˇ»"
 5598        },
 5599        indoc! {"
 5600            «// This selected long comment needs to
 5601            // be wrapped.ˇ»"
 5602        },
 5603        cpp_language.clone(),
 5604        &mut cx,
 5605    );
 5606
 5607    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5608    assert_rewrap(
 5609        indoc! {"
 5610            // ˇThis is the first line.
 5611            // Thisˇ is the second line.
 5612            // This is the thirdˇ line, all part of one paragraph.
 5613         "},
 5614        indoc! {"
 5615            // ˇThis is the first line. Thisˇ is the
 5616            // second line. This is the thirdˇ line,
 5617            // all part of one paragraph.
 5618         "},
 5619        cpp_language.clone(),
 5620        &mut cx,
 5621    );
 5622
 5623    // Test multiple cursors in different paragraphs trigger separate rewraps
 5624    assert_rewrap(
 5625        indoc! {"
 5626            // ˇThis is the first paragraph, first line.
 5627            // ˇThis is the first paragraph, second line.
 5628
 5629            // ˇThis is the second paragraph, first line.
 5630            // ˇThis is the second paragraph, second line.
 5631        "},
 5632        indoc! {"
 5633            // ˇThis is the first paragraph, first
 5634            // line. ˇThis is the first paragraph,
 5635            // second line.
 5636
 5637            // ˇThis is the second paragraph, first
 5638            // line. ˇThis is the second paragraph,
 5639            // second line.
 5640        "},
 5641        cpp_language.clone(),
 5642        &mut cx,
 5643    );
 5644
 5645    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5646    assert_rewrap(
 5647        indoc! {"
 5648            «// A regular long long comment to be wrapped.
 5649            /// A documentation long comment to be wrapped.ˇ»
 5650          "},
 5651        indoc! {"
 5652            «// A regular long long comment to be
 5653            // wrapped.
 5654            /// A documentation long comment to be
 5655            /// wrapped.ˇ»
 5656          "},
 5657        rust_language.clone(),
 5658        &mut cx,
 5659    );
 5660
 5661    // Test that change in indentation level trigger seperate rewraps
 5662    assert_rewrap(
 5663        indoc! {"
 5664            fn foo() {
 5665                «// This is a long comment at the base indent.
 5666                    // This is a long comment at the next indent.ˇ»
 5667            }
 5668        "},
 5669        indoc! {"
 5670            fn foo() {
 5671                «// This is a long comment at the
 5672                // base indent.
 5673                    // This is a long comment at the
 5674                    // next indent.ˇ»
 5675            }
 5676        "},
 5677        rust_language.clone(),
 5678        &mut cx,
 5679    );
 5680
 5681    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5682    assert_rewrap(
 5683        indoc! {"
 5684            # ˇThis is a long comment using a pound sign.
 5685        "},
 5686        indoc! {"
 5687            # ˇThis is a long comment using a pound
 5688            # sign.
 5689        "},
 5690        python_language,
 5691        &mut cx,
 5692    );
 5693
 5694    // Test rewrapping only affects comments, not code even when selected
 5695    assert_rewrap(
 5696        indoc! {"
 5697            «/// This doc comment is long and should be wrapped.
 5698            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5699        "},
 5700        indoc! {"
 5701            «/// This doc comment is long and should
 5702            /// be wrapped.
 5703            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5704        "},
 5705        rust_language.clone(),
 5706        &mut cx,
 5707    );
 5708
 5709    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5710    assert_rewrap(
 5711        indoc! {"
 5712            # Header
 5713
 5714            A long long long line of markdown text to wrap.ˇ
 5715         "},
 5716        indoc! {"
 5717            # Header
 5718
 5719            A long long long line of markdown text
 5720            to wrap.ˇ
 5721         "},
 5722        markdown_language.clone(),
 5723        &mut cx,
 5724    );
 5725
 5726    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5727    assert_rewrap(
 5728        indoc! {"
 5729            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5730            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5731            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5732        "},
 5733        indoc! {"
 5734            «1. This is a numbered list item that is
 5735               very long and needs to be wrapped
 5736               properly.
 5737            2. This is a numbered list item that is
 5738               very long and needs to be wrapped
 5739               properly.
 5740            - This is an unordered list item that is
 5741              also very long and should not merge
 5742              with the numbered item.ˇ»
 5743        "},
 5744        markdown_language.clone(),
 5745        &mut cx,
 5746    );
 5747
 5748    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5749    assert_rewrap(
 5750        indoc! {"
 5751            «1. This is a numbered list item that is
 5752            very long and needs to be wrapped
 5753            properly.
 5754            2. This is a numbered list item that is
 5755            very long and needs to be wrapped
 5756            properly.
 5757            - This is an unordered list item that is
 5758            also very long and should not merge with
 5759            the numbered item.ˇ»
 5760        "},
 5761        indoc! {"
 5762            «1. This is a numbered list item that is
 5763               very long and needs to be wrapped
 5764               properly.
 5765            2. This is a numbered list item that is
 5766               very long and needs to be wrapped
 5767               properly.
 5768            - This is an unordered list item that is
 5769              also very long and should not merge
 5770              with the numbered item.ˇ»
 5771        "},
 5772        markdown_language.clone(),
 5773        &mut cx,
 5774    );
 5775
 5776    // Test that rewrapping maintain indents even when they already exists.
 5777    assert_rewrap(
 5778        indoc! {"
 5779            «1. This is a numbered list
 5780               item that is very long and needs to be wrapped properly.
 5781            2. This is a numbered list
 5782               item that is very long and needs to be wrapped properly.
 5783            - This is an unordered list item that is also very long and
 5784              should not merge with the numbered item.ˇ»
 5785        "},
 5786        indoc! {"
 5787            «1. This is a numbered list item that is
 5788               very long and needs to be wrapped
 5789               properly.
 5790            2. This is a numbered list item that is
 5791               very long and needs to be wrapped
 5792               properly.
 5793            - This is an unordered list item that is
 5794              also very long and should not merge
 5795              with the numbered item.ˇ»
 5796        "},
 5797        markdown_language,
 5798        &mut cx,
 5799    );
 5800
 5801    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5802    assert_rewrap(
 5803        indoc! {"
 5804            ˇThis is a very long line of plain text that will be wrapped.
 5805        "},
 5806        indoc! {"
 5807            ˇThis is a very long line of plain text
 5808            that will be wrapped.
 5809        "},
 5810        plaintext_language.clone(),
 5811        &mut cx,
 5812    );
 5813
 5814    // Test that non-commented code acts as a paragraph boundary within a selection
 5815    assert_rewrap(
 5816        indoc! {"
 5817               «// This is the first long comment block to be wrapped.
 5818               fn my_func(a: u32);
 5819               // This is the second long comment block to be wrapped.ˇ»
 5820           "},
 5821        indoc! {"
 5822               «// This is the first long comment block
 5823               // to be wrapped.
 5824               fn my_func(a: u32);
 5825               // This is the second long comment block
 5826               // to be wrapped.ˇ»
 5827           "},
 5828        rust_language,
 5829        &mut cx,
 5830    );
 5831
 5832    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5833    assert_rewrap(
 5834        indoc! {"
 5835            «ˇThis is a very long line that will be wrapped.
 5836
 5837            This is another paragraph in the same selection.»
 5838
 5839            «\tThis is a very long indented line that will be wrapped.ˇ»
 5840         "},
 5841        indoc! {"
 5842            «ˇThis is a very long line that will be
 5843            wrapped.
 5844
 5845            This is another paragraph in the same
 5846            selection.»
 5847
 5848            «\tThis is a very long indented line
 5849            \tthat will be wrapped.ˇ»
 5850         "},
 5851        plaintext_language,
 5852        &mut cx,
 5853    );
 5854
 5855    // Test that an empty comment line acts as a paragraph boundary
 5856    assert_rewrap(
 5857        indoc! {"
 5858            // ˇThis is a long comment that will be wrapped.
 5859            //
 5860            // And this is another long comment that will also be wrapped.ˇ
 5861         "},
 5862        indoc! {"
 5863            // ˇThis is a long comment that will be
 5864            // wrapped.
 5865            //
 5866            // And this is another long comment that
 5867            // will also be wrapped.ˇ
 5868         "},
 5869        cpp_language,
 5870        &mut cx,
 5871    );
 5872
 5873    #[track_caller]
 5874    fn assert_rewrap(
 5875        unwrapped_text: &str,
 5876        wrapped_text: &str,
 5877        language: Arc<Language>,
 5878        cx: &mut EditorTestContext,
 5879    ) {
 5880        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5881        cx.set_state(unwrapped_text);
 5882        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5883        cx.assert_editor_state(wrapped_text);
 5884    }
 5885}
 5886
 5887#[gpui::test]
 5888async fn test_hard_wrap(cx: &mut TestAppContext) {
 5889    init_test(cx, |_| {});
 5890    let mut cx = EditorTestContext::new(cx).await;
 5891
 5892    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 5893    cx.update_editor(|editor, _, cx| {
 5894        editor.set_hard_wrap(Some(14), cx);
 5895    });
 5896
 5897    cx.set_state(indoc!(
 5898        "
 5899        one two three ˇ
 5900        "
 5901    ));
 5902    cx.simulate_input("four");
 5903    cx.run_until_parked();
 5904
 5905    cx.assert_editor_state(indoc!(
 5906        "
 5907        one two three
 5908        fourˇ
 5909        "
 5910    ));
 5911
 5912    cx.update_editor(|editor, window, cx| {
 5913        editor.newline(&Default::default(), window, cx);
 5914    });
 5915    cx.run_until_parked();
 5916    cx.assert_editor_state(indoc!(
 5917        "
 5918        one two three
 5919        four
 5920        ˇ
 5921        "
 5922    ));
 5923
 5924    cx.simulate_input("five");
 5925    cx.run_until_parked();
 5926    cx.assert_editor_state(indoc!(
 5927        "
 5928        one two three
 5929        four
 5930        fiveˇ
 5931        "
 5932    ));
 5933
 5934    cx.update_editor(|editor, window, cx| {
 5935        editor.newline(&Default::default(), window, cx);
 5936    });
 5937    cx.run_until_parked();
 5938    cx.simulate_input("# ");
 5939    cx.run_until_parked();
 5940    cx.assert_editor_state(indoc!(
 5941        "
 5942        one two three
 5943        four
 5944        five
 5945        # ˇ
 5946        "
 5947    ));
 5948
 5949    cx.update_editor(|editor, window, cx| {
 5950        editor.newline(&Default::default(), window, cx);
 5951    });
 5952    cx.run_until_parked();
 5953    cx.assert_editor_state(indoc!(
 5954        "
 5955        one two three
 5956        four
 5957        five
 5958        #\x20
 5959 5960        "
 5961    ));
 5962
 5963    cx.simulate_input(" 6");
 5964    cx.run_until_parked();
 5965    cx.assert_editor_state(indoc!(
 5966        "
 5967        one two three
 5968        four
 5969        five
 5970        #
 5971        # 6ˇ
 5972        "
 5973    ));
 5974}
 5975
 5976#[gpui::test]
 5977async fn test_clipboard(cx: &mut TestAppContext) {
 5978    init_test(cx, |_| {});
 5979
 5980    let mut cx = EditorTestContext::new(cx).await;
 5981
 5982    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 5983    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5984    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 5985
 5986    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 5987    cx.set_state("two ˇfour ˇsix ˇ");
 5988    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5989    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 5990
 5991    // Paste again but with only two cursors. Since the number of cursors doesn't
 5992    // match the number of slices in the clipboard, the entire clipboard text
 5993    // is pasted at each cursor.
 5994    cx.set_state("ˇtwo one✅ four three six five ˇ");
 5995    cx.update_editor(|e, window, cx| {
 5996        e.handle_input("( ", window, cx);
 5997        e.paste(&Paste, window, cx);
 5998        e.handle_input(") ", window, cx);
 5999    });
 6000    cx.assert_editor_state(
 6001        &([
 6002            "( one✅ ",
 6003            "three ",
 6004            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6005            "three ",
 6006            "five ) ˇ",
 6007        ]
 6008        .join("\n")),
 6009    );
 6010
 6011    // Cut with three selections, one of which is full-line.
 6012    cx.set_state(indoc! {"
 6013        1«2ˇ»3
 6014        4ˇ567
 6015        «8ˇ»9"});
 6016    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6017    cx.assert_editor_state(indoc! {"
 6018        1ˇ3
 6019        ˇ9"});
 6020
 6021    // Paste with three selections, noticing how the copied selection that was full-line
 6022    // gets inserted before the second cursor.
 6023    cx.set_state(indoc! {"
 6024        1ˇ3
 6025 6026        «oˇ»ne"});
 6027    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6028    cx.assert_editor_state(indoc! {"
 6029        12ˇ3
 6030        4567
 6031 6032        8ˇne"});
 6033
 6034    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6035    cx.set_state(indoc! {"
 6036        The quick brown
 6037        fox juˇmps over
 6038        the lazy dog"});
 6039    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6040    assert_eq!(
 6041        cx.read_from_clipboard()
 6042            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6043        Some("fox jumps over\n".to_string())
 6044    );
 6045
 6046    // Paste with three selections, noticing how the copied full-line selection is inserted
 6047    // before the empty selections but replaces the selection that is non-empty.
 6048    cx.set_state(indoc! {"
 6049        Tˇhe quick brown
 6050        «foˇ»x jumps over
 6051        tˇhe lazy dog"});
 6052    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6053    cx.assert_editor_state(indoc! {"
 6054        fox jumps over
 6055        Tˇhe quick brown
 6056        fox jumps over
 6057        ˇx jumps over
 6058        fox jumps over
 6059        tˇhe lazy dog"});
 6060}
 6061
 6062#[gpui::test]
 6063async fn test_copy_trim(cx: &mut TestAppContext) {
 6064    init_test(cx, |_| {});
 6065
 6066    let mut cx = EditorTestContext::new(cx).await;
 6067    cx.set_state(
 6068        r#"            «for selection in selections.iter() {
 6069            let mut start = selection.start;
 6070            let mut end = selection.end;
 6071            let is_entire_line = selection.is_empty();
 6072            if is_entire_line {
 6073                start = Point::new(start.row, 0);ˇ»
 6074                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6075            }
 6076        "#,
 6077    );
 6078    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6079    assert_eq!(
 6080        cx.read_from_clipboard()
 6081            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6082        Some(
 6083            "for selection in selections.iter() {
 6084            let mut start = selection.start;
 6085            let mut end = selection.end;
 6086            let is_entire_line = selection.is_empty();
 6087            if is_entire_line {
 6088                start = Point::new(start.row, 0);"
 6089                .to_string()
 6090        ),
 6091        "Regular copying preserves all indentation selected",
 6092    );
 6093    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6094    assert_eq!(
 6095        cx.read_from_clipboard()
 6096            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6097        Some(
 6098            "for selection in selections.iter() {
 6099let mut start = selection.start;
 6100let mut end = selection.end;
 6101let is_entire_line = selection.is_empty();
 6102if is_entire_line {
 6103    start = Point::new(start.row, 0);"
 6104                .to_string()
 6105        ),
 6106        "Copying with stripping should strip all leading whitespaces"
 6107    );
 6108
 6109    cx.set_state(
 6110        r#"       «     for selection in selections.iter() {
 6111            let mut start = selection.start;
 6112            let mut end = selection.end;
 6113            let is_entire_line = selection.is_empty();
 6114            if is_entire_line {
 6115                start = Point::new(start.row, 0);ˇ»
 6116                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6117            }
 6118        "#,
 6119    );
 6120    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6121    assert_eq!(
 6122        cx.read_from_clipboard()
 6123            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6124        Some(
 6125            "     for selection in selections.iter() {
 6126            let mut start = selection.start;
 6127            let mut end = selection.end;
 6128            let is_entire_line = selection.is_empty();
 6129            if is_entire_line {
 6130                start = Point::new(start.row, 0);"
 6131                .to_string()
 6132        ),
 6133        "Regular copying preserves all indentation selected",
 6134    );
 6135    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6136    assert_eq!(
 6137        cx.read_from_clipboard()
 6138            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6139        Some(
 6140            "for selection in selections.iter() {
 6141let mut start = selection.start;
 6142let mut end = selection.end;
 6143let is_entire_line = selection.is_empty();
 6144if is_entire_line {
 6145    start = Point::new(start.row, 0);"
 6146                .to_string()
 6147        ),
 6148        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6149    );
 6150
 6151    cx.set_state(
 6152        r#"       «ˇ     for selection in selections.iter() {
 6153            let mut start = selection.start;
 6154            let mut end = selection.end;
 6155            let is_entire_line = selection.is_empty();
 6156            if is_entire_line {
 6157                start = Point::new(start.row, 0);»
 6158                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6159            }
 6160        "#,
 6161    );
 6162    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6163    assert_eq!(
 6164        cx.read_from_clipboard()
 6165            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6166        Some(
 6167            "     for selection in selections.iter() {
 6168            let mut start = selection.start;
 6169            let mut end = selection.end;
 6170            let is_entire_line = selection.is_empty();
 6171            if is_entire_line {
 6172                start = Point::new(start.row, 0);"
 6173                .to_string()
 6174        ),
 6175        "Regular copying for reverse selection works the same",
 6176    );
 6177    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6178    assert_eq!(
 6179        cx.read_from_clipboard()
 6180            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6181        Some(
 6182            "for selection in selections.iter() {
 6183let mut start = selection.start;
 6184let mut end = selection.end;
 6185let is_entire_line = selection.is_empty();
 6186if is_entire_line {
 6187    start = Point::new(start.row, 0);"
 6188                .to_string()
 6189        ),
 6190        "Copying with stripping for reverse selection works the same"
 6191    );
 6192
 6193    cx.set_state(
 6194        r#"            for selection «in selections.iter() {
 6195            let mut start = selection.start;
 6196            let mut end = selection.end;
 6197            let is_entire_line = selection.is_empty();
 6198            if is_entire_line {
 6199                start = Point::new(start.row, 0);ˇ»
 6200                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6201            }
 6202        "#,
 6203    );
 6204    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6205    assert_eq!(
 6206        cx.read_from_clipboard()
 6207            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6208        Some(
 6209            "in selections.iter() {
 6210            let mut start = selection.start;
 6211            let mut end = selection.end;
 6212            let is_entire_line = selection.is_empty();
 6213            if is_entire_line {
 6214                start = Point::new(start.row, 0);"
 6215                .to_string()
 6216        ),
 6217        "When selecting past the indent, the copying works as usual",
 6218    );
 6219    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6220    assert_eq!(
 6221        cx.read_from_clipboard()
 6222            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6223        Some(
 6224            "in selections.iter() {
 6225            let mut start = selection.start;
 6226            let mut end = selection.end;
 6227            let is_entire_line = selection.is_empty();
 6228            if is_entire_line {
 6229                start = Point::new(start.row, 0);"
 6230                .to_string()
 6231        ),
 6232        "When selecting past the indent, nothing is trimmed"
 6233    );
 6234
 6235    cx.set_state(
 6236        r#"            «for selection in selections.iter() {
 6237            let mut start = selection.start;
 6238
 6239            let mut end = selection.end;
 6240            let is_entire_line = selection.is_empty();
 6241            if is_entire_line {
 6242                start = Point::new(start.row, 0);
 6243ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6244            }
 6245        "#,
 6246    );
 6247    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6248    assert_eq!(
 6249        cx.read_from_clipboard()
 6250            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6251        Some(
 6252            "for selection in selections.iter() {
 6253let mut start = selection.start;
 6254
 6255let mut end = selection.end;
 6256let is_entire_line = selection.is_empty();
 6257if is_entire_line {
 6258    start = Point::new(start.row, 0);
 6259"
 6260            .to_string()
 6261        ),
 6262        "Copying with stripping should ignore empty lines"
 6263    );
 6264}
 6265
 6266#[gpui::test]
 6267async fn test_paste_multiline(cx: &mut TestAppContext) {
 6268    init_test(cx, |_| {});
 6269
 6270    let mut cx = EditorTestContext::new(cx).await;
 6271    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6272
 6273    // Cut an indented block, without the leading whitespace.
 6274    cx.set_state(indoc! {"
 6275        const a: B = (
 6276            c(),
 6277            «d(
 6278                e,
 6279                f
 6280            )ˇ»
 6281        );
 6282    "});
 6283    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6284    cx.assert_editor_state(indoc! {"
 6285        const a: B = (
 6286            c(),
 6287            ˇ
 6288        );
 6289    "});
 6290
 6291    // Paste it at the same position.
 6292    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6293    cx.assert_editor_state(indoc! {"
 6294        const a: B = (
 6295            c(),
 6296            d(
 6297                e,
 6298                f
 6299 6300        );
 6301    "});
 6302
 6303    // Paste it at a line with a lower indent level.
 6304    cx.set_state(indoc! {"
 6305        ˇ
 6306        const a: B = (
 6307            c(),
 6308        );
 6309    "});
 6310    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6311    cx.assert_editor_state(indoc! {"
 6312        d(
 6313            e,
 6314            f
 6315 6316        const a: B = (
 6317            c(),
 6318        );
 6319    "});
 6320
 6321    // Cut an indented block, with the leading whitespace.
 6322    cx.set_state(indoc! {"
 6323        const a: B = (
 6324            c(),
 6325        «    d(
 6326                e,
 6327                f
 6328            )
 6329        ˇ»);
 6330    "});
 6331    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6332    cx.assert_editor_state(indoc! {"
 6333        const a: B = (
 6334            c(),
 6335        ˇ);
 6336    "});
 6337
 6338    // Paste it at the same position.
 6339    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6340    cx.assert_editor_state(indoc! {"
 6341        const a: B = (
 6342            c(),
 6343            d(
 6344                e,
 6345                f
 6346            )
 6347        ˇ);
 6348    "});
 6349
 6350    // Paste it at a line with a higher indent level.
 6351    cx.set_state(indoc! {"
 6352        const a: B = (
 6353            c(),
 6354            d(
 6355                e,
 6356 6357            )
 6358        );
 6359    "});
 6360    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6361    cx.assert_editor_state(indoc! {"
 6362        const a: B = (
 6363            c(),
 6364            d(
 6365                e,
 6366                f    d(
 6367                    e,
 6368                    f
 6369                )
 6370        ˇ
 6371            )
 6372        );
 6373    "});
 6374
 6375    // Copy an indented block, starting mid-line
 6376    cx.set_state(indoc! {"
 6377        const a: B = (
 6378            c(),
 6379            somethin«g(
 6380                e,
 6381                f
 6382            )ˇ»
 6383        );
 6384    "});
 6385    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6386
 6387    // Paste it on a line with a lower indent level
 6388    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6389    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6390    cx.assert_editor_state(indoc! {"
 6391        const a: B = (
 6392            c(),
 6393            something(
 6394                e,
 6395                f
 6396            )
 6397        );
 6398        g(
 6399            e,
 6400            f
 6401"});
 6402}
 6403
 6404#[gpui::test]
 6405async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6406    init_test(cx, |_| {});
 6407
 6408    cx.write_to_clipboard(ClipboardItem::new_string(
 6409        "    d(\n        e\n    );\n".into(),
 6410    ));
 6411
 6412    let mut cx = EditorTestContext::new(cx).await;
 6413    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6414
 6415    cx.set_state(indoc! {"
 6416        fn a() {
 6417            b();
 6418            if c() {
 6419                ˇ
 6420            }
 6421        }
 6422    "});
 6423
 6424    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6425    cx.assert_editor_state(indoc! {"
 6426        fn a() {
 6427            b();
 6428            if c() {
 6429                d(
 6430                    e
 6431                );
 6432        ˇ
 6433            }
 6434        }
 6435    "});
 6436
 6437    cx.set_state(indoc! {"
 6438        fn a() {
 6439            b();
 6440            ˇ
 6441        }
 6442    "});
 6443
 6444    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6445    cx.assert_editor_state(indoc! {"
 6446        fn a() {
 6447            b();
 6448            d(
 6449                e
 6450            );
 6451        ˇ
 6452        }
 6453    "});
 6454}
 6455
 6456#[gpui::test]
 6457fn test_select_all(cx: &mut TestAppContext) {
 6458    init_test(cx, |_| {});
 6459
 6460    let editor = cx.add_window(|window, cx| {
 6461        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6462        build_editor(buffer, window, cx)
 6463    });
 6464    _ = editor.update(cx, |editor, window, cx| {
 6465        editor.select_all(&SelectAll, window, cx);
 6466        assert_eq!(
 6467            editor.selections.display_ranges(cx),
 6468            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6469        );
 6470    });
 6471}
 6472
 6473#[gpui::test]
 6474fn test_select_line(cx: &mut TestAppContext) {
 6475    init_test(cx, |_| {});
 6476
 6477    let editor = cx.add_window(|window, cx| {
 6478        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6479        build_editor(buffer, window, cx)
 6480    });
 6481    _ = editor.update(cx, |editor, window, cx| {
 6482        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6483            s.select_display_ranges([
 6484                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6485                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6486                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6487                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6488            ])
 6489        });
 6490        editor.select_line(&SelectLine, window, cx);
 6491        assert_eq!(
 6492            editor.selections.display_ranges(cx),
 6493            vec![
 6494                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6495                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6496            ]
 6497        );
 6498    });
 6499
 6500    _ = editor.update(cx, |editor, window, cx| {
 6501        editor.select_line(&SelectLine, window, cx);
 6502        assert_eq!(
 6503            editor.selections.display_ranges(cx),
 6504            vec![
 6505                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6506                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6507            ]
 6508        );
 6509    });
 6510
 6511    _ = editor.update(cx, |editor, window, cx| {
 6512        editor.select_line(&SelectLine, window, cx);
 6513        assert_eq!(
 6514            editor.selections.display_ranges(cx),
 6515            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6516        );
 6517    });
 6518}
 6519
 6520#[gpui::test]
 6521async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6522    init_test(cx, |_| {});
 6523    let mut cx = EditorTestContext::new(cx).await;
 6524
 6525    #[track_caller]
 6526    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6527        cx.set_state(initial_state);
 6528        cx.update_editor(|e, window, cx| {
 6529            e.split_selection_into_lines(&Default::default(), window, cx)
 6530        });
 6531        cx.assert_editor_state(expected_state);
 6532    }
 6533
 6534    // Selection starts and ends at the middle of lines, left-to-right
 6535    test(
 6536        &mut cx,
 6537        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6538        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6539    );
 6540    // Same thing, right-to-left
 6541    test(
 6542        &mut cx,
 6543        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6544        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6545    );
 6546
 6547    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6548    test(
 6549        &mut cx,
 6550        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6551        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6552    );
 6553    // Same thing, right-to-left
 6554    test(
 6555        &mut cx,
 6556        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6557        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6558    );
 6559
 6560    // Whole buffer, left-to-right, last line ends with newline
 6561    test(
 6562        &mut cx,
 6563        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6564        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6565    );
 6566    // Same thing, right-to-left
 6567    test(
 6568        &mut cx,
 6569        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6570        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6571    );
 6572
 6573    // Starts at the end of a line, ends at the start of another
 6574    test(
 6575        &mut cx,
 6576        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6577        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6578    );
 6579}
 6580
 6581#[gpui::test]
 6582async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6583    init_test(cx, |_| {});
 6584
 6585    let editor = cx.add_window(|window, cx| {
 6586        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6587        build_editor(buffer, window, cx)
 6588    });
 6589
 6590    // setup
 6591    _ = editor.update(cx, |editor, window, cx| {
 6592        editor.fold_creases(
 6593            vec![
 6594                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 6595                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 6596                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 6597            ],
 6598            true,
 6599            window,
 6600            cx,
 6601        );
 6602        assert_eq!(
 6603            editor.display_text(cx),
 6604            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6605        );
 6606    });
 6607
 6608    _ = editor.update(cx, |editor, window, cx| {
 6609        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6610            s.select_display_ranges([
 6611                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6612                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6613                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6614                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 6615            ])
 6616        });
 6617        editor.split_selection_into_lines(&Default::default(), window, cx);
 6618        assert_eq!(
 6619            editor.display_text(cx),
 6620            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 6621        );
 6622    });
 6623    EditorTestContext::for_editor(editor, cx)
 6624        .await
 6625        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 6626
 6627    _ = editor.update(cx, |editor, window, cx| {
 6628        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6629            s.select_display_ranges([
 6630                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 6631            ])
 6632        });
 6633        editor.split_selection_into_lines(&Default::default(), window, cx);
 6634        assert_eq!(
 6635            editor.display_text(cx),
 6636            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 6637        );
 6638        assert_eq!(
 6639            editor.selections.display_ranges(cx),
 6640            [
 6641                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 6642                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 6643                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 6644                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 6645                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 6646                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 6647                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 6648            ]
 6649        );
 6650    });
 6651    EditorTestContext::for_editor(editor, cx)
 6652        .await
 6653        .assert_editor_state(
 6654            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 6655        );
 6656}
 6657
 6658#[gpui::test]
 6659async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 6660    init_test(cx, |_| {});
 6661
 6662    let mut cx = EditorTestContext::new(cx).await;
 6663
 6664    cx.set_state(indoc!(
 6665        r#"abc
 6666           defˇghi
 6667
 6668           jk
 6669           nlmo
 6670           "#
 6671    ));
 6672
 6673    cx.update_editor(|editor, window, cx| {
 6674        editor.add_selection_above(&Default::default(), window, cx);
 6675    });
 6676
 6677    cx.assert_editor_state(indoc!(
 6678        r#"abcˇ
 6679           defˇghi
 6680
 6681           jk
 6682           nlmo
 6683           "#
 6684    ));
 6685
 6686    cx.update_editor(|editor, window, cx| {
 6687        editor.add_selection_above(&Default::default(), window, cx);
 6688    });
 6689
 6690    cx.assert_editor_state(indoc!(
 6691        r#"abcˇ
 6692            defˇghi
 6693
 6694            jk
 6695            nlmo
 6696            "#
 6697    ));
 6698
 6699    cx.update_editor(|editor, window, cx| {
 6700        editor.add_selection_below(&Default::default(), window, cx);
 6701    });
 6702
 6703    cx.assert_editor_state(indoc!(
 6704        r#"abc
 6705           defˇghi
 6706
 6707           jk
 6708           nlmo
 6709           "#
 6710    ));
 6711
 6712    cx.update_editor(|editor, window, cx| {
 6713        editor.undo_selection(&Default::default(), window, cx);
 6714    });
 6715
 6716    cx.assert_editor_state(indoc!(
 6717        r#"abcˇ
 6718           defˇghi
 6719
 6720           jk
 6721           nlmo
 6722           "#
 6723    ));
 6724
 6725    cx.update_editor(|editor, window, cx| {
 6726        editor.redo_selection(&Default::default(), window, cx);
 6727    });
 6728
 6729    cx.assert_editor_state(indoc!(
 6730        r#"abc
 6731           defˇghi
 6732
 6733           jk
 6734           nlmo
 6735           "#
 6736    ));
 6737
 6738    cx.update_editor(|editor, window, cx| {
 6739        editor.add_selection_below(&Default::default(), window, cx);
 6740    });
 6741
 6742    cx.assert_editor_state(indoc!(
 6743        r#"abc
 6744           defˇghi
 6745           ˇ
 6746           jk
 6747           nlmo
 6748           "#
 6749    ));
 6750
 6751    cx.update_editor(|editor, window, cx| {
 6752        editor.add_selection_below(&Default::default(), window, cx);
 6753    });
 6754
 6755    cx.assert_editor_state(indoc!(
 6756        r#"abc
 6757           defˇghi
 6758           ˇ
 6759           jkˇ
 6760           nlmo
 6761           "#
 6762    ));
 6763
 6764    cx.update_editor(|editor, window, cx| {
 6765        editor.add_selection_below(&Default::default(), window, cx);
 6766    });
 6767
 6768    cx.assert_editor_state(indoc!(
 6769        r#"abc
 6770           defˇghi
 6771           ˇ
 6772           jkˇ
 6773           nlmˇo
 6774           "#
 6775    ));
 6776
 6777    cx.update_editor(|editor, window, cx| {
 6778        editor.add_selection_below(&Default::default(), window, cx);
 6779    });
 6780
 6781    cx.assert_editor_state(indoc!(
 6782        r#"abc
 6783           defˇghi
 6784           ˇ
 6785           jkˇ
 6786           nlmˇo
 6787           ˇ"#
 6788    ));
 6789
 6790    // change selections
 6791    cx.set_state(indoc!(
 6792        r#"abc
 6793           def«ˇg»hi
 6794
 6795           jk
 6796           nlmo
 6797           "#
 6798    ));
 6799
 6800    cx.update_editor(|editor, window, cx| {
 6801        editor.add_selection_below(&Default::default(), window, cx);
 6802    });
 6803
 6804    cx.assert_editor_state(indoc!(
 6805        r#"abc
 6806           def«ˇg»hi
 6807
 6808           jk
 6809           nlm«ˇo»
 6810           "#
 6811    ));
 6812
 6813    cx.update_editor(|editor, window, cx| {
 6814        editor.add_selection_below(&Default::default(), window, cx);
 6815    });
 6816
 6817    cx.assert_editor_state(indoc!(
 6818        r#"abc
 6819           def«ˇg»hi
 6820
 6821           jk
 6822           nlm«ˇo»
 6823           "#
 6824    ));
 6825
 6826    cx.update_editor(|editor, window, cx| {
 6827        editor.add_selection_above(&Default::default(), window, cx);
 6828    });
 6829
 6830    cx.assert_editor_state(indoc!(
 6831        r#"abc
 6832           def«ˇg»hi
 6833
 6834           jk
 6835           nlmo
 6836           "#
 6837    ));
 6838
 6839    cx.update_editor(|editor, window, cx| {
 6840        editor.add_selection_above(&Default::default(), window, cx);
 6841    });
 6842
 6843    cx.assert_editor_state(indoc!(
 6844        r#"abc
 6845           def«ˇg»hi
 6846
 6847           jk
 6848           nlmo
 6849           "#
 6850    ));
 6851
 6852    // Change selections again
 6853    cx.set_state(indoc!(
 6854        r#"a«bc
 6855           defgˇ»hi
 6856
 6857           jk
 6858           nlmo
 6859           "#
 6860    ));
 6861
 6862    cx.update_editor(|editor, window, cx| {
 6863        editor.add_selection_below(&Default::default(), window, cx);
 6864    });
 6865
 6866    cx.assert_editor_state(indoc!(
 6867        r#"a«bcˇ»
 6868           d«efgˇ»hi
 6869
 6870           j«kˇ»
 6871           nlmo
 6872           "#
 6873    ));
 6874
 6875    cx.update_editor(|editor, window, cx| {
 6876        editor.add_selection_below(&Default::default(), window, cx);
 6877    });
 6878    cx.assert_editor_state(indoc!(
 6879        r#"a«bcˇ»
 6880           d«efgˇ»hi
 6881
 6882           j«kˇ»
 6883           n«lmoˇ»
 6884           "#
 6885    ));
 6886    cx.update_editor(|editor, window, cx| {
 6887        editor.add_selection_above(&Default::default(), window, cx);
 6888    });
 6889
 6890    cx.assert_editor_state(indoc!(
 6891        r#"a«bcˇ»
 6892           d«efgˇ»hi
 6893
 6894           j«kˇ»
 6895           nlmo
 6896           "#
 6897    ));
 6898
 6899    // Change selections again
 6900    cx.set_state(indoc!(
 6901        r#"abc
 6902           d«ˇefghi
 6903
 6904           jk
 6905           nlm»o
 6906           "#
 6907    ));
 6908
 6909    cx.update_editor(|editor, window, cx| {
 6910        editor.add_selection_above(&Default::default(), window, cx);
 6911    });
 6912
 6913    cx.assert_editor_state(indoc!(
 6914        r#"a«ˇbc»
 6915           d«ˇef»ghi
 6916
 6917           j«ˇk»
 6918           n«ˇlm»o
 6919           "#
 6920    ));
 6921
 6922    cx.update_editor(|editor, window, cx| {
 6923        editor.add_selection_below(&Default::default(), window, cx);
 6924    });
 6925
 6926    cx.assert_editor_state(indoc!(
 6927        r#"abc
 6928           d«ˇef»ghi
 6929
 6930           j«ˇk»
 6931           n«ˇlm»o
 6932           "#
 6933    ));
 6934}
 6935
 6936#[gpui::test]
 6937async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 6938    init_test(cx, |_| {});
 6939    let mut cx = EditorTestContext::new(cx).await;
 6940
 6941    cx.set_state(indoc!(
 6942        r#"line onˇe
 6943           liˇne two
 6944           line three
 6945           line four"#
 6946    ));
 6947
 6948    cx.update_editor(|editor, window, cx| {
 6949        editor.add_selection_below(&Default::default(), window, cx);
 6950    });
 6951
 6952    // test multiple cursors expand in the same direction
 6953    cx.assert_editor_state(indoc!(
 6954        r#"line onˇe
 6955           liˇne twˇo
 6956           liˇne three
 6957           line four"#
 6958    ));
 6959
 6960    cx.update_editor(|editor, window, cx| {
 6961        editor.add_selection_below(&Default::default(), window, cx);
 6962    });
 6963
 6964    cx.update_editor(|editor, window, cx| {
 6965        editor.add_selection_below(&Default::default(), window, cx);
 6966    });
 6967
 6968    // test multiple cursors expand below overflow
 6969    cx.assert_editor_state(indoc!(
 6970        r#"line onˇe
 6971           liˇne twˇo
 6972           liˇne thˇree
 6973           liˇne foˇur"#
 6974    ));
 6975
 6976    cx.update_editor(|editor, window, cx| {
 6977        editor.add_selection_above(&Default::default(), window, cx);
 6978    });
 6979
 6980    // test multiple cursors retrieves back correctly
 6981    cx.assert_editor_state(indoc!(
 6982        r#"line onˇe
 6983           liˇne twˇo
 6984           liˇne thˇree
 6985           line four"#
 6986    ));
 6987
 6988    cx.update_editor(|editor, window, cx| {
 6989        editor.add_selection_above(&Default::default(), window, cx);
 6990    });
 6991
 6992    cx.update_editor(|editor, window, cx| {
 6993        editor.add_selection_above(&Default::default(), window, cx);
 6994    });
 6995
 6996    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 6997    cx.assert_editor_state(indoc!(
 6998        r#"liˇne onˇe
 6999           liˇne two
 7000           line three
 7001           line four"#
 7002    ));
 7003
 7004    cx.update_editor(|editor, window, cx| {
 7005        editor.undo_selection(&Default::default(), window, cx);
 7006    });
 7007
 7008    // test undo
 7009    cx.assert_editor_state(indoc!(
 7010        r#"line onˇe
 7011           liˇne twˇo
 7012           line three
 7013           line four"#
 7014    ));
 7015
 7016    cx.update_editor(|editor, window, cx| {
 7017        editor.redo_selection(&Default::default(), window, cx);
 7018    });
 7019
 7020    // test redo
 7021    cx.assert_editor_state(indoc!(
 7022        r#"liˇne onˇe
 7023           liˇne two
 7024           line three
 7025           line four"#
 7026    ));
 7027
 7028    cx.set_state(indoc!(
 7029        r#"abcd
 7030           ef«ghˇ»
 7031           ijkl
 7032           «mˇ»nop"#
 7033    ));
 7034
 7035    cx.update_editor(|editor, window, cx| {
 7036        editor.add_selection_above(&Default::default(), window, cx);
 7037    });
 7038
 7039    // test multiple selections expand in the same direction
 7040    cx.assert_editor_state(indoc!(
 7041        r#"ab«cdˇ»
 7042           ef«ghˇ»
 7043           «iˇ»jkl
 7044           «mˇ»nop"#
 7045    ));
 7046
 7047    cx.update_editor(|editor, window, cx| {
 7048        editor.add_selection_above(&Default::default(), window, cx);
 7049    });
 7050
 7051    // test multiple selection upward overflow
 7052    cx.assert_editor_state(indoc!(
 7053        r#"ab«cdˇ»
 7054           «eˇ»f«ghˇ»
 7055           «iˇ»jkl
 7056           «mˇ»nop"#
 7057    ));
 7058
 7059    cx.update_editor(|editor, window, cx| {
 7060        editor.add_selection_below(&Default::default(), window, cx);
 7061    });
 7062
 7063    // test multiple selection retrieves back correctly
 7064    cx.assert_editor_state(indoc!(
 7065        r#"abcd
 7066           ef«ghˇ»
 7067           «iˇ»jkl
 7068           «mˇ»nop"#
 7069    ));
 7070
 7071    cx.update_editor(|editor, window, cx| {
 7072        editor.add_selection_below(&Default::default(), window, cx);
 7073    });
 7074
 7075    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7076    cx.assert_editor_state(indoc!(
 7077        r#"abcd
 7078           ef«ghˇ»
 7079           ij«klˇ»
 7080           «mˇ»nop"#
 7081    ));
 7082
 7083    cx.update_editor(|editor, window, cx| {
 7084        editor.undo_selection(&Default::default(), window, cx);
 7085    });
 7086
 7087    // test undo
 7088    cx.assert_editor_state(indoc!(
 7089        r#"abcd
 7090           ef«ghˇ»
 7091           «iˇ»jkl
 7092           «mˇ»nop"#
 7093    ));
 7094
 7095    cx.update_editor(|editor, window, cx| {
 7096        editor.redo_selection(&Default::default(), window, cx);
 7097    });
 7098
 7099    // test redo
 7100    cx.assert_editor_state(indoc!(
 7101        r#"abcd
 7102           ef«ghˇ»
 7103           ij«klˇ»
 7104           «mˇ»nop"#
 7105    ));
 7106}
 7107
 7108#[gpui::test]
 7109async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7110    init_test(cx, |_| {});
 7111    let mut cx = EditorTestContext::new(cx).await;
 7112
 7113    cx.set_state(indoc!(
 7114        r#"line onˇe
 7115           liˇne two
 7116           line three
 7117           line four"#
 7118    ));
 7119
 7120    cx.update_editor(|editor, window, cx| {
 7121        editor.add_selection_below(&Default::default(), window, cx);
 7122        editor.add_selection_below(&Default::default(), window, cx);
 7123        editor.add_selection_below(&Default::default(), window, cx);
 7124    });
 7125
 7126    // initial state with two multi cursor groups
 7127    cx.assert_editor_state(indoc!(
 7128        r#"line onˇe
 7129           liˇne twˇo
 7130           liˇne thˇree
 7131           liˇne foˇur"#
 7132    ));
 7133
 7134    // add single cursor in middle - simulate opt click
 7135    cx.update_editor(|editor, window, cx| {
 7136        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7137        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7138        editor.end_selection(window, cx);
 7139    });
 7140
 7141    cx.assert_editor_state(indoc!(
 7142        r#"line onˇe
 7143           liˇne twˇo
 7144           liˇneˇ thˇree
 7145           liˇne foˇur"#
 7146    ));
 7147
 7148    cx.update_editor(|editor, window, cx| {
 7149        editor.add_selection_above(&Default::default(), window, cx);
 7150    });
 7151
 7152    // test new added selection expands above and existing selection shrinks
 7153    cx.assert_editor_state(indoc!(
 7154        r#"line onˇe
 7155           liˇneˇ twˇo
 7156           liˇneˇ thˇree
 7157           line four"#
 7158    ));
 7159
 7160    cx.update_editor(|editor, window, cx| {
 7161        editor.add_selection_above(&Default::default(), window, cx);
 7162    });
 7163
 7164    // test new added selection expands above and existing selection shrinks
 7165    cx.assert_editor_state(indoc!(
 7166        r#"lineˇ onˇe
 7167           liˇneˇ twˇo
 7168           lineˇ three
 7169           line four"#
 7170    ));
 7171
 7172    // intial state with two selection groups
 7173    cx.set_state(indoc!(
 7174        r#"abcd
 7175           ef«ghˇ»
 7176           ijkl
 7177           «mˇ»nop"#
 7178    ));
 7179
 7180    cx.update_editor(|editor, window, cx| {
 7181        editor.add_selection_above(&Default::default(), window, cx);
 7182        editor.add_selection_above(&Default::default(), window, cx);
 7183    });
 7184
 7185    cx.assert_editor_state(indoc!(
 7186        r#"ab«cdˇ»
 7187           «eˇ»f«ghˇ»
 7188           «iˇ»jkl
 7189           «mˇ»nop"#
 7190    ));
 7191
 7192    // add single selection in middle - simulate opt drag
 7193    cx.update_editor(|editor, window, cx| {
 7194        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7195        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7196        editor.update_selection(
 7197            DisplayPoint::new(DisplayRow(2), 4),
 7198            0,
 7199            gpui::Point::<f32>::default(),
 7200            window,
 7201            cx,
 7202        );
 7203        editor.end_selection(window, cx);
 7204    });
 7205
 7206    cx.assert_editor_state(indoc!(
 7207        r#"ab«cdˇ»
 7208           «eˇ»f«ghˇ»
 7209           «iˇ»jk«lˇ»
 7210           «mˇ»nop"#
 7211    ));
 7212
 7213    cx.update_editor(|editor, window, cx| {
 7214        editor.add_selection_below(&Default::default(), window, cx);
 7215    });
 7216
 7217    // test new added selection expands below, others shrinks from above
 7218    cx.assert_editor_state(indoc!(
 7219        r#"abcd
 7220           ef«ghˇ»
 7221           «iˇ»jk«lˇ»
 7222           «mˇ»no«pˇ»"#
 7223    ));
 7224}
 7225
 7226#[gpui::test]
 7227async fn test_select_next(cx: &mut TestAppContext) {
 7228    init_test(cx, |_| {});
 7229
 7230    let mut cx = EditorTestContext::new(cx).await;
 7231    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7232
 7233    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7234        .unwrap();
 7235    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7236
 7237    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7238        .unwrap();
 7239    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7240
 7241    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7242    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7243
 7244    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7245    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7246
 7247    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7248        .unwrap();
 7249    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7250
 7251    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7252        .unwrap();
 7253    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7254
 7255    // Test selection direction should be preserved
 7256    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7257
 7258    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7259        .unwrap();
 7260    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7261}
 7262
 7263#[gpui::test]
 7264async fn test_select_all_matches(cx: &mut TestAppContext) {
 7265    init_test(cx, |_| {});
 7266
 7267    let mut cx = EditorTestContext::new(cx).await;
 7268
 7269    // Test caret-only selections
 7270    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7271    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7272        .unwrap();
 7273    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7274
 7275    // Test left-to-right selections
 7276    cx.set_state("abc\n«abcˇ»\nabc");
 7277    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7278        .unwrap();
 7279    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7280
 7281    // Test right-to-left selections
 7282    cx.set_state("abc\n«ˇabc»\nabc");
 7283    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7284        .unwrap();
 7285    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7286
 7287    // Test selecting whitespace with caret selection
 7288    cx.set_state("abc\nˇ   abc\nabc");
 7289    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7290        .unwrap();
 7291    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7292
 7293    // Test selecting whitespace with left-to-right selection
 7294    cx.set_state("abc\n«ˇ  »abc\nabc");
 7295    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7296        .unwrap();
 7297    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7298
 7299    // Test no matches with right-to-left selection
 7300    cx.set_state("abc\n«  ˇ»abc\nabc");
 7301    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7302        .unwrap();
 7303    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7304
 7305    // Test with a single word and clip_at_line_ends=true (#29823)
 7306    cx.set_state("aˇbc");
 7307    cx.update_editor(|e, window, cx| {
 7308        e.set_clip_at_line_ends(true, cx);
 7309        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7310        e.set_clip_at_line_ends(false, cx);
 7311    });
 7312    cx.assert_editor_state("«abcˇ»");
 7313}
 7314
 7315#[gpui::test]
 7316async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7317    init_test(cx, |_| {});
 7318
 7319    let mut cx = EditorTestContext::new(cx).await;
 7320
 7321    let large_body_1 = "\nd".repeat(200);
 7322    let large_body_2 = "\ne".repeat(200);
 7323
 7324    cx.set_state(&format!(
 7325        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7326    ));
 7327    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7328        let scroll_position = editor.scroll_position(cx);
 7329        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7330        scroll_position
 7331    });
 7332
 7333    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7334        .unwrap();
 7335    cx.assert_editor_state(&format!(
 7336        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7337    ));
 7338    let scroll_position_after_selection =
 7339        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7340    assert_eq!(
 7341        initial_scroll_position, scroll_position_after_selection,
 7342        "Scroll position should not change after selecting all matches"
 7343    );
 7344}
 7345
 7346#[gpui::test]
 7347async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7348    init_test(cx, |_| {});
 7349
 7350    let mut cx = EditorLspTestContext::new_rust(
 7351        lsp::ServerCapabilities {
 7352            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7353            ..Default::default()
 7354        },
 7355        cx,
 7356    )
 7357    .await;
 7358
 7359    cx.set_state(indoc! {"
 7360        line 1
 7361        line 2
 7362        linˇe 3
 7363        line 4
 7364        line 5
 7365    "});
 7366
 7367    // Make an edit
 7368    cx.update_editor(|editor, window, cx| {
 7369        editor.handle_input("X", window, cx);
 7370    });
 7371
 7372    // Move cursor to a different position
 7373    cx.update_editor(|editor, window, cx| {
 7374        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7375            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7376        });
 7377    });
 7378
 7379    cx.assert_editor_state(indoc! {"
 7380        line 1
 7381        line 2
 7382        linXe 3
 7383        line 4
 7384        liˇne 5
 7385    "});
 7386
 7387    cx.lsp
 7388        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7389            Ok(Some(vec![lsp::TextEdit::new(
 7390                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7391                "PREFIX ".to_string(),
 7392            )]))
 7393        });
 7394
 7395    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7396        .unwrap()
 7397        .await
 7398        .unwrap();
 7399
 7400    cx.assert_editor_state(indoc! {"
 7401        PREFIX line 1
 7402        line 2
 7403        linXe 3
 7404        line 4
 7405        liˇne 5
 7406    "});
 7407
 7408    // Undo formatting
 7409    cx.update_editor(|editor, window, cx| {
 7410        editor.undo(&Default::default(), window, cx);
 7411    });
 7412
 7413    // Verify cursor moved back to position after edit
 7414    cx.assert_editor_state(indoc! {"
 7415        line 1
 7416        line 2
 7417        linXˇe 3
 7418        line 4
 7419        line 5
 7420    "});
 7421}
 7422
 7423#[gpui::test]
 7424async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7425    init_test(cx, |_| {});
 7426
 7427    let mut cx = EditorTestContext::new(cx).await;
 7428
 7429    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 7430    cx.update_editor(|editor, window, cx| {
 7431        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7432    });
 7433
 7434    cx.set_state(indoc! {"
 7435        line 1
 7436        line 2
 7437        linˇe 3
 7438        line 4
 7439        line 5
 7440        line 6
 7441        line 7
 7442        line 8
 7443        line 9
 7444        line 10
 7445    "});
 7446
 7447    let snapshot = cx.buffer_snapshot();
 7448    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7449
 7450    cx.update(|_, cx| {
 7451        provider.update(cx, |provider, _| {
 7452            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 7453                id: None,
 7454                edits: vec![(edit_position..edit_position, "X".into())],
 7455                edit_preview: None,
 7456            }))
 7457        })
 7458    });
 7459
 7460    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 7461    cx.update_editor(|editor, window, cx| {
 7462        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7463    });
 7464
 7465    cx.assert_editor_state(indoc! {"
 7466        line 1
 7467        line 2
 7468        lineXˇ 3
 7469        line 4
 7470        line 5
 7471        line 6
 7472        line 7
 7473        line 8
 7474        line 9
 7475        line 10
 7476    "});
 7477
 7478    cx.update_editor(|editor, window, cx| {
 7479        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7480            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7481        });
 7482    });
 7483
 7484    cx.assert_editor_state(indoc! {"
 7485        line 1
 7486        line 2
 7487        lineX 3
 7488        line 4
 7489        line 5
 7490        line 6
 7491        line 7
 7492        line 8
 7493        line 9
 7494        liˇne 10
 7495    "});
 7496
 7497    cx.update_editor(|editor, window, cx| {
 7498        editor.undo(&Default::default(), window, cx);
 7499    });
 7500
 7501    cx.assert_editor_state(indoc! {"
 7502        line 1
 7503        line 2
 7504        lineˇ 3
 7505        line 4
 7506        line 5
 7507        line 6
 7508        line 7
 7509        line 8
 7510        line 9
 7511        line 10
 7512    "});
 7513}
 7514
 7515#[gpui::test]
 7516async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7517    init_test(cx, |_| {});
 7518
 7519    let mut cx = EditorTestContext::new(cx).await;
 7520    cx.set_state(
 7521        r#"let foo = 2;
 7522lˇet foo = 2;
 7523let fooˇ = 2;
 7524let foo = 2;
 7525let foo = ˇ2;"#,
 7526    );
 7527
 7528    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7529        .unwrap();
 7530    cx.assert_editor_state(
 7531        r#"let foo = 2;
 7532«letˇ» foo = 2;
 7533let «fooˇ» = 2;
 7534let foo = 2;
 7535let foo = «2ˇ»;"#,
 7536    );
 7537
 7538    // noop for multiple selections with different contents
 7539    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7540        .unwrap();
 7541    cx.assert_editor_state(
 7542        r#"let foo = 2;
 7543«letˇ» foo = 2;
 7544let «fooˇ» = 2;
 7545let foo = 2;
 7546let foo = «2ˇ»;"#,
 7547    );
 7548
 7549    // Test last selection direction should be preserved
 7550    cx.set_state(
 7551        r#"let foo = 2;
 7552let foo = 2;
 7553let «fooˇ» = 2;
 7554let «ˇfoo» = 2;
 7555let foo = 2;"#,
 7556    );
 7557
 7558    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7559        .unwrap();
 7560    cx.assert_editor_state(
 7561        r#"let foo = 2;
 7562let foo = 2;
 7563let «fooˇ» = 2;
 7564let «ˇfoo» = 2;
 7565let «ˇfoo» = 2;"#,
 7566    );
 7567}
 7568
 7569#[gpui::test]
 7570async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7571    init_test(cx, |_| {});
 7572
 7573    let mut cx =
 7574        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7575
 7576    cx.assert_editor_state(indoc! {"
 7577        ˇbbb
 7578        ccc
 7579
 7580        bbb
 7581        ccc
 7582        "});
 7583    cx.dispatch_action(SelectPrevious::default());
 7584    cx.assert_editor_state(indoc! {"
 7585                «bbbˇ»
 7586                ccc
 7587
 7588                bbb
 7589                ccc
 7590                "});
 7591    cx.dispatch_action(SelectPrevious::default());
 7592    cx.assert_editor_state(indoc! {"
 7593                «bbbˇ»
 7594                ccc
 7595
 7596                «bbbˇ»
 7597                ccc
 7598                "});
 7599}
 7600
 7601#[gpui::test]
 7602async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 7603    init_test(cx, |_| {});
 7604
 7605    let mut cx = EditorTestContext::new(cx).await;
 7606    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7607
 7608    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7609        .unwrap();
 7610    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7611
 7612    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7613        .unwrap();
 7614    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7615
 7616    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7617    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7618
 7619    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7620    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 7621
 7622    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7623        .unwrap();
 7624    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 7625
 7626    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7627        .unwrap();
 7628    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7629}
 7630
 7631#[gpui::test]
 7632async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 7633    init_test(cx, |_| {});
 7634
 7635    let mut cx = EditorTestContext::new(cx).await;
 7636    cx.set_state("");
 7637
 7638    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7639        .unwrap();
 7640    cx.assert_editor_state("«aˇ»");
 7641    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7642        .unwrap();
 7643    cx.assert_editor_state("«aˇ»");
 7644}
 7645
 7646#[gpui::test]
 7647async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 7648    init_test(cx, |_| {});
 7649
 7650    let mut cx = EditorTestContext::new(cx).await;
 7651    cx.set_state(
 7652        r#"let foo = 2;
 7653lˇet foo = 2;
 7654let fooˇ = 2;
 7655let foo = 2;
 7656let foo = ˇ2;"#,
 7657    );
 7658
 7659    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7660        .unwrap();
 7661    cx.assert_editor_state(
 7662        r#"let foo = 2;
 7663«letˇ» foo = 2;
 7664let «fooˇ» = 2;
 7665let foo = 2;
 7666let foo = «2ˇ»;"#,
 7667    );
 7668
 7669    // noop for multiple selections with different contents
 7670    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7671        .unwrap();
 7672    cx.assert_editor_state(
 7673        r#"let foo = 2;
 7674«letˇ» foo = 2;
 7675let «fooˇ» = 2;
 7676let foo = 2;
 7677let foo = «2ˇ»;"#,
 7678    );
 7679}
 7680
 7681#[gpui::test]
 7682async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 7683    init_test(cx, |_| {});
 7684
 7685    let mut cx = EditorTestContext::new(cx).await;
 7686    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7687
 7688    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7689        .unwrap();
 7690    // selection direction is preserved
 7691    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7692
 7693    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7694        .unwrap();
 7695    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7696
 7697    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7698    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 7699
 7700    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7701    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 7702
 7703    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7704        .unwrap();
 7705    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 7706
 7707    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 7708        .unwrap();
 7709    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 7710}
 7711
 7712#[gpui::test]
 7713async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 7714    init_test(cx, |_| {});
 7715
 7716    let language = Arc::new(Language::new(
 7717        LanguageConfig::default(),
 7718        Some(tree_sitter_rust::LANGUAGE.into()),
 7719    ));
 7720
 7721    let text = r#"
 7722        use mod1::mod2::{mod3, mod4};
 7723
 7724        fn fn_1(param1: bool, param2: &str) {
 7725            let var1 = "text";
 7726        }
 7727    "#
 7728    .unindent();
 7729
 7730    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7731    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7732    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7733
 7734    editor
 7735        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7736        .await;
 7737
 7738    editor.update_in(cx, |editor, window, cx| {
 7739        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7740            s.select_display_ranges([
 7741                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 7742                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 7743                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 7744            ]);
 7745        });
 7746        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7747    });
 7748    editor.update(cx, |editor, cx| {
 7749        assert_text_with_selections(
 7750            editor,
 7751            indoc! {r#"
 7752                use mod1::mod2::{mod3, «mod4ˇ»};
 7753
 7754                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7755                    let var1 = "«ˇtext»";
 7756                }
 7757            "#},
 7758            cx,
 7759        );
 7760    });
 7761
 7762    editor.update_in(cx, |editor, window, cx| {
 7763        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7764    });
 7765    editor.update(cx, |editor, cx| {
 7766        assert_text_with_selections(
 7767            editor,
 7768            indoc! {r#"
 7769                use mod1::mod2::«{mod3, mod4}ˇ»;
 7770
 7771                «ˇfn fn_1(param1: bool, param2: &str) {
 7772                    let var1 = "text";
 7773 7774            "#},
 7775            cx,
 7776        );
 7777    });
 7778
 7779    editor.update_in(cx, |editor, window, cx| {
 7780        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7781    });
 7782    assert_eq!(
 7783        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7784        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7785    );
 7786
 7787    // Trying to expand the selected syntax node one more time has no effect.
 7788    editor.update_in(cx, |editor, window, cx| {
 7789        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7790    });
 7791    assert_eq!(
 7792        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 7793        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 7794    );
 7795
 7796    editor.update_in(cx, |editor, window, cx| {
 7797        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7798    });
 7799    editor.update(cx, |editor, cx| {
 7800        assert_text_with_selections(
 7801            editor,
 7802            indoc! {r#"
 7803                use mod1::mod2::«{mod3, mod4}ˇ»;
 7804
 7805                «ˇfn fn_1(param1: bool, param2: &str) {
 7806                    let var1 = "text";
 7807 7808            "#},
 7809            cx,
 7810        );
 7811    });
 7812
 7813    editor.update_in(cx, |editor, window, cx| {
 7814        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7815    });
 7816    editor.update(cx, |editor, cx| {
 7817        assert_text_with_selections(
 7818            editor,
 7819            indoc! {r#"
 7820                use mod1::mod2::{mod3, «mod4ˇ»};
 7821
 7822                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7823                    let var1 = "«ˇtext»";
 7824                }
 7825            "#},
 7826            cx,
 7827        );
 7828    });
 7829
 7830    editor.update_in(cx, |editor, window, cx| {
 7831        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7832    });
 7833    editor.update(cx, |editor, cx| {
 7834        assert_text_with_selections(
 7835            editor,
 7836            indoc! {r#"
 7837                use mod1::mod2::{mod3, mo«ˇ»d4};
 7838
 7839                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7840                    let var1 = "te«ˇ»xt";
 7841                }
 7842            "#},
 7843            cx,
 7844        );
 7845    });
 7846
 7847    // Trying to shrink the selected syntax node one more time has no effect.
 7848    editor.update_in(cx, |editor, window, cx| {
 7849        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 7850    });
 7851    editor.update_in(cx, |editor, _, cx| {
 7852        assert_text_with_selections(
 7853            editor,
 7854            indoc! {r#"
 7855                use mod1::mod2::{mod3, mo«ˇ»d4};
 7856
 7857                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 7858                    let var1 = "te«ˇ»xt";
 7859                }
 7860            "#},
 7861            cx,
 7862        );
 7863    });
 7864
 7865    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 7866    // a fold.
 7867    editor.update_in(cx, |editor, window, cx| {
 7868        editor.fold_creases(
 7869            vec![
 7870                Crease::simple(
 7871                    Point::new(0, 21)..Point::new(0, 24),
 7872                    FoldPlaceholder::test(),
 7873                ),
 7874                Crease::simple(
 7875                    Point::new(3, 20)..Point::new(3, 22),
 7876                    FoldPlaceholder::test(),
 7877                ),
 7878            ],
 7879            true,
 7880            window,
 7881            cx,
 7882        );
 7883        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7884    });
 7885    editor.update(cx, |editor, cx| {
 7886        assert_text_with_selections(
 7887            editor,
 7888            indoc! {r#"
 7889                use mod1::mod2::«{mod3, mod4}ˇ»;
 7890
 7891                fn fn_1«ˇ(param1: bool, param2: &str)» {
 7892                    let var1 = "«ˇtext»";
 7893                }
 7894            "#},
 7895            cx,
 7896        );
 7897    });
 7898}
 7899
 7900#[gpui::test]
 7901async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 7902    init_test(cx, |_| {});
 7903
 7904    let language = Arc::new(Language::new(
 7905        LanguageConfig::default(),
 7906        Some(tree_sitter_rust::LANGUAGE.into()),
 7907    ));
 7908
 7909    let text = "let a = 2;";
 7910
 7911    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7912    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7913    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7914
 7915    editor
 7916        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7917        .await;
 7918
 7919    // Test case 1: Cursor at end of word
 7920    editor.update_in(cx, |editor, window, cx| {
 7921        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7922            s.select_display_ranges([
 7923                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 7924            ]);
 7925        });
 7926    });
 7927    editor.update(cx, |editor, cx| {
 7928        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 7929    });
 7930    editor.update_in(cx, |editor, window, cx| {
 7931        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7932    });
 7933    editor.update(cx, |editor, cx| {
 7934        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 7935    });
 7936    editor.update_in(cx, |editor, window, cx| {
 7937        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7938    });
 7939    editor.update(cx, |editor, cx| {
 7940        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7941    });
 7942
 7943    // Test case 2: Cursor at end of statement
 7944    editor.update_in(cx, |editor, window, cx| {
 7945        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7946            s.select_display_ranges([
 7947                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 7948            ]);
 7949        });
 7950    });
 7951    editor.update(cx, |editor, cx| {
 7952        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 7953    });
 7954    editor.update_in(cx, |editor, window, cx| {
 7955        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 7956    });
 7957    editor.update(cx, |editor, cx| {
 7958        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 7959    });
 7960}
 7961
 7962#[gpui::test]
 7963async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 7964    init_test(cx, |_| {});
 7965
 7966    let language = Arc::new(Language::new(
 7967        LanguageConfig::default(),
 7968        Some(tree_sitter_rust::LANGUAGE.into()),
 7969    ));
 7970
 7971    let text = r#"
 7972        use mod1::mod2::{mod3, mod4};
 7973
 7974        fn fn_1(param1: bool, param2: &str) {
 7975            let var1 = "hello world";
 7976        }
 7977    "#
 7978    .unindent();
 7979
 7980    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7981    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7982    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7983
 7984    editor
 7985        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7986        .await;
 7987
 7988    // Test 1: Cursor on a letter of a string word
 7989    editor.update_in(cx, |editor, window, cx| {
 7990        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7991            s.select_display_ranges([
 7992                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 7993            ]);
 7994        });
 7995    });
 7996    editor.update_in(cx, |editor, window, cx| {
 7997        assert_text_with_selections(
 7998            editor,
 7999            indoc! {r#"
 8000                use mod1::mod2::{mod3, mod4};
 8001
 8002                fn fn_1(param1: bool, param2: &str) {
 8003                    let var1 = "hˇello world";
 8004                }
 8005            "#},
 8006            cx,
 8007        );
 8008        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8009        assert_text_with_selections(
 8010            editor,
 8011            indoc! {r#"
 8012                use mod1::mod2::{mod3, mod4};
 8013
 8014                fn fn_1(param1: bool, param2: &str) {
 8015                    let var1 = "«ˇhello» world";
 8016                }
 8017            "#},
 8018            cx,
 8019        );
 8020    });
 8021
 8022    // Test 2: Partial selection within a word
 8023    editor.update_in(cx, |editor, window, cx| {
 8024        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8025            s.select_display_ranges([
 8026                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 8027            ]);
 8028        });
 8029    });
 8030    editor.update_in(cx, |editor, window, cx| {
 8031        assert_text_with_selections(
 8032            editor,
 8033            indoc! {r#"
 8034                use mod1::mod2::{mod3, mod4};
 8035
 8036                fn fn_1(param1: bool, param2: &str) {
 8037                    let var1 = "h«elˇ»lo world";
 8038                }
 8039            "#},
 8040            cx,
 8041        );
 8042        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8043        assert_text_with_selections(
 8044            editor,
 8045            indoc! {r#"
 8046                use mod1::mod2::{mod3, mod4};
 8047
 8048                fn fn_1(param1: bool, param2: &str) {
 8049                    let var1 = "«ˇhello» world";
 8050                }
 8051            "#},
 8052            cx,
 8053        );
 8054    });
 8055
 8056    // Test 3: Complete word already selected
 8057    editor.update_in(cx, |editor, window, cx| {
 8058        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8059            s.select_display_ranges([
 8060                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 8061            ]);
 8062        });
 8063    });
 8064    editor.update_in(cx, |editor, window, cx| {
 8065        assert_text_with_selections(
 8066            editor,
 8067            indoc! {r#"
 8068                use mod1::mod2::{mod3, mod4};
 8069
 8070                fn fn_1(param1: bool, param2: &str) {
 8071                    let var1 = "«helloˇ» world";
 8072                }
 8073            "#},
 8074            cx,
 8075        );
 8076        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8077        assert_text_with_selections(
 8078            editor,
 8079            indoc! {r#"
 8080                use mod1::mod2::{mod3, mod4};
 8081
 8082                fn fn_1(param1: bool, param2: &str) {
 8083                    let var1 = "«hello worldˇ»";
 8084                }
 8085            "#},
 8086            cx,
 8087        );
 8088    });
 8089
 8090    // Test 4: Selection spanning across words
 8091    editor.update_in(cx, |editor, window, cx| {
 8092        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8093            s.select_display_ranges([
 8094                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 8095            ]);
 8096        });
 8097    });
 8098    editor.update_in(cx, |editor, window, cx| {
 8099        assert_text_with_selections(
 8100            editor,
 8101            indoc! {r#"
 8102                use mod1::mod2::{mod3, mod4};
 8103
 8104                fn fn_1(param1: bool, param2: &str) {
 8105                    let var1 = "hel«lo woˇ»rld";
 8106                }
 8107            "#},
 8108            cx,
 8109        );
 8110        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8111        assert_text_with_selections(
 8112            editor,
 8113            indoc! {r#"
 8114                use mod1::mod2::{mod3, mod4};
 8115
 8116                fn fn_1(param1: bool, param2: &str) {
 8117                    let var1 = "«ˇhello world»";
 8118                }
 8119            "#},
 8120            cx,
 8121        );
 8122    });
 8123
 8124    // Test 5: Expansion beyond string
 8125    editor.update_in(cx, |editor, window, cx| {
 8126        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8127        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8128        assert_text_with_selections(
 8129            editor,
 8130            indoc! {r#"
 8131                use mod1::mod2::{mod3, mod4};
 8132
 8133                fn fn_1(param1: bool, param2: &str) {
 8134                    «ˇlet var1 = "hello world";»
 8135                }
 8136            "#},
 8137            cx,
 8138        );
 8139    });
 8140}
 8141
 8142#[gpui::test]
 8143async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 8144    init_test(cx, |_| {});
 8145
 8146    let mut cx = EditorTestContext::new(cx).await;
 8147
 8148    let language = Arc::new(Language::new(
 8149        LanguageConfig::default(),
 8150        Some(tree_sitter_rust::LANGUAGE.into()),
 8151    ));
 8152
 8153    cx.update_buffer(|buffer, cx| {
 8154        buffer.set_language(Some(language), cx);
 8155    });
 8156
 8157    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 8158    cx.update_editor(|editor, window, cx| {
 8159        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 8160    });
 8161
 8162    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 8163}
 8164
 8165#[gpui::test]
 8166async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 8167    init_test(cx, |_| {});
 8168
 8169    let base_text = r#"
 8170        impl A {
 8171            // this is an uncommitted comment
 8172
 8173            fn b() {
 8174                c();
 8175            }
 8176
 8177            // this is another uncommitted comment
 8178
 8179            fn d() {
 8180                // e
 8181                // f
 8182            }
 8183        }
 8184
 8185        fn g() {
 8186            // h
 8187        }
 8188    "#
 8189    .unindent();
 8190
 8191    let text = r#"
 8192        ˇimpl A {
 8193
 8194            fn b() {
 8195                c();
 8196            }
 8197
 8198            fn d() {
 8199                // e
 8200                // f
 8201            }
 8202        }
 8203
 8204        fn g() {
 8205            // h
 8206        }
 8207    "#
 8208    .unindent();
 8209
 8210    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8211    cx.set_state(&text);
 8212    cx.set_head_text(&base_text);
 8213    cx.update_editor(|editor, window, cx| {
 8214        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8215    });
 8216
 8217    cx.assert_state_with_diff(
 8218        "
 8219        ˇimpl A {
 8220      -     // this is an uncommitted comment
 8221
 8222            fn b() {
 8223                c();
 8224            }
 8225
 8226      -     // this is another uncommitted comment
 8227      -
 8228            fn d() {
 8229                // e
 8230                // f
 8231            }
 8232        }
 8233
 8234        fn g() {
 8235            // h
 8236        }
 8237    "
 8238        .unindent(),
 8239    );
 8240
 8241    let expected_display_text = "
 8242        impl A {
 8243            // this is an uncommitted comment
 8244
 8245            fn b() {
 8246 8247            }
 8248
 8249            // this is another uncommitted comment
 8250
 8251            fn d() {
 8252 8253            }
 8254        }
 8255
 8256        fn g() {
 8257 8258        }
 8259        "
 8260    .unindent();
 8261
 8262    cx.update_editor(|editor, window, cx| {
 8263        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8264        assert_eq!(editor.display_text(cx), expected_display_text);
 8265    });
 8266}
 8267
 8268#[gpui::test]
 8269async fn test_autoindent(cx: &mut TestAppContext) {
 8270    init_test(cx, |_| {});
 8271
 8272    let language = Arc::new(
 8273        Language::new(
 8274            LanguageConfig {
 8275                brackets: BracketPairConfig {
 8276                    pairs: vec![
 8277                        BracketPair {
 8278                            start: "{".to_string(),
 8279                            end: "}".to_string(),
 8280                            close: false,
 8281                            surround: false,
 8282                            newline: true,
 8283                        },
 8284                        BracketPair {
 8285                            start: "(".to_string(),
 8286                            end: ")".to_string(),
 8287                            close: false,
 8288                            surround: false,
 8289                            newline: true,
 8290                        },
 8291                    ],
 8292                    ..Default::default()
 8293                },
 8294                ..Default::default()
 8295            },
 8296            Some(tree_sitter_rust::LANGUAGE.into()),
 8297        )
 8298        .with_indents_query(
 8299            r#"
 8300                (_ "(" ")" @end) @indent
 8301                (_ "{" "}" @end) @indent
 8302            "#,
 8303        )
 8304        .unwrap(),
 8305    );
 8306
 8307    let text = "fn a() {}";
 8308
 8309    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8310    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8311    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8312    editor
 8313        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8314        .await;
 8315
 8316    editor.update_in(cx, |editor, window, cx| {
 8317        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8318            s.select_ranges([5..5, 8..8, 9..9])
 8319        });
 8320        editor.newline(&Newline, window, cx);
 8321        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8322        assert_eq!(
 8323            editor.selections.ranges(cx),
 8324            &[
 8325                Point::new(1, 4)..Point::new(1, 4),
 8326                Point::new(3, 4)..Point::new(3, 4),
 8327                Point::new(5, 0)..Point::new(5, 0)
 8328            ]
 8329        );
 8330    });
 8331}
 8332
 8333#[gpui::test]
 8334async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 8335    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 8336
 8337    let language = Arc::new(
 8338        Language::new(
 8339            LanguageConfig {
 8340                brackets: BracketPairConfig {
 8341                    pairs: vec![
 8342                        BracketPair {
 8343                            start: "{".to_string(),
 8344                            end: "}".to_string(),
 8345                            close: false,
 8346                            surround: false,
 8347                            newline: true,
 8348                        },
 8349                        BracketPair {
 8350                            start: "(".to_string(),
 8351                            end: ")".to_string(),
 8352                            close: false,
 8353                            surround: false,
 8354                            newline: true,
 8355                        },
 8356                    ],
 8357                    ..Default::default()
 8358                },
 8359                ..Default::default()
 8360            },
 8361            Some(tree_sitter_rust::LANGUAGE.into()),
 8362        )
 8363        .with_indents_query(
 8364            r#"
 8365                (_ "(" ")" @end) @indent
 8366                (_ "{" "}" @end) @indent
 8367            "#,
 8368        )
 8369        .unwrap(),
 8370    );
 8371
 8372    let text = "fn a() {}";
 8373
 8374    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8375    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8376    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8377    editor
 8378        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8379        .await;
 8380
 8381    editor.update_in(cx, |editor, window, cx| {
 8382        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8383            s.select_ranges([5..5, 8..8, 9..9])
 8384        });
 8385        editor.newline(&Newline, window, cx);
 8386        assert_eq!(
 8387            editor.text(cx),
 8388            indoc!(
 8389                "
 8390                fn a(
 8391
 8392                ) {
 8393
 8394                }
 8395                "
 8396            )
 8397        );
 8398        assert_eq!(
 8399            editor.selections.ranges(cx),
 8400            &[
 8401                Point::new(1, 0)..Point::new(1, 0),
 8402                Point::new(3, 0)..Point::new(3, 0),
 8403                Point::new(5, 0)..Point::new(5, 0)
 8404            ]
 8405        );
 8406    });
 8407}
 8408
 8409#[gpui::test]
 8410async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 8411    init_test(cx, |settings| {
 8412        settings.defaults.auto_indent = Some(true);
 8413        settings.languages.0.insert(
 8414            "python".into(),
 8415            LanguageSettingsContent {
 8416                auto_indent: Some(false),
 8417                ..Default::default()
 8418            },
 8419        );
 8420    });
 8421
 8422    let mut cx = EditorTestContext::new(cx).await;
 8423
 8424    let injected_language = Arc::new(
 8425        Language::new(
 8426            LanguageConfig {
 8427                brackets: BracketPairConfig {
 8428                    pairs: vec![
 8429                        BracketPair {
 8430                            start: "{".to_string(),
 8431                            end: "}".to_string(),
 8432                            close: false,
 8433                            surround: false,
 8434                            newline: true,
 8435                        },
 8436                        BracketPair {
 8437                            start: "(".to_string(),
 8438                            end: ")".to_string(),
 8439                            close: true,
 8440                            surround: false,
 8441                            newline: true,
 8442                        },
 8443                    ],
 8444                    ..Default::default()
 8445                },
 8446                name: "python".into(),
 8447                ..Default::default()
 8448            },
 8449            Some(tree_sitter_python::LANGUAGE.into()),
 8450        )
 8451        .with_indents_query(
 8452            r#"
 8453                (_ "(" ")" @end) @indent
 8454                (_ "{" "}" @end) @indent
 8455            "#,
 8456        )
 8457        .unwrap(),
 8458    );
 8459
 8460    let language = Arc::new(
 8461        Language::new(
 8462            LanguageConfig {
 8463                brackets: BracketPairConfig {
 8464                    pairs: vec![
 8465                        BracketPair {
 8466                            start: "{".to_string(),
 8467                            end: "}".to_string(),
 8468                            close: false,
 8469                            surround: false,
 8470                            newline: true,
 8471                        },
 8472                        BracketPair {
 8473                            start: "(".to_string(),
 8474                            end: ")".to_string(),
 8475                            close: true,
 8476                            surround: false,
 8477                            newline: true,
 8478                        },
 8479                    ],
 8480                    ..Default::default()
 8481                },
 8482                name: LanguageName::new("rust"),
 8483                ..Default::default()
 8484            },
 8485            Some(tree_sitter_rust::LANGUAGE.into()),
 8486        )
 8487        .with_indents_query(
 8488            r#"
 8489                (_ "(" ")" @end) @indent
 8490                (_ "{" "}" @end) @indent
 8491            "#,
 8492        )
 8493        .unwrap()
 8494        .with_injection_query(
 8495            r#"
 8496            (macro_invocation
 8497                macro: (identifier) @_macro_name
 8498                (token_tree) @injection.content
 8499                (#set! injection.language "python"))
 8500           "#,
 8501        )
 8502        .unwrap(),
 8503    );
 8504
 8505    cx.language_registry().add(injected_language);
 8506    cx.language_registry().add(language.clone());
 8507
 8508    cx.update_buffer(|buffer, cx| {
 8509        buffer.set_language(Some(language), cx);
 8510    });
 8511
 8512    cx.set_state(r#"struct A {ˇ}"#);
 8513
 8514    cx.update_editor(|editor, window, cx| {
 8515        editor.newline(&Default::default(), window, cx);
 8516    });
 8517
 8518    cx.assert_editor_state(indoc!(
 8519        "struct A {
 8520            ˇ
 8521        }"
 8522    ));
 8523
 8524    cx.set_state(r#"select_biased!(ˇ)"#);
 8525
 8526    cx.update_editor(|editor, window, cx| {
 8527        editor.newline(&Default::default(), window, cx);
 8528        editor.handle_input("def ", window, cx);
 8529        editor.handle_input("(", window, cx);
 8530        editor.newline(&Default::default(), window, cx);
 8531        editor.handle_input("a", window, cx);
 8532    });
 8533
 8534    cx.assert_editor_state(indoc!(
 8535        "select_biased!(
 8536        def (
 8537 8538        )
 8539        )"
 8540    ));
 8541}
 8542
 8543#[gpui::test]
 8544async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8545    init_test(cx, |_| {});
 8546
 8547    {
 8548        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8549        cx.set_state(indoc! {"
 8550            impl A {
 8551
 8552                fn b() {}
 8553
 8554            «fn c() {
 8555
 8556            }ˇ»
 8557            }
 8558        "});
 8559
 8560        cx.update_editor(|editor, window, cx| {
 8561            editor.autoindent(&Default::default(), window, cx);
 8562        });
 8563
 8564        cx.assert_editor_state(indoc! {"
 8565            impl A {
 8566
 8567                fn b() {}
 8568
 8569                «fn c() {
 8570
 8571                }ˇ»
 8572            }
 8573        "});
 8574    }
 8575
 8576    {
 8577        let mut cx = EditorTestContext::new_multibuffer(
 8578            cx,
 8579            [indoc! { "
 8580                impl A {
 8581                «
 8582                // a
 8583                fn b(){}
 8584                »
 8585                «
 8586                    }
 8587                    fn c(){}
 8588                »
 8589            "}],
 8590        );
 8591
 8592        let buffer = cx.update_editor(|editor, _, cx| {
 8593            let buffer = editor.buffer().update(cx, |buffer, _| {
 8594                buffer.all_buffers().iter().next().unwrap().clone()
 8595            });
 8596            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 8597            buffer
 8598        });
 8599
 8600        cx.run_until_parked();
 8601        cx.update_editor(|editor, window, cx| {
 8602            editor.select_all(&Default::default(), window, cx);
 8603            editor.autoindent(&Default::default(), window, cx)
 8604        });
 8605        cx.run_until_parked();
 8606
 8607        cx.update(|_, cx| {
 8608            assert_eq!(
 8609                buffer.read(cx).text(),
 8610                indoc! { "
 8611                    impl A {
 8612
 8613                        // a
 8614                        fn b(){}
 8615
 8616
 8617                    }
 8618                    fn c(){}
 8619
 8620                " }
 8621            )
 8622        });
 8623    }
 8624}
 8625
 8626#[gpui::test]
 8627async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 8628    init_test(cx, |_| {});
 8629
 8630    let mut cx = EditorTestContext::new(cx).await;
 8631
 8632    let language = Arc::new(Language::new(
 8633        LanguageConfig {
 8634            brackets: BracketPairConfig {
 8635                pairs: vec![
 8636                    BracketPair {
 8637                        start: "{".to_string(),
 8638                        end: "}".to_string(),
 8639                        close: true,
 8640                        surround: true,
 8641                        newline: true,
 8642                    },
 8643                    BracketPair {
 8644                        start: "(".to_string(),
 8645                        end: ")".to_string(),
 8646                        close: true,
 8647                        surround: true,
 8648                        newline: true,
 8649                    },
 8650                    BracketPair {
 8651                        start: "/*".to_string(),
 8652                        end: " */".to_string(),
 8653                        close: true,
 8654                        surround: true,
 8655                        newline: true,
 8656                    },
 8657                    BracketPair {
 8658                        start: "[".to_string(),
 8659                        end: "]".to_string(),
 8660                        close: false,
 8661                        surround: false,
 8662                        newline: true,
 8663                    },
 8664                    BracketPair {
 8665                        start: "\"".to_string(),
 8666                        end: "\"".to_string(),
 8667                        close: true,
 8668                        surround: true,
 8669                        newline: false,
 8670                    },
 8671                    BracketPair {
 8672                        start: "<".to_string(),
 8673                        end: ">".to_string(),
 8674                        close: false,
 8675                        surround: true,
 8676                        newline: true,
 8677                    },
 8678                ],
 8679                ..Default::default()
 8680            },
 8681            autoclose_before: "})]".to_string(),
 8682            ..Default::default()
 8683        },
 8684        Some(tree_sitter_rust::LANGUAGE.into()),
 8685    ));
 8686
 8687    cx.language_registry().add(language.clone());
 8688    cx.update_buffer(|buffer, cx| {
 8689        buffer.set_language(Some(language), cx);
 8690    });
 8691
 8692    cx.set_state(
 8693        &r#"
 8694            🏀ˇ
 8695            εˇ
 8696            ❤️ˇ
 8697        "#
 8698        .unindent(),
 8699    );
 8700
 8701    // autoclose multiple nested brackets at multiple cursors
 8702    cx.update_editor(|editor, window, cx| {
 8703        editor.handle_input("{", window, cx);
 8704        editor.handle_input("{", window, cx);
 8705        editor.handle_input("{", window, cx);
 8706    });
 8707    cx.assert_editor_state(
 8708        &"
 8709            🏀{{{ˇ}}}
 8710            ε{{{ˇ}}}
 8711            ❤️{{{ˇ}}}
 8712        "
 8713        .unindent(),
 8714    );
 8715
 8716    // insert a different closing bracket
 8717    cx.update_editor(|editor, window, cx| {
 8718        editor.handle_input(")", window, cx);
 8719    });
 8720    cx.assert_editor_state(
 8721        &"
 8722            🏀{{{)ˇ}}}
 8723            ε{{{)ˇ}}}
 8724            ❤️{{{)ˇ}}}
 8725        "
 8726        .unindent(),
 8727    );
 8728
 8729    // skip over the auto-closed brackets when typing a closing bracket
 8730    cx.update_editor(|editor, window, cx| {
 8731        editor.move_right(&MoveRight, window, cx);
 8732        editor.handle_input("}", window, cx);
 8733        editor.handle_input("}", window, cx);
 8734        editor.handle_input("}", window, cx);
 8735    });
 8736    cx.assert_editor_state(
 8737        &"
 8738            🏀{{{)}}}}ˇ
 8739            ε{{{)}}}}ˇ
 8740            ❤️{{{)}}}}ˇ
 8741        "
 8742        .unindent(),
 8743    );
 8744
 8745    // autoclose multi-character pairs
 8746    cx.set_state(
 8747        &"
 8748            ˇ
 8749            ˇ
 8750        "
 8751        .unindent(),
 8752    );
 8753    cx.update_editor(|editor, window, cx| {
 8754        editor.handle_input("/", window, cx);
 8755        editor.handle_input("*", window, cx);
 8756    });
 8757    cx.assert_editor_state(
 8758        &"
 8759            /*ˇ */
 8760            /*ˇ */
 8761        "
 8762        .unindent(),
 8763    );
 8764
 8765    // one cursor autocloses a multi-character pair, one cursor
 8766    // does not autoclose.
 8767    cx.set_state(
 8768        &"
 8769 8770            ˇ
 8771        "
 8772        .unindent(),
 8773    );
 8774    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 8775    cx.assert_editor_state(
 8776        &"
 8777            /*ˇ */
 8778 8779        "
 8780        .unindent(),
 8781    );
 8782
 8783    // Don't autoclose if the next character isn't whitespace and isn't
 8784    // listed in the language's "autoclose_before" section.
 8785    cx.set_state("ˇa b");
 8786    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8787    cx.assert_editor_state("{ˇa b");
 8788
 8789    // Don't autoclose if `close` is false for the bracket pair
 8790    cx.set_state("ˇ");
 8791    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 8792    cx.assert_editor_state("");
 8793
 8794    // Surround with brackets if text is selected
 8795    cx.set_state("«aˇ» b");
 8796    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8797    cx.assert_editor_state("{«aˇ»} b");
 8798
 8799    // Autoclose when not immediately after a word character
 8800    cx.set_state("a ˇ");
 8801    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8802    cx.assert_editor_state("a \"ˇ\"");
 8803
 8804    // Autoclose pair where the start and end characters are the same
 8805    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8806    cx.assert_editor_state("a \"\"ˇ");
 8807
 8808    // Don't autoclose when immediately after a word character
 8809    cx.set_state("");
 8810    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8811    cx.assert_editor_state("a\"ˇ");
 8812
 8813    // Do autoclose when after a non-word character
 8814    cx.set_state("");
 8815    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 8816    cx.assert_editor_state("{\"ˇ\"");
 8817
 8818    // Non identical pairs autoclose regardless of preceding character
 8819    cx.set_state("");
 8820    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 8821    cx.assert_editor_state("a{ˇ}");
 8822
 8823    // Don't autoclose pair if autoclose is disabled
 8824    cx.set_state("ˇ");
 8825    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8826    cx.assert_editor_state("");
 8827
 8828    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 8829    cx.set_state("«aˇ» b");
 8830    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 8831    cx.assert_editor_state("<«aˇ»> b");
 8832}
 8833
 8834#[gpui::test]
 8835async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 8836    init_test(cx, |settings| {
 8837        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 8838    });
 8839
 8840    let mut cx = EditorTestContext::new(cx).await;
 8841
 8842    let language = Arc::new(Language::new(
 8843        LanguageConfig {
 8844            brackets: BracketPairConfig {
 8845                pairs: vec![
 8846                    BracketPair {
 8847                        start: "{".to_string(),
 8848                        end: "}".to_string(),
 8849                        close: true,
 8850                        surround: true,
 8851                        newline: true,
 8852                    },
 8853                    BracketPair {
 8854                        start: "(".to_string(),
 8855                        end: ")".to_string(),
 8856                        close: true,
 8857                        surround: true,
 8858                        newline: true,
 8859                    },
 8860                    BracketPair {
 8861                        start: "[".to_string(),
 8862                        end: "]".to_string(),
 8863                        close: false,
 8864                        surround: false,
 8865                        newline: true,
 8866                    },
 8867                ],
 8868                ..Default::default()
 8869            },
 8870            autoclose_before: "})]".to_string(),
 8871            ..Default::default()
 8872        },
 8873        Some(tree_sitter_rust::LANGUAGE.into()),
 8874    ));
 8875
 8876    cx.language_registry().add(language.clone());
 8877    cx.update_buffer(|buffer, cx| {
 8878        buffer.set_language(Some(language), cx);
 8879    });
 8880
 8881    cx.set_state(
 8882        &"
 8883            ˇ
 8884            ˇ
 8885            ˇ
 8886        "
 8887        .unindent(),
 8888    );
 8889
 8890    // ensure only matching closing brackets are skipped over
 8891    cx.update_editor(|editor, window, cx| {
 8892        editor.handle_input("}", window, cx);
 8893        editor.move_left(&MoveLeft, window, cx);
 8894        editor.handle_input(")", window, cx);
 8895        editor.move_left(&MoveLeft, window, cx);
 8896    });
 8897    cx.assert_editor_state(
 8898        &"
 8899            ˇ)}
 8900            ˇ)}
 8901            ˇ)}
 8902        "
 8903        .unindent(),
 8904    );
 8905
 8906    // skip-over closing brackets at multiple cursors
 8907    cx.update_editor(|editor, window, cx| {
 8908        editor.handle_input(")", window, cx);
 8909        editor.handle_input("}", window, cx);
 8910    });
 8911    cx.assert_editor_state(
 8912        &"
 8913            )}ˇ
 8914            )}ˇ
 8915            )}ˇ
 8916        "
 8917        .unindent(),
 8918    );
 8919
 8920    // ignore non-close brackets
 8921    cx.update_editor(|editor, window, cx| {
 8922        editor.handle_input("]", window, cx);
 8923        editor.move_left(&MoveLeft, window, cx);
 8924        editor.handle_input("]", window, cx);
 8925    });
 8926    cx.assert_editor_state(
 8927        &"
 8928            )}]ˇ]
 8929            )}]ˇ]
 8930            )}]ˇ]
 8931        "
 8932        .unindent(),
 8933    );
 8934}
 8935
 8936#[gpui::test]
 8937async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 8938    init_test(cx, |_| {});
 8939
 8940    let mut cx = EditorTestContext::new(cx).await;
 8941
 8942    let html_language = Arc::new(
 8943        Language::new(
 8944            LanguageConfig {
 8945                name: "HTML".into(),
 8946                brackets: BracketPairConfig {
 8947                    pairs: vec![
 8948                        BracketPair {
 8949                            start: "<".into(),
 8950                            end: ">".into(),
 8951                            close: true,
 8952                            ..Default::default()
 8953                        },
 8954                        BracketPair {
 8955                            start: "{".into(),
 8956                            end: "}".into(),
 8957                            close: true,
 8958                            ..Default::default()
 8959                        },
 8960                        BracketPair {
 8961                            start: "(".into(),
 8962                            end: ")".into(),
 8963                            close: true,
 8964                            ..Default::default()
 8965                        },
 8966                    ],
 8967                    ..Default::default()
 8968                },
 8969                autoclose_before: "})]>".into(),
 8970                ..Default::default()
 8971            },
 8972            Some(tree_sitter_html::LANGUAGE.into()),
 8973        )
 8974        .with_injection_query(
 8975            r#"
 8976            (script_element
 8977                (raw_text) @injection.content
 8978                (#set! injection.language "javascript"))
 8979            "#,
 8980        )
 8981        .unwrap(),
 8982    );
 8983
 8984    let javascript_language = Arc::new(Language::new(
 8985        LanguageConfig {
 8986            name: "JavaScript".into(),
 8987            brackets: BracketPairConfig {
 8988                pairs: vec![
 8989                    BracketPair {
 8990                        start: "/*".into(),
 8991                        end: " */".into(),
 8992                        close: true,
 8993                        ..Default::default()
 8994                    },
 8995                    BracketPair {
 8996                        start: "{".into(),
 8997                        end: "}".into(),
 8998                        close: true,
 8999                        ..Default::default()
 9000                    },
 9001                    BracketPair {
 9002                        start: "(".into(),
 9003                        end: ")".into(),
 9004                        close: true,
 9005                        ..Default::default()
 9006                    },
 9007                ],
 9008                ..Default::default()
 9009            },
 9010            autoclose_before: "})]>".into(),
 9011            ..Default::default()
 9012        },
 9013        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9014    ));
 9015
 9016    cx.language_registry().add(html_language.clone());
 9017    cx.language_registry().add(javascript_language);
 9018    cx.executor().run_until_parked();
 9019
 9020    cx.update_buffer(|buffer, cx| {
 9021        buffer.set_language(Some(html_language), cx);
 9022    });
 9023
 9024    cx.set_state(
 9025        &r#"
 9026            <body>ˇ
 9027                <script>
 9028                    var x = 1;ˇ
 9029                </script>
 9030            </body>ˇ
 9031        "#
 9032        .unindent(),
 9033    );
 9034
 9035    // Precondition: different languages are active at different locations.
 9036    cx.update_editor(|editor, window, cx| {
 9037        let snapshot = editor.snapshot(window, cx);
 9038        let cursors = editor.selections.ranges::<usize>(cx);
 9039        let languages = cursors
 9040            .iter()
 9041            .map(|c| snapshot.language_at(c.start).unwrap().name())
 9042            .collect::<Vec<_>>();
 9043        assert_eq!(
 9044            languages,
 9045            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 9046        );
 9047    });
 9048
 9049    // Angle brackets autoclose in HTML, but not JavaScript.
 9050    cx.update_editor(|editor, window, cx| {
 9051        editor.handle_input("<", window, cx);
 9052        editor.handle_input("a", window, cx);
 9053    });
 9054    cx.assert_editor_state(
 9055        &r#"
 9056            <body><aˇ>
 9057                <script>
 9058                    var x = 1;<aˇ
 9059                </script>
 9060            </body><aˇ>
 9061        "#
 9062        .unindent(),
 9063    );
 9064
 9065    // Curly braces and parens autoclose in both HTML and JavaScript.
 9066    cx.update_editor(|editor, window, cx| {
 9067        editor.handle_input(" b=", window, cx);
 9068        editor.handle_input("{", window, cx);
 9069        editor.handle_input("c", window, cx);
 9070        editor.handle_input("(", window, cx);
 9071    });
 9072    cx.assert_editor_state(
 9073        &r#"
 9074            <body><a b={c(ˇ)}>
 9075                <script>
 9076                    var x = 1;<a b={c(ˇ)}
 9077                </script>
 9078            </body><a b={c(ˇ)}>
 9079        "#
 9080        .unindent(),
 9081    );
 9082
 9083    // Brackets that were already autoclosed are skipped.
 9084    cx.update_editor(|editor, window, cx| {
 9085        editor.handle_input(")", window, cx);
 9086        editor.handle_input("d", window, cx);
 9087        editor.handle_input("}", window, cx);
 9088    });
 9089    cx.assert_editor_state(
 9090        &r#"
 9091            <body><a b={c()d}ˇ>
 9092                <script>
 9093                    var x = 1;<a b={c()d}ˇ
 9094                </script>
 9095            </body><a b={c()d}ˇ>
 9096        "#
 9097        .unindent(),
 9098    );
 9099    cx.update_editor(|editor, window, cx| {
 9100        editor.handle_input(">", window, cx);
 9101    });
 9102    cx.assert_editor_state(
 9103        &r#"
 9104            <body><a b={c()d}>ˇ
 9105                <script>
 9106                    var x = 1;<a b={c()d}>ˇ
 9107                </script>
 9108            </body><a b={c()d}>ˇ
 9109        "#
 9110        .unindent(),
 9111    );
 9112
 9113    // Reset
 9114    cx.set_state(
 9115        &r#"
 9116            <body>ˇ
 9117                <script>
 9118                    var x = 1;ˇ
 9119                </script>
 9120            </body>ˇ
 9121        "#
 9122        .unindent(),
 9123    );
 9124
 9125    cx.update_editor(|editor, window, cx| {
 9126        editor.handle_input("<", window, cx);
 9127    });
 9128    cx.assert_editor_state(
 9129        &r#"
 9130            <body><ˇ>
 9131                <script>
 9132                    var x = 1;<ˇ
 9133                </script>
 9134            </body><ˇ>
 9135        "#
 9136        .unindent(),
 9137    );
 9138
 9139    // When backspacing, the closing angle brackets are removed.
 9140    cx.update_editor(|editor, window, cx| {
 9141        editor.backspace(&Backspace, window, cx);
 9142    });
 9143    cx.assert_editor_state(
 9144        &r#"
 9145            <body>ˇ
 9146                <script>
 9147                    var x = 1;ˇ
 9148                </script>
 9149            </body>ˇ
 9150        "#
 9151        .unindent(),
 9152    );
 9153
 9154    // Block comments autoclose in JavaScript, but not HTML.
 9155    cx.update_editor(|editor, window, cx| {
 9156        editor.handle_input("/", window, cx);
 9157        editor.handle_input("*", window, cx);
 9158    });
 9159    cx.assert_editor_state(
 9160        &r#"
 9161            <body>/*ˇ
 9162                <script>
 9163                    var x = 1;/*ˇ */
 9164                </script>
 9165            </body>/*ˇ
 9166        "#
 9167        .unindent(),
 9168    );
 9169}
 9170
 9171#[gpui::test]
 9172async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 9173    init_test(cx, |_| {});
 9174
 9175    let mut cx = EditorTestContext::new(cx).await;
 9176
 9177    let rust_language = Arc::new(
 9178        Language::new(
 9179            LanguageConfig {
 9180                name: "Rust".into(),
 9181                brackets: serde_json::from_value(json!([
 9182                    { "start": "{", "end": "}", "close": true, "newline": true },
 9183                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 9184                ]))
 9185                .unwrap(),
 9186                autoclose_before: "})]>".into(),
 9187                ..Default::default()
 9188            },
 9189            Some(tree_sitter_rust::LANGUAGE.into()),
 9190        )
 9191        .with_override_query("(string_literal) @string")
 9192        .unwrap(),
 9193    );
 9194
 9195    cx.language_registry().add(rust_language.clone());
 9196    cx.update_buffer(|buffer, cx| {
 9197        buffer.set_language(Some(rust_language), cx);
 9198    });
 9199
 9200    cx.set_state(
 9201        &r#"
 9202            let x = ˇ
 9203        "#
 9204        .unindent(),
 9205    );
 9206
 9207    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 9208    cx.update_editor(|editor, window, cx| {
 9209        editor.handle_input("\"", window, cx);
 9210    });
 9211    cx.assert_editor_state(
 9212        &r#"
 9213            let x = "ˇ"
 9214        "#
 9215        .unindent(),
 9216    );
 9217
 9218    // Inserting another quotation mark. The cursor moves across the existing
 9219    // automatically-inserted quotation mark.
 9220    cx.update_editor(|editor, window, cx| {
 9221        editor.handle_input("\"", window, cx);
 9222    });
 9223    cx.assert_editor_state(
 9224        &r#"
 9225            let x = ""ˇ
 9226        "#
 9227        .unindent(),
 9228    );
 9229
 9230    // Reset
 9231    cx.set_state(
 9232        &r#"
 9233            let x = ˇ
 9234        "#
 9235        .unindent(),
 9236    );
 9237
 9238    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 9239    cx.update_editor(|editor, window, cx| {
 9240        editor.handle_input("\"", window, cx);
 9241        editor.handle_input(" ", window, cx);
 9242        editor.move_left(&Default::default(), window, cx);
 9243        editor.handle_input("\\", window, cx);
 9244        editor.handle_input("\"", window, cx);
 9245    });
 9246    cx.assert_editor_state(
 9247        &r#"
 9248            let x = "\"ˇ "
 9249        "#
 9250        .unindent(),
 9251    );
 9252
 9253    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 9254    // mark. Nothing is inserted.
 9255    cx.update_editor(|editor, window, cx| {
 9256        editor.move_right(&Default::default(), window, cx);
 9257        editor.handle_input("\"", window, cx);
 9258    });
 9259    cx.assert_editor_state(
 9260        &r#"
 9261            let x = "\" "ˇ
 9262        "#
 9263        .unindent(),
 9264    );
 9265}
 9266
 9267#[gpui::test]
 9268async fn test_surround_with_pair(cx: &mut TestAppContext) {
 9269    init_test(cx, |_| {});
 9270
 9271    let language = Arc::new(Language::new(
 9272        LanguageConfig {
 9273            brackets: BracketPairConfig {
 9274                pairs: vec![
 9275                    BracketPair {
 9276                        start: "{".to_string(),
 9277                        end: "}".to_string(),
 9278                        close: true,
 9279                        surround: true,
 9280                        newline: true,
 9281                    },
 9282                    BracketPair {
 9283                        start: "/* ".to_string(),
 9284                        end: "*/".to_string(),
 9285                        close: true,
 9286                        surround: true,
 9287                        ..Default::default()
 9288                    },
 9289                ],
 9290                ..Default::default()
 9291            },
 9292            ..Default::default()
 9293        },
 9294        Some(tree_sitter_rust::LANGUAGE.into()),
 9295    ));
 9296
 9297    let text = r#"
 9298        a
 9299        b
 9300        c
 9301    "#
 9302    .unindent();
 9303
 9304    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9305    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9306    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9307    editor
 9308        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9309        .await;
 9310
 9311    editor.update_in(cx, |editor, window, cx| {
 9312        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9313            s.select_display_ranges([
 9314                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9315                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9316                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 9317            ])
 9318        });
 9319
 9320        editor.handle_input("{", window, cx);
 9321        editor.handle_input("{", window, cx);
 9322        editor.handle_input("{", window, cx);
 9323        assert_eq!(
 9324            editor.text(cx),
 9325            "
 9326                {{{a}}}
 9327                {{{b}}}
 9328                {{{c}}}
 9329            "
 9330            .unindent()
 9331        );
 9332        assert_eq!(
 9333            editor.selections.display_ranges(cx),
 9334            [
 9335                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 9336                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 9337                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 9338            ]
 9339        );
 9340
 9341        editor.undo(&Undo, window, cx);
 9342        editor.undo(&Undo, window, cx);
 9343        editor.undo(&Undo, window, cx);
 9344        assert_eq!(
 9345            editor.text(cx),
 9346            "
 9347                a
 9348                b
 9349                c
 9350            "
 9351            .unindent()
 9352        );
 9353        assert_eq!(
 9354            editor.selections.display_ranges(cx),
 9355            [
 9356                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9357                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9358                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9359            ]
 9360        );
 9361
 9362        // Ensure inserting the first character of a multi-byte bracket pair
 9363        // doesn't surround the selections with the bracket.
 9364        editor.handle_input("/", window, cx);
 9365        assert_eq!(
 9366            editor.text(cx),
 9367            "
 9368                /
 9369                /
 9370                /
 9371            "
 9372            .unindent()
 9373        );
 9374        assert_eq!(
 9375            editor.selections.display_ranges(cx),
 9376            [
 9377                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9378                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9379                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9380            ]
 9381        );
 9382
 9383        editor.undo(&Undo, window, cx);
 9384        assert_eq!(
 9385            editor.text(cx),
 9386            "
 9387                a
 9388                b
 9389                c
 9390            "
 9391            .unindent()
 9392        );
 9393        assert_eq!(
 9394            editor.selections.display_ranges(cx),
 9395            [
 9396                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9397                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9398                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9399            ]
 9400        );
 9401
 9402        // Ensure inserting the last character of a multi-byte bracket pair
 9403        // doesn't surround the selections with the bracket.
 9404        editor.handle_input("*", window, cx);
 9405        assert_eq!(
 9406            editor.text(cx),
 9407            "
 9408                *
 9409                *
 9410                *
 9411            "
 9412            .unindent()
 9413        );
 9414        assert_eq!(
 9415            editor.selections.display_ranges(cx),
 9416            [
 9417                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9418                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9419                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9420            ]
 9421        );
 9422    });
 9423}
 9424
 9425#[gpui::test]
 9426async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9427    init_test(cx, |_| {});
 9428
 9429    let language = Arc::new(Language::new(
 9430        LanguageConfig {
 9431            brackets: BracketPairConfig {
 9432                pairs: vec![BracketPair {
 9433                    start: "{".to_string(),
 9434                    end: "}".to_string(),
 9435                    close: true,
 9436                    surround: true,
 9437                    newline: true,
 9438                }],
 9439                ..Default::default()
 9440            },
 9441            autoclose_before: "}".to_string(),
 9442            ..Default::default()
 9443        },
 9444        Some(tree_sitter_rust::LANGUAGE.into()),
 9445    ));
 9446
 9447    let text = r#"
 9448        a
 9449        b
 9450        c
 9451    "#
 9452    .unindent();
 9453
 9454    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9455    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9456    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9457    editor
 9458        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9459        .await;
 9460
 9461    editor.update_in(cx, |editor, window, cx| {
 9462        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9463            s.select_ranges([
 9464                Point::new(0, 1)..Point::new(0, 1),
 9465                Point::new(1, 1)..Point::new(1, 1),
 9466                Point::new(2, 1)..Point::new(2, 1),
 9467            ])
 9468        });
 9469
 9470        editor.handle_input("{", window, cx);
 9471        editor.handle_input("{", window, cx);
 9472        editor.handle_input("_", window, cx);
 9473        assert_eq!(
 9474            editor.text(cx),
 9475            "
 9476                a{{_}}
 9477                b{{_}}
 9478                c{{_}}
 9479            "
 9480            .unindent()
 9481        );
 9482        assert_eq!(
 9483            editor.selections.ranges::<Point>(cx),
 9484            [
 9485                Point::new(0, 4)..Point::new(0, 4),
 9486                Point::new(1, 4)..Point::new(1, 4),
 9487                Point::new(2, 4)..Point::new(2, 4)
 9488            ]
 9489        );
 9490
 9491        editor.backspace(&Default::default(), window, cx);
 9492        editor.backspace(&Default::default(), window, cx);
 9493        assert_eq!(
 9494            editor.text(cx),
 9495            "
 9496                a{}
 9497                b{}
 9498                c{}
 9499            "
 9500            .unindent()
 9501        );
 9502        assert_eq!(
 9503            editor.selections.ranges::<Point>(cx),
 9504            [
 9505                Point::new(0, 2)..Point::new(0, 2),
 9506                Point::new(1, 2)..Point::new(1, 2),
 9507                Point::new(2, 2)..Point::new(2, 2)
 9508            ]
 9509        );
 9510
 9511        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9512        assert_eq!(
 9513            editor.text(cx),
 9514            "
 9515                a
 9516                b
 9517                c
 9518            "
 9519            .unindent()
 9520        );
 9521        assert_eq!(
 9522            editor.selections.ranges::<Point>(cx),
 9523            [
 9524                Point::new(0, 1)..Point::new(0, 1),
 9525                Point::new(1, 1)..Point::new(1, 1),
 9526                Point::new(2, 1)..Point::new(2, 1)
 9527            ]
 9528        );
 9529    });
 9530}
 9531
 9532#[gpui::test]
 9533async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9534    init_test(cx, |settings| {
 9535        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9536    });
 9537
 9538    let mut cx = EditorTestContext::new(cx).await;
 9539
 9540    let language = Arc::new(Language::new(
 9541        LanguageConfig {
 9542            brackets: BracketPairConfig {
 9543                pairs: vec![
 9544                    BracketPair {
 9545                        start: "{".to_string(),
 9546                        end: "}".to_string(),
 9547                        close: true,
 9548                        surround: true,
 9549                        newline: true,
 9550                    },
 9551                    BracketPair {
 9552                        start: "(".to_string(),
 9553                        end: ")".to_string(),
 9554                        close: true,
 9555                        surround: true,
 9556                        newline: true,
 9557                    },
 9558                    BracketPair {
 9559                        start: "[".to_string(),
 9560                        end: "]".to_string(),
 9561                        close: false,
 9562                        surround: true,
 9563                        newline: true,
 9564                    },
 9565                ],
 9566                ..Default::default()
 9567            },
 9568            autoclose_before: "})]".to_string(),
 9569            ..Default::default()
 9570        },
 9571        Some(tree_sitter_rust::LANGUAGE.into()),
 9572    ));
 9573
 9574    cx.language_registry().add(language.clone());
 9575    cx.update_buffer(|buffer, cx| {
 9576        buffer.set_language(Some(language), cx);
 9577    });
 9578
 9579    cx.set_state(
 9580        &"
 9581            {(ˇ)}
 9582            [[ˇ]]
 9583            {(ˇ)}
 9584        "
 9585        .unindent(),
 9586    );
 9587
 9588    cx.update_editor(|editor, window, cx| {
 9589        editor.backspace(&Default::default(), window, cx);
 9590        editor.backspace(&Default::default(), window, cx);
 9591    });
 9592
 9593    cx.assert_editor_state(
 9594        &"
 9595            ˇ
 9596            ˇ]]
 9597            ˇ
 9598        "
 9599        .unindent(),
 9600    );
 9601
 9602    cx.update_editor(|editor, window, cx| {
 9603        editor.handle_input("{", window, cx);
 9604        editor.handle_input("{", window, cx);
 9605        editor.move_right(&MoveRight, window, cx);
 9606        editor.move_right(&MoveRight, window, cx);
 9607        editor.move_left(&MoveLeft, window, cx);
 9608        editor.move_left(&MoveLeft, window, cx);
 9609        editor.backspace(&Default::default(), window, cx);
 9610    });
 9611
 9612    cx.assert_editor_state(
 9613        &"
 9614            {ˇ}
 9615            {ˇ}]]
 9616            {ˇ}
 9617        "
 9618        .unindent(),
 9619    );
 9620
 9621    cx.update_editor(|editor, window, cx| {
 9622        editor.backspace(&Default::default(), window, cx);
 9623    });
 9624
 9625    cx.assert_editor_state(
 9626        &"
 9627            ˇ
 9628            ˇ]]
 9629            ˇ
 9630        "
 9631        .unindent(),
 9632    );
 9633}
 9634
 9635#[gpui::test]
 9636async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 9637    init_test(cx, |_| {});
 9638
 9639    let language = Arc::new(Language::new(
 9640        LanguageConfig::default(),
 9641        Some(tree_sitter_rust::LANGUAGE.into()),
 9642    ));
 9643
 9644    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 9645    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9646    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9647    editor
 9648        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9649        .await;
 9650
 9651    editor.update_in(cx, |editor, window, cx| {
 9652        editor.set_auto_replace_emoji_shortcode(true);
 9653
 9654        editor.handle_input("Hello ", window, cx);
 9655        editor.handle_input(":wave", window, cx);
 9656        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 9657
 9658        editor.handle_input(":", window, cx);
 9659        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 9660
 9661        editor.handle_input(" :smile", window, cx);
 9662        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 9663
 9664        editor.handle_input(":", window, cx);
 9665        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 9666
 9667        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 9668        editor.handle_input(":wave", window, cx);
 9669        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 9670
 9671        editor.handle_input(":", window, cx);
 9672        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 9673
 9674        editor.handle_input(":1", window, cx);
 9675        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 9676
 9677        editor.handle_input(":", window, cx);
 9678        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 9679
 9680        // Ensure shortcode does not get replaced when it is part of a word
 9681        editor.handle_input(" Test:wave", window, cx);
 9682        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 9683
 9684        editor.handle_input(":", window, cx);
 9685        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 9686
 9687        editor.set_auto_replace_emoji_shortcode(false);
 9688
 9689        // Ensure shortcode does not get replaced when auto replace is off
 9690        editor.handle_input(" :wave", window, cx);
 9691        assert_eq!(
 9692            editor.text(cx),
 9693            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 9694        );
 9695
 9696        editor.handle_input(":", window, cx);
 9697        assert_eq!(
 9698            editor.text(cx),
 9699            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 9700        );
 9701    });
 9702}
 9703
 9704#[gpui::test]
 9705async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 9706    init_test(cx, |_| {});
 9707
 9708    let (text, insertion_ranges) = marked_text_ranges(
 9709        indoc! {"
 9710            ˇ
 9711        "},
 9712        false,
 9713    );
 9714
 9715    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 9716    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9717
 9718    _ = editor.update_in(cx, |editor, window, cx| {
 9719        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 9720
 9721        editor
 9722            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9723            .unwrap();
 9724
 9725        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 9726            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 9727            assert_eq!(editor.text(cx), expected_text);
 9728            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 9729        }
 9730
 9731        assert(
 9732            editor,
 9733            cx,
 9734            indoc! {"
 9735            type «» =•
 9736            "},
 9737        );
 9738
 9739        assert!(editor.context_menu_visible(), "There should be a matches");
 9740    });
 9741}
 9742
 9743#[gpui::test]
 9744async fn test_snippets(cx: &mut TestAppContext) {
 9745    init_test(cx, |_| {});
 9746
 9747    let mut cx = EditorTestContext::new(cx).await;
 9748
 9749    cx.set_state(indoc! {"
 9750        a.ˇ b
 9751        a.ˇ b
 9752        a.ˇ b
 9753    "});
 9754
 9755    cx.update_editor(|editor, window, cx| {
 9756        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 9757        let insertion_ranges = editor
 9758            .selections
 9759            .all(cx)
 9760            .iter()
 9761            .map(|s| s.range())
 9762            .collect::<Vec<_>>();
 9763        editor
 9764            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9765            .unwrap();
 9766    });
 9767
 9768    cx.assert_editor_state(indoc! {"
 9769        a.f(«oneˇ», two, «threeˇ») b
 9770        a.f(«oneˇ», two, «threeˇ») b
 9771        a.f(«oneˇ», two, «threeˇ») b
 9772    "});
 9773
 9774    // Can't move earlier than the first tab stop
 9775    cx.update_editor(|editor, window, cx| {
 9776        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9777    });
 9778    cx.assert_editor_state(indoc! {"
 9779        a.f(«oneˇ», two, «threeˇ») b
 9780        a.f(«oneˇ», two, «threeˇ») b
 9781        a.f(«oneˇ», two, «threeˇ») b
 9782    "});
 9783
 9784    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9785    cx.assert_editor_state(indoc! {"
 9786        a.f(one, «twoˇ», three) b
 9787        a.f(one, «twoˇ», three) b
 9788        a.f(one, «twoˇ», three) b
 9789    "});
 9790
 9791    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
 9792    cx.assert_editor_state(indoc! {"
 9793        a.f(«oneˇ», two, «threeˇ») b
 9794        a.f(«oneˇ», two, «threeˇ») b
 9795        a.f(«oneˇ», two, «threeˇ») b
 9796    "});
 9797
 9798    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9799    cx.assert_editor_state(indoc! {"
 9800        a.f(one, «twoˇ», three) b
 9801        a.f(one, «twoˇ», three) b
 9802        a.f(one, «twoˇ», three) b
 9803    "});
 9804    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9805    cx.assert_editor_state(indoc! {"
 9806        a.f(one, two, three)ˇ b
 9807        a.f(one, two, three)ˇ b
 9808        a.f(one, two, three)ˇ b
 9809    "});
 9810
 9811    // As soon as the last tab stop is reached, snippet state is gone
 9812    cx.update_editor(|editor, window, cx| {
 9813        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
 9814    });
 9815    cx.assert_editor_state(indoc! {"
 9816        a.f(one, two, three)ˇ b
 9817        a.f(one, two, three)ˇ b
 9818        a.f(one, two, three)ˇ b
 9819    "});
 9820}
 9821
 9822#[gpui::test]
 9823async fn test_snippet_indentation(cx: &mut TestAppContext) {
 9824    init_test(cx, |_| {});
 9825
 9826    let mut cx = EditorTestContext::new(cx).await;
 9827
 9828    cx.update_editor(|editor, window, cx| {
 9829        let snippet = Snippet::parse(indoc! {"
 9830            /*
 9831             * Multiline comment with leading indentation
 9832             *
 9833             * $1
 9834             */
 9835            $0"})
 9836        .unwrap();
 9837        let insertion_ranges = editor
 9838            .selections
 9839            .all(cx)
 9840            .iter()
 9841            .map(|s| s.range())
 9842            .collect::<Vec<_>>();
 9843        editor
 9844            .insert_snippet(&insertion_ranges, snippet, window, cx)
 9845            .unwrap();
 9846    });
 9847
 9848    cx.assert_editor_state(indoc! {"
 9849        /*
 9850         * Multiline comment with leading indentation
 9851         *
 9852         * ˇ
 9853         */
 9854    "});
 9855
 9856    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
 9857    cx.assert_editor_state(indoc! {"
 9858        /*
 9859         * Multiline comment with leading indentation
 9860         *
 9861         *•
 9862         */
 9863        ˇ"});
 9864}
 9865
 9866#[gpui::test]
 9867async fn test_document_format_during_save(cx: &mut TestAppContext) {
 9868    init_test(cx, |_| {});
 9869
 9870    let fs = FakeFs::new(cx.executor());
 9871    fs.insert_file(path!("/file.rs"), Default::default()).await;
 9872
 9873    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 9874
 9875    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9876    language_registry.add(rust_lang());
 9877    let mut fake_servers = language_registry.register_fake_lsp(
 9878        "Rust",
 9879        FakeLspAdapter {
 9880            capabilities: lsp::ServerCapabilities {
 9881                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 9882                ..Default::default()
 9883            },
 9884            ..Default::default()
 9885        },
 9886    );
 9887
 9888    let buffer = project
 9889        .update(cx, |project, cx| {
 9890            project.open_local_buffer(path!("/file.rs"), cx)
 9891        })
 9892        .await
 9893        .unwrap();
 9894
 9895    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9896    let (editor, cx) = cx.add_window_view(|window, cx| {
 9897        build_editor_with_project(project.clone(), buffer, window, cx)
 9898    });
 9899    editor.update_in(cx, |editor, window, cx| {
 9900        editor.set_text("one\ntwo\nthree\n", window, cx)
 9901    });
 9902    assert!(cx.read(|cx| editor.is_dirty(cx)));
 9903
 9904    cx.executor().start_waiting();
 9905    let fake_server = fake_servers.next().await.unwrap();
 9906
 9907    {
 9908        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9909            move |params, _| async move {
 9910                assert_eq!(
 9911                    params.text_document.uri,
 9912                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
 9913                );
 9914                assert_eq!(params.options.tab_size, 4);
 9915                Ok(Some(vec![lsp::TextEdit::new(
 9916                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 9917                    ", ".to_string(),
 9918                )]))
 9919            },
 9920        );
 9921        let save = editor
 9922            .update_in(cx, |editor, window, cx| {
 9923                editor.save(
 9924                    SaveOptions {
 9925                        format: true,
 9926                        autosave: false,
 9927                    },
 9928                    project.clone(),
 9929                    window,
 9930                    cx,
 9931                )
 9932            })
 9933            .unwrap();
 9934        cx.executor().start_waiting();
 9935        save.await;
 9936
 9937        assert_eq!(
 9938            editor.update(cx, |editor, cx| editor.text(cx)),
 9939            "one, two\nthree\n"
 9940        );
 9941        assert!(!cx.read(|cx| editor.is_dirty(cx)));
 9942    }
 9943
 9944    {
 9945        editor.update_in(cx, |editor, window, cx| {
 9946            editor.set_text("one\ntwo\nthree\n", window, cx)
 9947        });
 9948        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9949
 9950        // Ensure we can still save even if formatting hangs.
 9951        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
 9952            move |params, _| async move {
 9953                assert_eq!(
 9954                    params.text_document.uri,
 9955                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
 9956                );
 9957                futures::future::pending::<()>().await;
 9958                unreachable!()
 9959            },
 9960        );
 9961        let save = editor
 9962            .update_in(cx, |editor, window, cx| {
 9963                editor.save(
 9964                    SaveOptions {
 9965                        format: true,
 9966                        autosave: false,
 9967                    },
 9968                    project.clone(),
 9969                    window,
 9970                    cx,
 9971                )
 9972            })
 9973            .unwrap();
 9974        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 9975        cx.executor().start_waiting();
 9976        save.await;
 9977        assert_eq!(
 9978            editor.update(cx, |editor, cx| editor.text(cx)),
 9979            "one\ntwo\nthree\n"
 9980        );
 9981    }
 9982
 9983    // Set rust language override and assert overridden tabsize is sent to language server
 9984    update_test_language_settings(cx, |settings| {
 9985        settings.languages.0.insert(
 9986            "Rust".into(),
 9987            LanguageSettingsContent {
 9988                tab_size: NonZeroU32::new(8),
 9989                ..Default::default()
 9990            },
 9991        );
 9992    });
 9993
 9994    {
 9995        editor.update_in(cx, |editor, window, cx| {
 9996            editor.set_text("somehting_new\n", window, cx)
 9997        });
 9998        assert!(cx.read(|cx| editor.is_dirty(cx)));
 9999        let _formatting_request_signal = fake_server
10000            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10001                assert_eq!(
10002                    params.text_document.uri,
10003                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10004                );
10005                assert_eq!(params.options.tab_size, 8);
10006                Ok(Some(vec![]))
10007            });
10008        let save = editor
10009            .update_in(cx, |editor, window, cx| {
10010                editor.save(
10011                    SaveOptions {
10012                        format: true,
10013                        autosave: false,
10014                    },
10015                    project.clone(),
10016                    window,
10017                    cx,
10018                )
10019            })
10020            .unwrap();
10021        cx.executor().start_waiting();
10022        save.await;
10023    }
10024}
10025
10026#[gpui::test]
10027async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
10028    init_test(cx, |settings| {
10029        settings.defaults.ensure_final_newline_on_save = Some(false);
10030    });
10031
10032    let fs = FakeFs::new(cx.executor());
10033    fs.insert_file(path!("/file.txt"), "foo".into()).await;
10034
10035    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
10036
10037    let buffer = project
10038        .update(cx, |project, cx| {
10039            project.open_local_buffer(path!("/file.txt"), cx)
10040        })
10041        .await
10042        .unwrap();
10043
10044    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10045    let (editor, cx) = cx.add_window_view(|window, cx| {
10046        build_editor_with_project(project.clone(), buffer, window, cx)
10047    });
10048    editor.update_in(cx, |editor, window, cx| {
10049        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
10050            s.select_ranges([0..0])
10051        });
10052    });
10053    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10054
10055    editor.update_in(cx, |editor, window, cx| {
10056        editor.handle_input("\n", window, cx)
10057    });
10058    cx.run_until_parked();
10059    save(&editor, &project, cx).await;
10060    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10061
10062    editor.update_in(cx, |editor, window, cx| {
10063        editor.undo(&Default::default(), window, cx);
10064    });
10065    save(&editor, &project, cx).await;
10066    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10067
10068    editor.update_in(cx, |editor, window, cx| {
10069        editor.redo(&Default::default(), window, cx);
10070    });
10071    cx.run_until_parked();
10072    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10073
10074    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
10075        let save = editor
10076            .update_in(cx, |editor, window, cx| {
10077                editor.save(
10078                    SaveOptions {
10079                        format: true,
10080                        autosave: false,
10081                    },
10082                    project.clone(),
10083                    window,
10084                    cx,
10085                )
10086            })
10087            .unwrap();
10088        cx.executor().start_waiting();
10089        save.await;
10090        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10091    }
10092}
10093
10094#[gpui::test]
10095async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
10096    init_test(cx, |_| {});
10097
10098    let cols = 4;
10099    let rows = 10;
10100    let sample_text_1 = sample_text(rows, cols, 'a');
10101    assert_eq!(
10102        sample_text_1,
10103        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10104    );
10105    let sample_text_2 = sample_text(rows, cols, 'l');
10106    assert_eq!(
10107        sample_text_2,
10108        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10109    );
10110    let sample_text_3 = sample_text(rows, cols, 'v');
10111    assert_eq!(
10112        sample_text_3,
10113        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10114    );
10115
10116    let fs = FakeFs::new(cx.executor());
10117    fs.insert_tree(
10118        path!("/a"),
10119        json!({
10120            "main.rs": sample_text_1,
10121            "other.rs": sample_text_2,
10122            "lib.rs": sample_text_3,
10123        }),
10124    )
10125    .await;
10126
10127    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10128    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10129    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10130
10131    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10132    language_registry.add(rust_lang());
10133    let mut fake_servers = language_registry.register_fake_lsp(
10134        "Rust",
10135        FakeLspAdapter {
10136            capabilities: lsp::ServerCapabilities {
10137                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10138                ..Default::default()
10139            },
10140            ..Default::default()
10141        },
10142    );
10143
10144    let worktree = project.update(cx, |project, cx| {
10145        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
10146        assert_eq!(worktrees.len(), 1);
10147        worktrees.pop().unwrap()
10148    });
10149    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10150
10151    let buffer_1 = project
10152        .update(cx, |project, cx| {
10153            project.open_buffer((worktree_id, "main.rs"), cx)
10154        })
10155        .await
10156        .unwrap();
10157    let buffer_2 = project
10158        .update(cx, |project, cx| {
10159            project.open_buffer((worktree_id, "other.rs"), cx)
10160        })
10161        .await
10162        .unwrap();
10163    let buffer_3 = project
10164        .update(cx, |project, cx| {
10165            project.open_buffer((worktree_id, "lib.rs"), cx)
10166        })
10167        .await
10168        .unwrap();
10169
10170    let multi_buffer = cx.new(|cx| {
10171        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10172        multi_buffer.push_excerpts(
10173            buffer_1.clone(),
10174            [
10175                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10176                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10177                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10178            ],
10179            cx,
10180        );
10181        multi_buffer.push_excerpts(
10182            buffer_2.clone(),
10183            [
10184                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10185                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10186                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10187            ],
10188            cx,
10189        );
10190        multi_buffer.push_excerpts(
10191            buffer_3.clone(),
10192            [
10193                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10194                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10195                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10196            ],
10197            cx,
10198        );
10199        multi_buffer
10200    });
10201    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
10202        Editor::new(
10203            EditorMode::full(),
10204            multi_buffer,
10205            Some(project.clone()),
10206            window,
10207            cx,
10208        )
10209    });
10210
10211    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10212        editor.change_selections(
10213            SelectionEffects::scroll(Autoscroll::Next),
10214            window,
10215            cx,
10216            |s| s.select_ranges(Some(1..2)),
10217        );
10218        editor.insert("|one|two|three|", window, cx);
10219    });
10220    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10221    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10222        editor.change_selections(
10223            SelectionEffects::scroll(Autoscroll::Next),
10224            window,
10225            cx,
10226            |s| s.select_ranges(Some(60..70)),
10227        );
10228        editor.insert("|four|five|six|", window, cx);
10229    });
10230    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10231
10232    // First two buffers should be edited, but not the third one.
10233    assert_eq!(
10234        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10235        "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}",
10236    );
10237    buffer_1.update(cx, |buffer, _| {
10238        assert!(buffer.is_dirty());
10239        assert_eq!(
10240            buffer.text(),
10241            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
10242        )
10243    });
10244    buffer_2.update(cx, |buffer, _| {
10245        assert!(buffer.is_dirty());
10246        assert_eq!(
10247            buffer.text(),
10248            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
10249        )
10250    });
10251    buffer_3.update(cx, |buffer, _| {
10252        assert!(!buffer.is_dirty());
10253        assert_eq!(buffer.text(), sample_text_3,)
10254    });
10255    cx.executor().run_until_parked();
10256
10257    cx.executor().start_waiting();
10258    let save = multi_buffer_editor
10259        .update_in(cx, |editor, window, cx| {
10260            editor.save(
10261                SaveOptions {
10262                    format: true,
10263                    autosave: false,
10264                },
10265                project.clone(),
10266                window,
10267                cx,
10268            )
10269        })
10270        .unwrap();
10271
10272    let fake_server = fake_servers.next().await.unwrap();
10273    fake_server
10274        .server
10275        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
10276            Ok(Some(vec![lsp::TextEdit::new(
10277                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10278                format!("[{} formatted]", params.text_document.uri),
10279            )]))
10280        })
10281        .detach();
10282    save.await;
10283
10284    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
10285    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
10286    assert_eq!(
10287        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10288        uri!(
10289            "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}"
10290        ),
10291    );
10292    buffer_1.update(cx, |buffer, _| {
10293        assert!(!buffer.is_dirty());
10294        assert_eq!(
10295            buffer.text(),
10296            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
10297        )
10298    });
10299    buffer_2.update(cx, |buffer, _| {
10300        assert!(!buffer.is_dirty());
10301        assert_eq!(
10302            buffer.text(),
10303            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
10304        )
10305    });
10306    buffer_3.update(cx, |buffer, _| {
10307        assert!(!buffer.is_dirty());
10308        assert_eq!(buffer.text(), sample_text_3,)
10309    });
10310}
10311
10312#[gpui::test]
10313async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
10314    init_test(cx, |_| {});
10315
10316    let fs = FakeFs::new(cx.executor());
10317    fs.insert_tree(
10318        path!("/dir"),
10319        json!({
10320            "file1.rs": "fn main() { println!(\"hello\"); }",
10321            "file2.rs": "fn test() { println!(\"test\"); }",
10322            "file3.rs": "fn other() { println!(\"other\"); }\n",
10323        }),
10324    )
10325    .await;
10326
10327    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
10328    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10329    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10330
10331    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10332    language_registry.add(rust_lang());
10333
10334    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
10335    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10336
10337    // Open three buffers
10338    let buffer_1 = project
10339        .update(cx, |project, cx| {
10340            project.open_buffer((worktree_id, "file1.rs"), cx)
10341        })
10342        .await
10343        .unwrap();
10344    let buffer_2 = project
10345        .update(cx, |project, cx| {
10346            project.open_buffer((worktree_id, "file2.rs"), cx)
10347        })
10348        .await
10349        .unwrap();
10350    let buffer_3 = project
10351        .update(cx, |project, cx| {
10352            project.open_buffer((worktree_id, "file3.rs"), cx)
10353        })
10354        .await
10355        .unwrap();
10356
10357    // Create a multi-buffer with all three buffers
10358    let multi_buffer = cx.new(|cx| {
10359        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10360        multi_buffer.push_excerpts(
10361            buffer_1.clone(),
10362            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10363            cx,
10364        );
10365        multi_buffer.push_excerpts(
10366            buffer_2.clone(),
10367            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10368            cx,
10369        );
10370        multi_buffer.push_excerpts(
10371            buffer_3.clone(),
10372            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10373            cx,
10374        );
10375        multi_buffer
10376    });
10377
10378    let editor = cx.new_window_entity(|window, cx| {
10379        Editor::new(
10380            EditorMode::full(),
10381            multi_buffer,
10382            Some(project.clone()),
10383            window,
10384            cx,
10385        )
10386    });
10387
10388    // Edit only the first buffer
10389    editor.update_in(cx, |editor, window, cx| {
10390        editor.change_selections(
10391            SelectionEffects::scroll(Autoscroll::Next),
10392            window,
10393            cx,
10394            |s| s.select_ranges(Some(10..10)),
10395        );
10396        editor.insert("// edited", window, cx);
10397    });
10398
10399    // Verify that only buffer 1 is dirty
10400    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
10401    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10402    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10403
10404    // Get write counts after file creation (files were created with initial content)
10405    // We expect each file to have been written once during creation
10406    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
10407    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
10408    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
10409
10410    // Perform autosave
10411    let save_task = editor.update_in(cx, |editor, window, cx| {
10412        editor.save(
10413            SaveOptions {
10414                format: true,
10415                autosave: true,
10416            },
10417            project.clone(),
10418            window,
10419            cx,
10420        )
10421    });
10422    save_task.await.unwrap();
10423
10424    // Only the dirty buffer should have been saved
10425    assert_eq!(
10426        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10427        1,
10428        "Buffer 1 was dirty, so it should have been written once during autosave"
10429    );
10430    assert_eq!(
10431        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10432        0,
10433        "Buffer 2 was clean, so it should not have been written during autosave"
10434    );
10435    assert_eq!(
10436        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10437        0,
10438        "Buffer 3 was clean, so it should not have been written during autosave"
10439    );
10440
10441    // Verify buffer states after autosave
10442    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10443    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10444    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10445
10446    // Now perform a manual save (format = true)
10447    let save_task = editor.update_in(cx, |editor, window, cx| {
10448        editor.save(
10449            SaveOptions {
10450                format: true,
10451                autosave: false,
10452            },
10453            project.clone(),
10454            window,
10455            cx,
10456        )
10457    });
10458    save_task.await.unwrap();
10459
10460    // During manual save, clean buffers don't get written to disk
10461    // They just get did_save called for language server notifications
10462    assert_eq!(
10463        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10464        1,
10465        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10466    );
10467    assert_eq!(
10468        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10469        0,
10470        "Buffer 2 should not have been written at all"
10471    );
10472    assert_eq!(
10473        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10474        0,
10475        "Buffer 3 should not have been written at all"
10476    );
10477}
10478
10479async fn setup_range_format_test(
10480    cx: &mut TestAppContext,
10481) -> (
10482    Entity<Project>,
10483    Entity<Editor>,
10484    &mut gpui::VisualTestContext,
10485    lsp::FakeLanguageServer,
10486) {
10487    init_test(cx, |_| {});
10488
10489    let fs = FakeFs::new(cx.executor());
10490    fs.insert_file(path!("/file.rs"), Default::default()).await;
10491
10492    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10493
10494    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10495    language_registry.add(rust_lang());
10496    let mut fake_servers = language_registry.register_fake_lsp(
10497        "Rust",
10498        FakeLspAdapter {
10499            capabilities: lsp::ServerCapabilities {
10500                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10501                ..lsp::ServerCapabilities::default()
10502            },
10503            ..FakeLspAdapter::default()
10504        },
10505    );
10506
10507    let buffer = project
10508        .update(cx, |project, cx| {
10509            project.open_local_buffer(path!("/file.rs"), cx)
10510        })
10511        .await
10512        .unwrap();
10513
10514    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10515    let (editor, cx) = cx.add_window_view(|window, cx| {
10516        build_editor_with_project(project.clone(), buffer, window, cx)
10517    });
10518
10519    cx.executor().start_waiting();
10520    let fake_server = fake_servers.next().await.unwrap();
10521
10522    (project, editor, cx, fake_server)
10523}
10524
10525#[gpui::test]
10526async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10527    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10528
10529    editor.update_in(cx, |editor, window, cx| {
10530        editor.set_text("one\ntwo\nthree\n", window, cx)
10531    });
10532    assert!(cx.read(|cx| editor.is_dirty(cx)));
10533
10534    let save = editor
10535        .update_in(cx, |editor, window, cx| {
10536            editor.save(
10537                SaveOptions {
10538                    format: true,
10539                    autosave: false,
10540                },
10541                project.clone(),
10542                window,
10543                cx,
10544            )
10545        })
10546        .unwrap();
10547    fake_server
10548        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10549            assert_eq!(
10550                params.text_document.uri,
10551                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10552            );
10553            assert_eq!(params.options.tab_size, 4);
10554            Ok(Some(vec![lsp::TextEdit::new(
10555                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10556                ", ".to_string(),
10557            )]))
10558        })
10559        .next()
10560        .await;
10561    cx.executor().start_waiting();
10562    save.await;
10563    assert_eq!(
10564        editor.update(cx, |editor, cx| editor.text(cx)),
10565        "one, two\nthree\n"
10566    );
10567    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10568}
10569
10570#[gpui::test]
10571async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
10572    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10573
10574    editor.update_in(cx, |editor, window, cx| {
10575        editor.set_text("one\ntwo\nthree\n", window, cx)
10576    });
10577    assert!(cx.read(|cx| editor.is_dirty(cx)));
10578
10579    // Test that save still works when formatting hangs
10580    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10581        move |params, _| async move {
10582            assert_eq!(
10583                params.text_document.uri,
10584                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10585            );
10586            futures::future::pending::<()>().await;
10587            unreachable!()
10588        },
10589    );
10590    let save = editor
10591        .update_in(cx, |editor, window, cx| {
10592            editor.save(
10593                SaveOptions {
10594                    format: true,
10595                    autosave: false,
10596                },
10597                project.clone(),
10598                window,
10599                cx,
10600            )
10601        })
10602        .unwrap();
10603    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10604    cx.executor().start_waiting();
10605    save.await;
10606    assert_eq!(
10607        editor.update(cx, |editor, cx| editor.text(cx)),
10608        "one\ntwo\nthree\n"
10609    );
10610    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10611}
10612
10613#[gpui::test]
10614async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
10615    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10616
10617    // Buffer starts clean, no formatting should be requested
10618    let save = editor
10619        .update_in(cx, |editor, window, cx| {
10620            editor.save(
10621                SaveOptions {
10622                    format: false,
10623                    autosave: false,
10624                },
10625                project.clone(),
10626                window,
10627                cx,
10628            )
10629        })
10630        .unwrap();
10631    let _pending_format_request = fake_server
10632        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
10633            panic!("Should not be invoked");
10634        })
10635        .next();
10636    cx.executor().start_waiting();
10637    save.await;
10638    cx.run_until_parked();
10639}
10640
10641#[gpui::test]
10642async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
10643    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10644
10645    // Set Rust language override and assert overridden tabsize is sent to language server
10646    update_test_language_settings(cx, |settings| {
10647        settings.languages.0.insert(
10648            "Rust".into(),
10649            LanguageSettingsContent {
10650                tab_size: NonZeroU32::new(8),
10651                ..Default::default()
10652            },
10653        );
10654    });
10655
10656    editor.update_in(cx, |editor, window, cx| {
10657        editor.set_text("something_new\n", window, cx)
10658    });
10659    assert!(cx.read(|cx| editor.is_dirty(cx)));
10660    let save = editor
10661        .update_in(cx, |editor, window, cx| {
10662            editor.save(
10663                SaveOptions {
10664                    format: true,
10665                    autosave: false,
10666                },
10667                project.clone(),
10668                window,
10669                cx,
10670            )
10671        })
10672        .unwrap();
10673    fake_server
10674        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10675            assert_eq!(
10676                params.text_document.uri,
10677                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10678            );
10679            assert_eq!(params.options.tab_size, 8);
10680            Ok(Some(Vec::new()))
10681        })
10682        .next()
10683        .await;
10684    save.await;
10685}
10686
10687#[gpui::test]
10688async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
10689    init_test(cx, |settings| {
10690        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
10691            Formatter::LanguageServer { name: None },
10692        )))
10693    });
10694
10695    let fs = FakeFs::new(cx.executor());
10696    fs.insert_file(path!("/file.rs"), Default::default()).await;
10697
10698    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10699
10700    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10701    language_registry.add(Arc::new(Language::new(
10702        LanguageConfig {
10703            name: "Rust".into(),
10704            matcher: LanguageMatcher {
10705                path_suffixes: vec!["rs".to_string()],
10706                ..Default::default()
10707            },
10708            ..LanguageConfig::default()
10709        },
10710        Some(tree_sitter_rust::LANGUAGE.into()),
10711    )));
10712    update_test_language_settings(cx, |settings| {
10713        // Enable Prettier formatting for the same buffer, and ensure
10714        // LSP is called instead of Prettier.
10715        settings.defaults.prettier = Some(PrettierSettings {
10716            allowed: true,
10717            ..PrettierSettings::default()
10718        });
10719    });
10720    let mut fake_servers = language_registry.register_fake_lsp(
10721        "Rust",
10722        FakeLspAdapter {
10723            capabilities: lsp::ServerCapabilities {
10724                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10725                ..Default::default()
10726            },
10727            ..Default::default()
10728        },
10729    );
10730
10731    let buffer = project
10732        .update(cx, |project, cx| {
10733            project.open_local_buffer(path!("/file.rs"), cx)
10734        })
10735        .await
10736        .unwrap();
10737
10738    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10739    let (editor, cx) = cx.add_window_view(|window, cx| {
10740        build_editor_with_project(project.clone(), buffer, window, cx)
10741    });
10742    editor.update_in(cx, |editor, window, cx| {
10743        editor.set_text("one\ntwo\nthree\n", window, cx)
10744    });
10745
10746    cx.executor().start_waiting();
10747    let fake_server = fake_servers.next().await.unwrap();
10748
10749    let format = editor
10750        .update_in(cx, |editor, window, cx| {
10751            editor.perform_format(
10752                project.clone(),
10753                FormatTrigger::Manual,
10754                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10755                window,
10756                cx,
10757            )
10758        })
10759        .unwrap();
10760    fake_server
10761        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10762            assert_eq!(
10763                params.text_document.uri,
10764                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10765            );
10766            assert_eq!(params.options.tab_size, 4);
10767            Ok(Some(vec![lsp::TextEdit::new(
10768                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10769                ", ".to_string(),
10770            )]))
10771        })
10772        .next()
10773        .await;
10774    cx.executor().start_waiting();
10775    format.await;
10776    assert_eq!(
10777        editor.update(cx, |editor, cx| editor.text(cx)),
10778        "one, two\nthree\n"
10779    );
10780
10781    editor.update_in(cx, |editor, window, cx| {
10782        editor.set_text("one\ntwo\nthree\n", window, cx)
10783    });
10784    // Ensure we don't lock if formatting hangs.
10785    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10786        move |params, _| async move {
10787            assert_eq!(
10788                params.text_document.uri,
10789                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10790            );
10791            futures::future::pending::<()>().await;
10792            unreachable!()
10793        },
10794    );
10795    let format = editor
10796        .update_in(cx, |editor, window, cx| {
10797            editor.perform_format(
10798                project,
10799                FormatTrigger::Manual,
10800                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10801                window,
10802                cx,
10803            )
10804        })
10805        .unwrap();
10806    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10807    cx.executor().start_waiting();
10808    format.await;
10809    assert_eq!(
10810        editor.update(cx, |editor, cx| editor.text(cx)),
10811        "one\ntwo\nthree\n"
10812    );
10813}
10814
10815#[gpui::test]
10816async fn test_multiple_formatters(cx: &mut TestAppContext) {
10817    init_test(cx, |settings| {
10818        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
10819        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
10820            Formatter::LanguageServer { name: None },
10821            Formatter::CodeActions(
10822                [
10823                    ("code-action-1".into(), true),
10824                    ("code-action-2".into(), true),
10825                ]
10826                .into_iter()
10827                .collect(),
10828            ),
10829        ])))
10830    });
10831
10832    let fs = FakeFs::new(cx.executor());
10833    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
10834        .await;
10835
10836    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10837    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10838    language_registry.add(rust_lang());
10839
10840    let mut fake_servers = language_registry.register_fake_lsp(
10841        "Rust",
10842        FakeLspAdapter {
10843            capabilities: lsp::ServerCapabilities {
10844                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10845                execute_command_provider: Some(lsp::ExecuteCommandOptions {
10846                    commands: vec!["the-command-for-code-action-1".into()],
10847                    ..Default::default()
10848                }),
10849                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
10850                ..Default::default()
10851            },
10852            ..Default::default()
10853        },
10854    );
10855
10856    let buffer = project
10857        .update(cx, |project, cx| {
10858            project.open_local_buffer(path!("/file.rs"), cx)
10859        })
10860        .await
10861        .unwrap();
10862
10863    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10864    let (editor, cx) = cx.add_window_view(|window, cx| {
10865        build_editor_with_project(project.clone(), buffer, window, cx)
10866    });
10867
10868    cx.executor().start_waiting();
10869
10870    let fake_server = fake_servers.next().await.unwrap();
10871    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10872        move |_params, _| async move {
10873            Ok(Some(vec![lsp::TextEdit::new(
10874                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10875                "applied-formatting\n".to_string(),
10876            )]))
10877        },
10878    );
10879    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
10880        move |params, _| async move {
10881            assert_eq!(
10882                params.context.only,
10883                Some(vec!["code-action-1".into(), "code-action-2".into()])
10884            );
10885            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
10886            Ok(Some(vec![
10887                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10888                    kind: Some("code-action-1".into()),
10889                    edit: Some(lsp::WorkspaceEdit::new(
10890                        [(
10891                            uri.clone(),
10892                            vec![lsp::TextEdit::new(
10893                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10894                                "applied-code-action-1-edit\n".to_string(),
10895                            )],
10896                        )]
10897                        .into_iter()
10898                        .collect(),
10899                    )),
10900                    command: Some(lsp::Command {
10901                        command: "the-command-for-code-action-1".into(),
10902                        ..Default::default()
10903                    }),
10904                    ..Default::default()
10905                }),
10906                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
10907                    kind: Some("code-action-2".into()),
10908                    edit: Some(lsp::WorkspaceEdit::new(
10909                        [(
10910                            uri,
10911                            vec![lsp::TextEdit::new(
10912                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
10913                                "applied-code-action-2-edit\n".to_string(),
10914                            )],
10915                        )]
10916                        .into_iter()
10917                        .collect(),
10918                    )),
10919                    ..Default::default()
10920                }),
10921            ]))
10922        },
10923    );
10924
10925    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
10926        move |params, _| async move { Ok(params) }
10927    });
10928
10929    let command_lock = Arc::new(futures::lock::Mutex::new(()));
10930    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
10931        let fake = fake_server.clone();
10932        let lock = command_lock.clone();
10933        move |params, _| {
10934            assert_eq!(params.command, "the-command-for-code-action-1");
10935            let fake = fake.clone();
10936            let lock = lock.clone();
10937            async move {
10938                lock.lock().await;
10939                fake.server
10940                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
10941                        label: None,
10942                        edit: lsp::WorkspaceEdit {
10943                            changes: Some(
10944                                [(
10945                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
10946                                    vec![lsp::TextEdit {
10947                                        range: lsp::Range::new(
10948                                            lsp::Position::new(0, 0),
10949                                            lsp::Position::new(0, 0),
10950                                        ),
10951                                        new_text: "applied-code-action-1-command\n".into(),
10952                                    }],
10953                                )]
10954                                .into_iter()
10955                                .collect(),
10956                            ),
10957                            ..Default::default()
10958                        },
10959                    })
10960                    .await
10961                    .into_response()
10962                    .unwrap();
10963                Ok(Some(json!(null)))
10964            }
10965        }
10966    });
10967
10968    cx.executor().start_waiting();
10969    editor
10970        .update_in(cx, |editor, window, cx| {
10971            editor.perform_format(
10972                project.clone(),
10973                FormatTrigger::Manual,
10974                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
10975                window,
10976                cx,
10977            )
10978        })
10979        .unwrap()
10980        .await;
10981    editor.update(cx, |editor, cx| {
10982        assert_eq!(
10983            editor.text(cx),
10984            r#"
10985                applied-code-action-2-edit
10986                applied-code-action-1-command
10987                applied-code-action-1-edit
10988                applied-formatting
10989                one
10990                two
10991                three
10992            "#
10993            .unindent()
10994        );
10995    });
10996
10997    editor.update_in(cx, |editor, window, cx| {
10998        editor.undo(&Default::default(), window, cx);
10999        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11000    });
11001
11002    // Perform a manual edit while waiting for an LSP command
11003    // that's being run as part of a formatting code action.
11004    let lock_guard = command_lock.lock().await;
11005    let format = editor
11006        .update_in(cx, |editor, window, cx| {
11007            editor.perform_format(
11008                project.clone(),
11009                FormatTrigger::Manual,
11010                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11011                window,
11012                cx,
11013            )
11014        })
11015        .unwrap();
11016    cx.run_until_parked();
11017    editor.update(cx, |editor, cx| {
11018        assert_eq!(
11019            editor.text(cx),
11020            r#"
11021                applied-code-action-1-edit
11022                applied-formatting
11023                one
11024                two
11025                three
11026            "#
11027            .unindent()
11028        );
11029
11030        editor.buffer.update(cx, |buffer, cx| {
11031            let ix = buffer.len(cx);
11032            buffer.edit([(ix..ix, "edited\n")], None, cx);
11033        });
11034    });
11035
11036    // Allow the LSP command to proceed. Because the buffer was edited,
11037    // the second code action will not be run.
11038    drop(lock_guard);
11039    format.await;
11040    editor.update_in(cx, |editor, window, cx| {
11041        assert_eq!(
11042            editor.text(cx),
11043            r#"
11044                applied-code-action-1-command
11045                applied-code-action-1-edit
11046                applied-formatting
11047                one
11048                two
11049                three
11050                edited
11051            "#
11052            .unindent()
11053        );
11054
11055        // The manual edit is undone first, because it is the last thing the user did
11056        // (even though the command completed afterwards).
11057        editor.undo(&Default::default(), window, cx);
11058        assert_eq!(
11059            editor.text(cx),
11060            r#"
11061                applied-code-action-1-command
11062                applied-code-action-1-edit
11063                applied-formatting
11064                one
11065                two
11066                three
11067            "#
11068            .unindent()
11069        );
11070
11071        // All the formatting (including the command, which completed after the manual edit)
11072        // is undone together.
11073        editor.undo(&Default::default(), window, cx);
11074        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11075    });
11076}
11077
11078#[gpui::test]
11079async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
11080    init_test(cx, |settings| {
11081        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11082            Formatter::LanguageServer { name: None },
11083        ])))
11084    });
11085
11086    let fs = FakeFs::new(cx.executor());
11087    fs.insert_file(path!("/file.ts"), Default::default()).await;
11088
11089    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11090
11091    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11092    language_registry.add(Arc::new(Language::new(
11093        LanguageConfig {
11094            name: "TypeScript".into(),
11095            matcher: LanguageMatcher {
11096                path_suffixes: vec!["ts".to_string()],
11097                ..Default::default()
11098            },
11099            ..LanguageConfig::default()
11100        },
11101        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11102    )));
11103    update_test_language_settings(cx, |settings| {
11104        settings.defaults.prettier = Some(PrettierSettings {
11105            allowed: true,
11106            ..PrettierSettings::default()
11107        });
11108    });
11109    let mut fake_servers = language_registry.register_fake_lsp(
11110        "TypeScript",
11111        FakeLspAdapter {
11112            capabilities: lsp::ServerCapabilities {
11113                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11114                ..Default::default()
11115            },
11116            ..Default::default()
11117        },
11118    );
11119
11120    let buffer = project
11121        .update(cx, |project, cx| {
11122            project.open_local_buffer(path!("/file.ts"), cx)
11123        })
11124        .await
11125        .unwrap();
11126
11127    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11128    let (editor, cx) = cx.add_window_view(|window, cx| {
11129        build_editor_with_project(project.clone(), buffer, window, cx)
11130    });
11131    editor.update_in(cx, |editor, window, cx| {
11132        editor.set_text(
11133            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11134            window,
11135            cx,
11136        )
11137    });
11138
11139    cx.executor().start_waiting();
11140    let fake_server = fake_servers.next().await.unwrap();
11141
11142    let format = editor
11143        .update_in(cx, |editor, window, cx| {
11144            editor.perform_code_action_kind(
11145                project.clone(),
11146                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11147                window,
11148                cx,
11149            )
11150        })
11151        .unwrap();
11152    fake_server
11153        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
11154            assert_eq!(
11155                params.text_document.uri,
11156                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
11157            );
11158            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11159                lsp::CodeAction {
11160                    title: "Organize Imports".to_string(),
11161                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
11162                    edit: Some(lsp::WorkspaceEdit {
11163                        changes: Some(
11164                            [(
11165                                params.text_document.uri.clone(),
11166                                vec![lsp::TextEdit::new(
11167                                    lsp::Range::new(
11168                                        lsp::Position::new(1, 0),
11169                                        lsp::Position::new(2, 0),
11170                                    ),
11171                                    "".to_string(),
11172                                )],
11173                            )]
11174                            .into_iter()
11175                            .collect(),
11176                        ),
11177                        ..Default::default()
11178                    }),
11179                    ..Default::default()
11180                },
11181            )]))
11182        })
11183        .next()
11184        .await;
11185    cx.executor().start_waiting();
11186    format.await;
11187    assert_eq!(
11188        editor.update(cx, |editor, cx| editor.text(cx)),
11189        "import { a } from 'module';\n\nconst x = a;\n"
11190    );
11191
11192    editor.update_in(cx, |editor, window, cx| {
11193        editor.set_text(
11194            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11195            window,
11196            cx,
11197        )
11198    });
11199    // Ensure we don't lock if code action hangs.
11200    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11201        move |params, _| async move {
11202            assert_eq!(
11203                params.text_document.uri,
11204                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
11205            );
11206            futures::future::pending::<()>().await;
11207            unreachable!()
11208        },
11209    );
11210    let format = editor
11211        .update_in(cx, |editor, window, cx| {
11212            editor.perform_code_action_kind(
11213                project,
11214                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11215                window,
11216                cx,
11217            )
11218        })
11219        .unwrap();
11220    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
11221    cx.executor().start_waiting();
11222    format.await;
11223    assert_eq!(
11224        editor.update(cx, |editor, cx| editor.text(cx)),
11225        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
11226    );
11227}
11228
11229#[gpui::test]
11230async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
11231    init_test(cx, |_| {});
11232
11233    let mut cx = EditorLspTestContext::new_rust(
11234        lsp::ServerCapabilities {
11235            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11236            ..Default::default()
11237        },
11238        cx,
11239    )
11240    .await;
11241
11242    cx.set_state(indoc! {"
11243        one.twoˇ
11244    "});
11245
11246    // The format request takes a long time. When it completes, it inserts
11247    // a newline and an indent before the `.`
11248    cx.lsp
11249        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
11250            let executor = cx.background_executor().clone();
11251            async move {
11252                executor.timer(Duration::from_millis(100)).await;
11253                Ok(Some(vec![lsp::TextEdit {
11254                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
11255                    new_text: "\n    ".into(),
11256                }]))
11257            }
11258        });
11259
11260    // Submit a format request.
11261    let format_1 = cx
11262        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11263        .unwrap();
11264    cx.executor().run_until_parked();
11265
11266    // Submit a second format request.
11267    let format_2 = cx
11268        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11269        .unwrap();
11270    cx.executor().run_until_parked();
11271
11272    // Wait for both format requests to complete
11273    cx.executor().advance_clock(Duration::from_millis(200));
11274    cx.executor().start_waiting();
11275    format_1.await.unwrap();
11276    cx.executor().start_waiting();
11277    format_2.await.unwrap();
11278
11279    // The formatting edits only happens once.
11280    cx.assert_editor_state(indoc! {"
11281        one
11282            .twoˇ
11283    "});
11284}
11285
11286#[gpui::test]
11287async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
11288    init_test(cx, |settings| {
11289        settings.defaults.formatter = Some(SelectedFormatter::Auto)
11290    });
11291
11292    let mut cx = EditorLspTestContext::new_rust(
11293        lsp::ServerCapabilities {
11294            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11295            ..Default::default()
11296        },
11297        cx,
11298    )
11299    .await;
11300
11301    // Set up a buffer white some trailing whitespace and no trailing newline.
11302    cx.set_state(
11303        &[
11304            "one ",   //
11305            "twoˇ",   //
11306            "three ", //
11307            "four",   //
11308        ]
11309        .join("\n"),
11310    );
11311
11312    // Submit a format request.
11313    let format = cx
11314        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11315        .unwrap();
11316
11317    // Record which buffer changes have been sent to the language server
11318    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
11319    cx.lsp
11320        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
11321            let buffer_changes = buffer_changes.clone();
11322            move |params, _| {
11323                buffer_changes.lock().extend(
11324                    params
11325                        .content_changes
11326                        .into_iter()
11327                        .map(|e| (e.range.unwrap(), e.text)),
11328                );
11329            }
11330        });
11331
11332    // Handle formatting requests to the language server.
11333    cx.lsp
11334        .set_request_handler::<lsp::request::Formatting, _, _>({
11335            let buffer_changes = buffer_changes.clone();
11336            move |_, _| {
11337                // When formatting is requested, trailing whitespace has already been stripped,
11338                // and the trailing newline has already been added.
11339                assert_eq!(
11340                    &buffer_changes.lock()[1..],
11341                    &[
11342                        (
11343                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
11344                            "".into()
11345                        ),
11346                        (
11347                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
11348                            "".into()
11349                        ),
11350                        (
11351                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
11352                            "\n".into()
11353                        ),
11354                    ]
11355                );
11356
11357                // Insert blank lines between each line of the buffer.
11358                async move {
11359                    Ok(Some(vec![
11360                        lsp::TextEdit {
11361                            range: lsp::Range::new(
11362                                lsp::Position::new(1, 0),
11363                                lsp::Position::new(1, 0),
11364                            ),
11365                            new_text: "\n".into(),
11366                        },
11367                        lsp::TextEdit {
11368                            range: lsp::Range::new(
11369                                lsp::Position::new(2, 0),
11370                                lsp::Position::new(2, 0),
11371                            ),
11372                            new_text: "\n".into(),
11373                        },
11374                    ]))
11375                }
11376            }
11377        });
11378
11379    // After formatting the buffer, the trailing whitespace is stripped,
11380    // a newline is appended, and the edits provided by the language server
11381    // have been applied.
11382    format.await.unwrap();
11383    cx.assert_editor_state(
11384        &[
11385            "one",   //
11386            "",      //
11387            "twoˇ",  //
11388            "",      //
11389            "three", //
11390            "four",  //
11391            "",      //
11392        ]
11393        .join("\n"),
11394    );
11395
11396    // Undoing the formatting undoes the trailing whitespace removal, the
11397    // trailing newline, and the LSP edits.
11398    cx.update_buffer(|buffer, cx| buffer.undo(cx));
11399    cx.assert_editor_state(
11400        &[
11401            "one ",   //
11402            "twoˇ",   //
11403            "three ", //
11404            "four",   //
11405        ]
11406        .join("\n"),
11407    );
11408}
11409
11410#[gpui::test]
11411async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
11412    cx: &mut TestAppContext,
11413) {
11414    init_test(cx, |_| {});
11415
11416    cx.update(|cx| {
11417        cx.update_global::<SettingsStore, _>(|settings, cx| {
11418            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11419                settings.auto_signature_help = Some(true);
11420            });
11421        });
11422    });
11423
11424    let mut cx = EditorLspTestContext::new_rust(
11425        lsp::ServerCapabilities {
11426            signature_help_provider: Some(lsp::SignatureHelpOptions {
11427                ..Default::default()
11428            }),
11429            ..Default::default()
11430        },
11431        cx,
11432    )
11433    .await;
11434
11435    let language = Language::new(
11436        LanguageConfig {
11437            name: "Rust".into(),
11438            brackets: BracketPairConfig {
11439                pairs: vec![
11440                    BracketPair {
11441                        start: "{".to_string(),
11442                        end: "}".to_string(),
11443                        close: true,
11444                        surround: true,
11445                        newline: true,
11446                    },
11447                    BracketPair {
11448                        start: "(".to_string(),
11449                        end: ")".to_string(),
11450                        close: true,
11451                        surround: true,
11452                        newline: true,
11453                    },
11454                    BracketPair {
11455                        start: "/*".to_string(),
11456                        end: " */".to_string(),
11457                        close: true,
11458                        surround: true,
11459                        newline: true,
11460                    },
11461                    BracketPair {
11462                        start: "[".to_string(),
11463                        end: "]".to_string(),
11464                        close: false,
11465                        surround: false,
11466                        newline: true,
11467                    },
11468                    BracketPair {
11469                        start: "\"".to_string(),
11470                        end: "\"".to_string(),
11471                        close: true,
11472                        surround: true,
11473                        newline: false,
11474                    },
11475                    BracketPair {
11476                        start: "<".to_string(),
11477                        end: ">".to_string(),
11478                        close: false,
11479                        surround: true,
11480                        newline: true,
11481                    },
11482                ],
11483                ..Default::default()
11484            },
11485            autoclose_before: "})]".to_string(),
11486            ..Default::default()
11487        },
11488        Some(tree_sitter_rust::LANGUAGE.into()),
11489    );
11490    let language = Arc::new(language);
11491
11492    cx.language_registry().add(language.clone());
11493    cx.update_buffer(|buffer, cx| {
11494        buffer.set_language(Some(language), cx);
11495    });
11496
11497    cx.set_state(
11498        &r#"
11499            fn main() {
11500                sampleˇ
11501            }
11502        "#
11503        .unindent(),
11504    );
11505
11506    cx.update_editor(|editor, window, cx| {
11507        editor.handle_input("(", window, cx);
11508    });
11509    cx.assert_editor_state(
11510        &"
11511            fn main() {
11512                sample(ˇ)
11513            }
11514        "
11515        .unindent(),
11516    );
11517
11518    let mocked_response = lsp::SignatureHelp {
11519        signatures: vec![lsp::SignatureInformation {
11520            label: "fn sample(param1: u8, param2: u8)".to_string(),
11521            documentation: None,
11522            parameters: Some(vec![
11523                lsp::ParameterInformation {
11524                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11525                    documentation: None,
11526                },
11527                lsp::ParameterInformation {
11528                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11529                    documentation: None,
11530                },
11531            ]),
11532            active_parameter: None,
11533        }],
11534        active_signature: Some(0),
11535        active_parameter: Some(0),
11536    };
11537    handle_signature_help_request(&mut cx, mocked_response).await;
11538
11539    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11540        .await;
11541
11542    cx.editor(|editor, _, _| {
11543        let signature_help_state = editor.signature_help_state.popover().cloned();
11544        let signature = signature_help_state.unwrap();
11545        assert_eq!(
11546            signature.signatures[signature.current_signature].label,
11547            "fn sample(param1: u8, param2: u8)"
11548        );
11549    });
11550}
11551
11552#[gpui::test]
11553async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11554    init_test(cx, |_| {});
11555
11556    cx.update(|cx| {
11557        cx.update_global::<SettingsStore, _>(|settings, cx| {
11558            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11559                settings.auto_signature_help = Some(false);
11560                settings.show_signature_help_after_edits = Some(false);
11561            });
11562        });
11563    });
11564
11565    let mut cx = EditorLspTestContext::new_rust(
11566        lsp::ServerCapabilities {
11567            signature_help_provider: Some(lsp::SignatureHelpOptions {
11568                ..Default::default()
11569            }),
11570            ..Default::default()
11571        },
11572        cx,
11573    )
11574    .await;
11575
11576    let language = Language::new(
11577        LanguageConfig {
11578            name: "Rust".into(),
11579            brackets: BracketPairConfig {
11580                pairs: vec![
11581                    BracketPair {
11582                        start: "{".to_string(),
11583                        end: "}".to_string(),
11584                        close: true,
11585                        surround: true,
11586                        newline: true,
11587                    },
11588                    BracketPair {
11589                        start: "(".to_string(),
11590                        end: ")".to_string(),
11591                        close: true,
11592                        surround: true,
11593                        newline: true,
11594                    },
11595                    BracketPair {
11596                        start: "/*".to_string(),
11597                        end: " */".to_string(),
11598                        close: true,
11599                        surround: true,
11600                        newline: true,
11601                    },
11602                    BracketPair {
11603                        start: "[".to_string(),
11604                        end: "]".to_string(),
11605                        close: false,
11606                        surround: false,
11607                        newline: true,
11608                    },
11609                    BracketPair {
11610                        start: "\"".to_string(),
11611                        end: "\"".to_string(),
11612                        close: true,
11613                        surround: true,
11614                        newline: false,
11615                    },
11616                    BracketPair {
11617                        start: "<".to_string(),
11618                        end: ">".to_string(),
11619                        close: false,
11620                        surround: true,
11621                        newline: true,
11622                    },
11623                ],
11624                ..Default::default()
11625            },
11626            autoclose_before: "})]".to_string(),
11627            ..Default::default()
11628        },
11629        Some(tree_sitter_rust::LANGUAGE.into()),
11630    );
11631    let language = Arc::new(language);
11632
11633    cx.language_registry().add(language.clone());
11634    cx.update_buffer(|buffer, cx| {
11635        buffer.set_language(Some(language), cx);
11636    });
11637
11638    // Ensure that signature_help is not called when no signature help is enabled.
11639    cx.set_state(
11640        &r#"
11641            fn main() {
11642                sampleˇ
11643            }
11644        "#
11645        .unindent(),
11646    );
11647    cx.update_editor(|editor, window, cx| {
11648        editor.handle_input("(", window, cx);
11649    });
11650    cx.assert_editor_state(
11651        &"
11652            fn main() {
11653                sample(ˇ)
11654            }
11655        "
11656        .unindent(),
11657    );
11658    cx.editor(|editor, _, _| {
11659        assert!(editor.signature_help_state.task().is_none());
11660    });
11661
11662    let mocked_response = lsp::SignatureHelp {
11663        signatures: vec![lsp::SignatureInformation {
11664            label: "fn sample(param1: u8, param2: u8)".to_string(),
11665            documentation: None,
11666            parameters: Some(vec![
11667                lsp::ParameterInformation {
11668                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11669                    documentation: None,
11670                },
11671                lsp::ParameterInformation {
11672                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11673                    documentation: None,
11674                },
11675            ]),
11676            active_parameter: None,
11677        }],
11678        active_signature: Some(0),
11679        active_parameter: Some(0),
11680    };
11681
11682    // Ensure that signature_help is called when enabled afte edits
11683    cx.update(|_, cx| {
11684        cx.update_global::<SettingsStore, _>(|settings, cx| {
11685            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11686                settings.auto_signature_help = Some(false);
11687                settings.show_signature_help_after_edits = Some(true);
11688            });
11689        });
11690    });
11691    cx.set_state(
11692        &r#"
11693            fn main() {
11694                sampleˇ
11695            }
11696        "#
11697        .unindent(),
11698    );
11699    cx.update_editor(|editor, window, cx| {
11700        editor.handle_input("(", window, cx);
11701    });
11702    cx.assert_editor_state(
11703        &"
11704            fn main() {
11705                sample(ˇ)
11706            }
11707        "
11708        .unindent(),
11709    );
11710    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11711    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11712        .await;
11713    cx.update_editor(|editor, _, _| {
11714        let signature_help_state = editor.signature_help_state.popover().cloned();
11715        assert!(signature_help_state.is_some());
11716        let signature = signature_help_state.unwrap();
11717        assert_eq!(
11718            signature.signatures[signature.current_signature].label,
11719            "fn sample(param1: u8, param2: u8)"
11720        );
11721        editor.signature_help_state = SignatureHelpState::default();
11722    });
11723
11724    // Ensure that signature_help is called when auto signature help override is enabled
11725    cx.update(|_, cx| {
11726        cx.update_global::<SettingsStore, _>(|settings, cx| {
11727            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11728                settings.auto_signature_help = Some(true);
11729                settings.show_signature_help_after_edits = Some(false);
11730            });
11731        });
11732    });
11733    cx.set_state(
11734        &r#"
11735            fn main() {
11736                sampleˇ
11737            }
11738        "#
11739        .unindent(),
11740    );
11741    cx.update_editor(|editor, window, cx| {
11742        editor.handle_input("(", window, cx);
11743    });
11744    cx.assert_editor_state(
11745        &"
11746            fn main() {
11747                sample(ˇ)
11748            }
11749        "
11750        .unindent(),
11751    );
11752    handle_signature_help_request(&mut cx, mocked_response).await;
11753    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11754        .await;
11755    cx.editor(|editor, _, _| {
11756        let signature_help_state = editor.signature_help_state.popover().cloned();
11757        assert!(signature_help_state.is_some());
11758        let signature = signature_help_state.unwrap();
11759        assert_eq!(
11760            signature.signatures[signature.current_signature].label,
11761            "fn sample(param1: u8, param2: u8)"
11762        );
11763    });
11764}
11765
11766#[gpui::test]
11767async fn test_signature_help(cx: &mut TestAppContext) {
11768    init_test(cx, |_| {});
11769    cx.update(|cx| {
11770        cx.update_global::<SettingsStore, _>(|settings, cx| {
11771            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11772                settings.auto_signature_help = Some(true);
11773            });
11774        });
11775    });
11776
11777    let mut cx = EditorLspTestContext::new_rust(
11778        lsp::ServerCapabilities {
11779            signature_help_provider: Some(lsp::SignatureHelpOptions {
11780                ..Default::default()
11781            }),
11782            ..Default::default()
11783        },
11784        cx,
11785    )
11786    .await;
11787
11788    // A test that directly calls `show_signature_help`
11789    cx.update_editor(|editor, window, cx| {
11790        editor.show_signature_help(&ShowSignatureHelp, window, cx);
11791    });
11792
11793    let mocked_response = lsp::SignatureHelp {
11794        signatures: vec![lsp::SignatureInformation {
11795            label: "fn sample(param1: u8, param2: u8)".to_string(),
11796            documentation: None,
11797            parameters: Some(vec![
11798                lsp::ParameterInformation {
11799                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11800                    documentation: None,
11801                },
11802                lsp::ParameterInformation {
11803                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11804                    documentation: None,
11805                },
11806            ]),
11807            active_parameter: None,
11808        }],
11809        active_signature: Some(0),
11810        active_parameter: Some(0),
11811    };
11812    handle_signature_help_request(&mut cx, mocked_response).await;
11813
11814    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11815        .await;
11816
11817    cx.editor(|editor, _, _| {
11818        let signature_help_state = editor.signature_help_state.popover().cloned();
11819        assert!(signature_help_state.is_some());
11820        let signature = signature_help_state.unwrap();
11821        assert_eq!(
11822            signature.signatures[signature.current_signature].label,
11823            "fn sample(param1: u8, param2: u8)"
11824        );
11825    });
11826
11827    // When exiting outside from inside the brackets, `signature_help` is closed.
11828    cx.set_state(indoc! {"
11829        fn main() {
11830            sample(ˇ);
11831        }
11832
11833        fn sample(param1: u8, param2: u8) {}
11834    "});
11835
11836    cx.update_editor(|editor, window, cx| {
11837        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11838            s.select_ranges([0..0])
11839        });
11840    });
11841
11842    let mocked_response = lsp::SignatureHelp {
11843        signatures: Vec::new(),
11844        active_signature: None,
11845        active_parameter: None,
11846    };
11847    handle_signature_help_request(&mut cx, mocked_response).await;
11848
11849    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
11850        .await;
11851
11852    cx.editor(|editor, _, _| {
11853        assert!(!editor.signature_help_state.is_shown());
11854    });
11855
11856    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
11857    cx.set_state(indoc! {"
11858        fn main() {
11859            sample(ˇ);
11860        }
11861
11862        fn sample(param1: u8, param2: u8) {}
11863    "});
11864
11865    let mocked_response = lsp::SignatureHelp {
11866        signatures: vec![lsp::SignatureInformation {
11867            label: "fn sample(param1: u8, param2: u8)".to_string(),
11868            documentation: None,
11869            parameters: Some(vec![
11870                lsp::ParameterInformation {
11871                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11872                    documentation: None,
11873                },
11874                lsp::ParameterInformation {
11875                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11876                    documentation: None,
11877                },
11878            ]),
11879            active_parameter: None,
11880        }],
11881        active_signature: Some(0),
11882        active_parameter: Some(0),
11883    };
11884    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11885    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11886        .await;
11887    cx.editor(|editor, _, _| {
11888        assert!(editor.signature_help_state.is_shown());
11889    });
11890
11891    // Restore the popover with more parameter input
11892    cx.set_state(indoc! {"
11893        fn main() {
11894            sample(param1, param2ˇ);
11895        }
11896
11897        fn sample(param1: u8, param2: u8) {}
11898    "});
11899
11900    let mocked_response = lsp::SignatureHelp {
11901        signatures: vec![lsp::SignatureInformation {
11902            label: "fn sample(param1: u8, param2: u8)".to_string(),
11903            documentation: None,
11904            parameters: Some(vec![
11905                lsp::ParameterInformation {
11906                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11907                    documentation: None,
11908                },
11909                lsp::ParameterInformation {
11910                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11911                    documentation: None,
11912                },
11913            ]),
11914            active_parameter: None,
11915        }],
11916        active_signature: Some(0),
11917        active_parameter: Some(1),
11918    };
11919    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11920    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11921        .await;
11922
11923    // When selecting a range, the popover is gone.
11924    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
11925    cx.update_editor(|editor, window, cx| {
11926        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11927            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
11928        })
11929    });
11930    cx.assert_editor_state(indoc! {"
11931        fn main() {
11932            sample(param1, «ˇparam2»);
11933        }
11934
11935        fn sample(param1: u8, param2: u8) {}
11936    "});
11937    cx.editor(|editor, _, _| {
11938        assert!(!editor.signature_help_state.is_shown());
11939    });
11940
11941    // When unselecting again, the popover is back if within the brackets.
11942    cx.update_editor(|editor, window, cx| {
11943        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11944            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11945        })
11946    });
11947    cx.assert_editor_state(indoc! {"
11948        fn main() {
11949            sample(param1, ˇparam2);
11950        }
11951
11952        fn sample(param1: u8, param2: u8) {}
11953    "});
11954    handle_signature_help_request(&mut cx, mocked_response).await;
11955    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11956        .await;
11957    cx.editor(|editor, _, _| {
11958        assert!(editor.signature_help_state.is_shown());
11959    });
11960
11961    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
11962    cx.update_editor(|editor, window, cx| {
11963        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11964            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
11965            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
11966        })
11967    });
11968    cx.assert_editor_state(indoc! {"
11969        fn main() {
11970            sample(param1, ˇparam2);
11971        }
11972
11973        fn sample(param1: u8, param2: u8) {}
11974    "});
11975
11976    let mocked_response = lsp::SignatureHelp {
11977        signatures: vec![lsp::SignatureInformation {
11978            label: "fn sample(param1: u8, param2: u8)".to_string(),
11979            documentation: None,
11980            parameters: Some(vec![
11981                lsp::ParameterInformation {
11982                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11983                    documentation: None,
11984                },
11985                lsp::ParameterInformation {
11986                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11987                    documentation: None,
11988                },
11989            ]),
11990            active_parameter: None,
11991        }],
11992        active_signature: Some(0),
11993        active_parameter: Some(1),
11994    };
11995    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
11996    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11997        .await;
11998    cx.update_editor(|editor, _, cx| {
11999        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
12000    });
12001    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12002        .await;
12003    cx.update_editor(|editor, window, cx| {
12004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12005            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12006        })
12007    });
12008    cx.assert_editor_state(indoc! {"
12009        fn main() {
12010            sample(param1, «ˇparam2»);
12011        }
12012
12013        fn sample(param1: u8, param2: u8) {}
12014    "});
12015    cx.update_editor(|editor, window, cx| {
12016        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12017            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12018        })
12019    });
12020    cx.assert_editor_state(indoc! {"
12021        fn main() {
12022            sample(param1, ˇparam2);
12023        }
12024
12025        fn sample(param1: u8, param2: u8) {}
12026    "});
12027    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
12028        .await;
12029}
12030
12031#[gpui::test]
12032async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
12033    init_test(cx, |_| {});
12034
12035    let mut cx = EditorLspTestContext::new_rust(
12036        lsp::ServerCapabilities {
12037            signature_help_provider: Some(lsp::SignatureHelpOptions {
12038                ..Default::default()
12039            }),
12040            ..Default::default()
12041        },
12042        cx,
12043    )
12044    .await;
12045
12046    cx.set_state(indoc! {"
12047        fn main() {
12048            overloadedˇ
12049        }
12050    "});
12051
12052    cx.update_editor(|editor, window, cx| {
12053        editor.handle_input("(", window, cx);
12054        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12055    });
12056
12057    // Mock response with 3 signatures
12058    let mocked_response = lsp::SignatureHelp {
12059        signatures: vec![
12060            lsp::SignatureInformation {
12061                label: "fn overloaded(x: i32)".to_string(),
12062                documentation: None,
12063                parameters: Some(vec![lsp::ParameterInformation {
12064                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12065                    documentation: None,
12066                }]),
12067                active_parameter: None,
12068            },
12069            lsp::SignatureInformation {
12070                label: "fn overloaded(x: i32, y: i32)".to_string(),
12071                documentation: None,
12072                parameters: Some(vec![
12073                    lsp::ParameterInformation {
12074                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12075                        documentation: None,
12076                    },
12077                    lsp::ParameterInformation {
12078                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
12079                        documentation: None,
12080                    },
12081                ]),
12082                active_parameter: None,
12083            },
12084            lsp::SignatureInformation {
12085                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
12086                documentation: None,
12087                parameters: Some(vec![
12088                    lsp::ParameterInformation {
12089                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12090                        documentation: None,
12091                    },
12092                    lsp::ParameterInformation {
12093                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
12094                        documentation: None,
12095                    },
12096                    lsp::ParameterInformation {
12097                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
12098                        documentation: None,
12099                    },
12100                ]),
12101                active_parameter: None,
12102            },
12103        ],
12104        active_signature: Some(1),
12105        active_parameter: Some(0),
12106    };
12107    handle_signature_help_request(&mut cx, mocked_response).await;
12108
12109    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12110        .await;
12111
12112    // Verify we have multiple signatures and the right one is selected
12113    cx.editor(|editor, _, _| {
12114        let popover = editor.signature_help_state.popover().cloned().unwrap();
12115        assert_eq!(popover.signatures.len(), 3);
12116        // active_signature was 1, so that should be the current
12117        assert_eq!(popover.current_signature, 1);
12118        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
12119        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
12120        assert_eq!(
12121            popover.signatures[2].label,
12122            "fn overloaded(x: i32, y: i32, z: i32)"
12123        );
12124    });
12125
12126    // Test navigation functionality
12127    cx.update_editor(|editor, window, cx| {
12128        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12129    });
12130
12131    cx.editor(|editor, _, _| {
12132        let popover = editor.signature_help_state.popover().cloned().unwrap();
12133        assert_eq!(popover.current_signature, 2);
12134    });
12135
12136    // Test wrap around
12137    cx.update_editor(|editor, window, cx| {
12138        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12139    });
12140
12141    cx.editor(|editor, _, _| {
12142        let popover = editor.signature_help_state.popover().cloned().unwrap();
12143        assert_eq!(popover.current_signature, 0);
12144    });
12145
12146    // Test previous navigation
12147    cx.update_editor(|editor, window, cx| {
12148        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
12149    });
12150
12151    cx.editor(|editor, _, _| {
12152        let popover = editor.signature_help_state.popover().cloned().unwrap();
12153        assert_eq!(popover.current_signature, 2);
12154    });
12155}
12156
12157#[gpui::test]
12158async fn test_completion_mode(cx: &mut TestAppContext) {
12159    init_test(cx, |_| {});
12160    let mut cx = EditorLspTestContext::new_rust(
12161        lsp::ServerCapabilities {
12162            completion_provider: Some(lsp::CompletionOptions {
12163                resolve_provider: Some(true),
12164                ..Default::default()
12165            }),
12166            ..Default::default()
12167        },
12168        cx,
12169    )
12170    .await;
12171
12172    struct Run {
12173        run_description: &'static str,
12174        initial_state: String,
12175        buffer_marked_text: String,
12176        completion_label: &'static str,
12177        completion_text: &'static str,
12178        expected_with_insert_mode: String,
12179        expected_with_replace_mode: String,
12180        expected_with_replace_subsequence_mode: String,
12181        expected_with_replace_suffix_mode: String,
12182    }
12183
12184    let runs = [
12185        Run {
12186            run_description: "Start of word matches completion text",
12187            initial_state: "before ediˇ after".into(),
12188            buffer_marked_text: "before <edi|> after".into(),
12189            completion_label: "editor",
12190            completion_text: "editor",
12191            expected_with_insert_mode: "before editorˇ after".into(),
12192            expected_with_replace_mode: "before editorˇ after".into(),
12193            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12194            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12195        },
12196        Run {
12197            run_description: "Accept same text at the middle of the word",
12198            initial_state: "before ediˇtor after".into(),
12199            buffer_marked_text: "before <edi|tor> after".into(),
12200            completion_label: "editor",
12201            completion_text: "editor",
12202            expected_with_insert_mode: "before editorˇtor after".into(),
12203            expected_with_replace_mode: "before editorˇ after".into(),
12204            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12205            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12206        },
12207        Run {
12208            run_description: "End of word matches completion text -- cursor at end",
12209            initial_state: "before torˇ after".into(),
12210            buffer_marked_text: "before <tor|> after".into(),
12211            completion_label: "editor",
12212            completion_text: "editor",
12213            expected_with_insert_mode: "before editorˇ after".into(),
12214            expected_with_replace_mode: "before editorˇ after".into(),
12215            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12216            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12217        },
12218        Run {
12219            run_description: "End of word matches completion text -- cursor at start",
12220            initial_state: "before ˇtor after".into(),
12221            buffer_marked_text: "before <|tor> after".into(),
12222            completion_label: "editor",
12223            completion_text: "editor",
12224            expected_with_insert_mode: "before editorˇtor after".into(),
12225            expected_with_replace_mode: "before editorˇ after".into(),
12226            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12227            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12228        },
12229        Run {
12230            run_description: "Prepend text containing whitespace",
12231            initial_state: "pˇfield: bool".into(),
12232            buffer_marked_text: "<p|field>: bool".into(),
12233            completion_label: "pub ",
12234            completion_text: "pub ",
12235            expected_with_insert_mode: "pub ˇfield: bool".into(),
12236            expected_with_replace_mode: "pub ˇ: bool".into(),
12237            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
12238            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
12239        },
12240        Run {
12241            run_description: "Add element to start of list",
12242            initial_state: "[element_ˇelement_2]".into(),
12243            buffer_marked_text: "[<element_|element_2>]".into(),
12244            completion_label: "element_1",
12245            completion_text: "element_1",
12246            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
12247            expected_with_replace_mode: "[element_1ˇ]".into(),
12248            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
12249            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
12250        },
12251        Run {
12252            run_description: "Add element to start of list -- first and second elements are equal",
12253            initial_state: "[elˇelement]".into(),
12254            buffer_marked_text: "[<el|element>]".into(),
12255            completion_label: "element",
12256            completion_text: "element",
12257            expected_with_insert_mode: "[elementˇelement]".into(),
12258            expected_with_replace_mode: "[elementˇ]".into(),
12259            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
12260            expected_with_replace_suffix_mode: "[elementˇ]".into(),
12261        },
12262        Run {
12263            run_description: "Ends with matching suffix",
12264            initial_state: "SubˇError".into(),
12265            buffer_marked_text: "<Sub|Error>".into(),
12266            completion_label: "SubscriptionError",
12267            completion_text: "SubscriptionError",
12268            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
12269            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12270            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12271            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
12272        },
12273        Run {
12274            run_description: "Suffix is a subsequence -- contiguous",
12275            initial_state: "SubˇErr".into(),
12276            buffer_marked_text: "<Sub|Err>".into(),
12277            completion_label: "SubscriptionError",
12278            completion_text: "SubscriptionError",
12279            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
12280            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12281            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12282            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
12283        },
12284        Run {
12285            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
12286            initial_state: "Suˇscrirr".into(),
12287            buffer_marked_text: "<Su|scrirr>".into(),
12288            completion_label: "SubscriptionError",
12289            completion_text: "SubscriptionError",
12290            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
12291            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12292            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12293            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
12294        },
12295        Run {
12296            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
12297            initial_state: "foo(indˇix)".into(),
12298            buffer_marked_text: "foo(<ind|ix>)".into(),
12299            completion_label: "node_index",
12300            completion_text: "node_index",
12301            expected_with_insert_mode: "foo(node_indexˇix)".into(),
12302            expected_with_replace_mode: "foo(node_indexˇ)".into(),
12303            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
12304            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
12305        },
12306        Run {
12307            run_description: "Replace range ends before cursor - should extend to cursor",
12308            initial_state: "before editˇo after".into(),
12309            buffer_marked_text: "before <{ed}>it|o after".into(),
12310            completion_label: "editor",
12311            completion_text: "editor",
12312            expected_with_insert_mode: "before editorˇo after".into(),
12313            expected_with_replace_mode: "before editorˇo after".into(),
12314            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
12315            expected_with_replace_suffix_mode: "before editorˇo after".into(),
12316        },
12317        Run {
12318            run_description: "Uses label for suffix matching",
12319            initial_state: "before ediˇtor after".into(),
12320            buffer_marked_text: "before <edi|tor> after".into(),
12321            completion_label: "editor",
12322            completion_text: "editor()",
12323            expected_with_insert_mode: "before editor()ˇtor after".into(),
12324            expected_with_replace_mode: "before editor()ˇ after".into(),
12325            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
12326            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
12327        },
12328        Run {
12329            run_description: "Case insensitive subsequence and suffix matching",
12330            initial_state: "before EDiˇtoR after".into(),
12331            buffer_marked_text: "before <EDi|toR> after".into(),
12332            completion_label: "editor",
12333            completion_text: "editor",
12334            expected_with_insert_mode: "before editorˇtoR after".into(),
12335            expected_with_replace_mode: "before editorˇ after".into(),
12336            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12337            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12338        },
12339    ];
12340
12341    for run in runs {
12342        let run_variations = [
12343            (LspInsertMode::Insert, run.expected_with_insert_mode),
12344            (LspInsertMode::Replace, run.expected_with_replace_mode),
12345            (
12346                LspInsertMode::ReplaceSubsequence,
12347                run.expected_with_replace_subsequence_mode,
12348            ),
12349            (
12350                LspInsertMode::ReplaceSuffix,
12351                run.expected_with_replace_suffix_mode,
12352            ),
12353        ];
12354
12355        for (lsp_insert_mode, expected_text) in run_variations {
12356            eprintln!(
12357                "run = {:?}, mode = {lsp_insert_mode:.?}",
12358                run.run_description,
12359            );
12360
12361            update_test_language_settings(&mut cx, |settings| {
12362                settings.defaults.completions = Some(CompletionSettings {
12363                    lsp_insert_mode,
12364                    words: WordsCompletionMode::Disabled,
12365                    words_min_length: 0,
12366                    lsp: true,
12367                    lsp_fetch_timeout_ms: 0,
12368                });
12369            });
12370
12371            cx.set_state(&run.initial_state);
12372            cx.update_editor(|editor, window, cx| {
12373                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12374            });
12375
12376            let counter = Arc::new(AtomicUsize::new(0));
12377            handle_completion_request_with_insert_and_replace(
12378                &mut cx,
12379                &run.buffer_marked_text,
12380                vec![(run.completion_label, run.completion_text)],
12381                counter.clone(),
12382            )
12383            .await;
12384            cx.condition(|editor, _| editor.context_menu_visible())
12385                .await;
12386            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12387
12388            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12389                editor
12390                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
12391                    .unwrap()
12392            });
12393            cx.assert_editor_state(&expected_text);
12394            handle_resolve_completion_request(&mut cx, None).await;
12395            apply_additional_edits.await.unwrap();
12396        }
12397    }
12398}
12399
12400#[gpui::test]
12401async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
12402    init_test(cx, |_| {});
12403    let mut cx = EditorLspTestContext::new_rust(
12404        lsp::ServerCapabilities {
12405            completion_provider: Some(lsp::CompletionOptions {
12406                resolve_provider: Some(true),
12407                ..Default::default()
12408            }),
12409            ..Default::default()
12410        },
12411        cx,
12412    )
12413    .await;
12414
12415    let initial_state = "SubˇError";
12416    let buffer_marked_text = "<Sub|Error>";
12417    let completion_text = "SubscriptionError";
12418    let expected_with_insert_mode = "SubscriptionErrorˇError";
12419    let expected_with_replace_mode = "SubscriptionErrorˇ";
12420
12421    update_test_language_settings(&mut cx, |settings| {
12422        settings.defaults.completions = Some(CompletionSettings {
12423            words: WordsCompletionMode::Disabled,
12424            words_min_length: 0,
12425            // set the opposite here to ensure that the action is overriding the default behavior
12426            lsp_insert_mode: LspInsertMode::Insert,
12427            lsp: true,
12428            lsp_fetch_timeout_ms: 0,
12429        });
12430    });
12431
12432    cx.set_state(initial_state);
12433    cx.update_editor(|editor, window, cx| {
12434        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12435    });
12436
12437    let counter = Arc::new(AtomicUsize::new(0));
12438    handle_completion_request_with_insert_and_replace(
12439        &mut cx,
12440        buffer_marked_text,
12441        vec![(completion_text, completion_text)],
12442        counter.clone(),
12443    )
12444    .await;
12445    cx.condition(|editor, _| editor.context_menu_visible())
12446        .await;
12447    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12448
12449    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12450        editor
12451            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12452            .unwrap()
12453    });
12454    cx.assert_editor_state(expected_with_replace_mode);
12455    handle_resolve_completion_request(&mut cx, None).await;
12456    apply_additional_edits.await.unwrap();
12457
12458    update_test_language_settings(&mut cx, |settings| {
12459        settings.defaults.completions = Some(CompletionSettings {
12460            words: WordsCompletionMode::Disabled,
12461            words_min_length: 0,
12462            // set the opposite here to ensure that the action is overriding the default behavior
12463            lsp_insert_mode: LspInsertMode::Replace,
12464            lsp: true,
12465            lsp_fetch_timeout_ms: 0,
12466        });
12467    });
12468
12469    cx.set_state(initial_state);
12470    cx.update_editor(|editor, window, cx| {
12471        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12472    });
12473    handle_completion_request_with_insert_and_replace(
12474        &mut cx,
12475        buffer_marked_text,
12476        vec![(completion_text, completion_text)],
12477        counter.clone(),
12478    )
12479    .await;
12480    cx.condition(|editor, _| editor.context_menu_visible())
12481        .await;
12482    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12483
12484    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12485        editor
12486            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12487            .unwrap()
12488    });
12489    cx.assert_editor_state(expected_with_insert_mode);
12490    handle_resolve_completion_request(&mut cx, None).await;
12491    apply_additional_edits.await.unwrap();
12492}
12493
12494#[gpui::test]
12495async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12496    init_test(cx, |_| {});
12497    let mut cx = EditorLspTestContext::new_rust(
12498        lsp::ServerCapabilities {
12499            completion_provider: Some(lsp::CompletionOptions {
12500                resolve_provider: Some(true),
12501                ..Default::default()
12502            }),
12503            ..Default::default()
12504        },
12505        cx,
12506    )
12507    .await;
12508
12509    // scenario: surrounding text matches completion text
12510    let completion_text = "to_offset";
12511    let initial_state = indoc! {"
12512        1. buf.to_offˇsuffix
12513        2. buf.to_offˇsuf
12514        3. buf.to_offˇfix
12515        4. buf.to_offˇ
12516        5. into_offˇensive
12517        6. ˇsuffix
12518        7. let ˇ //
12519        8. aaˇzz
12520        9. buf.to_off«zzzzzˇ»suffix
12521        10. buf.«ˇzzzzz»suffix
12522        11. to_off«ˇzzzzz»
12523
12524        buf.to_offˇsuffix  // newest cursor
12525    "};
12526    let completion_marked_buffer = indoc! {"
12527        1. buf.to_offsuffix
12528        2. buf.to_offsuf
12529        3. buf.to_offfix
12530        4. buf.to_off
12531        5. into_offensive
12532        6. suffix
12533        7. let  //
12534        8. aazz
12535        9. buf.to_offzzzzzsuffix
12536        10. buf.zzzzzsuffix
12537        11. to_offzzzzz
12538
12539        buf.<to_off|suffix>  // newest cursor
12540    "};
12541    let expected = indoc! {"
12542        1. buf.to_offsetˇ
12543        2. buf.to_offsetˇsuf
12544        3. buf.to_offsetˇfix
12545        4. buf.to_offsetˇ
12546        5. into_offsetˇensive
12547        6. to_offsetˇsuffix
12548        7. let to_offsetˇ //
12549        8. aato_offsetˇzz
12550        9. buf.to_offsetˇ
12551        10. buf.to_offsetˇsuffix
12552        11. to_offsetˇ
12553
12554        buf.to_offsetˇ  // newest cursor
12555    "};
12556    cx.set_state(initial_state);
12557    cx.update_editor(|editor, window, cx| {
12558        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12559    });
12560    handle_completion_request_with_insert_and_replace(
12561        &mut cx,
12562        completion_marked_buffer,
12563        vec![(completion_text, completion_text)],
12564        Arc::new(AtomicUsize::new(0)),
12565    )
12566    .await;
12567    cx.condition(|editor, _| editor.context_menu_visible())
12568        .await;
12569    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12570        editor
12571            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12572            .unwrap()
12573    });
12574    cx.assert_editor_state(expected);
12575    handle_resolve_completion_request(&mut cx, None).await;
12576    apply_additional_edits.await.unwrap();
12577
12578    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12579    let completion_text = "foo_and_bar";
12580    let initial_state = indoc! {"
12581        1. ooanbˇ
12582        2. zooanbˇ
12583        3. ooanbˇz
12584        4. zooanbˇz
12585        5. ooanˇ
12586        6. oanbˇ
12587
12588        ooanbˇ
12589    "};
12590    let completion_marked_buffer = indoc! {"
12591        1. ooanb
12592        2. zooanb
12593        3. ooanbz
12594        4. zooanbz
12595        5. ooan
12596        6. oanb
12597
12598        <ooanb|>
12599    "};
12600    let expected = indoc! {"
12601        1. foo_and_barˇ
12602        2. zfoo_and_barˇ
12603        3. foo_and_barˇz
12604        4. zfoo_and_barˇz
12605        5. ooanfoo_and_barˇ
12606        6. oanbfoo_and_barˇ
12607
12608        foo_and_barˇ
12609    "};
12610    cx.set_state(initial_state);
12611    cx.update_editor(|editor, window, cx| {
12612        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12613    });
12614    handle_completion_request_with_insert_and_replace(
12615        &mut cx,
12616        completion_marked_buffer,
12617        vec![(completion_text, completion_text)],
12618        Arc::new(AtomicUsize::new(0)),
12619    )
12620    .await;
12621    cx.condition(|editor, _| editor.context_menu_visible())
12622        .await;
12623    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12624        editor
12625            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12626            .unwrap()
12627    });
12628    cx.assert_editor_state(expected);
12629    handle_resolve_completion_request(&mut cx, None).await;
12630    apply_additional_edits.await.unwrap();
12631
12632    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
12633    // (expects the same as if it was inserted at the end)
12634    let completion_text = "foo_and_bar";
12635    let initial_state = indoc! {"
12636        1. ooˇanb
12637        2. zooˇanb
12638        3. ooˇanbz
12639        4. zooˇanbz
12640
12641        ooˇanb
12642    "};
12643    let completion_marked_buffer = indoc! {"
12644        1. ooanb
12645        2. zooanb
12646        3. ooanbz
12647        4. zooanbz
12648
12649        <oo|anb>
12650    "};
12651    let expected = indoc! {"
12652        1. foo_and_barˇ
12653        2. zfoo_and_barˇ
12654        3. foo_and_barˇz
12655        4. zfoo_and_barˇz
12656
12657        foo_and_barˇ
12658    "};
12659    cx.set_state(initial_state);
12660    cx.update_editor(|editor, window, cx| {
12661        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12662    });
12663    handle_completion_request_with_insert_and_replace(
12664        &mut cx,
12665        completion_marked_buffer,
12666        vec![(completion_text, completion_text)],
12667        Arc::new(AtomicUsize::new(0)),
12668    )
12669    .await;
12670    cx.condition(|editor, _| editor.context_menu_visible())
12671        .await;
12672    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12673        editor
12674            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12675            .unwrap()
12676    });
12677    cx.assert_editor_state(expected);
12678    handle_resolve_completion_request(&mut cx, None).await;
12679    apply_additional_edits.await.unwrap();
12680}
12681
12682// This used to crash
12683#[gpui::test]
12684async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
12685    init_test(cx, |_| {});
12686
12687    let buffer_text = indoc! {"
12688        fn main() {
12689            10.satu;
12690
12691            //
12692            // separate cursors so they open in different excerpts (manually reproducible)
12693            //
12694
12695            10.satu20;
12696        }
12697    "};
12698    let multibuffer_text_with_selections = indoc! {"
12699        fn main() {
12700            10.satuˇ;
12701
12702            //
12703
12704            //
12705
12706            10.satuˇ20;
12707        }
12708    "};
12709    let expected_multibuffer = indoc! {"
12710        fn main() {
12711            10.saturating_sub()ˇ;
12712
12713            //
12714
12715            //
12716
12717            10.saturating_sub()ˇ;
12718        }
12719    "};
12720
12721    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
12722    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
12723
12724    let fs = FakeFs::new(cx.executor());
12725    fs.insert_tree(
12726        path!("/a"),
12727        json!({
12728            "main.rs": buffer_text,
12729        }),
12730    )
12731    .await;
12732
12733    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12734    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12735    language_registry.add(rust_lang());
12736    let mut fake_servers = language_registry.register_fake_lsp(
12737        "Rust",
12738        FakeLspAdapter {
12739            capabilities: lsp::ServerCapabilities {
12740                completion_provider: Some(lsp::CompletionOptions {
12741                    resolve_provider: None,
12742                    ..lsp::CompletionOptions::default()
12743                }),
12744                ..lsp::ServerCapabilities::default()
12745            },
12746            ..FakeLspAdapter::default()
12747        },
12748    );
12749    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12750    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12751    let buffer = project
12752        .update(cx, |project, cx| {
12753            project.open_local_buffer(path!("/a/main.rs"), cx)
12754        })
12755        .await
12756        .unwrap();
12757
12758    let multi_buffer = cx.new(|cx| {
12759        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
12760        multi_buffer.push_excerpts(
12761            buffer.clone(),
12762            [ExcerptRange::new(0..first_excerpt_end)],
12763            cx,
12764        );
12765        multi_buffer.push_excerpts(
12766            buffer.clone(),
12767            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
12768            cx,
12769        );
12770        multi_buffer
12771    });
12772
12773    let editor = workspace
12774        .update(cx, |_, window, cx| {
12775            cx.new(|cx| {
12776                Editor::new(
12777                    EditorMode::Full {
12778                        scale_ui_elements_with_buffer_font_size: false,
12779                        show_active_line_background: false,
12780                        sized_by_content: false,
12781                    },
12782                    multi_buffer.clone(),
12783                    Some(project.clone()),
12784                    window,
12785                    cx,
12786                )
12787            })
12788        })
12789        .unwrap();
12790
12791    let pane = workspace
12792        .update(cx, |workspace, _, _| workspace.active_pane().clone())
12793        .unwrap();
12794    pane.update_in(cx, |pane, window, cx| {
12795        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
12796    });
12797
12798    let fake_server = fake_servers.next().await.unwrap();
12799
12800    editor.update_in(cx, |editor, window, cx| {
12801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12802            s.select_ranges([
12803                Point::new(1, 11)..Point::new(1, 11),
12804                Point::new(7, 11)..Point::new(7, 11),
12805            ])
12806        });
12807
12808        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
12809    });
12810
12811    editor.update_in(cx, |editor, window, cx| {
12812        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12813    });
12814
12815    fake_server
12816        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
12817            let completion_item = lsp::CompletionItem {
12818                label: "saturating_sub()".into(),
12819                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12820                    lsp::InsertReplaceEdit {
12821                        new_text: "saturating_sub()".to_owned(),
12822                        insert: lsp::Range::new(
12823                            lsp::Position::new(7, 7),
12824                            lsp::Position::new(7, 11),
12825                        ),
12826                        replace: lsp::Range::new(
12827                            lsp::Position::new(7, 7),
12828                            lsp::Position::new(7, 13),
12829                        ),
12830                    },
12831                )),
12832                ..lsp::CompletionItem::default()
12833            };
12834
12835            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
12836        })
12837        .next()
12838        .await
12839        .unwrap();
12840
12841    cx.condition(&editor, |editor, _| editor.context_menu_visible())
12842        .await;
12843
12844    editor
12845        .update_in(cx, |editor, window, cx| {
12846            editor
12847                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12848                .unwrap()
12849        })
12850        .await
12851        .unwrap();
12852
12853    editor.update(cx, |editor, cx| {
12854        assert_text_with_selections(editor, expected_multibuffer, cx);
12855    })
12856}
12857
12858#[gpui::test]
12859async fn test_completion(cx: &mut TestAppContext) {
12860    init_test(cx, |_| {});
12861
12862    let mut cx = EditorLspTestContext::new_rust(
12863        lsp::ServerCapabilities {
12864            completion_provider: Some(lsp::CompletionOptions {
12865                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
12866                resolve_provider: Some(true),
12867                ..Default::default()
12868            }),
12869            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
12870            ..Default::default()
12871        },
12872        cx,
12873    )
12874    .await;
12875    let counter = Arc::new(AtomicUsize::new(0));
12876
12877    cx.set_state(indoc! {"
12878        oneˇ
12879        two
12880        three
12881    "});
12882    cx.simulate_keystroke(".");
12883    handle_completion_request(
12884        indoc! {"
12885            one.|<>
12886            two
12887            three
12888        "},
12889        vec!["first_completion", "second_completion"],
12890        true,
12891        counter.clone(),
12892        &mut cx,
12893    )
12894    .await;
12895    cx.condition(|editor, _| editor.context_menu_visible())
12896        .await;
12897    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12898
12899    let _handler = handle_signature_help_request(
12900        &mut cx,
12901        lsp::SignatureHelp {
12902            signatures: vec![lsp::SignatureInformation {
12903                label: "test signature".to_string(),
12904                documentation: None,
12905                parameters: Some(vec![lsp::ParameterInformation {
12906                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
12907                    documentation: None,
12908                }]),
12909                active_parameter: None,
12910            }],
12911            active_signature: None,
12912            active_parameter: None,
12913        },
12914    );
12915    cx.update_editor(|editor, window, cx| {
12916        assert!(
12917            !editor.signature_help_state.is_shown(),
12918            "No signature help was called for"
12919        );
12920        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12921    });
12922    cx.run_until_parked();
12923    cx.update_editor(|editor, _, _| {
12924        assert!(
12925            !editor.signature_help_state.is_shown(),
12926            "No signature help should be shown when completions menu is open"
12927        );
12928    });
12929
12930    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12931        editor.context_menu_next(&Default::default(), window, cx);
12932        editor
12933            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12934            .unwrap()
12935    });
12936    cx.assert_editor_state(indoc! {"
12937        one.second_completionˇ
12938        two
12939        three
12940    "});
12941
12942    handle_resolve_completion_request(
12943        &mut cx,
12944        Some(vec![
12945            (
12946                //This overlaps with the primary completion edit which is
12947                //misbehavior from the LSP spec, test that we filter it out
12948                indoc! {"
12949                    one.second_ˇcompletion
12950                    two
12951                    threeˇ
12952                "},
12953                "overlapping additional edit",
12954            ),
12955            (
12956                indoc! {"
12957                    one.second_completion
12958                    two
12959                    threeˇ
12960                "},
12961                "\nadditional edit",
12962            ),
12963        ]),
12964    )
12965    .await;
12966    apply_additional_edits.await.unwrap();
12967    cx.assert_editor_state(indoc! {"
12968        one.second_completionˇ
12969        two
12970        three
12971        additional edit
12972    "});
12973
12974    cx.set_state(indoc! {"
12975        one.second_completion
12976        twoˇ
12977        threeˇ
12978        additional edit
12979    "});
12980    cx.simulate_keystroke(" ");
12981    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12982    cx.simulate_keystroke("s");
12983    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
12984
12985    cx.assert_editor_state(indoc! {"
12986        one.second_completion
12987        two sˇ
12988        three sˇ
12989        additional edit
12990    "});
12991    handle_completion_request(
12992        indoc! {"
12993            one.second_completion
12994            two s
12995            three <s|>
12996            additional edit
12997        "},
12998        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
12999        true,
13000        counter.clone(),
13001        &mut cx,
13002    )
13003    .await;
13004    cx.condition(|editor, _| editor.context_menu_visible())
13005        .await;
13006    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13007
13008    cx.simulate_keystroke("i");
13009
13010    handle_completion_request(
13011        indoc! {"
13012            one.second_completion
13013            two si
13014            three <si|>
13015            additional edit
13016        "},
13017        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13018        true,
13019        counter.clone(),
13020        &mut cx,
13021    )
13022    .await;
13023    cx.condition(|editor, _| editor.context_menu_visible())
13024        .await;
13025    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13026
13027    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13028        editor
13029            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13030            .unwrap()
13031    });
13032    cx.assert_editor_state(indoc! {"
13033        one.second_completion
13034        two sixth_completionˇ
13035        three sixth_completionˇ
13036        additional edit
13037    "});
13038
13039    apply_additional_edits.await.unwrap();
13040
13041    update_test_language_settings(&mut cx, |settings| {
13042        settings.defaults.show_completions_on_input = Some(false);
13043    });
13044    cx.set_state("editorˇ");
13045    cx.simulate_keystroke(".");
13046    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13047    cx.simulate_keystrokes("c l o");
13048    cx.assert_editor_state("editor.cloˇ");
13049    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13050    cx.update_editor(|editor, window, cx| {
13051        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13052    });
13053    handle_completion_request(
13054        "editor.<clo|>",
13055        vec!["close", "clobber"],
13056        true,
13057        counter.clone(),
13058        &mut cx,
13059    )
13060    .await;
13061    cx.condition(|editor, _| editor.context_menu_visible())
13062        .await;
13063    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
13064
13065    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13066        editor
13067            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13068            .unwrap()
13069    });
13070    cx.assert_editor_state("editor.clobberˇ");
13071    handle_resolve_completion_request(&mut cx, None).await;
13072    apply_additional_edits.await.unwrap();
13073}
13074
13075#[gpui::test]
13076async fn test_completion_reuse(cx: &mut TestAppContext) {
13077    init_test(cx, |_| {});
13078
13079    let mut cx = EditorLspTestContext::new_rust(
13080        lsp::ServerCapabilities {
13081            completion_provider: Some(lsp::CompletionOptions {
13082                trigger_characters: Some(vec![".".to_string()]),
13083                ..Default::default()
13084            }),
13085            ..Default::default()
13086        },
13087        cx,
13088    )
13089    .await;
13090
13091    let counter = Arc::new(AtomicUsize::new(0));
13092    cx.set_state("objˇ");
13093    cx.simulate_keystroke(".");
13094
13095    // Initial completion request returns complete results
13096    let is_incomplete = false;
13097    handle_completion_request(
13098        "obj.|<>",
13099        vec!["a", "ab", "abc"],
13100        is_incomplete,
13101        counter.clone(),
13102        &mut cx,
13103    )
13104    .await;
13105    cx.run_until_parked();
13106    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13107    cx.assert_editor_state("obj.ˇ");
13108    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13109
13110    // Type "a" - filters existing completions
13111    cx.simulate_keystroke("a");
13112    cx.run_until_parked();
13113    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13114    cx.assert_editor_state("obj.aˇ");
13115    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13116
13117    // Type "b" - filters existing completions
13118    cx.simulate_keystroke("b");
13119    cx.run_until_parked();
13120    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13121    cx.assert_editor_state("obj.abˇ");
13122    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13123
13124    // Type "c" - filters existing completions
13125    cx.simulate_keystroke("c");
13126    cx.run_until_parked();
13127    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13128    cx.assert_editor_state("obj.abcˇ");
13129    check_displayed_completions(vec!["abc"], &mut cx);
13130
13131    // Backspace to delete "c" - filters existing completions
13132    cx.update_editor(|editor, window, cx| {
13133        editor.backspace(&Backspace, window, cx);
13134    });
13135    cx.run_until_parked();
13136    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13137    cx.assert_editor_state("obj.abˇ");
13138    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13139
13140    // Moving cursor to the left dismisses menu.
13141    cx.update_editor(|editor, window, cx| {
13142        editor.move_left(&MoveLeft, window, cx);
13143    });
13144    cx.run_until_parked();
13145    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13146    cx.assert_editor_state("obj.aˇb");
13147    cx.update_editor(|editor, _, _| {
13148        assert_eq!(editor.context_menu_visible(), false);
13149    });
13150
13151    // Type "b" - new request
13152    cx.simulate_keystroke("b");
13153    let is_incomplete = false;
13154    handle_completion_request(
13155        "obj.<ab|>a",
13156        vec!["ab", "abc"],
13157        is_incomplete,
13158        counter.clone(),
13159        &mut cx,
13160    )
13161    .await;
13162    cx.run_until_parked();
13163    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13164    cx.assert_editor_state("obj.abˇb");
13165    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13166
13167    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
13168    cx.update_editor(|editor, window, cx| {
13169        editor.backspace(&Backspace, window, cx);
13170    });
13171    let is_incomplete = false;
13172    handle_completion_request(
13173        "obj.<a|>b",
13174        vec!["a", "ab", "abc"],
13175        is_incomplete,
13176        counter.clone(),
13177        &mut cx,
13178    )
13179    .await;
13180    cx.run_until_parked();
13181    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13182    cx.assert_editor_state("obj.aˇb");
13183    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13184
13185    // Backspace to delete "a" - dismisses menu.
13186    cx.update_editor(|editor, window, cx| {
13187        editor.backspace(&Backspace, window, cx);
13188    });
13189    cx.run_until_parked();
13190    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13191    cx.assert_editor_state("obj.ˇb");
13192    cx.update_editor(|editor, _, _| {
13193        assert_eq!(editor.context_menu_visible(), false);
13194    });
13195}
13196
13197#[gpui::test]
13198async fn test_word_completion(cx: &mut TestAppContext) {
13199    let lsp_fetch_timeout_ms = 10;
13200    init_test(cx, |language_settings| {
13201        language_settings.defaults.completions = Some(CompletionSettings {
13202            words: WordsCompletionMode::Fallback,
13203            words_min_length: 0,
13204            lsp: true,
13205            lsp_fetch_timeout_ms: 10,
13206            lsp_insert_mode: LspInsertMode::Insert,
13207        });
13208    });
13209
13210    let mut cx = EditorLspTestContext::new_rust(
13211        lsp::ServerCapabilities {
13212            completion_provider: Some(lsp::CompletionOptions {
13213                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13214                ..lsp::CompletionOptions::default()
13215            }),
13216            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13217            ..lsp::ServerCapabilities::default()
13218        },
13219        cx,
13220    )
13221    .await;
13222
13223    let throttle_completions = Arc::new(AtomicBool::new(false));
13224
13225    let lsp_throttle_completions = throttle_completions.clone();
13226    let _completion_requests_handler =
13227        cx.lsp
13228            .server
13229            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
13230                let lsp_throttle_completions = lsp_throttle_completions.clone();
13231                let cx = cx.clone();
13232                async move {
13233                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
13234                        cx.background_executor()
13235                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
13236                            .await;
13237                    }
13238                    Ok(Some(lsp::CompletionResponse::Array(vec![
13239                        lsp::CompletionItem {
13240                            label: "first".into(),
13241                            ..lsp::CompletionItem::default()
13242                        },
13243                        lsp::CompletionItem {
13244                            label: "last".into(),
13245                            ..lsp::CompletionItem::default()
13246                        },
13247                    ])))
13248                }
13249            });
13250
13251    cx.set_state(indoc! {"
13252        oneˇ
13253        two
13254        three
13255    "});
13256    cx.simulate_keystroke(".");
13257    cx.executor().run_until_parked();
13258    cx.condition(|editor, _| editor.context_menu_visible())
13259        .await;
13260    cx.update_editor(|editor, window, cx| {
13261        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13262        {
13263            assert_eq!(
13264                completion_menu_entries(menu),
13265                &["first", "last"],
13266                "When LSP server is fast to reply, no fallback word completions are used"
13267            );
13268        } else {
13269            panic!("expected completion menu to be open");
13270        }
13271        editor.cancel(&Cancel, window, cx);
13272    });
13273    cx.executor().run_until_parked();
13274    cx.condition(|editor, _| !editor.context_menu_visible())
13275        .await;
13276
13277    throttle_completions.store(true, atomic::Ordering::Release);
13278    cx.simulate_keystroke(".");
13279    cx.executor()
13280        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
13281    cx.executor().run_until_parked();
13282    cx.condition(|editor, _| editor.context_menu_visible())
13283        .await;
13284    cx.update_editor(|editor, _, _| {
13285        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13286        {
13287            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
13288                "When LSP server is slow, document words can be shown instead, if configured accordingly");
13289        } else {
13290            panic!("expected completion menu to be open");
13291        }
13292    });
13293}
13294
13295#[gpui::test]
13296async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
13297    init_test(cx, |language_settings| {
13298        language_settings.defaults.completions = Some(CompletionSettings {
13299            words: WordsCompletionMode::Enabled,
13300            words_min_length: 0,
13301            lsp: true,
13302            lsp_fetch_timeout_ms: 0,
13303            lsp_insert_mode: LspInsertMode::Insert,
13304        });
13305    });
13306
13307    let mut cx = EditorLspTestContext::new_rust(
13308        lsp::ServerCapabilities {
13309            completion_provider: Some(lsp::CompletionOptions {
13310                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13311                ..lsp::CompletionOptions::default()
13312            }),
13313            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13314            ..lsp::ServerCapabilities::default()
13315        },
13316        cx,
13317    )
13318    .await;
13319
13320    let _completion_requests_handler =
13321        cx.lsp
13322            .server
13323            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13324                Ok(Some(lsp::CompletionResponse::Array(vec![
13325                    lsp::CompletionItem {
13326                        label: "first".into(),
13327                        ..lsp::CompletionItem::default()
13328                    },
13329                    lsp::CompletionItem {
13330                        label: "last".into(),
13331                        ..lsp::CompletionItem::default()
13332                    },
13333                ])))
13334            });
13335
13336    cx.set_state(indoc! {"ˇ
13337        first
13338        last
13339        second
13340    "});
13341    cx.simulate_keystroke(".");
13342    cx.executor().run_until_parked();
13343    cx.condition(|editor, _| editor.context_menu_visible())
13344        .await;
13345    cx.update_editor(|editor, _, _| {
13346        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13347        {
13348            assert_eq!(
13349                completion_menu_entries(menu),
13350                &["first", "last", "second"],
13351                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
13352            );
13353        } else {
13354            panic!("expected completion menu to be open");
13355        }
13356    });
13357}
13358
13359#[gpui::test]
13360async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
13361    init_test(cx, |language_settings| {
13362        language_settings.defaults.completions = Some(CompletionSettings {
13363            words: WordsCompletionMode::Disabled,
13364            words_min_length: 0,
13365            lsp: true,
13366            lsp_fetch_timeout_ms: 0,
13367            lsp_insert_mode: LspInsertMode::Insert,
13368        });
13369    });
13370
13371    let mut cx = EditorLspTestContext::new_rust(
13372        lsp::ServerCapabilities {
13373            completion_provider: Some(lsp::CompletionOptions {
13374                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13375                ..lsp::CompletionOptions::default()
13376            }),
13377            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13378            ..lsp::ServerCapabilities::default()
13379        },
13380        cx,
13381    )
13382    .await;
13383
13384    let _completion_requests_handler =
13385        cx.lsp
13386            .server
13387            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13388                panic!("LSP completions should not be queried when dealing with word completions")
13389            });
13390
13391    cx.set_state(indoc! {"ˇ
13392        first
13393        last
13394        second
13395    "});
13396    cx.update_editor(|editor, window, cx| {
13397        editor.show_word_completions(&ShowWordCompletions, window, cx);
13398    });
13399    cx.executor().run_until_parked();
13400    cx.condition(|editor, _| editor.context_menu_visible())
13401        .await;
13402    cx.update_editor(|editor, _, _| {
13403        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13404        {
13405            assert_eq!(
13406                completion_menu_entries(menu),
13407                &["first", "last", "second"],
13408                "`ShowWordCompletions` action should show word completions"
13409            );
13410        } else {
13411            panic!("expected completion menu to be open");
13412        }
13413    });
13414
13415    cx.simulate_keystroke("l");
13416    cx.executor().run_until_parked();
13417    cx.condition(|editor, _| editor.context_menu_visible())
13418        .await;
13419    cx.update_editor(|editor, _, _| {
13420        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13421        {
13422            assert_eq!(
13423                completion_menu_entries(menu),
13424                &["last"],
13425                "After showing word completions, further editing should filter them and not query the LSP"
13426            );
13427        } else {
13428            panic!("expected completion menu to be open");
13429        }
13430    });
13431}
13432
13433#[gpui::test]
13434async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
13435    init_test(cx, |language_settings| {
13436        language_settings.defaults.completions = Some(CompletionSettings {
13437            words: WordsCompletionMode::Fallback,
13438            words_min_length: 0,
13439            lsp: false,
13440            lsp_fetch_timeout_ms: 0,
13441            lsp_insert_mode: LspInsertMode::Insert,
13442        });
13443    });
13444
13445    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13446
13447    cx.set_state(indoc! {"ˇ
13448        0_usize
13449        let
13450        33
13451        4.5f32
13452    "});
13453    cx.update_editor(|editor, window, cx| {
13454        editor.show_completions(&ShowCompletions::default(), window, cx);
13455    });
13456    cx.executor().run_until_parked();
13457    cx.condition(|editor, _| editor.context_menu_visible())
13458        .await;
13459    cx.update_editor(|editor, window, cx| {
13460        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13461        {
13462            assert_eq!(
13463                completion_menu_entries(menu),
13464                &["let"],
13465                "With no digits in the completion query, no digits should be in the word completions"
13466            );
13467        } else {
13468            panic!("expected completion menu to be open");
13469        }
13470        editor.cancel(&Cancel, window, cx);
13471    });
13472
13473    cx.set_state(indoc! {"13474        0_usize
13475        let
13476        3
13477        33.35f32
13478    "});
13479    cx.update_editor(|editor, window, cx| {
13480        editor.show_completions(&ShowCompletions::default(), window, cx);
13481    });
13482    cx.executor().run_until_parked();
13483    cx.condition(|editor, _| editor.context_menu_visible())
13484        .await;
13485    cx.update_editor(|editor, _, _| {
13486        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13487        {
13488            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
13489                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13490        } else {
13491            panic!("expected completion menu to be open");
13492        }
13493    });
13494}
13495
13496#[gpui::test]
13497async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
13498    init_test(cx, |language_settings| {
13499        language_settings.defaults.completions = Some(CompletionSettings {
13500            words: WordsCompletionMode::Enabled,
13501            words_min_length: 3,
13502            lsp: true,
13503            lsp_fetch_timeout_ms: 0,
13504            lsp_insert_mode: LspInsertMode::Insert,
13505        });
13506    });
13507
13508    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13509    cx.set_state(indoc! {"ˇ
13510        wow
13511        wowen
13512        wowser
13513    "});
13514    cx.simulate_keystroke("w");
13515    cx.executor().run_until_parked();
13516    cx.update_editor(|editor, _, _| {
13517        if editor.context_menu.borrow_mut().is_some() {
13518            panic!(
13519                "expected completion menu to be hidden, as words completion threshold is not met"
13520            );
13521        }
13522    });
13523
13524    cx.simulate_keystroke("o");
13525    cx.executor().run_until_parked();
13526    cx.update_editor(|editor, _, _| {
13527        if editor.context_menu.borrow_mut().is_some() {
13528            panic!(
13529                "expected completion menu to be hidden, as words completion threshold is not met still"
13530            );
13531        }
13532    });
13533
13534    cx.simulate_keystroke("w");
13535    cx.executor().run_until_parked();
13536    cx.update_editor(|editor, _, _| {
13537        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13538        {
13539            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
13540        } else {
13541            panic!("expected completion menu to be open after the word completions threshold is met");
13542        }
13543    });
13544}
13545
13546fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13547    let position = || lsp::Position {
13548        line: params.text_document_position.position.line,
13549        character: params.text_document_position.position.character,
13550    };
13551    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13552        range: lsp::Range {
13553            start: position(),
13554            end: position(),
13555        },
13556        new_text: text.to_string(),
13557    }))
13558}
13559
13560#[gpui::test]
13561async fn test_multiline_completion(cx: &mut TestAppContext) {
13562    init_test(cx, |_| {});
13563
13564    let fs = FakeFs::new(cx.executor());
13565    fs.insert_tree(
13566        path!("/a"),
13567        json!({
13568            "main.ts": "a",
13569        }),
13570    )
13571    .await;
13572
13573    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13574    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13575    let typescript_language = Arc::new(Language::new(
13576        LanguageConfig {
13577            name: "TypeScript".into(),
13578            matcher: LanguageMatcher {
13579                path_suffixes: vec!["ts".to_string()],
13580                ..LanguageMatcher::default()
13581            },
13582            line_comments: vec!["// ".into()],
13583            ..LanguageConfig::default()
13584        },
13585        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13586    ));
13587    language_registry.add(typescript_language.clone());
13588    let mut fake_servers = language_registry.register_fake_lsp(
13589        "TypeScript",
13590        FakeLspAdapter {
13591            capabilities: lsp::ServerCapabilities {
13592                completion_provider: Some(lsp::CompletionOptions {
13593                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13594                    ..lsp::CompletionOptions::default()
13595                }),
13596                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13597                ..lsp::ServerCapabilities::default()
13598            },
13599            // Emulate vtsls label generation
13600            label_for_completion: Some(Box::new(|item, _| {
13601                let text = if let Some(description) = item
13602                    .label_details
13603                    .as_ref()
13604                    .and_then(|label_details| label_details.description.as_ref())
13605                {
13606                    format!("{} {}", item.label, description)
13607                } else if let Some(detail) = &item.detail {
13608                    format!("{} {}", item.label, detail)
13609                } else {
13610                    item.label.clone()
13611                };
13612                let len = text.len();
13613                Some(language::CodeLabel {
13614                    text,
13615                    runs: Vec::new(),
13616                    filter_range: 0..len,
13617                })
13618            })),
13619            ..FakeLspAdapter::default()
13620        },
13621    );
13622    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13623    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13624    let worktree_id = workspace
13625        .update(cx, |workspace, _window, cx| {
13626            workspace.project().update(cx, |project, cx| {
13627                project.worktrees(cx).next().unwrap().read(cx).id()
13628            })
13629        })
13630        .unwrap();
13631    let _buffer = project
13632        .update(cx, |project, cx| {
13633            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
13634        })
13635        .await
13636        .unwrap();
13637    let editor = workspace
13638        .update(cx, |workspace, window, cx| {
13639            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
13640        })
13641        .unwrap()
13642        .await
13643        .unwrap()
13644        .downcast::<Editor>()
13645        .unwrap();
13646    let fake_server = fake_servers.next().await.unwrap();
13647
13648    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
13649    let multiline_label_2 = "a\nb\nc\n";
13650    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
13651    let multiline_description = "d\ne\nf\n";
13652    let multiline_detail_2 = "g\nh\ni\n";
13653
13654    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
13655        move |params, _| async move {
13656            Ok(Some(lsp::CompletionResponse::Array(vec![
13657                lsp::CompletionItem {
13658                    label: multiline_label.to_string(),
13659                    text_edit: gen_text_edit(&params, "new_text_1"),
13660                    ..lsp::CompletionItem::default()
13661                },
13662                lsp::CompletionItem {
13663                    label: "single line label 1".to_string(),
13664                    detail: Some(multiline_detail.to_string()),
13665                    text_edit: gen_text_edit(&params, "new_text_2"),
13666                    ..lsp::CompletionItem::default()
13667                },
13668                lsp::CompletionItem {
13669                    label: "single line label 2".to_string(),
13670                    label_details: Some(lsp::CompletionItemLabelDetails {
13671                        description: Some(multiline_description.to_string()),
13672                        detail: None,
13673                    }),
13674                    text_edit: gen_text_edit(&params, "new_text_2"),
13675                    ..lsp::CompletionItem::default()
13676                },
13677                lsp::CompletionItem {
13678                    label: multiline_label_2.to_string(),
13679                    detail: Some(multiline_detail_2.to_string()),
13680                    text_edit: gen_text_edit(&params, "new_text_3"),
13681                    ..lsp::CompletionItem::default()
13682                },
13683                lsp::CompletionItem {
13684                    label: "Label with many     spaces and \t but without newlines".to_string(),
13685                    detail: Some(
13686                        "Details with many     spaces and \t but without newlines".to_string(),
13687                    ),
13688                    text_edit: gen_text_edit(&params, "new_text_4"),
13689                    ..lsp::CompletionItem::default()
13690                },
13691            ])))
13692        },
13693    );
13694
13695    editor.update_in(cx, |editor, window, cx| {
13696        cx.focus_self(window);
13697        editor.move_to_end(&MoveToEnd, window, cx);
13698        editor.handle_input(".", window, cx);
13699    });
13700    cx.run_until_parked();
13701    completion_handle.next().await.unwrap();
13702
13703    editor.update(cx, |editor, _| {
13704        assert!(editor.context_menu_visible());
13705        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13706        {
13707            let completion_labels = menu
13708                .completions
13709                .borrow()
13710                .iter()
13711                .map(|c| c.label.text.clone())
13712                .collect::<Vec<_>>();
13713            assert_eq!(
13714                completion_labels,
13715                &[
13716                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
13717                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
13718                    "single line label 2 d e f ",
13719                    "a b c g h i ",
13720                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
13721                ],
13722                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
13723            );
13724
13725            for completion in menu
13726                .completions
13727                .borrow()
13728                .iter() {
13729                    assert_eq!(
13730                        completion.label.filter_range,
13731                        0..completion.label.text.len(),
13732                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
13733                    );
13734                }
13735        } else {
13736            panic!("expected completion menu to be open");
13737        }
13738    });
13739}
13740
13741#[gpui::test]
13742async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
13743    init_test(cx, |_| {});
13744    let mut cx = EditorLspTestContext::new_rust(
13745        lsp::ServerCapabilities {
13746            completion_provider: Some(lsp::CompletionOptions {
13747                trigger_characters: Some(vec![".".to_string()]),
13748                ..Default::default()
13749            }),
13750            ..Default::default()
13751        },
13752        cx,
13753    )
13754    .await;
13755    cx.lsp
13756        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13757            Ok(Some(lsp::CompletionResponse::Array(vec![
13758                lsp::CompletionItem {
13759                    label: "first".into(),
13760                    ..Default::default()
13761                },
13762                lsp::CompletionItem {
13763                    label: "last".into(),
13764                    ..Default::default()
13765                },
13766            ])))
13767        });
13768    cx.set_state("variableˇ");
13769    cx.simulate_keystroke(".");
13770    cx.executor().run_until_parked();
13771
13772    cx.update_editor(|editor, _, _| {
13773        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13774        {
13775            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
13776        } else {
13777            panic!("expected completion menu to be open");
13778        }
13779    });
13780
13781    cx.update_editor(|editor, window, cx| {
13782        editor.move_page_down(&MovePageDown::default(), window, cx);
13783        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13784        {
13785            assert!(
13786                menu.selected_item == 1,
13787                "expected PageDown to select the last item from the context menu"
13788            );
13789        } else {
13790            panic!("expected completion menu to stay open after PageDown");
13791        }
13792    });
13793
13794    cx.update_editor(|editor, window, cx| {
13795        editor.move_page_up(&MovePageUp::default(), window, cx);
13796        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13797        {
13798            assert!(
13799                menu.selected_item == 0,
13800                "expected PageUp to select the first item from the context menu"
13801            );
13802        } else {
13803            panic!("expected completion menu to stay open after PageUp");
13804        }
13805    });
13806}
13807
13808#[gpui::test]
13809async fn test_as_is_completions(cx: &mut TestAppContext) {
13810    init_test(cx, |_| {});
13811    let mut cx = EditorLspTestContext::new_rust(
13812        lsp::ServerCapabilities {
13813            completion_provider: Some(lsp::CompletionOptions {
13814                ..Default::default()
13815            }),
13816            ..Default::default()
13817        },
13818        cx,
13819    )
13820    .await;
13821    cx.lsp
13822        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13823            Ok(Some(lsp::CompletionResponse::Array(vec![
13824                lsp::CompletionItem {
13825                    label: "unsafe".into(),
13826                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13827                        range: lsp::Range {
13828                            start: lsp::Position {
13829                                line: 1,
13830                                character: 2,
13831                            },
13832                            end: lsp::Position {
13833                                line: 1,
13834                                character: 3,
13835                            },
13836                        },
13837                        new_text: "unsafe".to_string(),
13838                    })),
13839                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
13840                    ..Default::default()
13841                },
13842            ])))
13843        });
13844    cx.set_state("fn a() {}\n");
13845    cx.executor().run_until_parked();
13846    cx.update_editor(|editor, window, cx| {
13847        editor.show_completions(
13848            &ShowCompletions {
13849                trigger: Some("\n".into()),
13850            },
13851            window,
13852            cx,
13853        );
13854    });
13855    cx.executor().run_until_parked();
13856
13857    cx.update_editor(|editor, window, cx| {
13858        editor.confirm_completion(&Default::default(), window, cx)
13859    });
13860    cx.executor().run_until_parked();
13861    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
13862}
13863
13864#[gpui::test]
13865async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
13866    init_test(cx, |_| {});
13867    let language =
13868        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
13869    let mut cx = EditorLspTestContext::new(
13870        language,
13871        lsp::ServerCapabilities {
13872            completion_provider: Some(lsp::CompletionOptions {
13873                ..lsp::CompletionOptions::default()
13874            }),
13875            ..lsp::ServerCapabilities::default()
13876        },
13877        cx,
13878    )
13879    .await;
13880
13881    cx.set_state(
13882        "#ifndef BAR_H
13883#define BAR_H
13884
13885#include <stdbool.h>
13886
13887int fn_branch(bool do_branch1, bool do_branch2);
13888
13889#endif // BAR_H
13890ˇ",
13891    );
13892    cx.executor().run_until_parked();
13893    cx.update_editor(|editor, window, cx| {
13894        editor.handle_input("#", window, cx);
13895    });
13896    cx.executor().run_until_parked();
13897    cx.update_editor(|editor, window, cx| {
13898        editor.handle_input("i", window, cx);
13899    });
13900    cx.executor().run_until_parked();
13901    cx.update_editor(|editor, window, cx| {
13902        editor.handle_input("n", window, cx);
13903    });
13904    cx.executor().run_until_parked();
13905    cx.assert_editor_state(
13906        "#ifndef BAR_H
13907#define BAR_H
13908
13909#include <stdbool.h>
13910
13911int fn_branch(bool do_branch1, bool do_branch2);
13912
13913#endif // BAR_H
13914#inˇ",
13915    );
13916
13917    cx.lsp
13918        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13919            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13920                is_incomplete: false,
13921                item_defaults: None,
13922                items: vec![lsp::CompletionItem {
13923                    kind: Some(lsp::CompletionItemKind::SNIPPET),
13924                    label_details: Some(lsp::CompletionItemLabelDetails {
13925                        detail: Some("header".to_string()),
13926                        description: None,
13927                    }),
13928                    label: " include".to_string(),
13929                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13930                        range: lsp::Range {
13931                            start: lsp::Position {
13932                                line: 8,
13933                                character: 1,
13934                            },
13935                            end: lsp::Position {
13936                                line: 8,
13937                                character: 1,
13938                            },
13939                        },
13940                        new_text: "include \"$0\"".to_string(),
13941                    })),
13942                    sort_text: Some("40b67681include".to_string()),
13943                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
13944                    filter_text: Some("include".to_string()),
13945                    insert_text: Some("include \"$0\"".to_string()),
13946                    ..lsp::CompletionItem::default()
13947                }],
13948            })))
13949        });
13950    cx.update_editor(|editor, window, cx| {
13951        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13952    });
13953    cx.executor().run_until_parked();
13954    cx.update_editor(|editor, window, cx| {
13955        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
13956    });
13957    cx.executor().run_until_parked();
13958    cx.assert_editor_state(
13959        "#ifndef BAR_H
13960#define BAR_H
13961
13962#include <stdbool.h>
13963
13964int fn_branch(bool do_branch1, bool do_branch2);
13965
13966#endif // BAR_H
13967#include \"ˇ\"",
13968    );
13969
13970    cx.lsp
13971        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13972            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
13973                is_incomplete: true,
13974                item_defaults: None,
13975                items: vec![lsp::CompletionItem {
13976                    kind: Some(lsp::CompletionItemKind::FILE),
13977                    label: "AGL/".to_string(),
13978                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13979                        range: lsp::Range {
13980                            start: lsp::Position {
13981                                line: 8,
13982                                character: 10,
13983                            },
13984                            end: lsp::Position {
13985                                line: 8,
13986                                character: 11,
13987                            },
13988                        },
13989                        new_text: "AGL/".to_string(),
13990                    })),
13991                    sort_text: Some("40b67681AGL/".to_string()),
13992                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
13993                    filter_text: Some("AGL/".to_string()),
13994                    insert_text: Some("AGL/".to_string()),
13995                    ..lsp::CompletionItem::default()
13996                }],
13997            })))
13998        });
13999    cx.update_editor(|editor, window, cx| {
14000        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14001    });
14002    cx.executor().run_until_parked();
14003    cx.update_editor(|editor, window, cx| {
14004        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14005    });
14006    cx.executor().run_until_parked();
14007    cx.assert_editor_state(
14008        r##"#ifndef BAR_H
14009#define BAR_H
14010
14011#include <stdbool.h>
14012
14013int fn_branch(bool do_branch1, bool do_branch2);
14014
14015#endif // BAR_H
14016#include "AGL/ˇ"##,
14017    );
14018
14019    cx.update_editor(|editor, window, cx| {
14020        editor.handle_input("\"", window, cx);
14021    });
14022    cx.executor().run_until_parked();
14023    cx.assert_editor_state(
14024        r##"#ifndef BAR_H
14025#define BAR_H
14026
14027#include <stdbool.h>
14028
14029int fn_branch(bool do_branch1, bool do_branch2);
14030
14031#endif // BAR_H
14032#include "AGL/"ˇ"##,
14033    );
14034}
14035
14036#[gpui::test]
14037async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
14038    init_test(cx, |_| {});
14039
14040    let mut cx = EditorLspTestContext::new_rust(
14041        lsp::ServerCapabilities {
14042            completion_provider: Some(lsp::CompletionOptions {
14043                trigger_characters: Some(vec![".".to_string()]),
14044                resolve_provider: Some(true),
14045                ..Default::default()
14046            }),
14047            ..Default::default()
14048        },
14049        cx,
14050    )
14051    .await;
14052
14053    cx.set_state("fn main() { let a = 2ˇ; }");
14054    cx.simulate_keystroke(".");
14055    let completion_item = lsp::CompletionItem {
14056        label: "Some".into(),
14057        kind: Some(lsp::CompletionItemKind::SNIPPET),
14058        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
14059        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
14060            kind: lsp::MarkupKind::Markdown,
14061            value: "```rust\nSome(2)\n```".to_string(),
14062        })),
14063        deprecated: Some(false),
14064        sort_text: Some("Some".to_string()),
14065        filter_text: Some("Some".to_string()),
14066        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14067        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14068            range: lsp::Range {
14069                start: lsp::Position {
14070                    line: 0,
14071                    character: 22,
14072                },
14073                end: lsp::Position {
14074                    line: 0,
14075                    character: 22,
14076                },
14077            },
14078            new_text: "Some(2)".to_string(),
14079        })),
14080        additional_text_edits: Some(vec![lsp::TextEdit {
14081            range: lsp::Range {
14082                start: lsp::Position {
14083                    line: 0,
14084                    character: 20,
14085                },
14086                end: lsp::Position {
14087                    line: 0,
14088                    character: 22,
14089                },
14090            },
14091            new_text: "".to_string(),
14092        }]),
14093        ..Default::default()
14094    };
14095
14096    let closure_completion_item = completion_item.clone();
14097    let counter = Arc::new(AtomicUsize::new(0));
14098    let counter_clone = counter.clone();
14099    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14100        let task_completion_item = closure_completion_item.clone();
14101        counter_clone.fetch_add(1, atomic::Ordering::Release);
14102        async move {
14103            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14104                is_incomplete: true,
14105                item_defaults: None,
14106                items: vec![task_completion_item],
14107            })))
14108        }
14109    });
14110
14111    cx.condition(|editor, _| editor.context_menu_visible())
14112        .await;
14113    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
14114    assert!(request.next().await.is_some());
14115    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14116
14117    cx.simulate_keystrokes("S o m");
14118    cx.condition(|editor, _| editor.context_menu_visible())
14119        .await;
14120    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
14121    assert!(request.next().await.is_some());
14122    assert!(request.next().await.is_some());
14123    assert!(request.next().await.is_some());
14124    request.close();
14125    assert!(request.next().await.is_none());
14126    assert_eq!(
14127        counter.load(atomic::Ordering::Acquire),
14128        4,
14129        "With the completions menu open, only one LSP request should happen per input"
14130    );
14131}
14132
14133#[gpui::test]
14134async fn test_toggle_comment(cx: &mut TestAppContext) {
14135    init_test(cx, |_| {});
14136    let mut cx = EditorTestContext::new(cx).await;
14137    let language = Arc::new(Language::new(
14138        LanguageConfig {
14139            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14140            ..Default::default()
14141        },
14142        Some(tree_sitter_rust::LANGUAGE.into()),
14143    ));
14144    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14145
14146    // If multiple selections intersect a line, the line is only toggled once.
14147    cx.set_state(indoc! {"
14148        fn a() {
14149            «//b();
14150            ˇ»// «c();
14151            //ˇ»  d();
14152        }
14153    "});
14154
14155    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14156
14157    cx.assert_editor_state(indoc! {"
14158        fn a() {
14159            «b();
14160            c();
14161            ˇ» d();
14162        }
14163    "});
14164
14165    // The comment prefix is inserted at the same column for every line in a
14166    // selection.
14167    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14168
14169    cx.assert_editor_state(indoc! {"
14170        fn a() {
14171            // «b();
14172            // c();
14173            ˇ»//  d();
14174        }
14175    "});
14176
14177    // If a selection ends at the beginning of a line, that line is not toggled.
14178    cx.set_selections_state(indoc! {"
14179        fn a() {
14180            // b();
14181            «// c();
14182        ˇ»    //  d();
14183        }
14184    "});
14185
14186    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14187
14188    cx.assert_editor_state(indoc! {"
14189        fn a() {
14190            // b();
14191            «c();
14192        ˇ»    //  d();
14193        }
14194    "});
14195
14196    // If a selection span a single line and is empty, the line is toggled.
14197    cx.set_state(indoc! {"
14198        fn a() {
14199            a();
14200            b();
14201        ˇ
14202        }
14203    "});
14204
14205    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14206
14207    cx.assert_editor_state(indoc! {"
14208        fn a() {
14209            a();
14210            b();
14211        //•ˇ
14212        }
14213    "});
14214
14215    // If a selection span multiple lines, empty lines are not toggled.
14216    cx.set_state(indoc! {"
14217        fn a() {
14218            «a();
14219
14220            c();ˇ»
14221        }
14222    "});
14223
14224    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14225
14226    cx.assert_editor_state(indoc! {"
14227        fn a() {
14228            // «a();
14229
14230            // c();ˇ»
14231        }
14232    "});
14233
14234    // If a selection includes multiple comment prefixes, all lines are uncommented.
14235    cx.set_state(indoc! {"
14236        fn a() {
14237            «// a();
14238            /// b();
14239            //! c();ˇ»
14240        }
14241    "});
14242
14243    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14244
14245    cx.assert_editor_state(indoc! {"
14246        fn a() {
14247            «a();
14248            b();
14249            c();ˇ»
14250        }
14251    "});
14252}
14253
14254#[gpui::test]
14255async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
14256    init_test(cx, |_| {});
14257    let mut cx = EditorTestContext::new(cx).await;
14258    let language = Arc::new(Language::new(
14259        LanguageConfig {
14260            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14261            ..Default::default()
14262        },
14263        Some(tree_sitter_rust::LANGUAGE.into()),
14264    ));
14265    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14266
14267    let toggle_comments = &ToggleComments {
14268        advance_downwards: false,
14269        ignore_indent: true,
14270    };
14271
14272    // If multiple selections intersect a line, the line is only toggled once.
14273    cx.set_state(indoc! {"
14274        fn a() {
14275        //    «b();
14276        //    c();
14277        //    ˇ» d();
14278        }
14279    "});
14280
14281    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14282
14283    cx.assert_editor_state(indoc! {"
14284        fn a() {
14285            «b();
14286            c();
14287            ˇ» d();
14288        }
14289    "});
14290
14291    // The comment prefix is inserted at the beginning of each line
14292    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14293
14294    cx.assert_editor_state(indoc! {"
14295        fn a() {
14296        //    «b();
14297        //    c();
14298        //    ˇ» d();
14299        }
14300    "});
14301
14302    // If a selection ends at the beginning of a line, that line is not toggled.
14303    cx.set_selections_state(indoc! {"
14304        fn a() {
14305        //    b();
14306        //    «c();
14307        ˇ»//     d();
14308        }
14309    "});
14310
14311    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14312
14313    cx.assert_editor_state(indoc! {"
14314        fn a() {
14315        //    b();
14316            «c();
14317        ˇ»//     d();
14318        }
14319    "});
14320
14321    // If a selection span a single line and is empty, the line is toggled.
14322    cx.set_state(indoc! {"
14323        fn a() {
14324            a();
14325            b();
14326        ˇ
14327        }
14328    "});
14329
14330    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14331
14332    cx.assert_editor_state(indoc! {"
14333        fn a() {
14334            a();
14335            b();
14336        //ˇ
14337        }
14338    "});
14339
14340    // If a selection span multiple lines, empty lines are not toggled.
14341    cx.set_state(indoc! {"
14342        fn a() {
14343            «a();
14344
14345            c();ˇ»
14346        }
14347    "});
14348
14349    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14350
14351    cx.assert_editor_state(indoc! {"
14352        fn a() {
14353        //    «a();
14354
14355        //    c();ˇ»
14356        }
14357    "});
14358
14359    // If a selection includes multiple comment prefixes, all lines are uncommented.
14360    cx.set_state(indoc! {"
14361        fn a() {
14362        //    «a();
14363        ///    b();
14364        //!    c();ˇ»
14365        }
14366    "});
14367
14368    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14369
14370    cx.assert_editor_state(indoc! {"
14371        fn a() {
14372            «a();
14373            b();
14374            c();ˇ»
14375        }
14376    "});
14377}
14378
14379#[gpui::test]
14380async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
14381    init_test(cx, |_| {});
14382
14383    let language = Arc::new(Language::new(
14384        LanguageConfig {
14385            line_comments: vec!["// ".into()],
14386            ..Default::default()
14387        },
14388        Some(tree_sitter_rust::LANGUAGE.into()),
14389    ));
14390
14391    let mut cx = EditorTestContext::new(cx).await;
14392
14393    cx.language_registry().add(language.clone());
14394    cx.update_buffer(|buffer, cx| {
14395        buffer.set_language(Some(language), cx);
14396    });
14397
14398    let toggle_comments = &ToggleComments {
14399        advance_downwards: true,
14400        ignore_indent: false,
14401    };
14402
14403    // Single cursor on one line -> advance
14404    // Cursor moves horizontally 3 characters as well on non-blank line
14405    cx.set_state(indoc!(
14406        "fn a() {
14407             ˇdog();
14408             cat();
14409        }"
14410    ));
14411    cx.update_editor(|editor, window, cx| {
14412        editor.toggle_comments(toggle_comments, window, cx);
14413    });
14414    cx.assert_editor_state(indoc!(
14415        "fn a() {
14416             // dog();
14417             catˇ();
14418        }"
14419    ));
14420
14421    // Single selection on one line -> don't advance
14422    cx.set_state(indoc!(
14423        "fn a() {
14424             «dog()ˇ»;
14425             cat();
14426        }"
14427    ));
14428    cx.update_editor(|editor, window, cx| {
14429        editor.toggle_comments(toggle_comments, window, cx);
14430    });
14431    cx.assert_editor_state(indoc!(
14432        "fn a() {
14433             // «dog()ˇ»;
14434             cat();
14435        }"
14436    ));
14437
14438    // Multiple cursors on one line -> advance
14439    cx.set_state(indoc!(
14440        "fn a() {
14441             ˇdˇog();
14442             cat();
14443        }"
14444    ));
14445    cx.update_editor(|editor, window, cx| {
14446        editor.toggle_comments(toggle_comments, window, cx);
14447    });
14448    cx.assert_editor_state(indoc!(
14449        "fn a() {
14450             // dog();
14451             catˇ(ˇ);
14452        }"
14453    ));
14454
14455    // Multiple cursors on one line, with selection -> don't advance
14456    cx.set_state(indoc!(
14457        "fn a() {
14458             ˇdˇog«()ˇ»;
14459             cat();
14460        }"
14461    ));
14462    cx.update_editor(|editor, window, cx| {
14463        editor.toggle_comments(toggle_comments, window, cx);
14464    });
14465    cx.assert_editor_state(indoc!(
14466        "fn a() {
14467             // ˇdˇog«()ˇ»;
14468             cat();
14469        }"
14470    ));
14471
14472    // Single cursor on one line -> advance
14473    // Cursor moves to column 0 on blank line
14474    cx.set_state(indoc!(
14475        "fn a() {
14476             ˇdog();
14477
14478             cat();
14479        }"
14480    ));
14481    cx.update_editor(|editor, window, cx| {
14482        editor.toggle_comments(toggle_comments, window, cx);
14483    });
14484    cx.assert_editor_state(indoc!(
14485        "fn a() {
14486             // dog();
14487        ˇ
14488             cat();
14489        }"
14490    ));
14491
14492    // Single cursor on one line -> advance
14493    // Cursor starts and ends at column 0
14494    cx.set_state(indoc!(
14495        "fn a() {
14496         ˇ    dog();
14497             cat();
14498        }"
14499    ));
14500    cx.update_editor(|editor, window, cx| {
14501        editor.toggle_comments(toggle_comments, window, cx);
14502    });
14503    cx.assert_editor_state(indoc!(
14504        "fn a() {
14505             // dog();
14506         ˇ    cat();
14507        }"
14508    ));
14509}
14510
14511#[gpui::test]
14512async fn test_toggle_block_comment(cx: &mut TestAppContext) {
14513    init_test(cx, |_| {});
14514
14515    let mut cx = EditorTestContext::new(cx).await;
14516
14517    let html_language = Arc::new(
14518        Language::new(
14519            LanguageConfig {
14520                name: "HTML".into(),
14521                block_comment: Some(BlockCommentConfig {
14522                    start: "<!-- ".into(),
14523                    prefix: "".into(),
14524                    end: " -->".into(),
14525                    tab_size: 0,
14526                }),
14527                ..Default::default()
14528            },
14529            Some(tree_sitter_html::LANGUAGE.into()),
14530        )
14531        .with_injection_query(
14532            r#"
14533            (script_element
14534                (raw_text) @injection.content
14535                (#set! injection.language "javascript"))
14536            "#,
14537        )
14538        .unwrap(),
14539    );
14540
14541    let javascript_language = Arc::new(Language::new(
14542        LanguageConfig {
14543            name: "JavaScript".into(),
14544            line_comments: vec!["// ".into()],
14545            ..Default::default()
14546        },
14547        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14548    ));
14549
14550    cx.language_registry().add(html_language.clone());
14551    cx.language_registry().add(javascript_language);
14552    cx.update_buffer(|buffer, cx| {
14553        buffer.set_language(Some(html_language), cx);
14554    });
14555
14556    // Toggle comments for empty selections
14557    cx.set_state(
14558        &r#"
14559            <p>A</p>ˇ
14560            <p>B</p>ˇ
14561            <p>C</p>ˇ
14562        "#
14563        .unindent(),
14564    );
14565    cx.update_editor(|editor, window, cx| {
14566        editor.toggle_comments(&ToggleComments::default(), window, cx)
14567    });
14568    cx.assert_editor_state(
14569        &r#"
14570            <!-- <p>A</p>ˇ -->
14571            <!-- <p>B</p>ˇ -->
14572            <!-- <p>C</p>ˇ -->
14573        "#
14574        .unindent(),
14575    );
14576    cx.update_editor(|editor, window, cx| {
14577        editor.toggle_comments(&ToggleComments::default(), window, cx)
14578    });
14579    cx.assert_editor_state(
14580        &r#"
14581            <p>A</p>ˇ
14582            <p>B</p>ˇ
14583            <p>C</p>ˇ
14584        "#
14585        .unindent(),
14586    );
14587
14588    // Toggle comments for mixture of empty and non-empty selections, where
14589    // multiple selections occupy a given line.
14590    cx.set_state(
14591        &r#"
14592            <p>A«</p>
14593            <p>ˇ»B</p>ˇ
14594            <p>C«</p>
14595            <p>ˇ»D</p>ˇ
14596        "#
14597        .unindent(),
14598    );
14599
14600    cx.update_editor(|editor, window, cx| {
14601        editor.toggle_comments(&ToggleComments::default(), window, cx)
14602    });
14603    cx.assert_editor_state(
14604        &r#"
14605            <!-- <p>A«</p>
14606            <p>ˇ»B</p>ˇ -->
14607            <!-- <p>C«</p>
14608            <p>ˇ»D</p>ˇ -->
14609        "#
14610        .unindent(),
14611    );
14612    cx.update_editor(|editor, window, cx| {
14613        editor.toggle_comments(&ToggleComments::default(), window, cx)
14614    });
14615    cx.assert_editor_state(
14616        &r#"
14617            <p>A«</p>
14618            <p>ˇ»B</p>ˇ
14619            <p>C«</p>
14620            <p>ˇ»D</p>ˇ
14621        "#
14622        .unindent(),
14623    );
14624
14625    // Toggle comments when different languages are active for different
14626    // selections.
14627    cx.set_state(
14628        &r#"
14629            ˇ<script>
14630                ˇvar x = new Y();
14631            ˇ</script>
14632        "#
14633        .unindent(),
14634    );
14635    cx.executor().run_until_parked();
14636    cx.update_editor(|editor, window, cx| {
14637        editor.toggle_comments(&ToggleComments::default(), window, cx)
14638    });
14639    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
14640    // Uncommenting and commenting from this position brings in even more wrong artifacts.
14641    cx.assert_editor_state(
14642        &r#"
14643            <!-- ˇ<script> -->
14644                // ˇvar x = new Y();
14645            <!-- ˇ</script> -->
14646        "#
14647        .unindent(),
14648    );
14649}
14650
14651#[gpui::test]
14652fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
14653    init_test(cx, |_| {});
14654
14655    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14656    let multibuffer = cx.new(|cx| {
14657        let mut multibuffer = MultiBuffer::new(ReadWrite);
14658        multibuffer.push_excerpts(
14659            buffer.clone(),
14660            [
14661                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
14662                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
14663            ],
14664            cx,
14665        );
14666        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
14667        multibuffer
14668    });
14669
14670    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14671    editor.update_in(cx, |editor, window, cx| {
14672        assert_eq!(editor.text(cx), "aaaa\nbbbb");
14673        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14674            s.select_ranges([
14675                Point::new(0, 0)..Point::new(0, 0),
14676                Point::new(1, 0)..Point::new(1, 0),
14677            ])
14678        });
14679
14680        editor.handle_input("X", window, cx);
14681        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
14682        assert_eq!(
14683            editor.selections.ranges(cx),
14684            [
14685                Point::new(0, 1)..Point::new(0, 1),
14686                Point::new(1, 1)..Point::new(1, 1),
14687            ]
14688        );
14689
14690        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
14691        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14692            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
14693        });
14694        editor.backspace(&Default::default(), window, cx);
14695        assert_eq!(editor.text(cx), "Xa\nbbb");
14696        assert_eq!(
14697            editor.selections.ranges(cx),
14698            [Point::new(1, 0)..Point::new(1, 0)]
14699        );
14700
14701        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14702            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
14703        });
14704        editor.backspace(&Default::default(), window, cx);
14705        assert_eq!(editor.text(cx), "X\nbb");
14706        assert_eq!(
14707            editor.selections.ranges(cx),
14708            [Point::new(0, 1)..Point::new(0, 1)]
14709        );
14710    });
14711}
14712
14713#[gpui::test]
14714fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
14715    init_test(cx, |_| {});
14716
14717    let markers = vec![('[', ']').into(), ('(', ')').into()];
14718    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
14719        indoc! {"
14720            [aaaa
14721            (bbbb]
14722            cccc)",
14723        },
14724        markers.clone(),
14725    );
14726    let excerpt_ranges = markers.into_iter().map(|marker| {
14727        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
14728        ExcerptRange::new(context)
14729    });
14730    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
14731    let multibuffer = cx.new(|cx| {
14732        let mut multibuffer = MultiBuffer::new(ReadWrite);
14733        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
14734        multibuffer
14735    });
14736
14737    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
14738    editor.update_in(cx, |editor, window, cx| {
14739        let (expected_text, selection_ranges) = marked_text_ranges(
14740            indoc! {"
14741                aaaa
14742                bˇbbb
14743                bˇbbˇb
14744                cccc"
14745            },
14746            true,
14747        );
14748        assert_eq!(editor.text(cx), expected_text);
14749        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14750            s.select_ranges(selection_ranges)
14751        });
14752
14753        editor.handle_input("X", window, cx);
14754
14755        let (expected_text, expected_selections) = marked_text_ranges(
14756            indoc! {"
14757                aaaa
14758                bXˇbbXb
14759                bXˇbbXˇb
14760                cccc"
14761            },
14762            false,
14763        );
14764        assert_eq!(editor.text(cx), expected_text);
14765        assert_eq!(editor.selections.ranges(cx), expected_selections);
14766
14767        editor.newline(&Newline, window, cx);
14768        let (expected_text, expected_selections) = marked_text_ranges(
14769            indoc! {"
14770                aaaa
14771                bX
14772                ˇbbX
14773                b
14774                bX
14775                ˇbbX
14776                ˇb
14777                cccc"
14778            },
14779            false,
14780        );
14781        assert_eq!(editor.text(cx), expected_text);
14782        assert_eq!(editor.selections.ranges(cx), expected_selections);
14783    });
14784}
14785
14786#[gpui::test]
14787fn test_refresh_selections(cx: &mut TestAppContext) {
14788    init_test(cx, |_| {});
14789
14790    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14791    let mut excerpt1_id = None;
14792    let multibuffer = cx.new(|cx| {
14793        let mut multibuffer = MultiBuffer::new(ReadWrite);
14794        excerpt1_id = multibuffer
14795            .push_excerpts(
14796                buffer.clone(),
14797                [
14798                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14799                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14800                ],
14801                cx,
14802            )
14803            .into_iter()
14804            .next();
14805        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14806        multibuffer
14807    });
14808
14809    let editor = cx.add_window(|window, cx| {
14810        let mut editor = build_editor(multibuffer.clone(), window, cx);
14811        let snapshot = editor.snapshot(window, cx);
14812        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14813            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
14814        });
14815        editor.begin_selection(
14816            Point::new(2, 1).to_display_point(&snapshot),
14817            true,
14818            1,
14819            window,
14820            cx,
14821        );
14822        assert_eq!(
14823            editor.selections.ranges(cx),
14824            [
14825                Point::new(1, 3)..Point::new(1, 3),
14826                Point::new(2, 1)..Point::new(2, 1),
14827            ]
14828        );
14829        editor
14830    });
14831
14832    // Refreshing selections is a no-op when excerpts haven't changed.
14833    _ = editor.update(cx, |editor, window, cx| {
14834        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14835        assert_eq!(
14836            editor.selections.ranges(cx),
14837            [
14838                Point::new(1, 3)..Point::new(1, 3),
14839                Point::new(2, 1)..Point::new(2, 1),
14840            ]
14841        );
14842    });
14843
14844    multibuffer.update(cx, |multibuffer, cx| {
14845        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14846    });
14847    _ = editor.update(cx, |editor, window, cx| {
14848        // Removing an excerpt causes the first selection to become degenerate.
14849        assert_eq!(
14850            editor.selections.ranges(cx),
14851            [
14852                Point::new(0, 0)..Point::new(0, 0),
14853                Point::new(0, 1)..Point::new(0, 1)
14854            ]
14855        );
14856
14857        // Refreshing selections will relocate the first selection to the original buffer
14858        // location.
14859        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14860        assert_eq!(
14861            editor.selections.ranges(cx),
14862            [
14863                Point::new(0, 1)..Point::new(0, 1),
14864                Point::new(0, 3)..Point::new(0, 3)
14865            ]
14866        );
14867        assert!(editor.selections.pending_anchor().is_some());
14868    });
14869}
14870
14871#[gpui::test]
14872fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
14873    init_test(cx, |_| {});
14874
14875    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
14876    let mut excerpt1_id = None;
14877    let multibuffer = cx.new(|cx| {
14878        let mut multibuffer = MultiBuffer::new(ReadWrite);
14879        excerpt1_id = multibuffer
14880            .push_excerpts(
14881                buffer.clone(),
14882                [
14883                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
14884                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
14885                ],
14886                cx,
14887            )
14888            .into_iter()
14889            .next();
14890        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
14891        multibuffer
14892    });
14893
14894    let editor = cx.add_window(|window, cx| {
14895        let mut editor = build_editor(multibuffer.clone(), window, cx);
14896        let snapshot = editor.snapshot(window, cx);
14897        editor.begin_selection(
14898            Point::new(1, 3).to_display_point(&snapshot),
14899            false,
14900            1,
14901            window,
14902            cx,
14903        );
14904        assert_eq!(
14905            editor.selections.ranges(cx),
14906            [Point::new(1, 3)..Point::new(1, 3)]
14907        );
14908        editor
14909    });
14910
14911    multibuffer.update(cx, |multibuffer, cx| {
14912        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
14913    });
14914    _ = editor.update(cx, |editor, window, cx| {
14915        assert_eq!(
14916            editor.selections.ranges(cx),
14917            [Point::new(0, 0)..Point::new(0, 0)]
14918        );
14919
14920        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
14921        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
14922        assert_eq!(
14923            editor.selections.ranges(cx),
14924            [Point::new(0, 3)..Point::new(0, 3)]
14925        );
14926        assert!(editor.selections.pending_anchor().is_some());
14927    });
14928}
14929
14930#[gpui::test]
14931async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
14932    init_test(cx, |_| {});
14933
14934    let language = Arc::new(
14935        Language::new(
14936            LanguageConfig {
14937                brackets: BracketPairConfig {
14938                    pairs: vec![
14939                        BracketPair {
14940                            start: "{".to_string(),
14941                            end: "}".to_string(),
14942                            close: true,
14943                            surround: true,
14944                            newline: true,
14945                        },
14946                        BracketPair {
14947                            start: "/* ".to_string(),
14948                            end: " */".to_string(),
14949                            close: true,
14950                            surround: true,
14951                            newline: true,
14952                        },
14953                    ],
14954                    ..Default::default()
14955                },
14956                ..Default::default()
14957            },
14958            Some(tree_sitter_rust::LANGUAGE.into()),
14959        )
14960        .with_indents_query("")
14961        .unwrap(),
14962    );
14963
14964    let text = concat!(
14965        "{   }\n",     //
14966        "  x\n",       //
14967        "  /*   */\n", //
14968        "x\n",         //
14969        "{{} }\n",     //
14970    );
14971
14972    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
14973    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
14974    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
14975    editor
14976        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
14977        .await;
14978
14979    editor.update_in(cx, |editor, window, cx| {
14980        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14981            s.select_display_ranges([
14982                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
14983                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
14984                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
14985            ])
14986        });
14987        editor.newline(&Newline, window, cx);
14988
14989        assert_eq!(
14990            editor.buffer().read(cx).read(cx).text(),
14991            concat!(
14992                "{ \n",    // Suppress rustfmt
14993                "\n",      //
14994                "}\n",     //
14995                "  x\n",   //
14996                "  /* \n", //
14997                "  \n",    //
14998                "  */\n",  //
14999                "x\n",     //
15000                "{{} \n",  //
15001                "}\n",     //
15002            )
15003        );
15004    });
15005}
15006
15007#[gpui::test]
15008fn test_highlighted_ranges(cx: &mut TestAppContext) {
15009    init_test(cx, |_| {});
15010
15011    let editor = cx.add_window(|window, cx| {
15012        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
15013        build_editor(buffer, window, cx)
15014    });
15015
15016    _ = editor.update(cx, |editor, window, cx| {
15017        struct Type1;
15018        struct Type2;
15019
15020        let buffer = editor.buffer.read(cx).snapshot(cx);
15021
15022        let anchor_range =
15023            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
15024
15025        editor.highlight_background::<Type1>(
15026            &[
15027                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
15028                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
15029                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
15030                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
15031            ],
15032            |_| Hsla::red(),
15033            cx,
15034        );
15035        editor.highlight_background::<Type2>(
15036            &[
15037                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
15038                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
15039                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
15040                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
15041            ],
15042            |_| Hsla::green(),
15043            cx,
15044        );
15045
15046        let snapshot = editor.snapshot(window, cx);
15047        let mut highlighted_ranges = editor.background_highlights_in_range(
15048            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
15049            &snapshot,
15050            cx.theme(),
15051        );
15052        // Enforce a consistent ordering based on color without relying on the ordering of the
15053        // highlight's `TypeId` which is non-executor.
15054        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
15055        assert_eq!(
15056            highlighted_ranges,
15057            &[
15058                (
15059                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
15060                    Hsla::red(),
15061                ),
15062                (
15063                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
15064                    Hsla::red(),
15065                ),
15066                (
15067                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
15068                    Hsla::green(),
15069                ),
15070                (
15071                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
15072                    Hsla::green(),
15073                ),
15074            ]
15075        );
15076        assert_eq!(
15077            editor.background_highlights_in_range(
15078                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
15079                &snapshot,
15080                cx.theme(),
15081            ),
15082            &[(
15083                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
15084                Hsla::red(),
15085            )]
15086        );
15087    });
15088}
15089
15090#[gpui::test]
15091async fn test_following(cx: &mut TestAppContext) {
15092    init_test(cx, |_| {});
15093
15094    let fs = FakeFs::new(cx.executor());
15095    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
15096
15097    let buffer = project.update(cx, |project, cx| {
15098        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
15099        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
15100    });
15101    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
15102    let follower = cx.update(|cx| {
15103        cx.open_window(
15104            WindowOptions {
15105                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
15106                    gpui::Point::new(px(0.), px(0.)),
15107                    gpui::Point::new(px(10.), px(80.)),
15108                ))),
15109                ..Default::default()
15110            },
15111            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
15112        )
15113        .unwrap()
15114    });
15115
15116    let is_still_following = Rc::new(RefCell::new(true));
15117    let follower_edit_event_count = Rc::new(RefCell::new(0));
15118    let pending_update = Rc::new(RefCell::new(None));
15119    let leader_entity = leader.root(cx).unwrap();
15120    let follower_entity = follower.root(cx).unwrap();
15121    _ = follower.update(cx, {
15122        let update = pending_update.clone();
15123        let is_still_following = is_still_following.clone();
15124        let follower_edit_event_count = follower_edit_event_count.clone();
15125        |_, window, cx| {
15126            cx.subscribe_in(
15127                &leader_entity,
15128                window,
15129                move |_, leader, event, window, cx| {
15130                    leader.read(cx).add_event_to_update_proto(
15131                        event,
15132                        &mut update.borrow_mut(),
15133                        window,
15134                        cx,
15135                    );
15136                },
15137            )
15138            .detach();
15139
15140            cx.subscribe_in(
15141                &follower_entity,
15142                window,
15143                move |_, _, event: &EditorEvent, _window, _cx| {
15144                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
15145                        *is_still_following.borrow_mut() = false;
15146                    }
15147
15148                    if let EditorEvent::BufferEdited = event {
15149                        *follower_edit_event_count.borrow_mut() += 1;
15150                    }
15151                },
15152            )
15153            .detach();
15154        }
15155    });
15156
15157    // Update the selections only
15158    _ = leader.update(cx, |leader, window, cx| {
15159        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15160            s.select_ranges([1..1])
15161        });
15162    });
15163    follower
15164        .update(cx, |follower, window, cx| {
15165            follower.apply_update_proto(
15166                &project,
15167                pending_update.borrow_mut().take().unwrap(),
15168                window,
15169                cx,
15170            )
15171        })
15172        .unwrap()
15173        .await
15174        .unwrap();
15175    _ = follower.update(cx, |follower, _, cx| {
15176        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
15177    });
15178    assert!(*is_still_following.borrow());
15179    assert_eq!(*follower_edit_event_count.borrow(), 0);
15180
15181    // Update the scroll position only
15182    _ = leader.update(cx, |leader, window, cx| {
15183        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15184    });
15185    follower
15186        .update(cx, |follower, window, cx| {
15187            follower.apply_update_proto(
15188                &project,
15189                pending_update.borrow_mut().take().unwrap(),
15190                window,
15191                cx,
15192            )
15193        })
15194        .unwrap()
15195        .await
15196        .unwrap();
15197    assert_eq!(
15198        follower
15199            .update(cx, |follower, _, cx| follower.scroll_position(cx))
15200            .unwrap(),
15201        gpui::Point::new(1.5, 3.5)
15202    );
15203    assert!(*is_still_following.borrow());
15204    assert_eq!(*follower_edit_event_count.borrow(), 0);
15205
15206    // Update the selections and scroll position. The follower's scroll position is updated
15207    // via autoscroll, not via the leader's exact scroll position.
15208    _ = leader.update(cx, |leader, window, cx| {
15209        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15210            s.select_ranges([0..0])
15211        });
15212        leader.request_autoscroll(Autoscroll::newest(), cx);
15213        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15214    });
15215    follower
15216        .update(cx, |follower, window, cx| {
15217            follower.apply_update_proto(
15218                &project,
15219                pending_update.borrow_mut().take().unwrap(),
15220                window,
15221                cx,
15222            )
15223        })
15224        .unwrap()
15225        .await
15226        .unwrap();
15227    _ = follower.update(cx, |follower, _, cx| {
15228        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
15229        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
15230    });
15231    assert!(*is_still_following.borrow());
15232
15233    // Creating a pending selection that precedes another selection
15234    _ = leader.update(cx, |leader, window, cx| {
15235        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15236            s.select_ranges([1..1])
15237        });
15238        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
15239    });
15240    follower
15241        .update(cx, |follower, window, cx| {
15242            follower.apply_update_proto(
15243                &project,
15244                pending_update.borrow_mut().take().unwrap(),
15245                window,
15246                cx,
15247            )
15248        })
15249        .unwrap()
15250        .await
15251        .unwrap();
15252    _ = follower.update(cx, |follower, _, cx| {
15253        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
15254    });
15255    assert!(*is_still_following.borrow());
15256
15257    // Extend the pending selection so that it surrounds another selection
15258    _ = leader.update(cx, |leader, window, cx| {
15259        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
15260    });
15261    follower
15262        .update(cx, |follower, window, cx| {
15263            follower.apply_update_proto(
15264                &project,
15265                pending_update.borrow_mut().take().unwrap(),
15266                window,
15267                cx,
15268            )
15269        })
15270        .unwrap()
15271        .await
15272        .unwrap();
15273    _ = follower.update(cx, |follower, _, cx| {
15274        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
15275    });
15276
15277    // Scrolling locally breaks the follow
15278    _ = follower.update(cx, |follower, window, cx| {
15279        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
15280        follower.set_scroll_anchor(
15281            ScrollAnchor {
15282                anchor: top_anchor,
15283                offset: gpui::Point::new(0.0, 0.5),
15284            },
15285            window,
15286            cx,
15287        );
15288    });
15289    assert!(!(*is_still_following.borrow()));
15290}
15291
15292#[gpui::test]
15293async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
15294    init_test(cx, |_| {});
15295
15296    let fs = FakeFs::new(cx.executor());
15297    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
15298    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15299    let pane = workspace
15300        .update(cx, |workspace, _, _| workspace.active_pane().clone())
15301        .unwrap();
15302
15303    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15304
15305    let leader = pane.update_in(cx, |_, window, cx| {
15306        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
15307        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
15308    });
15309
15310    // Start following the editor when it has no excerpts.
15311    let mut state_message =
15312        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
15313    let workspace_entity = workspace.root(cx).unwrap();
15314    let follower_1 = cx
15315        .update_window(*workspace.deref(), |_, window, cx| {
15316            Editor::from_state_proto(
15317                workspace_entity,
15318                ViewId {
15319                    creator: CollaboratorId::PeerId(PeerId::default()),
15320                    id: 0,
15321                },
15322                &mut state_message,
15323                window,
15324                cx,
15325            )
15326        })
15327        .unwrap()
15328        .unwrap()
15329        .await
15330        .unwrap();
15331
15332    let update_message = Rc::new(RefCell::new(None));
15333    follower_1.update_in(cx, {
15334        let update = update_message.clone();
15335        |_, window, cx| {
15336            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
15337                leader.read(cx).add_event_to_update_proto(
15338                    event,
15339                    &mut update.borrow_mut(),
15340                    window,
15341                    cx,
15342                );
15343            })
15344            .detach();
15345        }
15346    });
15347
15348    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
15349        (
15350            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
15351            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
15352        )
15353    });
15354
15355    // Insert some excerpts.
15356    leader.update(cx, |leader, cx| {
15357        leader.buffer.update(cx, |multibuffer, cx| {
15358            multibuffer.set_excerpts_for_path(
15359                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
15360                buffer_1.clone(),
15361                vec![
15362                    Point::row_range(0..3),
15363                    Point::row_range(1..6),
15364                    Point::row_range(12..15),
15365                ],
15366                0,
15367                cx,
15368            );
15369            multibuffer.set_excerpts_for_path(
15370                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
15371                buffer_2.clone(),
15372                vec![Point::row_range(0..6), Point::row_range(8..12)],
15373                0,
15374                cx,
15375            );
15376        });
15377    });
15378
15379    // Apply the update of adding the excerpts.
15380    follower_1
15381        .update_in(cx, |follower, window, cx| {
15382            follower.apply_update_proto(
15383                &project,
15384                update_message.borrow().clone().unwrap(),
15385                window,
15386                cx,
15387            )
15388        })
15389        .await
15390        .unwrap();
15391    assert_eq!(
15392        follower_1.update(cx, |editor, cx| editor.text(cx)),
15393        leader.update(cx, |editor, cx| editor.text(cx))
15394    );
15395    update_message.borrow_mut().take();
15396
15397    // Start following separately after it already has excerpts.
15398    let mut state_message =
15399        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
15400    let workspace_entity = workspace.root(cx).unwrap();
15401    let follower_2 = cx
15402        .update_window(*workspace.deref(), |_, window, cx| {
15403            Editor::from_state_proto(
15404                workspace_entity,
15405                ViewId {
15406                    creator: CollaboratorId::PeerId(PeerId::default()),
15407                    id: 0,
15408                },
15409                &mut state_message,
15410                window,
15411                cx,
15412            )
15413        })
15414        .unwrap()
15415        .unwrap()
15416        .await
15417        .unwrap();
15418    assert_eq!(
15419        follower_2.update(cx, |editor, cx| editor.text(cx)),
15420        leader.update(cx, |editor, cx| editor.text(cx))
15421    );
15422
15423    // Remove some excerpts.
15424    leader.update(cx, |leader, cx| {
15425        leader.buffer.update(cx, |multibuffer, cx| {
15426            let excerpt_ids = multibuffer.excerpt_ids();
15427            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
15428            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
15429        });
15430    });
15431
15432    // Apply the update of removing the excerpts.
15433    follower_1
15434        .update_in(cx, |follower, window, cx| {
15435            follower.apply_update_proto(
15436                &project,
15437                update_message.borrow().clone().unwrap(),
15438                window,
15439                cx,
15440            )
15441        })
15442        .await
15443        .unwrap();
15444    follower_2
15445        .update_in(cx, |follower, window, cx| {
15446            follower.apply_update_proto(
15447                &project,
15448                update_message.borrow().clone().unwrap(),
15449                window,
15450                cx,
15451            )
15452        })
15453        .await
15454        .unwrap();
15455    update_message.borrow_mut().take();
15456    assert_eq!(
15457        follower_1.update(cx, |editor, cx| editor.text(cx)),
15458        leader.update(cx, |editor, cx| editor.text(cx))
15459    );
15460}
15461
15462#[gpui::test]
15463async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15464    init_test(cx, |_| {});
15465
15466    let mut cx = EditorTestContext::new(cx).await;
15467    let lsp_store =
15468        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
15469
15470    cx.set_state(indoc! {"
15471        ˇfn func(abc def: i32) -> u32 {
15472        }
15473    "});
15474
15475    cx.update(|_, cx| {
15476        lsp_store.update(cx, |lsp_store, cx| {
15477            lsp_store
15478                .update_diagnostics(
15479                    LanguageServerId(0),
15480                    lsp::PublishDiagnosticsParams {
15481                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
15482                        version: None,
15483                        diagnostics: vec![
15484                            lsp::Diagnostic {
15485                                range: lsp::Range::new(
15486                                    lsp::Position::new(0, 11),
15487                                    lsp::Position::new(0, 12),
15488                                ),
15489                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15490                                ..Default::default()
15491                            },
15492                            lsp::Diagnostic {
15493                                range: lsp::Range::new(
15494                                    lsp::Position::new(0, 12),
15495                                    lsp::Position::new(0, 15),
15496                                ),
15497                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15498                                ..Default::default()
15499                            },
15500                            lsp::Diagnostic {
15501                                range: lsp::Range::new(
15502                                    lsp::Position::new(0, 25),
15503                                    lsp::Position::new(0, 28),
15504                                ),
15505                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15506                                ..Default::default()
15507                            },
15508                        ],
15509                    },
15510                    None,
15511                    DiagnosticSourceKind::Pushed,
15512                    &[],
15513                    cx,
15514                )
15515                .unwrap()
15516        });
15517    });
15518
15519    executor.run_until_parked();
15520
15521    cx.update_editor(|editor, window, cx| {
15522        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15523    });
15524
15525    cx.assert_editor_state(indoc! {"
15526        fn func(abc def: i32) -> ˇu32 {
15527        }
15528    "});
15529
15530    cx.update_editor(|editor, window, cx| {
15531        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15532    });
15533
15534    cx.assert_editor_state(indoc! {"
15535        fn func(abc ˇdef: i32) -> u32 {
15536        }
15537    "});
15538
15539    cx.update_editor(|editor, window, cx| {
15540        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15541    });
15542
15543    cx.assert_editor_state(indoc! {"
15544        fn func(abcˇ def: i32) -> u32 {
15545        }
15546    "});
15547
15548    cx.update_editor(|editor, window, cx| {
15549        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15550    });
15551
15552    cx.assert_editor_state(indoc! {"
15553        fn func(abc def: i32) -> ˇu32 {
15554        }
15555    "});
15556}
15557
15558#[gpui::test]
15559async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15560    init_test(cx, |_| {});
15561
15562    let mut cx = EditorTestContext::new(cx).await;
15563
15564    let diff_base = r#"
15565        use some::mod;
15566
15567        const A: u32 = 42;
15568
15569        fn main() {
15570            println!("hello");
15571
15572            println!("world");
15573        }
15574        "#
15575    .unindent();
15576
15577    // Edits are modified, removed, modified, added
15578    cx.set_state(
15579        &r#"
15580        use some::modified;
15581
15582        ˇ
15583        fn main() {
15584            println!("hello there");
15585
15586            println!("around the");
15587            println!("world");
15588        }
15589        "#
15590        .unindent(),
15591    );
15592
15593    cx.set_head_text(&diff_base);
15594    executor.run_until_parked();
15595
15596    cx.update_editor(|editor, window, cx| {
15597        //Wrap around the bottom of the buffer
15598        for _ in 0..3 {
15599            editor.go_to_next_hunk(&GoToHunk, window, cx);
15600        }
15601    });
15602
15603    cx.assert_editor_state(
15604        &r#"
15605        ˇuse some::modified;
15606
15607
15608        fn main() {
15609            println!("hello there");
15610
15611            println!("around the");
15612            println!("world");
15613        }
15614        "#
15615        .unindent(),
15616    );
15617
15618    cx.update_editor(|editor, window, cx| {
15619        //Wrap around the top of the buffer
15620        for _ in 0..2 {
15621            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15622        }
15623    });
15624
15625    cx.assert_editor_state(
15626        &r#"
15627        use some::modified;
15628
15629
15630        fn main() {
15631        ˇ    println!("hello there");
15632
15633            println!("around the");
15634            println!("world");
15635        }
15636        "#
15637        .unindent(),
15638    );
15639
15640    cx.update_editor(|editor, window, cx| {
15641        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15642    });
15643
15644    cx.assert_editor_state(
15645        &r#"
15646        use some::modified;
15647
15648        ˇ
15649        fn main() {
15650            println!("hello there");
15651
15652            println!("around the");
15653            println!("world");
15654        }
15655        "#
15656        .unindent(),
15657    );
15658
15659    cx.update_editor(|editor, window, cx| {
15660        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15661    });
15662
15663    cx.assert_editor_state(
15664        &r#"
15665        ˇuse some::modified;
15666
15667
15668        fn main() {
15669            println!("hello there");
15670
15671            println!("around the");
15672            println!("world");
15673        }
15674        "#
15675        .unindent(),
15676    );
15677
15678    cx.update_editor(|editor, window, cx| {
15679        for _ in 0..2 {
15680            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
15681        }
15682    });
15683
15684    cx.assert_editor_state(
15685        &r#"
15686        use some::modified;
15687
15688
15689        fn main() {
15690        ˇ    println!("hello there");
15691
15692            println!("around the");
15693            println!("world");
15694        }
15695        "#
15696        .unindent(),
15697    );
15698
15699    cx.update_editor(|editor, window, cx| {
15700        editor.fold(&Fold, window, cx);
15701    });
15702
15703    cx.update_editor(|editor, window, cx| {
15704        editor.go_to_next_hunk(&GoToHunk, window, cx);
15705    });
15706
15707    cx.assert_editor_state(
15708        &r#"
15709        ˇuse some::modified;
15710
15711
15712        fn main() {
15713            println!("hello there");
15714
15715            println!("around the");
15716            println!("world");
15717        }
15718        "#
15719        .unindent(),
15720    );
15721}
15722
15723#[test]
15724fn test_split_words() {
15725    fn split(text: &str) -> Vec<&str> {
15726        split_words(text).collect()
15727    }
15728
15729    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
15730    assert_eq!(split("hello_world"), &["hello_", "world"]);
15731    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
15732    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
15733    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
15734    assert_eq!(split("helloworld"), &["helloworld"]);
15735
15736    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
15737}
15738
15739#[gpui::test]
15740async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
15741    init_test(cx, |_| {});
15742
15743    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
15744    let mut assert = |before, after| {
15745        let _state_context = cx.set_state(before);
15746        cx.run_until_parked();
15747        cx.update_editor(|editor, window, cx| {
15748            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
15749        });
15750        cx.run_until_parked();
15751        cx.assert_editor_state(after);
15752    };
15753
15754    // Outside bracket jumps to outside of matching bracket
15755    assert("console.logˇ(var);", "console.log(var)ˇ;");
15756    assert("console.log(var)ˇ;", "console.logˇ(var);");
15757
15758    // Inside bracket jumps to inside of matching bracket
15759    assert("console.log(ˇvar);", "console.log(varˇ);");
15760    assert("console.log(varˇ);", "console.log(ˇvar);");
15761
15762    // When outside a bracket and inside, favor jumping to the inside bracket
15763    assert(
15764        "console.log('foo', [1, 2, 3]ˇ);",
15765        "console.log(ˇ'foo', [1, 2, 3]);",
15766    );
15767    assert(
15768        "console.log(ˇ'foo', [1, 2, 3]);",
15769        "console.log('foo', [1, 2, 3]ˇ);",
15770    );
15771
15772    // Bias forward if two options are equally likely
15773    assert(
15774        "let result = curried_fun()ˇ();",
15775        "let result = curried_fun()()ˇ;",
15776    );
15777
15778    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
15779    assert(
15780        indoc! {"
15781            function test() {
15782                console.log('test')ˇ
15783            }"},
15784        indoc! {"
15785            function test() {
15786                console.logˇ('test')
15787            }"},
15788    );
15789}
15790
15791#[gpui::test]
15792async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
15793    init_test(cx, |_| {});
15794
15795    let fs = FakeFs::new(cx.executor());
15796    fs.insert_tree(
15797        path!("/a"),
15798        json!({
15799            "main.rs": "fn main() { let a = 5; }",
15800            "other.rs": "// Test file",
15801        }),
15802    )
15803    .await;
15804    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15805
15806    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15807    language_registry.add(Arc::new(Language::new(
15808        LanguageConfig {
15809            name: "Rust".into(),
15810            matcher: LanguageMatcher {
15811                path_suffixes: vec!["rs".to_string()],
15812                ..Default::default()
15813            },
15814            brackets: BracketPairConfig {
15815                pairs: vec![BracketPair {
15816                    start: "{".to_string(),
15817                    end: "}".to_string(),
15818                    close: true,
15819                    surround: true,
15820                    newline: true,
15821                }],
15822                disabled_scopes_by_bracket_ix: Vec::new(),
15823            },
15824            ..Default::default()
15825        },
15826        Some(tree_sitter_rust::LANGUAGE.into()),
15827    )));
15828    let mut fake_servers = language_registry.register_fake_lsp(
15829        "Rust",
15830        FakeLspAdapter {
15831            capabilities: lsp::ServerCapabilities {
15832                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15833                    first_trigger_character: "{".to_string(),
15834                    more_trigger_character: None,
15835                }),
15836                ..Default::default()
15837            },
15838            ..Default::default()
15839        },
15840    );
15841
15842    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15843
15844    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15845
15846    let worktree_id = workspace
15847        .update(cx, |workspace, _, cx| {
15848            workspace.project().update(cx, |project, cx| {
15849                project.worktrees(cx).next().unwrap().read(cx).id()
15850            })
15851        })
15852        .unwrap();
15853
15854    let buffer = project
15855        .update(cx, |project, cx| {
15856            project.open_local_buffer(path!("/a/main.rs"), cx)
15857        })
15858        .await
15859        .unwrap();
15860    let editor_handle = workspace
15861        .update(cx, |workspace, window, cx| {
15862            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
15863        })
15864        .unwrap()
15865        .await
15866        .unwrap()
15867        .downcast::<Editor>()
15868        .unwrap();
15869
15870    cx.executor().start_waiting();
15871    let fake_server = fake_servers.next().await.unwrap();
15872
15873    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
15874        |params, _| async move {
15875            assert_eq!(
15876                params.text_document_position.text_document.uri,
15877                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
15878            );
15879            assert_eq!(
15880                params.text_document_position.position,
15881                lsp::Position::new(0, 21),
15882            );
15883
15884            Ok(Some(vec![lsp::TextEdit {
15885                new_text: "]".to_string(),
15886                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
15887            }]))
15888        },
15889    );
15890
15891    editor_handle.update_in(cx, |editor, window, cx| {
15892        window.focus(&editor.focus_handle(cx));
15893        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15894            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
15895        });
15896        editor.handle_input("{", window, cx);
15897    });
15898
15899    cx.executor().run_until_parked();
15900
15901    buffer.update(cx, |buffer, _| {
15902        assert_eq!(
15903            buffer.text(),
15904            "fn main() { let a = {5}; }",
15905            "No extra braces from on type formatting should appear in the buffer"
15906        )
15907    });
15908}
15909
15910#[gpui::test(iterations = 20, seeds(31))]
15911async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
15912    init_test(cx, |_| {});
15913
15914    let mut cx = EditorLspTestContext::new_rust(
15915        lsp::ServerCapabilities {
15916            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
15917                first_trigger_character: ".".to_string(),
15918                more_trigger_character: None,
15919            }),
15920            ..Default::default()
15921        },
15922        cx,
15923    )
15924    .await;
15925
15926    cx.update_buffer(|buffer, _| {
15927        // This causes autoindent to be async.
15928        buffer.set_sync_parse_timeout(Duration::ZERO)
15929    });
15930
15931    cx.set_state("fn c() {\n    d()ˇ\n}\n");
15932    cx.simulate_keystroke("\n");
15933    cx.run_until_parked();
15934
15935    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
15936    let mut request =
15937        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
15938            let buffer_cloned = buffer_cloned.clone();
15939            async move {
15940                buffer_cloned.update(&mut cx, |buffer, _| {
15941                    assert_eq!(
15942                        buffer.text(),
15943                        "fn c() {\n    d()\n        .\n}\n",
15944                        "OnTypeFormatting should triggered after autoindent applied"
15945                    )
15946                })?;
15947
15948                Ok(Some(vec![]))
15949            }
15950        });
15951
15952    cx.simulate_keystroke(".");
15953    cx.run_until_parked();
15954
15955    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
15956    assert!(request.next().await.is_some());
15957    request.close();
15958    assert!(request.next().await.is_none());
15959}
15960
15961#[gpui::test]
15962async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
15963    init_test(cx, |_| {});
15964
15965    let fs = FakeFs::new(cx.executor());
15966    fs.insert_tree(
15967        path!("/a"),
15968        json!({
15969            "main.rs": "fn main() { let a = 5; }",
15970            "other.rs": "// Test file",
15971        }),
15972    )
15973    .await;
15974
15975    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15976
15977    let server_restarts = Arc::new(AtomicUsize::new(0));
15978    let closure_restarts = Arc::clone(&server_restarts);
15979    let language_server_name = "test language server";
15980    let language_name: LanguageName = "Rust".into();
15981
15982    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15983    language_registry.add(Arc::new(Language::new(
15984        LanguageConfig {
15985            name: language_name.clone(),
15986            matcher: LanguageMatcher {
15987                path_suffixes: vec!["rs".to_string()],
15988                ..Default::default()
15989            },
15990            ..Default::default()
15991        },
15992        Some(tree_sitter_rust::LANGUAGE.into()),
15993    )));
15994    let mut fake_servers = language_registry.register_fake_lsp(
15995        "Rust",
15996        FakeLspAdapter {
15997            name: language_server_name,
15998            initialization_options: Some(json!({
15999                "testOptionValue": true
16000            })),
16001            initializer: Some(Box::new(move |fake_server| {
16002                let task_restarts = Arc::clone(&closure_restarts);
16003                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
16004                    task_restarts.fetch_add(1, atomic::Ordering::Release);
16005                    futures::future::ready(Ok(()))
16006                });
16007            })),
16008            ..Default::default()
16009        },
16010    );
16011
16012    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16013    let _buffer = project
16014        .update(cx, |project, cx| {
16015            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
16016        })
16017        .await
16018        .unwrap();
16019    let _fake_server = fake_servers.next().await.unwrap();
16020    update_test_language_settings(cx, |language_settings| {
16021        language_settings.languages.0.insert(
16022            language_name.clone(),
16023            LanguageSettingsContent {
16024                tab_size: NonZeroU32::new(8),
16025                ..Default::default()
16026            },
16027        );
16028    });
16029    cx.executor().run_until_parked();
16030    assert_eq!(
16031        server_restarts.load(atomic::Ordering::Acquire),
16032        0,
16033        "Should not restart LSP server on an unrelated change"
16034    );
16035
16036    update_test_project_settings(cx, |project_settings| {
16037        project_settings.lsp.insert(
16038            "Some other server name".into(),
16039            LspSettings {
16040                binary: None,
16041                settings: None,
16042                initialization_options: Some(json!({
16043                    "some other init value": false
16044                })),
16045                enable_lsp_tasks: false,
16046            },
16047        );
16048    });
16049    cx.executor().run_until_parked();
16050    assert_eq!(
16051        server_restarts.load(atomic::Ordering::Acquire),
16052        0,
16053        "Should not restart LSP server on an unrelated LSP settings change"
16054    );
16055
16056    update_test_project_settings(cx, |project_settings| {
16057        project_settings.lsp.insert(
16058            language_server_name.into(),
16059            LspSettings {
16060                binary: None,
16061                settings: None,
16062                initialization_options: Some(json!({
16063                    "anotherInitValue": false
16064                })),
16065                enable_lsp_tasks: false,
16066            },
16067        );
16068    });
16069    cx.executor().run_until_parked();
16070    assert_eq!(
16071        server_restarts.load(atomic::Ordering::Acquire),
16072        1,
16073        "Should restart LSP server on a related LSP settings change"
16074    );
16075
16076    update_test_project_settings(cx, |project_settings| {
16077        project_settings.lsp.insert(
16078            language_server_name.into(),
16079            LspSettings {
16080                binary: None,
16081                settings: None,
16082                initialization_options: Some(json!({
16083                    "anotherInitValue": false
16084                })),
16085                enable_lsp_tasks: false,
16086            },
16087        );
16088    });
16089    cx.executor().run_until_parked();
16090    assert_eq!(
16091        server_restarts.load(atomic::Ordering::Acquire),
16092        1,
16093        "Should not restart LSP server on a related LSP settings change that is the same"
16094    );
16095
16096    update_test_project_settings(cx, |project_settings| {
16097        project_settings.lsp.insert(
16098            language_server_name.into(),
16099            LspSettings {
16100                binary: None,
16101                settings: None,
16102                initialization_options: None,
16103                enable_lsp_tasks: false,
16104            },
16105        );
16106    });
16107    cx.executor().run_until_parked();
16108    assert_eq!(
16109        server_restarts.load(atomic::Ordering::Acquire),
16110        2,
16111        "Should restart LSP server on another related LSP settings change"
16112    );
16113}
16114
16115#[gpui::test]
16116async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
16117    init_test(cx, |_| {});
16118
16119    let mut cx = EditorLspTestContext::new_rust(
16120        lsp::ServerCapabilities {
16121            completion_provider: Some(lsp::CompletionOptions {
16122                trigger_characters: Some(vec![".".to_string()]),
16123                resolve_provider: Some(true),
16124                ..Default::default()
16125            }),
16126            ..Default::default()
16127        },
16128        cx,
16129    )
16130    .await;
16131
16132    cx.set_state("fn main() { let a = 2ˇ; }");
16133    cx.simulate_keystroke(".");
16134    let completion_item = lsp::CompletionItem {
16135        label: "some".into(),
16136        kind: Some(lsp::CompletionItemKind::SNIPPET),
16137        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16138        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16139            kind: lsp::MarkupKind::Markdown,
16140            value: "```rust\nSome(2)\n```".to_string(),
16141        })),
16142        deprecated: Some(false),
16143        sort_text: Some("fffffff2".to_string()),
16144        filter_text: Some("some".to_string()),
16145        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16146        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16147            range: lsp::Range {
16148                start: lsp::Position {
16149                    line: 0,
16150                    character: 22,
16151                },
16152                end: lsp::Position {
16153                    line: 0,
16154                    character: 22,
16155                },
16156            },
16157            new_text: "Some(2)".to_string(),
16158        })),
16159        additional_text_edits: Some(vec![lsp::TextEdit {
16160            range: lsp::Range {
16161                start: lsp::Position {
16162                    line: 0,
16163                    character: 20,
16164                },
16165                end: lsp::Position {
16166                    line: 0,
16167                    character: 22,
16168                },
16169            },
16170            new_text: "".to_string(),
16171        }]),
16172        ..Default::default()
16173    };
16174
16175    let closure_completion_item = completion_item.clone();
16176    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16177        let task_completion_item = closure_completion_item.clone();
16178        async move {
16179            Ok(Some(lsp::CompletionResponse::Array(vec![
16180                task_completion_item,
16181            ])))
16182        }
16183    });
16184
16185    request.next().await;
16186
16187    cx.condition(|editor, _| editor.context_menu_visible())
16188        .await;
16189    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16190        editor
16191            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16192            .unwrap()
16193    });
16194    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
16195
16196    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16197        let task_completion_item = completion_item.clone();
16198        async move { Ok(task_completion_item) }
16199    })
16200    .next()
16201    .await
16202    .unwrap();
16203    apply_additional_edits.await.unwrap();
16204    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
16205}
16206
16207#[gpui::test]
16208async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
16209    init_test(cx, |_| {});
16210
16211    let mut cx = EditorLspTestContext::new_rust(
16212        lsp::ServerCapabilities {
16213            completion_provider: Some(lsp::CompletionOptions {
16214                trigger_characters: Some(vec![".".to_string()]),
16215                resolve_provider: Some(true),
16216                ..Default::default()
16217            }),
16218            ..Default::default()
16219        },
16220        cx,
16221    )
16222    .await;
16223
16224    cx.set_state("fn main() { let a = 2ˇ; }");
16225    cx.simulate_keystroke(".");
16226
16227    let item1 = lsp::CompletionItem {
16228        label: "method id()".to_string(),
16229        filter_text: Some("id".to_string()),
16230        detail: None,
16231        documentation: None,
16232        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16233            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16234            new_text: ".id".to_string(),
16235        })),
16236        ..lsp::CompletionItem::default()
16237    };
16238
16239    let item2 = lsp::CompletionItem {
16240        label: "other".to_string(),
16241        filter_text: Some("other".to_string()),
16242        detail: None,
16243        documentation: None,
16244        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16245            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16246            new_text: ".other".to_string(),
16247        })),
16248        ..lsp::CompletionItem::default()
16249    };
16250
16251    let item1 = item1.clone();
16252    cx.set_request_handler::<lsp::request::Completion, _, _>({
16253        let item1 = item1.clone();
16254        move |_, _, _| {
16255            let item1 = item1.clone();
16256            let item2 = item2.clone();
16257            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
16258        }
16259    })
16260    .next()
16261    .await;
16262
16263    cx.condition(|editor, _| editor.context_menu_visible())
16264        .await;
16265    cx.update_editor(|editor, _, _| {
16266        let context_menu = editor.context_menu.borrow_mut();
16267        let context_menu = context_menu
16268            .as_ref()
16269            .expect("Should have the context menu deployed");
16270        match context_menu {
16271            CodeContextMenu::Completions(completions_menu) => {
16272                let completions = completions_menu.completions.borrow_mut();
16273                assert_eq!(
16274                    completions
16275                        .iter()
16276                        .map(|completion| &completion.label.text)
16277                        .collect::<Vec<_>>(),
16278                    vec!["method id()", "other"]
16279                )
16280            }
16281            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16282        }
16283    });
16284
16285    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
16286        let item1 = item1.clone();
16287        move |_, item_to_resolve, _| {
16288            let item1 = item1.clone();
16289            async move {
16290                if item1 == item_to_resolve {
16291                    Ok(lsp::CompletionItem {
16292                        label: "method id()".to_string(),
16293                        filter_text: Some("id".to_string()),
16294                        detail: Some("Now resolved!".to_string()),
16295                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
16296                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16297                            range: lsp::Range::new(
16298                                lsp::Position::new(0, 22),
16299                                lsp::Position::new(0, 22),
16300                            ),
16301                            new_text: ".id".to_string(),
16302                        })),
16303                        ..lsp::CompletionItem::default()
16304                    })
16305                } else {
16306                    Ok(item_to_resolve)
16307                }
16308            }
16309        }
16310    })
16311    .next()
16312    .await
16313    .unwrap();
16314    cx.run_until_parked();
16315
16316    cx.update_editor(|editor, window, cx| {
16317        editor.context_menu_next(&Default::default(), window, cx);
16318    });
16319
16320    cx.update_editor(|editor, _, _| {
16321        let context_menu = editor.context_menu.borrow_mut();
16322        let context_menu = context_menu
16323            .as_ref()
16324            .expect("Should have the context menu deployed");
16325        match context_menu {
16326            CodeContextMenu::Completions(completions_menu) => {
16327                let completions = completions_menu.completions.borrow_mut();
16328                assert_eq!(
16329                    completions
16330                        .iter()
16331                        .map(|completion| &completion.label.text)
16332                        .collect::<Vec<_>>(),
16333                    vec!["method id() Now resolved!", "other"],
16334                    "Should update first completion label, but not second as the filter text did not match."
16335                );
16336            }
16337            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16338        }
16339    });
16340}
16341
16342#[gpui::test]
16343async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
16344    init_test(cx, |_| {});
16345    let mut cx = EditorLspTestContext::new_rust(
16346        lsp::ServerCapabilities {
16347            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
16348            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
16349            completion_provider: Some(lsp::CompletionOptions {
16350                resolve_provider: Some(true),
16351                ..Default::default()
16352            }),
16353            ..Default::default()
16354        },
16355        cx,
16356    )
16357    .await;
16358    cx.set_state(indoc! {"
16359        struct TestStruct {
16360            field: i32
16361        }
16362
16363        fn mainˇ() {
16364            let unused_var = 42;
16365            let test_struct = TestStruct { field: 42 };
16366        }
16367    "});
16368    let symbol_range = cx.lsp_range(indoc! {"
16369        struct TestStruct {
16370            field: i32
16371        }
16372
16373        «fn main»() {
16374            let unused_var = 42;
16375            let test_struct = TestStruct { field: 42 };
16376        }
16377    "});
16378    let mut hover_requests =
16379        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
16380            Ok(Some(lsp::Hover {
16381                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
16382                    kind: lsp::MarkupKind::Markdown,
16383                    value: "Function documentation".to_string(),
16384                }),
16385                range: Some(symbol_range),
16386            }))
16387        });
16388
16389    // Case 1: Test that code action menu hide hover popover
16390    cx.dispatch_action(Hover);
16391    hover_requests.next().await;
16392    cx.condition(|editor, _| editor.hover_state.visible()).await;
16393    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
16394        move |_, _, _| async move {
16395            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
16396                lsp::CodeAction {
16397                    title: "Remove unused variable".to_string(),
16398                    kind: Some(CodeActionKind::QUICKFIX),
16399                    edit: Some(lsp::WorkspaceEdit {
16400                        changes: Some(
16401                            [(
16402                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
16403                                vec![lsp::TextEdit {
16404                                    range: lsp::Range::new(
16405                                        lsp::Position::new(5, 4),
16406                                        lsp::Position::new(5, 27),
16407                                    ),
16408                                    new_text: "".to_string(),
16409                                }],
16410                            )]
16411                            .into_iter()
16412                            .collect(),
16413                        ),
16414                        ..Default::default()
16415                    }),
16416                    ..Default::default()
16417                },
16418            )]))
16419        },
16420    );
16421    cx.update_editor(|editor, window, cx| {
16422        editor.toggle_code_actions(
16423            &ToggleCodeActions {
16424                deployed_from: None,
16425                quick_launch: false,
16426            },
16427            window,
16428            cx,
16429        );
16430    });
16431    code_action_requests.next().await;
16432    cx.run_until_parked();
16433    cx.condition(|editor, _| editor.context_menu_visible())
16434        .await;
16435    cx.update_editor(|editor, _, _| {
16436        assert!(
16437            !editor.hover_state.visible(),
16438            "Hover popover should be hidden when code action menu is shown"
16439        );
16440        // Hide code actions
16441        editor.context_menu.take();
16442    });
16443
16444    // Case 2: Test that code completions hide hover popover
16445    cx.dispatch_action(Hover);
16446    hover_requests.next().await;
16447    cx.condition(|editor, _| editor.hover_state.visible()).await;
16448    let counter = Arc::new(AtomicUsize::new(0));
16449    let mut completion_requests =
16450        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16451            let counter = counter.clone();
16452            async move {
16453                counter.fetch_add(1, atomic::Ordering::Release);
16454                Ok(Some(lsp::CompletionResponse::Array(vec![
16455                    lsp::CompletionItem {
16456                        label: "main".into(),
16457                        kind: Some(lsp::CompletionItemKind::FUNCTION),
16458                        detail: Some("() -> ()".to_string()),
16459                        ..Default::default()
16460                    },
16461                    lsp::CompletionItem {
16462                        label: "TestStruct".into(),
16463                        kind: Some(lsp::CompletionItemKind::STRUCT),
16464                        detail: Some("struct TestStruct".to_string()),
16465                        ..Default::default()
16466                    },
16467                ])))
16468            }
16469        });
16470    cx.update_editor(|editor, window, cx| {
16471        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
16472    });
16473    completion_requests.next().await;
16474    cx.condition(|editor, _| editor.context_menu_visible())
16475        .await;
16476    cx.update_editor(|editor, _, _| {
16477        assert!(
16478            !editor.hover_state.visible(),
16479            "Hover popover should be hidden when completion menu is shown"
16480        );
16481    });
16482}
16483
16484#[gpui::test]
16485async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
16486    init_test(cx, |_| {});
16487
16488    let mut cx = EditorLspTestContext::new_rust(
16489        lsp::ServerCapabilities {
16490            completion_provider: Some(lsp::CompletionOptions {
16491                trigger_characters: Some(vec![".".to_string()]),
16492                resolve_provider: Some(true),
16493                ..Default::default()
16494            }),
16495            ..Default::default()
16496        },
16497        cx,
16498    )
16499    .await;
16500
16501    cx.set_state("fn main() { let a = 2ˇ; }");
16502    cx.simulate_keystroke(".");
16503
16504    let unresolved_item_1 = lsp::CompletionItem {
16505        label: "id".to_string(),
16506        filter_text: Some("id".to_string()),
16507        detail: None,
16508        documentation: None,
16509        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16510            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16511            new_text: ".id".to_string(),
16512        })),
16513        ..lsp::CompletionItem::default()
16514    };
16515    let resolved_item_1 = lsp::CompletionItem {
16516        additional_text_edits: Some(vec![lsp::TextEdit {
16517            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16518            new_text: "!!".to_string(),
16519        }]),
16520        ..unresolved_item_1.clone()
16521    };
16522    let unresolved_item_2 = lsp::CompletionItem {
16523        label: "other".to_string(),
16524        filter_text: Some("other".to_string()),
16525        detail: None,
16526        documentation: None,
16527        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16528            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16529            new_text: ".other".to_string(),
16530        })),
16531        ..lsp::CompletionItem::default()
16532    };
16533    let resolved_item_2 = lsp::CompletionItem {
16534        additional_text_edits: Some(vec![lsp::TextEdit {
16535            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16536            new_text: "??".to_string(),
16537        }]),
16538        ..unresolved_item_2.clone()
16539    };
16540
16541    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
16542    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
16543    cx.lsp
16544        .server
16545        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16546            let unresolved_item_1 = unresolved_item_1.clone();
16547            let resolved_item_1 = resolved_item_1.clone();
16548            let unresolved_item_2 = unresolved_item_2.clone();
16549            let resolved_item_2 = resolved_item_2.clone();
16550            let resolve_requests_1 = resolve_requests_1.clone();
16551            let resolve_requests_2 = resolve_requests_2.clone();
16552            move |unresolved_request, _| {
16553                let unresolved_item_1 = unresolved_item_1.clone();
16554                let resolved_item_1 = resolved_item_1.clone();
16555                let unresolved_item_2 = unresolved_item_2.clone();
16556                let resolved_item_2 = resolved_item_2.clone();
16557                let resolve_requests_1 = resolve_requests_1.clone();
16558                let resolve_requests_2 = resolve_requests_2.clone();
16559                async move {
16560                    if unresolved_request == unresolved_item_1 {
16561                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
16562                        Ok(resolved_item_1.clone())
16563                    } else if unresolved_request == unresolved_item_2 {
16564                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
16565                        Ok(resolved_item_2.clone())
16566                    } else {
16567                        panic!("Unexpected completion item {unresolved_request:?}")
16568                    }
16569                }
16570            }
16571        })
16572        .detach();
16573
16574    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16575        let unresolved_item_1 = unresolved_item_1.clone();
16576        let unresolved_item_2 = unresolved_item_2.clone();
16577        async move {
16578            Ok(Some(lsp::CompletionResponse::Array(vec![
16579                unresolved_item_1,
16580                unresolved_item_2,
16581            ])))
16582        }
16583    })
16584    .next()
16585    .await;
16586
16587    cx.condition(|editor, _| editor.context_menu_visible())
16588        .await;
16589    cx.update_editor(|editor, _, _| {
16590        let context_menu = editor.context_menu.borrow_mut();
16591        let context_menu = context_menu
16592            .as_ref()
16593            .expect("Should have the context menu deployed");
16594        match context_menu {
16595            CodeContextMenu::Completions(completions_menu) => {
16596                let completions = completions_menu.completions.borrow_mut();
16597                assert_eq!(
16598                    completions
16599                        .iter()
16600                        .map(|completion| &completion.label.text)
16601                        .collect::<Vec<_>>(),
16602                    vec!["id", "other"]
16603                )
16604            }
16605            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16606        }
16607    });
16608    cx.run_until_parked();
16609
16610    cx.update_editor(|editor, window, cx| {
16611        editor.context_menu_next(&ContextMenuNext, window, cx);
16612    });
16613    cx.run_until_parked();
16614    cx.update_editor(|editor, window, cx| {
16615        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16616    });
16617    cx.run_until_parked();
16618    cx.update_editor(|editor, window, cx| {
16619        editor.context_menu_next(&ContextMenuNext, window, cx);
16620    });
16621    cx.run_until_parked();
16622    cx.update_editor(|editor, window, cx| {
16623        editor
16624            .compose_completion(&ComposeCompletion::default(), window, cx)
16625            .expect("No task returned")
16626    })
16627    .await
16628    .expect("Completion failed");
16629    cx.run_until_parked();
16630
16631    cx.update_editor(|editor, _, cx| {
16632        assert_eq!(
16633            resolve_requests_1.load(atomic::Ordering::Acquire),
16634            1,
16635            "Should always resolve once despite multiple selections"
16636        );
16637        assert_eq!(
16638            resolve_requests_2.load(atomic::Ordering::Acquire),
16639            1,
16640            "Should always resolve once after multiple selections and applying the completion"
16641        );
16642        assert_eq!(
16643            editor.text(cx),
16644            "fn main() { let a = ??.other; }",
16645            "Should use resolved data when applying the completion"
16646        );
16647    });
16648}
16649
16650#[gpui::test]
16651async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
16652    init_test(cx, |_| {});
16653
16654    let item_0 = lsp::CompletionItem {
16655        label: "abs".into(),
16656        insert_text: Some("abs".into()),
16657        data: Some(json!({ "very": "special"})),
16658        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
16659        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
16660            lsp::InsertReplaceEdit {
16661                new_text: "abs".to_string(),
16662                insert: lsp::Range::default(),
16663                replace: lsp::Range::default(),
16664            },
16665        )),
16666        ..lsp::CompletionItem::default()
16667    };
16668    let items = iter::once(item_0.clone())
16669        .chain((11..51).map(|i| lsp::CompletionItem {
16670            label: format!("item_{}", i),
16671            insert_text: Some(format!("item_{}", i)),
16672            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16673            ..lsp::CompletionItem::default()
16674        }))
16675        .collect::<Vec<_>>();
16676
16677    let default_commit_characters = vec!["?".to_string()];
16678    let default_data = json!({ "default": "data"});
16679    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
16680    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
16681    let default_edit_range = lsp::Range {
16682        start: lsp::Position {
16683            line: 0,
16684            character: 5,
16685        },
16686        end: lsp::Position {
16687            line: 0,
16688            character: 5,
16689        },
16690    };
16691
16692    let mut cx = EditorLspTestContext::new_rust(
16693        lsp::ServerCapabilities {
16694            completion_provider: Some(lsp::CompletionOptions {
16695                trigger_characters: Some(vec![".".to_string()]),
16696                resolve_provider: Some(true),
16697                ..Default::default()
16698            }),
16699            ..Default::default()
16700        },
16701        cx,
16702    )
16703    .await;
16704
16705    cx.set_state("fn main() { let a = 2ˇ; }");
16706    cx.simulate_keystroke(".");
16707
16708    let completion_data = default_data.clone();
16709    let completion_characters = default_commit_characters.clone();
16710    let completion_items = items.clone();
16711    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16712        let default_data = completion_data.clone();
16713        let default_commit_characters = completion_characters.clone();
16714        let items = completion_items.clone();
16715        async move {
16716            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16717                items,
16718                item_defaults: Some(lsp::CompletionListItemDefaults {
16719                    data: Some(default_data.clone()),
16720                    commit_characters: Some(default_commit_characters.clone()),
16721                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
16722                        default_edit_range,
16723                    )),
16724                    insert_text_format: Some(default_insert_text_format),
16725                    insert_text_mode: Some(default_insert_text_mode),
16726                }),
16727                ..lsp::CompletionList::default()
16728            })))
16729        }
16730    })
16731    .next()
16732    .await;
16733
16734    let resolved_items = Arc::new(Mutex::new(Vec::new()));
16735    cx.lsp
16736        .server
16737        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16738            let closure_resolved_items = resolved_items.clone();
16739            move |item_to_resolve, _| {
16740                let closure_resolved_items = closure_resolved_items.clone();
16741                async move {
16742                    closure_resolved_items.lock().push(item_to_resolve.clone());
16743                    Ok(item_to_resolve)
16744                }
16745            }
16746        })
16747        .detach();
16748
16749    cx.condition(|editor, _| editor.context_menu_visible())
16750        .await;
16751    cx.run_until_parked();
16752    cx.update_editor(|editor, _, _| {
16753        let menu = editor.context_menu.borrow_mut();
16754        match menu.as_ref().expect("should have the completions menu") {
16755            CodeContextMenu::Completions(completions_menu) => {
16756                assert_eq!(
16757                    completions_menu
16758                        .entries
16759                        .borrow()
16760                        .iter()
16761                        .map(|mat| mat.string.clone())
16762                        .collect::<Vec<String>>(),
16763                    items
16764                        .iter()
16765                        .map(|completion| completion.label.clone())
16766                        .collect::<Vec<String>>()
16767                );
16768            }
16769            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
16770        }
16771    });
16772    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
16773    // with 4 from the end.
16774    assert_eq!(
16775        *resolved_items.lock(),
16776        [&items[0..16], &items[items.len() - 4..items.len()]]
16777            .concat()
16778            .iter()
16779            .cloned()
16780            .map(|mut item| {
16781                if item.data.is_none() {
16782                    item.data = Some(default_data.clone());
16783                }
16784                item
16785            })
16786            .collect::<Vec<lsp::CompletionItem>>(),
16787        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
16788    );
16789    resolved_items.lock().clear();
16790
16791    cx.update_editor(|editor, window, cx| {
16792        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
16793    });
16794    cx.run_until_parked();
16795    // Completions that have already been resolved are skipped.
16796    assert_eq!(
16797        *resolved_items.lock(),
16798        items[items.len() - 17..items.len() - 4]
16799            .iter()
16800            .cloned()
16801            .map(|mut item| {
16802                if item.data.is_none() {
16803                    item.data = Some(default_data.clone());
16804                }
16805                item
16806            })
16807            .collect::<Vec<lsp::CompletionItem>>()
16808    );
16809    resolved_items.lock().clear();
16810}
16811
16812#[gpui::test]
16813async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
16814    init_test(cx, |_| {});
16815
16816    let mut cx = EditorLspTestContext::new(
16817        Language::new(
16818            LanguageConfig {
16819                matcher: LanguageMatcher {
16820                    path_suffixes: vec!["jsx".into()],
16821                    ..Default::default()
16822                },
16823                overrides: [(
16824                    "element".into(),
16825                    LanguageConfigOverride {
16826                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
16827                        ..Default::default()
16828                    },
16829                )]
16830                .into_iter()
16831                .collect(),
16832                ..Default::default()
16833            },
16834            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16835        )
16836        .with_override_query("(jsx_self_closing_element) @element")
16837        .unwrap(),
16838        lsp::ServerCapabilities {
16839            completion_provider: Some(lsp::CompletionOptions {
16840                trigger_characters: Some(vec![":".to_string()]),
16841                ..Default::default()
16842            }),
16843            ..Default::default()
16844        },
16845        cx,
16846    )
16847    .await;
16848
16849    cx.lsp
16850        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16851            Ok(Some(lsp::CompletionResponse::Array(vec![
16852                lsp::CompletionItem {
16853                    label: "bg-blue".into(),
16854                    ..Default::default()
16855                },
16856                lsp::CompletionItem {
16857                    label: "bg-red".into(),
16858                    ..Default::default()
16859                },
16860                lsp::CompletionItem {
16861                    label: "bg-yellow".into(),
16862                    ..Default::default()
16863                },
16864            ])))
16865        });
16866
16867    cx.set_state(r#"<p class="bgˇ" />"#);
16868
16869    // Trigger completion when typing a dash, because the dash is an extra
16870    // word character in the 'element' scope, which contains the cursor.
16871    cx.simulate_keystroke("-");
16872    cx.executor().run_until_parked();
16873    cx.update_editor(|editor, _, _| {
16874        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16875        {
16876            assert_eq!(
16877                completion_menu_entries(menu),
16878                &["bg-blue", "bg-red", "bg-yellow"]
16879            );
16880        } else {
16881            panic!("expected completion menu to be open");
16882        }
16883    });
16884
16885    cx.simulate_keystroke("l");
16886    cx.executor().run_until_parked();
16887    cx.update_editor(|editor, _, _| {
16888        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16889        {
16890            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
16891        } else {
16892            panic!("expected completion menu to be open");
16893        }
16894    });
16895
16896    // When filtering completions, consider the character after the '-' to
16897    // be the start of a subword.
16898    cx.set_state(r#"<p class="yelˇ" />"#);
16899    cx.simulate_keystroke("l");
16900    cx.executor().run_until_parked();
16901    cx.update_editor(|editor, _, _| {
16902        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
16903        {
16904            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
16905        } else {
16906            panic!("expected completion menu to be open");
16907        }
16908    });
16909}
16910
16911fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
16912    let entries = menu.entries.borrow();
16913    entries.iter().map(|mat| mat.string.clone()).collect()
16914}
16915
16916#[gpui::test]
16917async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
16918    init_test(cx, |settings| {
16919        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
16920            Formatter::Prettier,
16921        )))
16922    });
16923
16924    let fs = FakeFs::new(cx.executor());
16925    fs.insert_file(path!("/file.ts"), Default::default()).await;
16926
16927    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
16928    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16929
16930    language_registry.add(Arc::new(Language::new(
16931        LanguageConfig {
16932            name: "TypeScript".into(),
16933            matcher: LanguageMatcher {
16934                path_suffixes: vec!["ts".to_string()],
16935                ..Default::default()
16936            },
16937            ..Default::default()
16938        },
16939        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
16940    )));
16941    update_test_language_settings(cx, |settings| {
16942        settings.defaults.prettier = Some(PrettierSettings {
16943            allowed: true,
16944            ..PrettierSettings::default()
16945        });
16946    });
16947
16948    let test_plugin = "test_plugin";
16949    let _ = language_registry.register_fake_lsp(
16950        "TypeScript",
16951        FakeLspAdapter {
16952            prettier_plugins: vec![test_plugin],
16953            ..Default::default()
16954        },
16955    );
16956
16957    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
16958    let buffer = project
16959        .update(cx, |project, cx| {
16960            project.open_local_buffer(path!("/file.ts"), cx)
16961        })
16962        .await
16963        .unwrap();
16964
16965    let buffer_text = "one\ntwo\nthree\n";
16966    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16967    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16968    editor.update_in(cx, |editor, window, cx| {
16969        editor.set_text(buffer_text, window, cx)
16970    });
16971
16972    editor
16973        .update_in(cx, |editor, window, cx| {
16974            editor.perform_format(
16975                project.clone(),
16976                FormatTrigger::Manual,
16977                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16978                window,
16979                cx,
16980            )
16981        })
16982        .unwrap()
16983        .await;
16984    assert_eq!(
16985        editor.update(cx, |editor, cx| editor.text(cx)),
16986        buffer_text.to_string() + prettier_format_suffix,
16987        "Test prettier formatting was not applied to the original buffer text",
16988    );
16989
16990    update_test_language_settings(cx, |settings| {
16991        settings.defaults.formatter = Some(SelectedFormatter::Auto)
16992    });
16993    let format = editor.update_in(cx, |editor, window, cx| {
16994        editor.perform_format(
16995            project.clone(),
16996            FormatTrigger::Manual,
16997            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
16998            window,
16999            cx,
17000        )
17001    });
17002    format.await.unwrap();
17003    assert_eq!(
17004        editor.update(cx, |editor, cx| editor.text(cx)),
17005        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
17006        "Autoformatting (via test prettier) was not applied to the original buffer text",
17007    );
17008}
17009
17010#[gpui::test]
17011async fn test_addition_reverts(cx: &mut TestAppContext) {
17012    init_test(cx, |_| {});
17013    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17014    let base_text = indoc! {r#"
17015        struct Row;
17016        struct Row1;
17017        struct Row2;
17018
17019        struct Row4;
17020        struct Row5;
17021        struct Row6;
17022
17023        struct Row8;
17024        struct Row9;
17025        struct Row10;"#};
17026
17027    // When addition hunks are not adjacent to carets, no hunk revert is performed
17028    assert_hunk_revert(
17029        indoc! {r#"struct Row;
17030                   struct Row1;
17031                   struct Row1.1;
17032                   struct Row1.2;
17033                   struct Row2;ˇ
17034
17035                   struct Row4;
17036                   struct Row5;
17037                   struct Row6;
17038
17039                   struct Row8;
17040                   ˇstruct Row9;
17041                   struct Row9.1;
17042                   struct Row9.2;
17043                   struct Row9.3;
17044                   struct Row10;"#},
17045        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
17046        indoc! {r#"struct Row;
17047                   struct Row1;
17048                   struct Row1.1;
17049                   struct Row1.2;
17050                   struct Row2;ˇ
17051
17052                   struct Row4;
17053                   struct Row5;
17054                   struct Row6;
17055
17056                   struct Row8;
17057                   ˇstruct Row9;
17058                   struct Row9.1;
17059                   struct Row9.2;
17060                   struct Row9.3;
17061                   struct Row10;"#},
17062        base_text,
17063        &mut cx,
17064    );
17065    // Same for selections
17066    assert_hunk_revert(
17067        indoc! {r#"struct Row;
17068                   struct Row1;
17069                   struct Row2;
17070                   struct Row2.1;
17071                   struct Row2.2;
17072                   «ˇ
17073                   struct Row4;
17074                   struct» Row5;
17075                   «struct Row6;
17076                   ˇ»
17077                   struct Row9.1;
17078                   struct Row9.2;
17079                   struct Row9.3;
17080                   struct Row8;
17081                   struct Row9;
17082                   struct Row10;"#},
17083        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
17084        indoc! {r#"struct Row;
17085                   struct Row1;
17086                   struct Row2;
17087                   struct Row2.1;
17088                   struct Row2.2;
17089                   «ˇ
17090                   struct Row4;
17091                   struct» Row5;
17092                   «struct Row6;
17093                   ˇ»
17094                   struct Row9.1;
17095                   struct Row9.2;
17096                   struct Row9.3;
17097                   struct Row8;
17098                   struct Row9;
17099                   struct Row10;"#},
17100        base_text,
17101        &mut cx,
17102    );
17103
17104    // When carets and selections intersect the addition hunks, those are reverted.
17105    // Adjacent carets got merged.
17106    assert_hunk_revert(
17107        indoc! {r#"struct Row;
17108                   ˇ// something on the top
17109                   struct Row1;
17110                   struct Row2;
17111                   struct Roˇw3.1;
17112                   struct Row2.2;
17113                   struct Row2.3;ˇ
17114
17115                   struct Row4;
17116                   struct ˇRow5.1;
17117                   struct Row5.2;
17118                   struct «Rowˇ»5.3;
17119                   struct Row5;
17120                   struct Row6;
17121                   ˇ
17122                   struct Row9.1;
17123                   struct «Rowˇ»9.2;
17124                   struct «ˇRow»9.3;
17125                   struct Row8;
17126                   struct Row9;
17127                   «ˇ// something on bottom»
17128                   struct Row10;"#},
17129        vec![
17130            DiffHunkStatusKind::Added,
17131            DiffHunkStatusKind::Added,
17132            DiffHunkStatusKind::Added,
17133            DiffHunkStatusKind::Added,
17134            DiffHunkStatusKind::Added,
17135        ],
17136        indoc! {r#"struct Row;
17137                   ˇstruct Row1;
17138                   struct Row2;
17139                   ˇ
17140                   struct Row4;
17141                   ˇstruct Row5;
17142                   struct Row6;
17143                   ˇ
17144                   ˇstruct Row8;
17145                   struct Row9;
17146                   ˇstruct Row10;"#},
17147        base_text,
17148        &mut cx,
17149    );
17150}
17151
17152#[gpui::test]
17153async fn test_modification_reverts(cx: &mut TestAppContext) {
17154    init_test(cx, |_| {});
17155    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17156    let base_text = indoc! {r#"
17157        struct Row;
17158        struct Row1;
17159        struct Row2;
17160
17161        struct Row4;
17162        struct Row5;
17163        struct Row6;
17164
17165        struct Row8;
17166        struct Row9;
17167        struct Row10;"#};
17168
17169    // Modification hunks behave the same as the addition ones.
17170    assert_hunk_revert(
17171        indoc! {r#"struct Row;
17172                   struct Row1;
17173                   struct Row33;
17174                   ˇ
17175                   struct Row4;
17176                   struct Row5;
17177                   struct Row6;
17178                   ˇ
17179                   struct Row99;
17180                   struct Row9;
17181                   struct Row10;"#},
17182        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17183        indoc! {r#"struct Row;
17184                   struct Row1;
17185                   struct Row33;
17186                   ˇ
17187                   struct Row4;
17188                   struct Row5;
17189                   struct Row6;
17190                   ˇ
17191                   struct Row99;
17192                   struct Row9;
17193                   struct Row10;"#},
17194        base_text,
17195        &mut cx,
17196    );
17197    assert_hunk_revert(
17198        indoc! {r#"struct Row;
17199                   struct Row1;
17200                   struct Row33;
17201                   «ˇ
17202                   struct Row4;
17203                   struct» Row5;
17204                   «struct Row6;
17205                   ˇ»
17206                   struct Row99;
17207                   struct Row9;
17208                   struct Row10;"#},
17209        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17210        indoc! {r#"struct Row;
17211                   struct Row1;
17212                   struct Row33;
17213                   «ˇ
17214                   struct Row4;
17215                   struct» Row5;
17216                   «struct Row6;
17217                   ˇ»
17218                   struct Row99;
17219                   struct Row9;
17220                   struct Row10;"#},
17221        base_text,
17222        &mut cx,
17223    );
17224
17225    assert_hunk_revert(
17226        indoc! {r#"ˇstruct Row1.1;
17227                   struct Row1;
17228                   «ˇstr»uct Row22;
17229
17230                   struct ˇRow44;
17231                   struct Row5;
17232                   struct «Rˇ»ow66;ˇ
17233
17234                   «struˇ»ct Row88;
17235                   struct Row9;
17236                   struct Row1011;ˇ"#},
17237        vec![
17238            DiffHunkStatusKind::Modified,
17239            DiffHunkStatusKind::Modified,
17240            DiffHunkStatusKind::Modified,
17241            DiffHunkStatusKind::Modified,
17242            DiffHunkStatusKind::Modified,
17243            DiffHunkStatusKind::Modified,
17244        ],
17245        indoc! {r#"struct Row;
17246                   ˇstruct Row1;
17247                   struct Row2;
17248                   ˇ
17249                   struct Row4;
17250                   ˇstruct Row5;
17251                   struct Row6;
17252                   ˇ
17253                   struct Row8;
17254                   ˇstruct Row9;
17255                   struct Row10;ˇ"#},
17256        base_text,
17257        &mut cx,
17258    );
17259}
17260
17261#[gpui::test]
17262async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
17263    init_test(cx, |_| {});
17264    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17265    let base_text = indoc! {r#"
17266        one
17267
17268        two
17269        three
17270        "#};
17271
17272    cx.set_head_text(base_text);
17273    cx.set_state("\nˇ\n");
17274    cx.executor().run_until_parked();
17275    cx.update_editor(|editor, _window, cx| {
17276        editor.expand_selected_diff_hunks(cx);
17277    });
17278    cx.executor().run_until_parked();
17279    cx.update_editor(|editor, window, cx| {
17280        editor.backspace(&Default::default(), window, cx);
17281    });
17282    cx.run_until_parked();
17283    cx.assert_state_with_diff(
17284        indoc! {r#"
17285
17286        - two
17287        - threeˇ
17288        +
17289        "#}
17290        .to_string(),
17291    );
17292}
17293
17294#[gpui::test]
17295async fn test_deletion_reverts(cx: &mut TestAppContext) {
17296    init_test(cx, |_| {});
17297    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17298    let base_text = indoc! {r#"struct Row;
17299struct Row1;
17300struct Row2;
17301
17302struct Row4;
17303struct Row5;
17304struct Row6;
17305
17306struct Row8;
17307struct Row9;
17308struct Row10;"#};
17309
17310    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
17311    assert_hunk_revert(
17312        indoc! {r#"struct Row;
17313                   struct Row2;
17314
17315                   ˇstruct Row4;
17316                   struct Row5;
17317                   struct Row6;
17318                   ˇ
17319                   struct Row8;
17320                   struct Row10;"#},
17321        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17322        indoc! {r#"struct Row;
17323                   struct Row2;
17324
17325                   ˇstruct Row4;
17326                   struct Row5;
17327                   struct Row6;
17328                   ˇ
17329                   struct Row8;
17330                   struct Row10;"#},
17331        base_text,
17332        &mut cx,
17333    );
17334    assert_hunk_revert(
17335        indoc! {r#"struct Row;
17336                   struct Row2;
17337
17338                   «ˇstruct Row4;
17339                   struct» Row5;
17340                   «struct Row6;
17341                   ˇ»
17342                   struct Row8;
17343                   struct Row10;"#},
17344        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17345        indoc! {r#"struct Row;
17346                   struct Row2;
17347
17348                   «ˇstruct Row4;
17349                   struct» Row5;
17350                   «struct Row6;
17351                   ˇ»
17352                   struct Row8;
17353                   struct Row10;"#},
17354        base_text,
17355        &mut cx,
17356    );
17357
17358    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
17359    assert_hunk_revert(
17360        indoc! {r#"struct Row;
17361                   ˇstruct Row2;
17362
17363                   struct Row4;
17364                   struct Row5;
17365                   struct Row6;
17366
17367                   struct Row8;ˇ
17368                   struct Row10;"#},
17369        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17370        indoc! {r#"struct Row;
17371                   struct Row1;
17372                   ˇstruct Row2;
17373
17374                   struct Row4;
17375                   struct Row5;
17376                   struct Row6;
17377
17378                   struct Row8;ˇ
17379                   struct Row9;
17380                   struct Row10;"#},
17381        base_text,
17382        &mut cx,
17383    );
17384    assert_hunk_revert(
17385        indoc! {r#"struct Row;
17386                   struct Row2«ˇ;
17387                   struct Row4;
17388                   struct» Row5;
17389                   «struct Row6;
17390
17391                   struct Row8;ˇ»
17392                   struct Row10;"#},
17393        vec![
17394            DiffHunkStatusKind::Deleted,
17395            DiffHunkStatusKind::Deleted,
17396            DiffHunkStatusKind::Deleted,
17397        ],
17398        indoc! {r#"struct Row;
17399                   struct Row1;
17400                   struct Row2«ˇ;
17401
17402                   struct Row4;
17403                   struct» Row5;
17404                   «struct Row6;
17405
17406                   struct Row8;ˇ»
17407                   struct Row9;
17408                   struct Row10;"#},
17409        base_text,
17410        &mut cx,
17411    );
17412}
17413
17414#[gpui::test]
17415async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
17416    init_test(cx, |_| {});
17417
17418    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
17419    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
17420    let base_text_3 =
17421        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
17422
17423    let text_1 = edit_first_char_of_every_line(base_text_1);
17424    let text_2 = edit_first_char_of_every_line(base_text_2);
17425    let text_3 = edit_first_char_of_every_line(base_text_3);
17426
17427    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
17428    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
17429    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
17430
17431    let multibuffer = cx.new(|cx| {
17432        let mut multibuffer = MultiBuffer::new(ReadWrite);
17433        multibuffer.push_excerpts(
17434            buffer_1.clone(),
17435            [
17436                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17437                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17438                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17439            ],
17440            cx,
17441        );
17442        multibuffer.push_excerpts(
17443            buffer_2.clone(),
17444            [
17445                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17446                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17447                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17448            ],
17449            cx,
17450        );
17451        multibuffer.push_excerpts(
17452            buffer_3.clone(),
17453            [
17454                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17455                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17456                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17457            ],
17458            cx,
17459        );
17460        multibuffer
17461    });
17462
17463    let fs = FakeFs::new(cx.executor());
17464    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
17465    let (editor, cx) = cx
17466        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
17467    editor.update_in(cx, |editor, _window, cx| {
17468        for (buffer, diff_base) in [
17469            (buffer_1.clone(), base_text_1),
17470            (buffer_2.clone(), base_text_2),
17471            (buffer_3.clone(), base_text_3),
17472        ] {
17473            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
17474            editor
17475                .buffer
17476                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17477        }
17478    });
17479    cx.executor().run_until_parked();
17480
17481    editor.update_in(cx, |editor, window, cx| {
17482        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}");
17483        editor.select_all(&SelectAll, window, cx);
17484        editor.git_restore(&Default::default(), window, cx);
17485    });
17486    cx.executor().run_until_parked();
17487
17488    // When all ranges are selected, all buffer hunks are reverted.
17489    editor.update(cx, |editor, cx| {
17490        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");
17491    });
17492    buffer_1.update(cx, |buffer, _| {
17493        assert_eq!(buffer.text(), base_text_1);
17494    });
17495    buffer_2.update(cx, |buffer, _| {
17496        assert_eq!(buffer.text(), base_text_2);
17497    });
17498    buffer_3.update(cx, |buffer, _| {
17499        assert_eq!(buffer.text(), base_text_3);
17500    });
17501
17502    editor.update_in(cx, |editor, window, cx| {
17503        editor.undo(&Default::default(), window, cx);
17504    });
17505
17506    editor.update_in(cx, |editor, window, cx| {
17507        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17508            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
17509        });
17510        editor.git_restore(&Default::default(), window, cx);
17511    });
17512
17513    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
17514    // but not affect buffer_2 and its related excerpts.
17515    editor.update(cx, |editor, cx| {
17516        assert_eq!(
17517            editor.text(cx),
17518            "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}"
17519        );
17520    });
17521    buffer_1.update(cx, |buffer, _| {
17522        assert_eq!(buffer.text(), base_text_1);
17523    });
17524    buffer_2.update(cx, |buffer, _| {
17525        assert_eq!(
17526            buffer.text(),
17527            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
17528        );
17529    });
17530    buffer_3.update(cx, |buffer, _| {
17531        assert_eq!(
17532            buffer.text(),
17533            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
17534        );
17535    });
17536
17537    fn edit_first_char_of_every_line(text: &str) -> String {
17538        text.split('\n')
17539            .map(|line| format!("X{}", &line[1..]))
17540            .collect::<Vec<_>>()
17541            .join("\n")
17542    }
17543}
17544
17545#[gpui::test]
17546async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
17547    init_test(cx, |_| {});
17548
17549    let cols = 4;
17550    let rows = 10;
17551    let sample_text_1 = sample_text(rows, cols, 'a');
17552    assert_eq!(
17553        sample_text_1,
17554        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
17555    );
17556    let sample_text_2 = sample_text(rows, cols, 'l');
17557    assert_eq!(
17558        sample_text_2,
17559        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
17560    );
17561    let sample_text_3 = sample_text(rows, cols, 'v');
17562    assert_eq!(
17563        sample_text_3,
17564        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
17565    );
17566
17567    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
17568    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
17569    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
17570
17571    let multi_buffer = cx.new(|cx| {
17572        let mut multibuffer = MultiBuffer::new(ReadWrite);
17573        multibuffer.push_excerpts(
17574            buffer_1.clone(),
17575            [
17576                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17577                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17578                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17579            ],
17580            cx,
17581        );
17582        multibuffer.push_excerpts(
17583            buffer_2.clone(),
17584            [
17585                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17586                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17587                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17588            ],
17589            cx,
17590        );
17591        multibuffer.push_excerpts(
17592            buffer_3.clone(),
17593            [
17594                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17595                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17596                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17597            ],
17598            cx,
17599        );
17600        multibuffer
17601    });
17602
17603    let fs = FakeFs::new(cx.executor());
17604    fs.insert_tree(
17605        "/a",
17606        json!({
17607            "main.rs": sample_text_1,
17608            "other.rs": sample_text_2,
17609            "lib.rs": sample_text_3,
17610        }),
17611    )
17612    .await;
17613    let project = Project::test(fs, ["/a".as_ref()], cx).await;
17614    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17615    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17616    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
17617        Editor::new(
17618            EditorMode::full(),
17619            multi_buffer,
17620            Some(project.clone()),
17621            window,
17622            cx,
17623        )
17624    });
17625    let multibuffer_item_id = workspace
17626        .update(cx, |workspace, window, cx| {
17627            assert!(
17628                workspace.active_item(cx).is_none(),
17629                "active item should be None before the first item is added"
17630            );
17631            workspace.add_item_to_active_pane(
17632                Box::new(multi_buffer_editor.clone()),
17633                None,
17634                true,
17635                window,
17636                cx,
17637            );
17638            let active_item = workspace
17639                .active_item(cx)
17640                .expect("should have an active item after adding the multi buffer");
17641            assert!(
17642                !active_item.is_singleton(cx),
17643                "A multi buffer was expected to active after adding"
17644            );
17645            active_item.item_id()
17646        })
17647        .unwrap();
17648    cx.executor().run_until_parked();
17649
17650    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17651        editor.change_selections(
17652            SelectionEffects::scroll(Autoscroll::Next),
17653            window,
17654            cx,
17655            |s| s.select_ranges(Some(1..2)),
17656        );
17657        editor.open_excerpts(&OpenExcerpts, window, cx);
17658    });
17659    cx.executor().run_until_parked();
17660    let first_item_id = workspace
17661        .update(cx, |workspace, window, cx| {
17662            let active_item = workspace
17663                .active_item(cx)
17664                .expect("should have an active item after navigating into the 1st buffer");
17665            let first_item_id = active_item.item_id();
17666            assert_ne!(
17667                first_item_id, multibuffer_item_id,
17668                "Should navigate into the 1st buffer and activate it"
17669            );
17670            assert!(
17671                active_item.is_singleton(cx),
17672                "New active item should be a singleton buffer"
17673            );
17674            assert_eq!(
17675                active_item
17676                    .act_as::<Editor>(cx)
17677                    .expect("should have navigated into an editor for the 1st buffer")
17678                    .read(cx)
17679                    .text(cx),
17680                sample_text_1
17681            );
17682
17683            workspace
17684                .go_back(workspace.active_pane().downgrade(), window, cx)
17685                .detach_and_log_err(cx);
17686
17687            first_item_id
17688        })
17689        .unwrap();
17690    cx.executor().run_until_parked();
17691    workspace
17692        .update(cx, |workspace, _, cx| {
17693            let active_item = workspace
17694                .active_item(cx)
17695                .expect("should have an active item after navigating back");
17696            assert_eq!(
17697                active_item.item_id(),
17698                multibuffer_item_id,
17699                "Should navigate back to the multi buffer"
17700            );
17701            assert!(!active_item.is_singleton(cx));
17702        })
17703        .unwrap();
17704
17705    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17706        editor.change_selections(
17707            SelectionEffects::scroll(Autoscroll::Next),
17708            window,
17709            cx,
17710            |s| s.select_ranges(Some(39..40)),
17711        );
17712        editor.open_excerpts(&OpenExcerpts, window, cx);
17713    });
17714    cx.executor().run_until_parked();
17715    let second_item_id = workspace
17716        .update(cx, |workspace, window, cx| {
17717            let active_item = workspace
17718                .active_item(cx)
17719                .expect("should have an active item after navigating into the 2nd buffer");
17720            let second_item_id = active_item.item_id();
17721            assert_ne!(
17722                second_item_id, multibuffer_item_id,
17723                "Should navigate away from the multibuffer"
17724            );
17725            assert_ne!(
17726                second_item_id, first_item_id,
17727                "Should navigate into the 2nd buffer and activate it"
17728            );
17729            assert!(
17730                active_item.is_singleton(cx),
17731                "New active item should be a singleton buffer"
17732            );
17733            assert_eq!(
17734                active_item
17735                    .act_as::<Editor>(cx)
17736                    .expect("should have navigated into an editor")
17737                    .read(cx)
17738                    .text(cx),
17739                sample_text_2
17740            );
17741
17742            workspace
17743                .go_back(workspace.active_pane().downgrade(), window, cx)
17744                .detach_and_log_err(cx);
17745
17746            second_item_id
17747        })
17748        .unwrap();
17749    cx.executor().run_until_parked();
17750    workspace
17751        .update(cx, |workspace, _, cx| {
17752            let active_item = workspace
17753                .active_item(cx)
17754                .expect("should have an active item after navigating back from the 2nd buffer");
17755            assert_eq!(
17756                active_item.item_id(),
17757                multibuffer_item_id,
17758                "Should navigate back from the 2nd buffer to the multi buffer"
17759            );
17760            assert!(!active_item.is_singleton(cx));
17761        })
17762        .unwrap();
17763
17764    multi_buffer_editor.update_in(cx, |editor, window, cx| {
17765        editor.change_selections(
17766            SelectionEffects::scroll(Autoscroll::Next),
17767            window,
17768            cx,
17769            |s| s.select_ranges(Some(70..70)),
17770        );
17771        editor.open_excerpts(&OpenExcerpts, window, cx);
17772    });
17773    cx.executor().run_until_parked();
17774    workspace
17775        .update(cx, |workspace, window, cx| {
17776            let active_item = workspace
17777                .active_item(cx)
17778                .expect("should have an active item after navigating into the 3rd buffer");
17779            let third_item_id = active_item.item_id();
17780            assert_ne!(
17781                third_item_id, multibuffer_item_id,
17782                "Should navigate into the 3rd buffer and activate it"
17783            );
17784            assert_ne!(third_item_id, first_item_id);
17785            assert_ne!(third_item_id, second_item_id);
17786            assert!(
17787                active_item.is_singleton(cx),
17788                "New active item should be a singleton buffer"
17789            );
17790            assert_eq!(
17791                active_item
17792                    .act_as::<Editor>(cx)
17793                    .expect("should have navigated into an editor")
17794                    .read(cx)
17795                    .text(cx),
17796                sample_text_3
17797            );
17798
17799            workspace
17800                .go_back(workspace.active_pane().downgrade(), window, cx)
17801                .detach_and_log_err(cx);
17802        })
17803        .unwrap();
17804    cx.executor().run_until_parked();
17805    workspace
17806        .update(cx, |workspace, _, cx| {
17807            let active_item = workspace
17808                .active_item(cx)
17809                .expect("should have an active item after navigating back from the 3rd buffer");
17810            assert_eq!(
17811                active_item.item_id(),
17812                multibuffer_item_id,
17813                "Should navigate back from the 3rd buffer to the multi buffer"
17814            );
17815            assert!(!active_item.is_singleton(cx));
17816        })
17817        .unwrap();
17818}
17819
17820#[gpui::test]
17821async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17822    init_test(cx, |_| {});
17823
17824    let mut cx = EditorTestContext::new(cx).await;
17825
17826    let diff_base = r#"
17827        use some::mod;
17828
17829        const A: u32 = 42;
17830
17831        fn main() {
17832            println!("hello");
17833
17834            println!("world");
17835        }
17836        "#
17837    .unindent();
17838
17839    cx.set_state(
17840        &r#"
17841        use some::modified;
17842
17843        ˇ
17844        fn main() {
17845            println!("hello there");
17846
17847            println!("around the");
17848            println!("world");
17849        }
17850        "#
17851        .unindent(),
17852    );
17853
17854    cx.set_head_text(&diff_base);
17855    executor.run_until_parked();
17856
17857    cx.update_editor(|editor, window, cx| {
17858        editor.go_to_next_hunk(&GoToHunk, window, cx);
17859        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17860    });
17861    executor.run_until_parked();
17862    cx.assert_state_with_diff(
17863        r#"
17864          use some::modified;
17865
17866
17867          fn main() {
17868        -     println!("hello");
17869        + ˇ    println!("hello there");
17870
17871              println!("around the");
17872              println!("world");
17873          }
17874        "#
17875        .unindent(),
17876    );
17877
17878    cx.update_editor(|editor, window, cx| {
17879        for _ in 0..2 {
17880            editor.go_to_next_hunk(&GoToHunk, window, cx);
17881            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17882        }
17883    });
17884    executor.run_until_parked();
17885    cx.assert_state_with_diff(
17886        r#"
17887        - use some::mod;
17888        + ˇuse some::modified;
17889
17890
17891          fn main() {
17892        -     println!("hello");
17893        +     println!("hello there");
17894
17895        +     println!("around the");
17896              println!("world");
17897          }
17898        "#
17899        .unindent(),
17900    );
17901
17902    cx.update_editor(|editor, window, cx| {
17903        editor.go_to_next_hunk(&GoToHunk, window, cx);
17904        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
17905    });
17906    executor.run_until_parked();
17907    cx.assert_state_with_diff(
17908        r#"
17909        - use some::mod;
17910        + use some::modified;
17911
17912        - const A: u32 = 42;
17913          ˇ
17914          fn main() {
17915        -     println!("hello");
17916        +     println!("hello there");
17917
17918        +     println!("around the");
17919              println!("world");
17920          }
17921        "#
17922        .unindent(),
17923    );
17924
17925    cx.update_editor(|editor, window, cx| {
17926        editor.cancel(&Cancel, window, cx);
17927    });
17928
17929    cx.assert_state_with_diff(
17930        r#"
17931          use some::modified;
17932
17933          ˇ
17934          fn main() {
17935              println!("hello there");
17936
17937              println!("around the");
17938              println!("world");
17939          }
17940        "#
17941        .unindent(),
17942    );
17943}
17944
17945#[gpui::test]
17946async fn test_diff_base_change_with_expanded_diff_hunks(
17947    executor: BackgroundExecutor,
17948    cx: &mut TestAppContext,
17949) {
17950    init_test(cx, |_| {});
17951
17952    let mut cx = EditorTestContext::new(cx).await;
17953
17954    let diff_base = r#"
17955        use some::mod1;
17956        use some::mod2;
17957
17958        const A: u32 = 42;
17959        const B: u32 = 42;
17960        const C: u32 = 42;
17961
17962        fn main() {
17963            println!("hello");
17964
17965            println!("world");
17966        }
17967        "#
17968    .unindent();
17969
17970    cx.set_state(
17971        &r#"
17972        use some::mod2;
17973
17974        const A: u32 = 42;
17975        const C: u32 = 42;
17976
17977        fn main(ˇ) {
17978            //println!("hello");
17979
17980            println!("world");
17981            //
17982            //
17983        }
17984        "#
17985        .unindent(),
17986    );
17987
17988    cx.set_head_text(&diff_base);
17989    executor.run_until_parked();
17990
17991    cx.update_editor(|editor, window, cx| {
17992        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
17993    });
17994    executor.run_until_parked();
17995    cx.assert_state_with_diff(
17996        r#"
17997        - use some::mod1;
17998          use some::mod2;
17999
18000          const A: u32 = 42;
18001        - const B: u32 = 42;
18002          const C: u32 = 42;
18003
18004          fn main(ˇ) {
18005        -     println!("hello");
18006        +     //println!("hello");
18007
18008              println!("world");
18009        +     //
18010        +     //
18011          }
18012        "#
18013        .unindent(),
18014    );
18015
18016    cx.set_head_text("new diff base!");
18017    executor.run_until_parked();
18018    cx.assert_state_with_diff(
18019        r#"
18020        - new diff base!
18021        + use some::mod2;
18022        +
18023        + const A: u32 = 42;
18024        + const C: u32 = 42;
18025        +
18026        + fn main(ˇ) {
18027        +     //println!("hello");
18028        +
18029        +     println!("world");
18030        +     //
18031        +     //
18032        + }
18033        "#
18034        .unindent(),
18035    );
18036}
18037
18038#[gpui::test]
18039async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
18040    init_test(cx, |_| {});
18041
18042    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
18043    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
18044    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
18045    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
18046    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
18047    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
18048
18049    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
18050    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
18051    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
18052
18053    let multi_buffer = cx.new(|cx| {
18054        let mut multibuffer = MultiBuffer::new(ReadWrite);
18055        multibuffer.push_excerpts(
18056            buffer_1.clone(),
18057            [
18058                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18059                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18060                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18061            ],
18062            cx,
18063        );
18064        multibuffer.push_excerpts(
18065            buffer_2.clone(),
18066            [
18067                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18068                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18069                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18070            ],
18071            cx,
18072        );
18073        multibuffer.push_excerpts(
18074            buffer_3.clone(),
18075            [
18076                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18077                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18078                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18079            ],
18080            cx,
18081        );
18082        multibuffer
18083    });
18084
18085    let editor =
18086        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18087    editor
18088        .update(cx, |editor, _window, cx| {
18089            for (buffer, diff_base) in [
18090                (buffer_1.clone(), file_1_old),
18091                (buffer_2.clone(), file_2_old),
18092                (buffer_3.clone(), file_3_old),
18093            ] {
18094                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18095                editor
18096                    .buffer
18097                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18098            }
18099        })
18100        .unwrap();
18101
18102    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18103    cx.run_until_parked();
18104
18105    cx.assert_editor_state(
18106        &"
18107            ˇaaa
18108            ccc
18109            ddd
18110
18111            ggg
18112            hhh
18113
18114
18115            lll
18116            mmm
18117            NNN
18118
18119            qqq
18120            rrr
18121
18122            uuu
18123            111
18124            222
18125            333
18126
18127            666
18128            777
18129
18130            000
18131            !!!"
18132        .unindent(),
18133    );
18134
18135    cx.update_editor(|editor, window, cx| {
18136        editor.select_all(&SelectAll, window, cx);
18137        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18138    });
18139    cx.executor().run_until_parked();
18140
18141    cx.assert_state_with_diff(
18142        "
18143            «aaa
18144          - bbb
18145            ccc
18146            ddd
18147
18148            ggg
18149            hhh
18150
18151
18152            lll
18153            mmm
18154          - nnn
18155          + NNN
18156
18157            qqq
18158            rrr
18159
18160            uuu
18161            111
18162            222
18163            333
18164
18165          + 666
18166            777
18167
18168            000
18169            !!!ˇ»"
18170            .unindent(),
18171    );
18172}
18173
18174#[gpui::test]
18175async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
18176    init_test(cx, |_| {});
18177
18178    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
18179    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
18180
18181    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
18182    let multi_buffer = cx.new(|cx| {
18183        let mut multibuffer = MultiBuffer::new(ReadWrite);
18184        multibuffer.push_excerpts(
18185            buffer.clone(),
18186            [
18187                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
18188                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
18189                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
18190            ],
18191            cx,
18192        );
18193        multibuffer
18194    });
18195
18196    let editor =
18197        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18198    editor
18199        .update(cx, |editor, _window, cx| {
18200            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
18201            editor
18202                .buffer
18203                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
18204        })
18205        .unwrap();
18206
18207    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18208    cx.run_until_parked();
18209
18210    cx.update_editor(|editor, window, cx| {
18211        editor.expand_all_diff_hunks(&Default::default(), window, cx)
18212    });
18213    cx.executor().run_until_parked();
18214
18215    // When the start of a hunk coincides with the start of its excerpt,
18216    // the hunk is expanded. When the start of a a hunk is earlier than
18217    // the start of its excerpt, the hunk is not expanded.
18218    cx.assert_state_with_diff(
18219        "
18220            ˇaaa
18221          - bbb
18222          + BBB
18223
18224          - ddd
18225          - eee
18226          + DDD
18227          + EEE
18228            fff
18229
18230            iii
18231        "
18232        .unindent(),
18233    );
18234}
18235
18236#[gpui::test]
18237async fn test_edits_around_expanded_insertion_hunks(
18238    executor: BackgroundExecutor,
18239    cx: &mut TestAppContext,
18240) {
18241    init_test(cx, |_| {});
18242
18243    let mut cx = EditorTestContext::new(cx).await;
18244
18245    let diff_base = r#"
18246        use some::mod1;
18247        use some::mod2;
18248
18249        const A: u32 = 42;
18250
18251        fn main() {
18252            println!("hello");
18253
18254            println!("world");
18255        }
18256        "#
18257    .unindent();
18258    executor.run_until_parked();
18259    cx.set_state(
18260        &r#"
18261        use some::mod1;
18262        use some::mod2;
18263
18264        const A: u32 = 42;
18265        const B: u32 = 42;
18266        const C: u32 = 42;
18267        ˇ
18268
18269        fn main() {
18270            println!("hello");
18271
18272            println!("world");
18273        }
18274        "#
18275        .unindent(),
18276    );
18277
18278    cx.set_head_text(&diff_base);
18279    executor.run_until_parked();
18280
18281    cx.update_editor(|editor, window, cx| {
18282        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18283    });
18284    executor.run_until_parked();
18285
18286    cx.assert_state_with_diff(
18287        r#"
18288        use some::mod1;
18289        use some::mod2;
18290
18291        const A: u32 = 42;
18292      + const B: u32 = 42;
18293      + const C: u32 = 42;
18294      + ˇ
18295
18296        fn main() {
18297            println!("hello");
18298
18299            println!("world");
18300        }
18301      "#
18302        .unindent(),
18303    );
18304
18305    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
18306    executor.run_until_parked();
18307
18308    cx.assert_state_with_diff(
18309        r#"
18310        use some::mod1;
18311        use some::mod2;
18312
18313        const A: u32 = 42;
18314      + const B: u32 = 42;
18315      + const C: u32 = 42;
18316      + const D: u32 = 42;
18317      + ˇ
18318
18319        fn main() {
18320            println!("hello");
18321
18322            println!("world");
18323        }
18324      "#
18325        .unindent(),
18326    );
18327
18328    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
18329    executor.run_until_parked();
18330
18331    cx.assert_state_with_diff(
18332        r#"
18333        use some::mod1;
18334        use some::mod2;
18335
18336        const A: u32 = 42;
18337      + const B: u32 = 42;
18338      + const C: u32 = 42;
18339      + const D: u32 = 42;
18340      + const E: u32 = 42;
18341      + ˇ
18342
18343        fn main() {
18344            println!("hello");
18345
18346            println!("world");
18347        }
18348      "#
18349        .unindent(),
18350    );
18351
18352    cx.update_editor(|editor, window, cx| {
18353        editor.delete_line(&DeleteLine, window, cx);
18354    });
18355    executor.run_until_parked();
18356
18357    cx.assert_state_with_diff(
18358        r#"
18359        use some::mod1;
18360        use some::mod2;
18361
18362        const A: u32 = 42;
18363      + const B: u32 = 42;
18364      + const C: u32 = 42;
18365      + const D: u32 = 42;
18366      + const E: u32 = 42;
18367        ˇ
18368        fn main() {
18369            println!("hello");
18370
18371            println!("world");
18372        }
18373      "#
18374        .unindent(),
18375    );
18376
18377    cx.update_editor(|editor, window, cx| {
18378        editor.move_up(&MoveUp, window, cx);
18379        editor.delete_line(&DeleteLine, window, cx);
18380        editor.move_up(&MoveUp, window, cx);
18381        editor.delete_line(&DeleteLine, window, cx);
18382        editor.move_up(&MoveUp, window, cx);
18383        editor.delete_line(&DeleteLine, window, cx);
18384    });
18385    executor.run_until_parked();
18386    cx.assert_state_with_diff(
18387        r#"
18388        use some::mod1;
18389        use some::mod2;
18390
18391        const A: u32 = 42;
18392      + const B: u32 = 42;
18393        ˇ
18394        fn main() {
18395            println!("hello");
18396
18397            println!("world");
18398        }
18399      "#
18400        .unindent(),
18401    );
18402
18403    cx.update_editor(|editor, window, cx| {
18404        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
18405        editor.delete_line(&DeleteLine, window, cx);
18406    });
18407    executor.run_until_parked();
18408    cx.assert_state_with_diff(
18409        r#"
18410        ˇ
18411        fn main() {
18412            println!("hello");
18413
18414            println!("world");
18415        }
18416      "#
18417        .unindent(),
18418    );
18419}
18420
18421#[gpui::test]
18422async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
18423    init_test(cx, |_| {});
18424
18425    let mut cx = EditorTestContext::new(cx).await;
18426    cx.set_head_text(indoc! { "
18427        one
18428        two
18429        three
18430        four
18431        five
18432        "
18433    });
18434    cx.set_state(indoc! { "
18435        one
18436        ˇthree
18437        five
18438    "});
18439    cx.run_until_parked();
18440    cx.update_editor(|editor, window, cx| {
18441        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18442    });
18443    cx.assert_state_with_diff(
18444        indoc! { "
18445        one
18446      - two
18447        ˇthree
18448      - four
18449        five
18450    "}
18451        .to_string(),
18452    );
18453    cx.update_editor(|editor, window, cx| {
18454        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18455    });
18456
18457    cx.assert_state_with_diff(
18458        indoc! { "
18459        one
18460        ˇthree
18461        five
18462    "}
18463        .to_string(),
18464    );
18465
18466    cx.set_state(indoc! { "
18467        one
18468        ˇTWO
18469        three
18470        four
18471        five
18472    "});
18473    cx.run_until_parked();
18474    cx.update_editor(|editor, window, cx| {
18475        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18476    });
18477
18478    cx.assert_state_with_diff(
18479        indoc! { "
18480            one
18481          - two
18482          + ˇTWO
18483            three
18484            four
18485            five
18486        "}
18487        .to_string(),
18488    );
18489    cx.update_editor(|editor, window, cx| {
18490        editor.move_up(&Default::default(), window, cx);
18491        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18492    });
18493    cx.assert_state_with_diff(
18494        indoc! { "
18495            one
18496            ˇTWO
18497            three
18498            four
18499            five
18500        "}
18501        .to_string(),
18502    );
18503}
18504
18505#[gpui::test]
18506async fn test_edits_around_expanded_deletion_hunks(
18507    executor: BackgroundExecutor,
18508    cx: &mut TestAppContext,
18509) {
18510    init_test(cx, |_| {});
18511
18512    let mut cx = EditorTestContext::new(cx).await;
18513
18514    let diff_base = r#"
18515        use some::mod1;
18516        use some::mod2;
18517
18518        const A: u32 = 42;
18519        const B: u32 = 42;
18520        const C: u32 = 42;
18521
18522
18523        fn main() {
18524            println!("hello");
18525
18526            println!("world");
18527        }
18528    "#
18529    .unindent();
18530    executor.run_until_parked();
18531    cx.set_state(
18532        &r#"
18533        use some::mod1;
18534        use some::mod2;
18535
18536        ˇconst B: u32 = 42;
18537        const C: u32 = 42;
18538
18539
18540        fn main() {
18541            println!("hello");
18542
18543            println!("world");
18544        }
18545        "#
18546        .unindent(),
18547    );
18548
18549    cx.set_head_text(&diff_base);
18550    executor.run_until_parked();
18551
18552    cx.update_editor(|editor, window, cx| {
18553        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18554    });
18555    executor.run_until_parked();
18556
18557    cx.assert_state_with_diff(
18558        r#"
18559        use some::mod1;
18560        use some::mod2;
18561
18562      - const A: u32 = 42;
18563        ˇconst B: u32 = 42;
18564        const C: u32 = 42;
18565
18566
18567        fn main() {
18568            println!("hello");
18569
18570            println!("world");
18571        }
18572      "#
18573        .unindent(),
18574    );
18575
18576    cx.update_editor(|editor, window, cx| {
18577        editor.delete_line(&DeleteLine, window, cx);
18578    });
18579    executor.run_until_parked();
18580    cx.assert_state_with_diff(
18581        r#"
18582        use some::mod1;
18583        use some::mod2;
18584
18585      - const A: u32 = 42;
18586      - const B: u32 = 42;
18587        ˇconst C: u32 = 42;
18588
18589
18590        fn main() {
18591            println!("hello");
18592
18593            println!("world");
18594        }
18595      "#
18596        .unindent(),
18597    );
18598
18599    cx.update_editor(|editor, window, cx| {
18600        editor.delete_line(&DeleteLine, window, cx);
18601    });
18602    executor.run_until_parked();
18603    cx.assert_state_with_diff(
18604        r#"
18605        use some::mod1;
18606        use some::mod2;
18607
18608      - const A: u32 = 42;
18609      - const B: u32 = 42;
18610      - const C: u32 = 42;
18611        ˇ
18612
18613        fn main() {
18614            println!("hello");
18615
18616            println!("world");
18617        }
18618      "#
18619        .unindent(),
18620    );
18621
18622    cx.update_editor(|editor, window, cx| {
18623        editor.handle_input("replacement", window, cx);
18624    });
18625    executor.run_until_parked();
18626    cx.assert_state_with_diff(
18627        r#"
18628        use some::mod1;
18629        use some::mod2;
18630
18631      - const A: u32 = 42;
18632      - const B: u32 = 42;
18633      - const C: u32 = 42;
18634      -
18635      + replacementˇ
18636
18637        fn main() {
18638            println!("hello");
18639
18640            println!("world");
18641        }
18642      "#
18643        .unindent(),
18644    );
18645}
18646
18647#[gpui::test]
18648async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18649    init_test(cx, |_| {});
18650
18651    let mut cx = EditorTestContext::new(cx).await;
18652
18653    let base_text = r#"
18654        one
18655        two
18656        three
18657        four
18658        five
18659    "#
18660    .unindent();
18661    executor.run_until_parked();
18662    cx.set_state(
18663        &r#"
18664        one
18665        two
18666        fˇour
18667        five
18668        "#
18669        .unindent(),
18670    );
18671
18672    cx.set_head_text(&base_text);
18673    executor.run_until_parked();
18674
18675    cx.update_editor(|editor, window, cx| {
18676        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18677    });
18678    executor.run_until_parked();
18679
18680    cx.assert_state_with_diff(
18681        r#"
18682          one
18683          two
18684        - three
18685          fˇour
18686          five
18687        "#
18688        .unindent(),
18689    );
18690
18691    cx.update_editor(|editor, window, cx| {
18692        editor.backspace(&Backspace, window, cx);
18693        editor.backspace(&Backspace, window, cx);
18694    });
18695    executor.run_until_parked();
18696    cx.assert_state_with_diff(
18697        r#"
18698          one
18699          two
18700        - threeˇ
18701        - four
18702        + our
18703          five
18704        "#
18705        .unindent(),
18706    );
18707}
18708
18709#[gpui::test]
18710async fn test_edit_after_expanded_modification_hunk(
18711    executor: BackgroundExecutor,
18712    cx: &mut TestAppContext,
18713) {
18714    init_test(cx, |_| {});
18715
18716    let mut cx = EditorTestContext::new(cx).await;
18717
18718    let diff_base = r#"
18719        use some::mod1;
18720        use some::mod2;
18721
18722        const A: u32 = 42;
18723        const B: u32 = 42;
18724        const C: u32 = 42;
18725        const D: u32 = 42;
18726
18727
18728        fn main() {
18729            println!("hello");
18730
18731            println!("world");
18732        }"#
18733    .unindent();
18734
18735    cx.set_state(
18736        &r#"
18737        use some::mod1;
18738        use some::mod2;
18739
18740        const A: u32 = 42;
18741        const B: u32 = 42;
18742        const C: u32 = 43ˇ
18743        const D: u32 = 42;
18744
18745
18746        fn main() {
18747            println!("hello");
18748
18749            println!("world");
18750        }"#
18751        .unindent(),
18752    );
18753
18754    cx.set_head_text(&diff_base);
18755    executor.run_until_parked();
18756    cx.update_editor(|editor, window, cx| {
18757        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18758    });
18759    executor.run_until_parked();
18760
18761    cx.assert_state_with_diff(
18762        r#"
18763        use some::mod1;
18764        use some::mod2;
18765
18766        const A: u32 = 42;
18767        const B: u32 = 42;
18768      - const C: u32 = 42;
18769      + const C: u32 = 43ˇ
18770        const D: u32 = 42;
18771
18772
18773        fn main() {
18774            println!("hello");
18775
18776            println!("world");
18777        }"#
18778        .unindent(),
18779    );
18780
18781    cx.update_editor(|editor, window, cx| {
18782        editor.handle_input("\nnew_line\n", window, cx);
18783    });
18784    executor.run_until_parked();
18785
18786    cx.assert_state_with_diff(
18787        r#"
18788        use some::mod1;
18789        use some::mod2;
18790
18791        const A: u32 = 42;
18792        const B: u32 = 42;
18793      - const C: u32 = 42;
18794      + const C: u32 = 43
18795      + new_line
18796      + ˇ
18797        const D: u32 = 42;
18798
18799
18800        fn main() {
18801            println!("hello");
18802
18803            println!("world");
18804        }"#
18805        .unindent(),
18806    );
18807}
18808
18809#[gpui::test]
18810async fn test_stage_and_unstage_added_file_hunk(
18811    executor: BackgroundExecutor,
18812    cx: &mut TestAppContext,
18813) {
18814    init_test(cx, |_| {});
18815
18816    let mut cx = EditorTestContext::new(cx).await;
18817    cx.update_editor(|editor, _, cx| {
18818        editor.set_expand_all_diff_hunks(cx);
18819    });
18820
18821    let working_copy = r#"
18822            ˇfn main() {
18823                println!("hello, world!");
18824            }
18825        "#
18826    .unindent();
18827
18828    cx.set_state(&working_copy);
18829    executor.run_until_parked();
18830
18831    cx.assert_state_with_diff(
18832        r#"
18833            + ˇfn main() {
18834            +     println!("hello, world!");
18835            + }
18836        "#
18837        .unindent(),
18838    );
18839    cx.assert_index_text(None);
18840
18841    cx.update_editor(|editor, window, cx| {
18842        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18843    });
18844    executor.run_until_parked();
18845    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
18846    cx.assert_state_with_diff(
18847        r#"
18848            + ˇfn main() {
18849            +     println!("hello, world!");
18850            + }
18851        "#
18852        .unindent(),
18853    );
18854
18855    cx.update_editor(|editor, window, cx| {
18856        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
18857    });
18858    executor.run_until_parked();
18859    cx.assert_index_text(None);
18860}
18861
18862async fn setup_indent_guides_editor(
18863    text: &str,
18864    cx: &mut TestAppContext,
18865) -> (BufferId, EditorTestContext) {
18866    init_test(cx, |_| {});
18867
18868    let mut cx = EditorTestContext::new(cx).await;
18869
18870    let buffer_id = cx.update_editor(|editor, window, cx| {
18871        editor.set_text(text, window, cx);
18872        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
18873
18874        buffer_ids[0]
18875    });
18876
18877    (buffer_id, cx)
18878}
18879
18880fn assert_indent_guides(
18881    range: Range<u32>,
18882    expected: Vec<IndentGuide>,
18883    active_indices: Option<Vec<usize>>,
18884    cx: &mut EditorTestContext,
18885) {
18886    let indent_guides = cx.update_editor(|editor, window, cx| {
18887        let snapshot = editor.snapshot(window, cx).display_snapshot;
18888        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
18889            editor,
18890            MultiBufferRow(range.start)..MultiBufferRow(range.end),
18891            true,
18892            &snapshot,
18893            cx,
18894        );
18895
18896        indent_guides.sort_by(|a, b| {
18897            a.depth.cmp(&b.depth).then(
18898                a.start_row
18899                    .cmp(&b.start_row)
18900                    .then(a.end_row.cmp(&b.end_row)),
18901            )
18902        });
18903        indent_guides
18904    });
18905
18906    if let Some(expected) = active_indices {
18907        let active_indices = cx.update_editor(|editor, window, cx| {
18908            let snapshot = editor.snapshot(window, cx).display_snapshot;
18909            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
18910        });
18911
18912        assert_eq!(
18913            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
18914            expected,
18915            "Active indent guide indices do not match"
18916        );
18917    }
18918
18919    assert_eq!(indent_guides, expected, "Indent guides do not match");
18920}
18921
18922fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
18923    IndentGuide {
18924        buffer_id,
18925        start_row: MultiBufferRow(start_row),
18926        end_row: MultiBufferRow(end_row),
18927        depth,
18928        tab_size: 4,
18929        settings: IndentGuideSettings {
18930            enabled: true,
18931            line_width: 1,
18932            active_line_width: 1,
18933            ..Default::default()
18934        },
18935    }
18936}
18937
18938#[gpui::test]
18939async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
18940    let (buffer_id, mut cx) = setup_indent_guides_editor(
18941        &"
18942        fn main() {
18943            let a = 1;
18944        }"
18945        .unindent(),
18946        cx,
18947    )
18948    .await;
18949
18950    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
18951}
18952
18953#[gpui::test]
18954async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
18955    let (buffer_id, mut cx) = setup_indent_guides_editor(
18956        &"
18957        fn main() {
18958            let a = 1;
18959            let b = 2;
18960        }"
18961        .unindent(),
18962        cx,
18963    )
18964    .await;
18965
18966    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
18967}
18968
18969#[gpui::test]
18970async fn test_indent_guide_nested(cx: &mut TestAppContext) {
18971    let (buffer_id, mut cx) = setup_indent_guides_editor(
18972        &"
18973        fn main() {
18974            let a = 1;
18975            if a == 3 {
18976                let b = 2;
18977            } else {
18978                let c = 3;
18979            }
18980        }"
18981        .unindent(),
18982        cx,
18983    )
18984    .await;
18985
18986    assert_indent_guides(
18987        0..8,
18988        vec![
18989            indent_guide(buffer_id, 1, 6, 0),
18990            indent_guide(buffer_id, 3, 3, 1),
18991            indent_guide(buffer_id, 5, 5, 1),
18992        ],
18993        None,
18994        &mut cx,
18995    );
18996}
18997
18998#[gpui::test]
18999async fn test_indent_guide_tab(cx: &mut TestAppContext) {
19000    let (buffer_id, mut cx) = setup_indent_guides_editor(
19001        &"
19002        fn main() {
19003            let a = 1;
19004                let b = 2;
19005            let c = 3;
19006        }"
19007        .unindent(),
19008        cx,
19009    )
19010    .await;
19011
19012    assert_indent_guides(
19013        0..5,
19014        vec![
19015            indent_guide(buffer_id, 1, 3, 0),
19016            indent_guide(buffer_id, 2, 2, 1),
19017        ],
19018        None,
19019        &mut cx,
19020    );
19021}
19022
19023#[gpui::test]
19024async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
19025    let (buffer_id, mut cx) = setup_indent_guides_editor(
19026        &"
19027        fn main() {
19028            let a = 1;
19029
19030            let c = 3;
19031        }"
19032        .unindent(),
19033        cx,
19034    )
19035    .await;
19036
19037    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
19038}
19039
19040#[gpui::test]
19041async fn test_indent_guide_complex(cx: &mut TestAppContext) {
19042    let (buffer_id, mut cx) = setup_indent_guides_editor(
19043        &"
19044        fn main() {
19045            let a = 1;
19046
19047            let c = 3;
19048
19049            if a == 3 {
19050                let b = 2;
19051            } else {
19052                let c = 3;
19053            }
19054        }"
19055        .unindent(),
19056        cx,
19057    )
19058    .await;
19059
19060    assert_indent_guides(
19061        0..11,
19062        vec![
19063            indent_guide(buffer_id, 1, 9, 0),
19064            indent_guide(buffer_id, 6, 6, 1),
19065            indent_guide(buffer_id, 8, 8, 1),
19066        ],
19067        None,
19068        &mut cx,
19069    );
19070}
19071
19072#[gpui::test]
19073async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
19074    let (buffer_id, mut cx) = setup_indent_guides_editor(
19075        &"
19076        fn main() {
19077            let a = 1;
19078
19079            let c = 3;
19080
19081            if a == 3 {
19082                let b = 2;
19083            } else {
19084                let c = 3;
19085            }
19086        }"
19087        .unindent(),
19088        cx,
19089    )
19090    .await;
19091
19092    assert_indent_guides(
19093        1..11,
19094        vec![
19095            indent_guide(buffer_id, 1, 9, 0),
19096            indent_guide(buffer_id, 6, 6, 1),
19097            indent_guide(buffer_id, 8, 8, 1),
19098        ],
19099        None,
19100        &mut cx,
19101    );
19102}
19103
19104#[gpui::test]
19105async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
19106    let (buffer_id, mut cx) = setup_indent_guides_editor(
19107        &"
19108        fn main() {
19109            let a = 1;
19110
19111            let c = 3;
19112
19113            if a == 3 {
19114                let b = 2;
19115            } else {
19116                let c = 3;
19117            }
19118        }"
19119        .unindent(),
19120        cx,
19121    )
19122    .await;
19123
19124    assert_indent_guides(
19125        1..10,
19126        vec![
19127            indent_guide(buffer_id, 1, 9, 0),
19128            indent_guide(buffer_id, 6, 6, 1),
19129            indent_guide(buffer_id, 8, 8, 1),
19130        ],
19131        None,
19132        &mut cx,
19133    );
19134}
19135
19136#[gpui::test]
19137async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
19138    let (buffer_id, mut cx) = setup_indent_guides_editor(
19139        &"
19140        fn main() {
19141            if a {
19142                b(
19143                    c,
19144                    d,
19145                )
19146            } else {
19147                e(
19148                    f
19149                )
19150            }
19151        }"
19152        .unindent(),
19153        cx,
19154    )
19155    .await;
19156
19157    assert_indent_guides(
19158        0..11,
19159        vec![
19160            indent_guide(buffer_id, 1, 10, 0),
19161            indent_guide(buffer_id, 2, 5, 1),
19162            indent_guide(buffer_id, 7, 9, 1),
19163            indent_guide(buffer_id, 3, 4, 2),
19164            indent_guide(buffer_id, 8, 8, 2),
19165        ],
19166        None,
19167        &mut cx,
19168    );
19169
19170    cx.update_editor(|editor, window, cx| {
19171        editor.fold_at(MultiBufferRow(2), window, cx);
19172        assert_eq!(
19173            editor.display_text(cx),
19174            "
19175            fn main() {
19176                if a {
19177                    b(⋯
19178                    )
19179                } else {
19180                    e(
19181                        f
19182                    )
19183                }
19184            }"
19185            .unindent()
19186        );
19187    });
19188
19189    assert_indent_guides(
19190        0..11,
19191        vec![
19192            indent_guide(buffer_id, 1, 10, 0),
19193            indent_guide(buffer_id, 2, 5, 1),
19194            indent_guide(buffer_id, 7, 9, 1),
19195            indent_guide(buffer_id, 8, 8, 2),
19196        ],
19197        None,
19198        &mut cx,
19199    );
19200}
19201
19202#[gpui::test]
19203async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
19204    let (buffer_id, mut cx) = setup_indent_guides_editor(
19205        &"
19206        block1
19207            block2
19208                block3
19209                    block4
19210            block2
19211        block1
19212        block1"
19213            .unindent(),
19214        cx,
19215    )
19216    .await;
19217
19218    assert_indent_guides(
19219        1..10,
19220        vec![
19221            indent_guide(buffer_id, 1, 4, 0),
19222            indent_guide(buffer_id, 2, 3, 1),
19223            indent_guide(buffer_id, 3, 3, 2),
19224        ],
19225        None,
19226        &mut cx,
19227    );
19228}
19229
19230#[gpui::test]
19231async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
19232    let (buffer_id, mut cx) = setup_indent_guides_editor(
19233        &"
19234        block1
19235            block2
19236                block3
19237
19238        block1
19239        block1"
19240            .unindent(),
19241        cx,
19242    )
19243    .await;
19244
19245    assert_indent_guides(
19246        0..6,
19247        vec![
19248            indent_guide(buffer_id, 1, 2, 0),
19249            indent_guide(buffer_id, 2, 2, 1),
19250        ],
19251        None,
19252        &mut cx,
19253    );
19254}
19255
19256#[gpui::test]
19257async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
19258    let (buffer_id, mut cx) = setup_indent_guides_editor(
19259        &"
19260        function component() {
19261        \treturn (
19262        \t\t\t
19263        \t\t<div>
19264        \t\t\t<abc></abc>
19265        \t\t</div>
19266        \t)
19267        }"
19268        .unindent(),
19269        cx,
19270    )
19271    .await;
19272
19273    assert_indent_guides(
19274        0..8,
19275        vec![
19276            indent_guide(buffer_id, 1, 6, 0),
19277            indent_guide(buffer_id, 2, 5, 1),
19278            indent_guide(buffer_id, 4, 4, 2),
19279        ],
19280        None,
19281        &mut cx,
19282    );
19283}
19284
19285#[gpui::test]
19286async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
19287    let (buffer_id, mut cx) = setup_indent_guides_editor(
19288        &"
19289        function component() {
19290        \treturn (
19291        \t
19292        \t\t<div>
19293        \t\t\t<abc></abc>
19294        \t\t</div>
19295        \t)
19296        }"
19297        .unindent(),
19298        cx,
19299    )
19300    .await;
19301
19302    assert_indent_guides(
19303        0..8,
19304        vec![
19305            indent_guide(buffer_id, 1, 6, 0),
19306            indent_guide(buffer_id, 2, 5, 1),
19307            indent_guide(buffer_id, 4, 4, 2),
19308        ],
19309        None,
19310        &mut cx,
19311    );
19312}
19313
19314#[gpui::test]
19315async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
19316    let (buffer_id, mut cx) = setup_indent_guides_editor(
19317        &"
19318        block1
19319
19320
19321
19322            block2
19323        "
19324        .unindent(),
19325        cx,
19326    )
19327    .await;
19328
19329    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19330}
19331
19332#[gpui::test]
19333async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
19334    let (buffer_id, mut cx) = setup_indent_guides_editor(
19335        &"
19336        def a:
19337        \tb = 3
19338        \tif True:
19339        \t\tc = 4
19340        \t\td = 5
19341        \tprint(b)
19342        "
19343        .unindent(),
19344        cx,
19345    )
19346    .await;
19347
19348    assert_indent_guides(
19349        0..6,
19350        vec![
19351            indent_guide(buffer_id, 1, 5, 0),
19352            indent_guide(buffer_id, 3, 4, 1),
19353        ],
19354        None,
19355        &mut cx,
19356    );
19357}
19358
19359#[gpui::test]
19360async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
19361    let (buffer_id, mut cx) = setup_indent_guides_editor(
19362        &"
19363    fn main() {
19364        let a = 1;
19365    }"
19366        .unindent(),
19367        cx,
19368    )
19369    .await;
19370
19371    cx.update_editor(|editor, window, cx| {
19372        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19373            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19374        });
19375    });
19376
19377    assert_indent_guides(
19378        0..3,
19379        vec![indent_guide(buffer_id, 1, 1, 0)],
19380        Some(vec![0]),
19381        &mut cx,
19382    );
19383}
19384
19385#[gpui::test]
19386async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
19387    let (buffer_id, mut cx) = setup_indent_guides_editor(
19388        &"
19389    fn main() {
19390        if 1 == 2 {
19391            let a = 1;
19392        }
19393    }"
19394        .unindent(),
19395        cx,
19396    )
19397    .await;
19398
19399    cx.update_editor(|editor, window, cx| {
19400        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19401            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19402        });
19403    });
19404
19405    assert_indent_guides(
19406        0..4,
19407        vec![
19408            indent_guide(buffer_id, 1, 3, 0),
19409            indent_guide(buffer_id, 2, 2, 1),
19410        ],
19411        Some(vec![1]),
19412        &mut cx,
19413    );
19414
19415    cx.update_editor(|editor, window, cx| {
19416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19417            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19418        });
19419    });
19420
19421    assert_indent_guides(
19422        0..4,
19423        vec![
19424            indent_guide(buffer_id, 1, 3, 0),
19425            indent_guide(buffer_id, 2, 2, 1),
19426        ],
19427        Some(vec![1]),
19428        &mut cx,
19429    );
19430
19431    cx.update_editor(|editor, window, cx| {
19432        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19433            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
19434        });
19435    });
19436
19437    assert_indent_guides(
19438        0..4,
19439        vec![
19440            indent_guide(buffer_id, 1, 3, 0),
19441            indent_guide(buffer_id, 2, 2, 1),
19442        ],
19443        Some(vec![0]),
19444        &mut cx,
19445    );
19446}
19447
19448#[gpui::test]
19449async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
19450    let (buffer_id, mut cx) = setup_indent_guides_editor(
19451        &"
19452    fn main() {
19453        let a = 1;
19454
19455        let b = 2;
19456    }"
19457        .unindent(),
19458        cx,
19459    )
19460    .await;
19461
19462    cx.update_editor(|editor, window, cx| {
19463        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19464            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19465        });
19466    });
19467
19468    assert_indent_guides(
19469        0..5,
19470        vec![indent_guide(buffer_id, 1, 3, 0)],
19471        Some(vec![0]),
19472        &mut cx,
19473    );
19474}
19475
19476#[gpui::test]
19477async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
19478    let (buffer_id, mut cx) = setup_indent_guides_editor(
19479        &"
19480    def m:
19481        a = 1
19482        pass"
19483            .unindent(),
19484        cx,
19485    )
19486    .await;
19487
19488    cx.update_editor(|editor, window, cx| {
19489        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19490            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19491        });
19492    });
19493
19494    assert_indent_guides(
19495        0..3,
19496        vec![indent_guide(buffer_id, 1, 2, 0)],
19497        Some(vec![0]),
19498        &mut cx,
19499    );
19500}
19501
19502#[gpui::test]
19503async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
19504    init_test(cx, |_| {});
19505    let mut cx = EditorTestContext::new(cx).await;
19506    let text = indoc! {
19507        "
19508        impl A {
19509            fn b() {
19510                0;
19511                3;
19512                5;
19513                6;
19514                7;
19515            }
19516        }
19517        "
19518    };
19519    let base_text = indoc! {
19520        "
19521        impl A {
19522            fn b() {
19523                0;
19524                1;
19525                2;
19526                3;
19527                4;
19528            }
19529            fn c() {
19530                5;
19531                6;
19532                7;
19533            }
19534        }
19535        "
19536    };
19537
19538    cx.update_editor(|editor, window, cx| {
19539        editor.set_text(text, window, cx);
19540
19541        editor.buffer().update(cx, |multibuffer, cx| {
19542            let buffer = multibuffer.as_singleton().unwrap();
19543            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
19544
19545            multibuffer.set_all_diff_hunks_expanded(cx);
19546            multibuffer.add_diff(diff, cx);
19547
19548            buffer.read(cx).remote_id()
19549        })
19550    });
19551    cx.run_until_parked();
19552
19553    cx.assert_state_with_diff(
19554        indoc! { "
19555          impl A {
19556              fn b() {
19557                  0;
19558        -         1;
19559        -         2;
19560                  3;
19561        -         4;
19562        -     }
19563        -     fn c() {
19564                  5;
19565                  6;
19566                  7;
19567              }
19568          }
19569          ˇ"
19570        }
19571        .to_string(),
19572    );
19573
19574    let mut actual_guides = cx.update_editor(|editor, window, cx| {
19575        editor
19576            .snapshot(window, cx)
19577            .buffer_snapshot
19578            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
19579            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
19580            .collect::<Vec<_>>()
19581    });
19582    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
19583    assert_eq!(
19584        actual_guides,
19585        vec![
19586            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
19587            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
19588            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
19589        ]
19590    );
19591}
19592
19593#[gpui::test]
19594async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19595    init_test(cx, |_| {});
19596    let mut cx = EditorTestContext::new(cx).await;
19597
19598    let diff_base = r#"
19599        a
19600        b
19601        c
19602        "#
19603    .unindent();
19604
19605    cx.set_state(
19606        &r#"
19607        ˇA
19608        b
19609        C
19610        "#
19611        .unindent(),
19612    );
19613    cx.set_head_text(&diff_base);
19614    cx.update_editor(|editor, window, cx| {
19615        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19616    });
19617    executor.run_until_parked();
19618
19619    let both_hunks_expanded = r#"
19620        - a
19621        + ˇA
19622          b
19623        - c
19624        + C
19625        "#
19626    .unindent();
19627
19628    cx.assert_state_with_diff(both_hunks_expanded.clone());
19629
19630    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19631        let snapshot = editor.snapshot(window, cx);
19632        let hunks = editor
19633            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19634            .collect::<Vec<_>>();
19635        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19636        let buffer_id = hunks[0].buffer_id;
19637        hunks
19638            .into_iter()
19639            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
19640            .collect::<Vec<_>>()
19641    });
19642    assert_eq!(hunk_ranges.len(), 2);
19643
19644    cx.update_editor(|editor, _, cx| {
19645        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19646    });
19647    executor.run_until_parked();
19648
19649    let second_hunk_expanded = r#"
19650          ˇA
19651          b
19652        - c
19653        + C
19654        "#
19655    .unindent();
19656
19657    cx.assert_state_with_diff(second_hunk_expanded);
19658
19659    cx.update_editor(|editor, _, cx| {
19660        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19661    });
19662    executor.run_until_parked();
19663
19664    cx.assert_state_with_diff(both_hunks_expanded.clone());
19665
19666    cx.update_editor(|editor, _, cx| {
19667        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19668    });
19669    executor.run_until_parked();
19670
19671    let first_hunk_expanded = r#"
19672        - a
19673        + ˇA
19674          b
19675          C
19676        "#
19677    .unindent();
19678
19679    cx.assert_state_with_diff(first_hunk_expanded);
19680
19681    cx.update_editor(|editor, _, cx| {
19682        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19683    });
19684    executor.run_until_parked();
19685
19686    cx.assert_state_with_diff(both_hunks_expanded);
19687
19688    cx.set_state(
19689        &r#"
19690        ˇA
19691        b
19692        "#
19693        .unindent(),
19694    );
19695    cx.run_until_parked();
19696
19697    // TODO this cursor position seems bad
19698    cx.assert_state_with_diff(
19699        r#"
19700        - ˇa
19701        + A
19702          b
19703        "#
19704        .unindent(),
19705    );
19706
19707    cx.update_editor(|editor, window, cx| {
19708        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19709    });
19710
19711    cx.assert_state_with_diff(
19712        r#"
19713            - ˇa
19714            + A
19715              b
19716            - c
19717            "#
19718        .unindent(),
19719    );
19720
19721    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19722        let snapshot = editor.snapshot(window, cx);
19723        let hunks = editor
19724            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19725            .collect::<Vec<_>>();
19726        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19727        let buffer_id = hunks[0].buffer_id;
19728        hunks
19729            .into_iter()
19730            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
19731            .collect::<Vec<_>>()
19732    });
19733    assert_eq!(hunk_ranges.len(), 2);
19734
19735    cx.update_editor(|editor, _, cx| {
19736        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
19737    });
19738    executor.run_until_parked();
19739
19740    cx.assert_state_with_diff(
19741        r#"
19742        - ˇa
19743        + A
19744          b
19745        "#
19746        .unindent(),
19747    );
19748}
19749
19750#[gpui::test]
19751async fn test_toggle_deletion_hunk_at_start_of_file(
19752    executor: BackgroundExecutor,
19753    cx: &mut TestAppContext,
19754) {
19755    init_test(cx, |_| {});
19756    let mut cx = EditorTestContext::new(cx).await;
19757
19758    let diff_base = r#"
19759        a
19760        b
19761        c
19762        "#
19763    .unindent();
19764
19765    cx.set_state(
19766        &r#"
19767        ˇb
19768        c
19769        "#
19770        .unindent(),
19771    );
19772    cx.set_head_text(&diff_base);
19773    cx.update_editor(|editor, window, cx| {
19774        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19775    });
19776    executor.run_until_parked();
19777
19778    let hunk_expanded = r#"
19779        - a
19780          ˇb
19781          c
19782        "#
19783    .unindent();
19784
19785    cx.assert_state_with_diff(hunk_expanded.clone());
19786
19787    let hunk_ranges = cx.update_editor(|editor, window, cx| {
19788        let snapshot = editor.snapshot(window, cx);
19789        let hunks = editor
19790            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19791            .collect::<Vec<_>>();
19792        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
19793        let buffer_id = hunks[0].buffer_id;
19794        hunks
19795            .into_iter()
19796            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
19797            .collect::<Vec<_>>()
19798    });
19799    assert_eq!(hunk_ranges.len(), 1);
19800
19801    cx.update_editor(|editor, _, cx| {
19802        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19803    });
19804    executor.run_until_parked();
19805
19806    let hunk_collapsed = r#"
19807          ˇb
19808          c
19809        "#
19810    .unindent();
19811
19812    cx.assert_state_with_diff(hunk_collapsed);
19813
19814    cx.update_editor(|editor, _, cx| {
19815        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
19816    });
19817    executor.run_until_parked();
19818
19819    cx.assert_state_with_diff(hunk_expanded);
19820}
19821
19822#[gpui::test]
19823async fn test_display_diff_hunks(cx: &mut TestAppContext) {
19824    init_test(cx, |_| {});
19825
19826    let fs = FakeFs::new(cx.executor());
19827    fs.insert_tree(
19828        path!("/test"),
19829        json!({
19830            ".git": {},
19831            "file-1": "ONE\n",
19832            "file-2": "TWO\n",
19833            "file-3": "THREE\n",
19834        }),
19835    )
19836    .await;
19837
19838    fs.set_head_for_repo(
19839        path!("/test/.git").as_ref(),
19840        &[
19841            ("file-1".into(), "one\n".into()),
19842            ("file-2".into(), "two\n".into()),
19843            ("file-3".into(), "three\n".into()),
19844        ],
19845        "deadbeef",
19846    );
19847
19848    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
19849    let mut buffers = vec![];
19850    for i in 1..=3 {
19851        let buffer = project
19852            .update(cx, |project, cx| {
19853                let path = format!(path!("/test/file-{}"), i);
19854                project.open_local_buffer(path, cx)
19855            })
19856            .await
19857            .unwrap();
19858        buffers.push(buffer);
19859    }
19860
19861    let multibuffer = cx.new(|cx| {
19862        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
19863        multibuffer.set_all_diff_hunks_expanded(cx);
19864        for buffer in &buffers {
19865            let snapshot = buffer.read(cx).snapshot();
19866            multibuffer.set_excerpts_for_path(
19867                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
19868                buffer.clone(),
19869                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
19870                2,
19871                cx,
19872            );
19873        }
19874        multibuffer
19875    });
19876
19877    let editor = cx.add_window(|window, cx| {
19878        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
19879    });
19880    cx.run_until_parked();
19881
19882    let snapshot = editor
19883        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
19884        .unwrap();
19885    let hunks = snapshot
19886        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
19887        .map(|hunk| match hunk {
19888            DisplayDiffHunk::Unfolded {
19889                display_row_range, ..
19890            } => display_row_range,
19891            DisplayDiffHunk::Folded { .. } => unreachable!(),
19892        })
19893        .collect::<Vec<_>>();
19894    assert_eq!(
19895        hunks,
19896        [
19897            DisplayRow(2)..DisplayRow(4),
19898            DisplayRow(7)..DisplayRow(9),
19899            DisplayRow(12)..DisplayRow(14),
19900        ]
19901    );
19902}
19903
19904#[gpui::test]
19905async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
19906    init_test(cx, |_| {});
19907
19908    let mut cx = EditorTestContext::new(cx).await;
19909    cx.set_head_text(indoc! { "
19910        one
19911        two
19912        three
19913        four
19914        five
19915        "
19916    });
19917    cx.set_index_text(indoc! { "
19918        one
19919        two
19920        three
19921        four
19922        five
19923        "
19924    });
19925    cx.set_state(indoc! {"
19926        one
19927        TWO
19928        ˇTHREE
19929        FOUR
19930        five
19931    "});
19932    cx.run_until_parked();
19933    cx.update_editor(|editor, window, cx| {
19934        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19935    });
19936    cx.run_until_parked();
19937    cx.assert_index_text(Some(indoc! {"
19938        one
19939        TWO
19940        THREE
19941        FOUR
19942        five
19943    "}));
19944    cx.set_state(indoc! { "
19945        one
19946        TWO
19947        ˇTHREE-HUNDRED
19948        FOUR
19949        five
19950    "});
19951    cx.run_until_parked();
19952    cx.update_editor(|editor, window, cx| {
19953        let snapshot = editor.snapshot(window, cx);
19954        let hunks = editor
19955            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
19956            .collect::<Vec<_>>();
19957        assert_eq!(hunks.len(), 1);
19958        assert_eq!(
19959            hunks[0].status(),
19960            DiffHunkStatus {
19961                kind: DiffHunkStatusKind::Modified,
19962                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
19963            }
19964        );
19965
19966        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19967    });
19968    cx.run_until_parked();
19969    cx.assert_index_text(Some(indoc! {"
19970        one
19971        TWO
19972        THREE-HUNDRED
19973        FOUR
19974        five
19975    "}));
19976}
19977
19978#[gpui::test]
19979fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
19980    init_test(cx, |_| {});
19981
19982    let editor = cx.add_window(|window, cx| {
19983        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
19984        build_editor(buffer, window, cx)
19985    });
19986
19987    let render_args = Arc::new(Mutex::new(None));
19988    let snapshot = editor
19989        .update(cx, |editor, window, cx| {
19990            let snapshot = editor.buffer().read(cx).snapshot(cx);
19991            let range =
19992                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
19993
19994            struct RenderArgs {
19995                row: MultiBufferRow,
19996                folded: bool,
19997                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
19998            }
19999
20000            let crease = Crease::inline(
20001                range,
20002                FoldPlaceholder::test(),
20003                {
20004                    let toggle_callback = render_args.clone();
20005                    move |row, folded, callback, _window, _cx| {
20006                        *toggle_callback.lock() = Some(RenderArgs {
20007                            row,
20008                            folded,
20009                            callback,
20010                        });
20011                        div()
20012                    }
20013                },
20014                |_row, _folded, _window, _cx| div(),
20015            );
20016
20017            editor.insert_creases(Some(crease), cx);
20018            let snapshot = editor.snapshot(window, cx);
20019            let _div =
20020                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
20021            snapshot
20022        })
20023        .unwrap();
20024
20025    let render_args = render_args.lock().take().unwrap();
20026    assert_eq!(render_args.row, MultiBufferRow(1));
20027    assert!(!render_args.folded);
20028    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
20029
20030    cx.update_window(*editor, |_, window, cx| {
20031        (render_args.callback)(true, window, cx)
20032    })
20033    .unwrap();
20034    let snapshot = editor
20035        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20036        .unwrap();
20037    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
20038
20039    cx.update_window(*editor, |_, window, cx| {
20040        (render_args.callback)(false, window, cx)
20041    })
20042    .unwrap();
20043    let snapshot = editor
20044        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20045        .unwrap();
20046    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
20047}
20048
20049#[gpui::test]
20050async fn test_input_text(cx: &mut TestAppContext) {
20051    init_test(cx, |_| {});
20052    let mut cx = EditorTestContext::new(cx).await;
20053
20054    cx.set_state(
20055        &r#"ˇone
20056        two
20057
20058        three
20059        fourˇ
20060        five
20061
20062        siˇx"#
20063            .unindent(),
20064    );
20065
20066    cx.dispatch_action(HandleInput(String::new()));
20067    cx.assert_editor_state(
20068        &r#"ˇone
20069        two
20070
20071        three
20072        fourˇ
20073        five
20074
20075        siˇx"#
20076            .unindent(),
20077    );
20078
20079    cx.dispatch_action(HandleInput("AAAA".to_string()));
20080    cx.assert_editor_state(
20081        &r#"AAAAˇone
20082        two
20083
20084        three
20085        fourAAAAˇ
20086        five
20087
20088        siAAAAˇx"#
20089            .unindent(),
20090    );
20091}
20092
20093#[gpui::test]
20094async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
20095    init_test(cx, |_| {});
20096
20097    let mut cx = EditorTestContext::new(cx).await;
20098    cx.set_state(
20099        r#"let foo = 1;
20100let foo = 2;
20101let foo = 3;
20102let fooˇ = 4;
20103let foo = 5;
20104let foo = 6;
20105let foo = 7;
20106let foo = 8;
20107let foo = 9;
20108let foo = 10;
20109let foo = 11;
20110let foo = 12;
20111let foo = 13;
20112let foo = 14;
20113let foo = 15;"#,
20114    );
20115
20116    cx.update_editor(|e, window, cx| {
20117        assert_eq!(
20118            e.next_scroll_position,
20119            NextScrollCursorCenterTopBottom::Center,
20120            "Default next scroll direction is center",
20121        );
20122
20123        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20124        assert_eq!(
20125            e.next_scroll_position,
20126            NextScrollCursorCenterTopBottom::Top,
20127            "After center, next scroll direction should be top",
20128        );
20129
20130        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20131        assert_eq!(
20132            e.next_scroll_position,
20133            NextScrollCursorCenterTopBottom::Bottom,
20134            "After top, next scroll direction should be bottom",
20135        );
20136
20137        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20138        assert_eq!(
20139            e.next_scroll_position,
20140            NextScrollCursorCenterTopBottom::Center,
20141            "After bottom, scrolling should start over",
20142        );
20143
20144        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20145        assert_eq!(
20146            e.next_scroll_position,
20147            NextScrollCursorCenterTopBottom::Top,
20148            "Scrolling continues if retriggered fast enough"
20149        );
20150    });
20151
20152    cx.executor()
20153        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
20154    cx.executor().run_until_parked();
20155    cx.update_editor(|e, _, _| {
20156        assert_eq!(
20157            e.next_scroll_position,
20158            NextScrollCursorCenterTopBottom::Center,
20159            "If scrolling is not triggered fast enough, it should reset"
20160        );
20161    });
20162}
20163
20164#[gpui::test]
20165async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
20166    init_test(cx, |_| {});
20167    let mut cx = EditorLspTestContext::new_rust(
20168        lsp::ServerCapabilities {
20169            definition_provider: Some(lsp::OneOf::Left(true)),
20170            references_provider: Some(lsp::OneOf::Left(true)),
20171            ..lsp::ServerCapabilities::default()
20172        },
20173        cx,
20174    )
20175    .await;
20176
20177    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
20178        let go_to_definition = cx
20179            .lsp
20180            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20181                move |params, _| async move {
20182                    if empty_go_to_definition {
20183                        Ok(None)
20184                    } else {
20185                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
20186                            uri: params.text_document_position_params.text_document.uri,
20187                            range: lsp::Range::new(
20188                                lsp::Position::new(4, 3),
20189                                lsp::Position::new(4, 6),
20190                            ),
20191                        })))
20192                    }
20193                },
20194            );
20195        let references = cx
20196            .lsp
20197            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
20198                Ok(Some(vec![lsp::Location {
20199                    uri: params.text_document_position.text_document.uri,
20200                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
20201                }]))
20202            });
20203        (go_to_definition, references)
20204    };
20205
20206    cx.set_state(
20207        &r#"fn one() {
20208            let mut a = ˇtwo();
20209        }
20210
20211        fn two() {}"#
20212            .unindent(),
20213    );
20214    set_up_lsp_handlers(false, &mut cx);
20215    let navigated = cx
20216        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20217        .await
20218        .expect("Failed to navigate to definition");
20219    assert_eq!(
20220        navigated,
20221        Navigated::Yes,
20222        "Should have navigated to definition from the GetDefinition response"
20223    );
20224    cx.assert_editor_state(
20225        &r#"fn one() {
20226            let mut a = two();
20227        }
20228
20229        fn «twoˇ»() {}"#
20230            .unindent(),
20231    );
20232
20233    let editors = cx.update_workspace(|workspace, _, cx| {
20234        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20235    });
20236    cx.update_editor(|_, _, test_editor_cx| {
20237        assert_eq!(
20238            editors.len(),
20239            1,
20240            "Initially, only one, test, editor should be open in the workspace"
20241        );
20242        assert_eq!(
20243            test_editor_cx.entity(),
20244            editors.last().expect("Asserted len is 1").clone()
20245        );
20246    });
20247
20248    set_up_lsp_handlers(true, &mut cx);
20249    let navigated = cx
20250        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20251        .await
20252        .expect("Failed to navigate to lookup references");
20253    assert_eq!(
20254        navigated,
20255        Navigated::Yes,
20256        "Should have navigated to references as a fallback after empty GoToDefinition response"
20257    );
20258    // We should not change the selections in the existing file,
20259    // if opening another milti buffer with the references
20260    cx.assert_editor_state(
20261        &r#"fn one() {
20262            let mut a = two();
20263        }
20264
20265        fn «twoˇ»() {}"#
20266            .unindent(),
20267    );
20268    let editors = cx.update_workspace(|workspace, _, cx| {
20269        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20270    });
20271    cx.update_editor(|_, _, test_editor_cx| {
20272        assert_eq!(
20273            editors.len(),
20274            2,
20275            "After falling back to references search, we open a new editor with the results"
20276        );
20277        let references_fallback_text = editors
20278            .into_iter()
20279            .find(|new_editor| *new_editor != test_editor_cx.entity())
20280            .expect("Should have one non-test editor now")
20281            .read(test_editor_cx)
20282            .text(test_editor_cx);
20283        assert_eq!(
20284            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
20285            "Should use the range from the references response and not the GoToDefinition one"
20286        );
20287    });
20288}
20289
20290#[gpui::test]
20291async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
20292    init_test(cx, |_| {});
20293    cx.update(|cx| {
20294        let mut editor_settings = EditorSettings::get_global(cx).clone();
20295        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
20296        EditorSettings::override_global(editor_settings, cx);
20297    });
20298    let mut cx = EditorLspTestContext::new_rust(
20299        lsp::ServerCapabilities {
20300            definition_provider: Some(lsp::OneOf::Left(true)),
20301            references_provider: Some(lsp::OneOf::Left(true)),
20302            ..lsp::ServerCapabilities::default()
20303        },
20304        cx,
20305    )
20306    .await;
20307    let original_state = r#"fn one() {
20308        let mut a = ˇtwo();
20309    }
20310
20311    fn two() {}"#
20312        .unindent();
20313    cx.set_state(&original_state);
20314
20315    let mut go_to_definition = cx
20316        .lsp
20317        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20318            move |_, _| async move { Ok(None) },
20319        );
20320    let _references = cx
20321        .lsp
20322        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
20323            panic!("Should not call for references with no go to definition fallback")
20324        });
20325
20326    let navigated = cx
20327        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20328        .await
20329        .expect("Failed to navigate to lookup references");
20330    go_to_definition
20331        .next()
20332        .await
20333        .expect("Should have called the go_to_definition handler");
20334
20335    assert_eq!(
20336        navigated,
20337        Navigated::No,
20338        "Should have navigated to references as a fallback after empty GoToDefinition response"
20339    );
20340    cx.assert_editor_state(&original_state);
20341    let editors = cx.update_workspace(|workspace, _, cx| {
20342        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20343    });
20344    cx.update_editor(|_, _, _| {
20345        assert_eq!(
20346            editors.len(),
20347            1,
20348            "After unsuccessful fallback, no other editor should have been opened"
20349        );
20350    });
20351}
20352
20353#[gpui::test]
20354async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
20355    init_test(cx, |_| {});
20356
20357    let language = Arc::new(Language::new(
20358        LanguageConfig::default(),
20359        Some(tree_sitter_rust::LANGUAGE.into()),
20360    ));
20361
20362    let text = r#"
20363        #[cfg(test)]
20364        mod tests() {
20365            #[test]
20366            fn runnable_1() {
20367                let a = 1;
20368            }
20369
20370            #[test]
20371            fn runnable_2() {
20372                let a = 1;
20373                let b = 2;
20374            }
20375        }
20376    "#
20377    .unindent();
20378
20379    let fs = FakeFs::new(cx.executor());
20380    fs.insert_file("/file.rs", Default::default()).await;
20381
20382    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20383    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20384    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20385    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
20386    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
20387
20388    let editor = cx.new_window_entity(|window, cx| {
20389        Editor::new(
20390            EditorMode::full(),
20391            multi_buffer,
20392            Some(project.clone()),
20393            window,
20394            cx,
20395        )
20396    });
20397
20398    editor.update_in(cx, |editor, window, cx| {
20399        let snapshot = editor.buffer().read(cx).snapshot(cx);
20400        editor.tasks.insert(
20401            (buffer.read(cx).remote_id(), 3),
20402            RunnableTasks {
20403                templates: vec![],
20404                offset: snapshot.anchor_before(43),
20405                column: 0,
20406                extra_variables: HashMap::default(),
20407                context_range: BufferOffset(43)..BufferOffset(85),
20408            },
20409        );
20410        editor.tasks.insert(
20411            (buffer.read(cx).remote_id(), 8),
20412            RunnableTasks {
20413                templates: vec![],
20414                offset: snapshot.anchor_before(86),
20415                column: 0,
20416                extra_variables: HashMap::default(),
20417                context_range: BufferOffset(86)..BufferOffset(191),
20418            },
20419        );
20420
20421        // Test finding task when cursor is inside function body
20422        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20423            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
20424        });
20425        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20426        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
20427
20428        // Test finding task when cursor is on function name
20429        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20430            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
20431        });
20432        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20433        assert_eq!(row, 8, "Should find task when cursor is on function name");
20434    });
20435}
20436
20437#[gpui::test]
20438async fn test_folding_buffers(cx: &mut TestAppContext) {
20439    init_test(cx, |_| {});
20440
20441    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20442    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
20443    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
20444
20445    let fs = FakeFs::new(cx.executor());
20446    fs.insert_tree(
20447        path!("/a"),
20448        json!({
20449            "first.rs": sample_text_1,
20450            "second.rs": sample_text_2,
20451            "third.rs": sample_text_3,
20452        }),
20453    )
20454    .await;
20455    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20456    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20457    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20458    let worktree = project.update(cx, |project, cx| {
20459        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20460        assert_eq!(worktrees.len(), 1);
20461        worktrees.pop().unwrap()
20462    });
20463    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20464
20465    let buffer_1 = project
20466        .update(cx, |project, cx| {
20467            project.open_buffer((worktree_id, "first.rs"), cx)
20468        })
20469        .await
20470        .unwrap();
20471    let buffer_2 = project
20472        .update(cx, |project, cx| {
20473            project.open_buffer((worktree_id, "second.rs"), cx)
20474        })
20475        .await
20476        .unwrap();
20477    let buffer_3 = project
20478        .update(cx, |project, cx| {
20479            project.open_buffer((worktree_id, "third.rs"), cx)
20480        })
20481        .await
20482        .unwrap();
20483
20484    let multi_buffer = cx.new(|cx| {
20485        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20486        multi_buffer.push_excerpts(
20487            buffer_1.clone(),
20488            [
20489                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20490                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20491                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20492            ],
20493            cx,
20494        );
20495        multi_buffer.push_excerpts(
20496            buffer_2.clone(),
20497            [
20498                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20499                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20500                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20501            ],
20502            cx,
20503        );
20504        multi_buffer.push_excerpts(
20505            buffer_3.clone(),
20506            [
20507                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20508                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20509                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20510            ],
20511            cx,
20512        );
20513        multi_buffer
20514    });
20515    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20516        Editor::new(
20517            EditorMode::full(),
20518            multi_buffer.clone(),
20519            Some(project.clone()),
20520            window,
20521            cx,
20522        )
20523    });
20524
20525    assert_eq!(
20526        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20527        "\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",
20528    );
20529
20530    multi_buffer_editor.update(cx, |editor, cx| {
20531        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20532    });
20533    assert_eq!(
20534        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20535        "\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",
20536        "After folding the first buffer, its text should not be displayed"
20537    );
20538
20539    multi_buffer_editor.update(cx, |editor, cx| {
20540        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20541    });
20542    assert_eq!(
20543        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20544        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20545        "After folding the second buffer, its text should not be displayed"
20546    );
20547
20548    multi_buffer_editor.update(cx, |editor, cx| {
20549        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20550    });
20551    assert_eq!(
20552        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20553        "\n\n\n\n\n",
20554        "After folding the third buffer, its text should not be displayed"
20555    );
20556
20557    // Emulate selection inside the fold logic, that should work
20558    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20559        editor
20560            .snapshot(window, cx)
20561            .next_line_boundary(Point::new(0, 4));
20562    });
20563
20564    multi_buffer_editor.update(cx, |editor, cx| {
20565        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20566    });
20567    assert_eq!(
20568        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20569        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20570        "After unfolding the second buffer, its text should be displayed"
20571    );
20572
20573    // Typing inside of buffer 1 causes that buffer to be unfolded.
20574    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20575        assert_eq!(
20576            multi_buffer
20577                .read(cx)
20578                .snapshot(cx)
20579                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
20580                .collect::<String>(),
20581            "bbbb"
20582        );
20583        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20584            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20585        });
20586        editor.handle_input("B", window, cx);
20587    });
20588
20589    assert_eq!(
20590        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20591        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20592        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
20593    );
20594
20595    multi_buffer_editor.update(cx, |editor, cx| {
20596        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20597    });
20598    assert_eq!(
20599        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20600        "\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",
20601        "After unfolding the all buffers, all original text should be displayed"
20602    );
20603}
20604
20605#[gpui::test]
20606async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
20607    init_test(cx, |_| {});
20608
20609    let sample_text_1 = "1111\n2222\n3333".to_string();
20610    let sample_text_2 = "4444\n5555\n6666".to_string();
20611    let sample_text_3 = "7777\n8888\n9999".to_string();
20612
20613    let fs = FakeFs::new(cx.executor());
20614    fs.insert_tree(
20615        path!("/a"),
20616        json!({
20617            "first.rs": sample_text_1,
20618            "second.rs": sample_text_2,
20619            "third.rs": sample_text_3,
20620        }),
20621    )
20622    .await;
20623    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20624    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20625    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20626    let worktree = project.update(cx, |project, cx| {
20627        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20628        assert_eq!(worktrees.len(), 1);
20629        worktrees.pop().unwrap()
20630    });
20631    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20632
20633    let buffer_1 = project
20634        .update(cx, |project, cx| {
20635            project.open_buffer((worktree_id, "first.rs"), cx)
20636        })
20637        .await
20638        .unwrap();
20639    let buffer_2 = project
20640        .update(cx, |project, cx| {
20641            project.open_buffer((worktree_id, "second.rs"), cx)
20642        })
20643        .await
20644        .unwrap();
20645    let buffer_3 = project
20646        .update(cx, |project, cx| {
20647            project.open_buffer((worktree_id, "third.rs"), cx)
20648        })
20649        .await
20650        .unwrap();
20651
20652    let multi_buffer = cx.new(|cx| {
20653        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20654        multi_buffer.push_excerpts(
20655            buffer_1.clone(),
20656            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20657            cx,
20658        );
20659        multi_buffer.push_excerpts(
20660            buffer_2.clone(),
20661            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20662            cx,
20663        );
20664        multi_buffer.push_excerpts(
20665            buffer_3.clone(),
20666            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
20667            cx,
20668        );
20669        multi_buffer
20670    });
20671
20672    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20673        Editor::new(
20674            EditorMode::full(),
20675            multi_buffer,
20676            Some(project.clone()),
20677            window,
20678            cx,
20679        )
20680    });
20681
20682    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
20683    assert_eq!(
20684        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20685        full_text,
20686    );
20687
20688    multi_buffer_editor.update(cx, |editor, cx| {
20689        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20690    });
20691    assert_eq!(
20692        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20693        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
20694        "After folding the first buffer, its text should not be displayed"
20695    );
20696
20697    multi_buffer_editor.update(cx, |editor, cx| {
20698        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20699    });
20700
20701    assert_eq!(
20702        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20703        "\n\n\n\n\n\n7777\n8888\n9999",
20704        "After folding the second buffer, its text should not be displayed"
20705    );
20706
20707    multi_buffer_editor.update(cx, |editor, cx| {
20708        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20709    });
20710    assert_eq!(
20711        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20712        "\n\n\n\n\n",
20713        "After folding the third buffer, its text should not be displayed"
20714    );
20715
20716    multi_buffer_editor.update(cx, |editor, cx| {
20717        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20718    });
20719    assert_eq!(
20720        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20721        "\n\n\n\n4444\n5555\n6666\n\n",
20722        "After unfolding the second buffer, its text should be displayed"
20723    );
20724
20725    multi_buffer_editor.update(cx, |editor, cx| {
20726        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
20727    });
20728    assert_eq!(
20729        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20730        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
20731        "After unfolding the first buffer, its text should be displayed"
20732    );
20733
20734    multi_buffer_editor.update(cx, |editor, cx| {
20735        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
20736    });
20737    assert_eq!(
20738        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20739        full_text,
20740        "After unfolding all buffers, all original text should be displayed"
20741    );
20742}
20743
20744#[gpui::test]
20745async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
20746    init_test(cx, |_| {});
20747
20748    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20749
20750    let fs = FakeFs::new(cx.executor());
20751    fs.insert_tree(
20752        path!("/a"),
20753        json!({
20754            "main.rs": sample_text,
20755        }),
20756    )
20757    .await;
20758    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20759    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20760    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20761    let worktree = project.update(cx, |project, cx| {
20762        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20763        assert_eq!(worktrees.len(), 1);
20764        worktrees.pop().unwrap()
20765    });
20766    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20767
20768    let buffer_1 = project
20769        .update(cx, |project, cx| {
20770            project.open_buffer((worktree_id, "main.rs"), cx)
20771        })
20772        .await
20773        .unwrap();
20774
20775    let multi_buffer = cx.new(|cx| {
20776        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20777        multi_buffer.push_excerpts(
20778            buffer_1.clone(),
20779            [ExcerptRange::new(
20780                Point::new(0, 0)
20781                    ..Point::new(
20782                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
20783                        0,
20784                    ),
20785            )],
20786            cx,
20787        );
20788        multi_buffer
20789    });
20790    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20791        Editor::new(
20792            EditorMode::full(),
20793            multi_buffer,
20794            Some(project.clone()),
20795            window,
20796            cx,
20797        )
20798    });
20799
20800    let selection_range = Point::new(1, 0)..Point::new(2, 0);
20801    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20802        enum TestHighlight {}
20803        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
20804        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
20805        editor.highlight_text::<TestHighlight>(
20806            vec![highlight_range.clone()],
20807            HighlightStyle::color(Hsla::green()),
20808            cx,
20809        );
20810        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20811            s.select_ranges(Some(highlight_range))
20812        });
20813    });
20814
20815    let full_text = format!("\n\n{sample_text}");
20816    assert_eq!(
20817        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20818        full_text,
20819    );
20820}
20821
20822#[gpui::test]
20823async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
20824    init_test(cx, |_| {});
20825    cx.update(|cx| {
20826        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
20827            "keymaps/default-linux.json",
20828            cx,
20829        )
20830        .unwrap();
20831        cx.bind_keys(default_key_bindings);
20832    });
20833
20834    let (editor, cx) = cx.add_window_view(|window, cx| {
20835        let multi_buffer = MultiBuffer::build_multi(
20836            [
20837                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
20838                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
20839                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
20840                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
20841            ],
20842            cx,
20843        );
20844        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
20845
20846        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
20847        // fold all but the second buffer, so that we test navigating between two
20848        // adjacent folded buffers, as well as folded buffers at the start and
20849        // end the multibuffer
20850        editor.fold_buffer(buffer_ids[0], cx);
20851        editor.fold_buffer(buffer_ids[2], cx);
20852        editor.fold_buffer(buffer_ids[3], cx);
20853
20854        editor
20855    });
20856    cx.simulate_resize(size(px(1000.), px(1000.)));
20857
20858    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
20859    cx.assert_excerpts_with_selections(indoc! {"
20860        [EXCERPT]
20861        ˇ[FOLDED]
20862        [EXCERPT]
20863        a1
20864        b1
20865        [EXCERPT]
20866        [FOLDED]
20867        [EXCERPT]
20868        [FOLDED]
20869        "
20870    });
20871    cx.simulate_keystroke("down");
20872    cx.assert_excerpts_with_selections(indoc! {"
20873        [EXCERPT]
20874        [FOLDED]
20875        [EXCERPT]
20876        ˇa1
20877        b1
20878        [EXCERPT]
20879        [FOLDED]
20880        [EXCERPT]
20881        [FOLDED]
20882        "
20883    });
20884    cx.simulate_keystroke("down");
20885    cx.assert_excerpts_with_selections(indoc! {"
20886        [EXCERPT]
20887        [FOLDED]
20888        [EXCERPT]
20889        a1
20890        ˇb1
20891        [EXCERPT]
20892        [FOLDED]
20893        [EXCERPT]
20894        [FOLDED]
20895        "
20896    });
20897    cx.simulate_keystroke("down");
20898    cx.assert_excerpts_with_selections(indoc! {"
20899        [EXCERPT]
20900        [FOLDED]
20901        [EXCERPT]
20902        a1
20903        b1
20904        ˇ[EXCERPT]
20905        [FOLDED]
20906        [EXCERPT]
20907        [FOLDED]
20908        "
20909    });
20910    cx.simulate_keystroke("down");
20911    cx.assert_excerpts_with_selections(indoc! {"
20912        [EXCERPT]
20913        [FOLDED]
20914        [EXCERPT]
20915        a1
20916        b1
20917        [EXCERPT]
20918        ˇ[FOLDED]
20919        [EXCERPT]
20920        [FOLDED]
20921        "
20922    });
20923    for _ in 0..5 {
20924        cx.simulate_keystroke("down");
20925        cx.assert_excerpts_with_selections(indoc! {"
20926            [EXCERPT]
20927            [FOLDED]
20928            [EXCERPT]
20929            a1
20930            b1
20931            [EXCERPT]
20932            [FOLDED]
20933            [EXCERPT]
20934            ˇ[FOLDED]
20935            "
20936        });
20937    }
20938
20939    cx.simulate_keystroke("up");
20940    cx.assert_excerpts_with_selections(indoc! {"
20941        [EXCERPT]
20942        [FOLDED]
20943        [EXCERPT]
20944        a1
20945        b1
20946        [EXCERPT]
20947        ˇ[FOLDED]
20948        [EXCERPT]
20949        [FOLDED]
20950        "
20951    });
20952    cx.simulate_keystroke("up");
20953    cx.assert_excerpts_with_selections(indoc! {"
20954        [EXCERPT]
20955        [FOLDED]
20956        [EXCERPT]
20957        a1
20958        b1
20959        ˇ[EXCERPT]
20960        [FOLDED]
20961        [EXCERPT]
20962        [FOLDED]
20963        "
20964    });
20965    cx.simulate_keystroke("up");
20966    cx.assert_excerpts_with_selections(indoc! {"
20967        [EXCERPT]
20968        [FOLDED]
20969        [EXCERPT]
20970        a1
20971        ˇb1
20972        [EXCERPT]
20973        [FOLDED]
20974        [EXCERPT]
20975        [FOLDED]
20976        "
20977    });
20978    cx.simulate_keystroke("up");
20979    cx.assert_excerpts_with_selections(indoc! {"
20980        [EXCERPT]
20981        [FOLDED]
20982        [EXCERPT]
20983        ˇa1
20984        b1
20985        [EXCERPT]
20986        [FOLDED]
20987        [EXCERPT]
20988        [FOLDED]
20989        "
20990    });
20991    for _ in 0..5 {
20992        cx.simulate_keystroke("up");
20993        cx.assert_excerpts_with_selections(indoc! {"
20994            [EXCERPT]
20995            ˇ[FOLDED]
20996            [EXCERPT]
20997            a1
20998            b1
20999            [EXCERPT]
21000            [FOLDED]
21001            [EXCERPT]
21002            [FOLDED]
21003            "
21004        });
21005    }
21006}
21007
21008#[gpui::test]
21009async fn test_edit_prediction_text(cx: &mut TestAppContext) {
21010    init_test(cx, |_| {});
21011
21012    // Simple insertion
21013    assert_highlighted_edits(
21014        "Hello, world!",
21015        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
21016        true,
21017        cx,
21018        |highlighted_edits, cx| {
21019            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
21020            assert_eq!(highlighted_edits.highlights.len(), 1);
21021            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
21022            assert_eq!(
21023                highlighted_edits.highlights[0].1.background_color,
21024                Some(cx.theme().status().created_background)
21025            );
21026        },
21027    )
21028    .await;
21029
21030    // Replacement
21031    assert_highlighted_edits(
21032        "This is a test.",
21033        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
21034        false,
21035        cx,
21036        |highlighted_edits, cx| {
21037            assert_eq!(highlighted_edits.text, "That is a test.");
21038            assert_eq!(highlighted_edits.highlights.len(), 1);
21039            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
21040            assert_eq!(
21041                highlighted_edits.highlights[0].1.background_color,
21042                Some(cx.theme().status().created_background)
21043            );
21044        },
21045    )
21046    .await;
21047
21048    // Multiple edits
21049    assert_highlighted_edits(
21050        "Hello, world!",
21051        vec![
21052            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
21053            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
21054        ],
21055        false,
21056        cx,
21057        |highlighted_edits, cx| {
21058            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
21059            assert_eq!(highlighted_edits.highlights.len(), 2);
21060            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
21061            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
21062            assert_eq!(
21063                highlighted_edits.highlights[0].1.background_color,
21064                Some(cx.theme().status().created_background)
21065            );
21066            assert_eq!(
21067                highlighted_edits.highlights[1].1.background_color,
21068                Some(cx.theme().status().created_background)
21069            );
21070        },
21071    )
21072    .await;
21073
21074    // Multiple lines with edits
21075    assert_highlighted_edits(
21076        "First line\nSecond line\nThird line\nFourth line",
21077        vec![
21078            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
21079            (
21080                Point::new(2, 0)..Point::new(2, 10),
21081                "New third line".to_string(),
21082            ),
21083            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
21084        ],
21085        false,
21086        cx,
21087        |highlighted_edits, cx| {
21088            assert_eq!(
21089                highlighted_edits.text,
21090                "Second modified\nNew third line\nFourth updated line"
21091            );
21092            assert_eq!(highlighted_edits.highlights.len(), 3);
21093            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
21094            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
21095            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
21096            for highlight in &highlighted_edits.highlights {
21097                assert_eq!(
21098                    highlight.1.background_color,
21099                    Some(cx.theme().status().created_background)
21100                );
21101            }
21102        },
21103    )
21104    .await;
21105}
21106
21107#[gpui::test]
21108async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
21109    init_test(cx, |_| {});
21110
21111    // Deletion
21112    assert_highlighted_edits(
21113        "Hello, world!",
21114        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
21115        true,
21116        cx,
21117        |highlighted_edits, cx| {
21118            assert_eq!(highlighted_edits.text, "Hello, world!");
21119            assert_eq!(highlighted_edits.highlights.len(), 1);
21120            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
21121            assert_eq!(
21122                highlighted_edits.highlights[0].1.background_color,
21123                Some(cx.theme().status().deleted_background)
21124            );
21125        },
21126    )
21127    .await;
21128
21129    // Insertion
21130    assert_highlighted_edits(
21131        "Hello, world!",
21132        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
21133        true,
21134        cx,
21135        |highlighted_edits, cx| {
21136            assert_eq!(highlighted_edits.highlights.len(), 1);
21137            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
21138            assert_eq!(
21139                highlighted_edits.highlights[0].1.background_color,
21140                Some(cx.theme().status().created_background)
21141            );
21142        },
21143    )
21144    .await;
21145}
21146
21147async fn assert_highlighted_edits(
21148    text: &str,
21149    edits: Vec<(Range<Point>, String)>,
21150    include_deletions: bool,
21151    cx: &mut TestAppContext,
21152    assertion_fn: impl Fn(HighlightedText, &App),
21153) {
21154    let window = cx.add_window(|window, cx| {
21155        let buffer = MultiBuffer::build_simple(text, cx);
21156        Editor::new(EditorMode::full(), buffer, None, window, cx)
21157    });
21158    let cx = &mut VisualTestContext::from_window(*window, cx);
21159
21160    let (buffer, snapshot) = window
21161        .update(cx, |editor, _window, cx| {
21162            (
21163                editor.buffer().clone(),
21164                editor.buffer().read(cx).snapshot(cx),
21165            )
21166        })
21167        .unwrap();
21168
21169    let edits = edits
21170        .into_iter()
21171        .map(|(range, edit)| {
21172            (
21173                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
21174                edit,
21175            )
21176        })
21177        .collect::<Vec<_>>();
21178
21179    let text_anchor_edits = edits
21180        .clone()
21181        .into_iter()
21182        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
21183        .collect::<Vec<_>>();
21184
21185    let edit_preview = window
21186        .update(cx, |_, _window, cx| {
21187            buffer
21188                .read(cx)
21189                .as_singleton()
21190                .unwrap()
21191                .read(cx)
21192                .preview_edits(text_anchor_edits.into(), cx)
21193        })
21194        .unwrap()
21195        .await;
21196
21197    cx.update(|_window, cx| {
21198        let highlighted_edits = edit_prediction_edit_text(
21199            snapshot.as_singleton().unwrap().2,
21200            &edits,
21201            &edit_preview,
21202            include_deletions,
21203            cx,
21204        );
21205        assertion_fn(highlighted_edits, cx)
21206    });
21207}
21208
21209#[track_caller]
21210fn assert_breakpoint(
21211    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
21212    path: &Arc<Path>,
21213    expected: Vec<(u32, Breakpoint)>,
21214) {
21215    if expected.is_empty() {
21216        assert!(!breakpoints.contains_key(path), "{}", path.display());
21217    } else {
21218        let mut breakpoint = breakpoints
21219            .get(path)
21220            .unwrap()
21221            .iter()
21222            .map(|breakpoint| {
21223                (
21224                    breakpoint.row,
21225                    Breakpoint {
21226                        message: breakpoint.message.clone(),
21227                        state: breakpoint.state,
21228                        condition: breakpoint.condition.clone(),
21229                        hit_condition: breakpoint.hit_condition.clone(),
21230                    },
21231                )
21232            })
21233            .collect::<Vec<_>>();
21234
21235        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
21236
21237        assert_eq!(expected, breakpoint);
21238    }
21239}
21240
21241fn add_log_breakpoint_at_cursor(
21242    editor: &mut Editor,
21243    log_message: &str,
21244    window: &mut Window,
21245    cx: &mut Context<Editor>,
21246) {
21247    let (anchor, bp) = editor
21248        .breakpoints_at_cursors(window, cx)
21249        .first()
21250        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
21251        .unwrap_or_else(|| {
21252            let cursor_position: Point = editor.selections.newest(cx).head();
21253
21254            let breakpoint_position = editor
21255                .snapshot(window, cx)
21256                .display_snapshot
21257                .buffer_snapshot
21258                .anchor_before(Point::new(cursor_position.row, 0));
21259
21260            (breakpoint_position, Breakpoint::new_log(log_message))
21261        });
21262
21263    editor.edit_breakpoint_at_anchor(
21264        anchor,
21265        bp,
21266        BreakpointEditAction::EditLogMessage(log_message.into()),
21267        cx,
21268    );
21269}
21270
21271#[gpui::test]
21272async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
21273    init_test(cx, |_| {});
21274
21275    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21276    let fs = FakeFs::new(cx.executor());
21277    fs.insert_tree(
21278        path!("/a"),
21279        json!({
21280            "main.rs": sample_text,
21281        }),
21282    )
21283    .await;
21284    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21285    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21286    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21287
21288    let fs = FakeFs::new(cx.executor());
21289    fs.insert_tree(
21290        path!("/a"),
21291        json!({
21292            "main.rs": sample_text,
21293        }),
21294    )
21295    .await;
21296    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21297    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21298    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21299    let worktree_id = workspace
21300        .update(cx, |workspace, _window, cx| {
21301            workspace.project().update(cx, |project, cx| {
21302                project.worktrees(cx).next().unwrap().read(cx).id()
21303            })
21304        })
21305        .unwrap();
21306
21307    let buffer = project
21308        .update(cx, |project, cx| {
21309            project.open_buffer((worktree_id, "main.rs"), cx)
21310        })
21311        .await
21312        .unwrap();
21313
21314    let (editor, cx) = cx.add_window_view(|window, cx| {
21315        Editor::new(
21316            EditorMode::full(),
21317            MultiBuffer::build_from_buffer(buffer, cx),
21318            Some(project.clone()),
21319            window,
21320            cx,
21321        )
21322    });
21323
21324    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21325    let abs_path = project.read_with(cx, |project, cx| {
21326        project
21327            .absolute_path(&project_path, cx)
21328            .map(Arc::from)
21329            .unwrap()
21330    });
21331
21332    // assert we can add breakpoint on the first line
21333    editor.update_in(cx, |editor, window, cx| {
21334        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21335        editor.move_to_end(&MoveToEnd, window, cx);
21336        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21337    });
21338
21339    let breakpoints = editor.update(cx, |editor, cx| {
21340        editor
21341            .breakpoint_store()
21342            .as_ref()
21343            .unwrap()
21344            .read(cx)
21345            .all_source_breakpoints(cx)
21346    });
21347
21348    assert_eq!(1, breakpoints.len());
21349    assert_breakpoint(
21350        &breakpoints,
21351        &abs_path,
21352        vec![
21353            (0, Breakpoint::new_standard()),
21354            (3, Breakpoint::new_standard()),
21355        ],
21356    );
21357
21358    editor.update_in(cx, |editor, window, cx| {
21359        editor.move_to_beginning(&MoveToBeginning, window, cx);
21360        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21361    });
21362
21363    let breakpoints = editor.update(cx, |editor, cx| {
21364        editor
21365            .breakpoint_store()
21366            .as_ref()
21367            .unwrap()
21368            .read(cx)
21369            .all_source_breakpoints(cx)
21370    });
21371
21372    assert_eq!(1, breakpoints.len());
21373    assert_breakpoint(
21374        &breakpoints,
21375        &abs_path,
21376        vec![(3, Breakpoint::new_standard())],
21377    );
21378
21379    editor.update_in(cx, |editor, window, cx| {
21380        editor.move_to_end(&MoveToEnd, window, cx);
21381        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21382    });
21383
21384    let breakpoints = editor.update(cx, |editor, cx| {
21385        editor
21386            .breakpoint_store()
21387            .as_ref()
21388            .unwrap()
21389            .read(cx)
21390            .all_source_breakpoints(cx)
21391    });
21392
21393    assert_eq!(0, breakpoints.len());
21394    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21395}
21396
21397#[gpui::test]
21398async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
21399    init_test(cx, |_| {});
21400
21401    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21402
21403    let fs = FakeFs::new(cx.executor());
21404    fs.insert_tree(
21405        path!("/a"),
21406        json!({
21407            "main.rs": sample_text,
21408        }),
21409    )
21410    .await;
21411    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21412    let (workspace, cx) =
21413        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21414
21415    let worktree_id = workspace.update(cx, |workspace, cx| {
21416        workspace.project().update(cx, |project, cx| {
21417            project.worktrees(cx).next().unwrap().read(cx).id()
21418        })
21419    });
21420
21421    let buffer = project
21422        .update(cx, |project, cx| {
21423            project.open_buffer((worktree_id, "main.rs"), cx)
21424        })
21425        .await
21426        .unwrap();
21427
21428    let (editor, cx) = cx.add_window_view(|window, cx| {
21429        Editor::new(
21430            EditorMode::full(),
21431            MultiBuffer::build_from_buffer(buffer, cx),
21432            Some(project.clone()),
21433            window,
21434            cx,
21435        )
21436    });
21437
21438    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21439    let abs_path = project.read_with(cx, |project, cx| {
21440        project
21441            .absolute_path(&project_path, cx)
21442            .map(Arc::from)
21443            .unwrap()
21444    });
21445
21446    editor.update_in(cx, |editor, window, cx| {
21447        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21448    });
21449
21450    let breakpoints = editor.update(cx, |editor, cx| {
21451        editor
21452            .breakpoint_store()
21453            .as_ref()
21454            .unwrap()
21455            .read(cx)
21456            .all_source_breakpoints(cx)
21457    });
21458
21459    assert_breakpoint(
21460        &breakpoints,
21461        &abs_path,
21462        vec![(0, Breakpoint::new_log("hello world"))],
21463    );
21464
21465    // Removing a log message from a log breakpoint should remove it
21466    editor.update_in(cx, |editor, window, cx| {
21467        add_log_breakpoint_at_cursor(editor, "", window, cx);
21468    });
21469
21470    let breakpoints = editor.update(cx, |editor, cx| {
21471        editor
21472            .breakpoint_store()
21473            .as_ref()
21474            .unwrap()
21475            .read(cx)
21476            .all_source_breakpoints(cx)
21477    });
21478
21479    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21480
21481    editor.update_in(cx, |editor, window, cx| {
21482        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21483        editor.move_to_end(&MoveToEnd, window, cx);
21484        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21485        // Not adding a log message to a standard breakpoint shouldn't remove it
21486        add_log_breakpoint_at_cursor(editor, "", window, cx);
21487    });
21488
21489    let breakpoints = editor.update(cx, |editor, cx| {
21490        editor
21491            .breakpoint_store()
21492            .as_ref()
21493            .unwrap()
21494            .read(cx)
21495            .all_source_breakpoints(cx)
21496    });
21497
21498    assert_breakpoint(
21499        &breakpoints,
21500        &abs_path,
21501        vec![
21502            (0, Breakpoint::new_standard()),
21503            (3, Breakpoint::new_standard()),
21504        ],
21505    );
21506
21507    editor.update_in(cx, |editor, window, cx| {
21508        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21509    });
21510
21511    let breakpoints = editor.update(cx, |editor, cx| {
21512        editor
21513            .breakpoint_store()
21514            .as_ref()
21515            .unwrap()
21516            .read(cx)
21517            .all_source_breakpoints(cx)
21518    });
21519
21520    assert_breakpoint(
21521        &breakpoints,
21522        &abs_path,
21523        vec![
21524            (0, Breakpoint::new_standard()),
21525            (3, Breakpoint::new_log("hello world")),
21526        ],
21527    );
21528
21529    editor.update_in(cx, |editor, window, cx| {
21530        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
21531    });
21532
21533    let breakpoints = editor.update(cx, |editor, cx| {
21534        editor
21535            .breakpoint_store()
21536            .as_ref()
21537            .unwrap()
21538            .read(cx)
21539            .all_source_breakpoints(cx)
21540    });
21541
21542    assert_breakpoint(
21543        &breakpoints,
21544        &abs_path,
21545        vec![
21546            (0, Breakpoint::new_standard()),
21547            (3, Breakpoint::new_log("hello Earth!!")),
21548        ],
21549    );
21550}
21551
21552/// This also tests that Editor::breakpoint_at_cursor_head is working properly
21553/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
21554/// or when breakpoints were placed out of order. This tests for a regression too
21555#[gpui::test]
21556async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
21557    init_test(cx, |_| {});
21558
21559    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21560    let fs = FakeFs::new(cx.executor());
21561    fs.insert_tree(
21562        path!("/a"),
21563        json!({
21564            "main.rs": sample_text,
21565        }),
21566    )
21567    .await;
21568    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21569    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21570    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21571
21572    let fs = FakeFs::new(cx.executor());
21573    fs.insert_tree(
21574        path!("/a"),
21575        json!({
21576            "main.rs": sample_text,
21577        }),
21578    )
21579    .await;
21580    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21581    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21582    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21583    let worktree_id = workspace
21584        .update(cx, |workspace, _window, cx| {
21585            workspace.project().update(cx, |project, cx| {
21586                project.worktrees(cx).next().unwrap().read(cx).id()
21587            })
21588        })
21589        .unwrap();
21590
21591    let buffer = project
21592        .update(cx, |project, cx| {
21593            project.open_buffer((worktree_id, "main.rs"), cx)
21594        })
21595        .await
21596        .unwrap();
21597
21598    let (editor, cx) = cx.add_window_view(|window, cx| {
21599        Editor::new(
21600            EditorMode::full(),
21601            MultiBuffer::build_from_buffer(buffer, cx),
21602            Some(project.clone()),
21603            window,
21604            cx,
21605        )
21606    });
21607
21608    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21609    let abs_path = project.read_with(cx, |project, cx| {
21610        project
21611            .absolute_path(&project_path, cx)
21612            .map(Arc::from)
21613            .unwrap()
21614    });
21615
21616    // assert we can add breakpoint on the first line
21617    editor.update_in(cx, |editor, window, cx| {
21618        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21619        editor.move_to_end(&MoveToEnd, window, cx);
21620        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21621        editor.move_up(&MoveUp, window, cx);
21622        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21623    });
21624
21625    let breakpoints = editor.update(cx, |editor, cx| {
21626        editor
21627            .breakpoint_store()
21628            .as_ref()
21629            .unwrap()
21630            .read(cx)
21631            .all_source_breakpoints(cx)
21632    });
21633
21634    assert_eq!(1, breakpoints.len());
21635    assert_breakpoint(
21636        &breakpoints,
21637        &abs_path,
21638        vec![
21639            (0, Breakpoint::new_standard()),
21640            (2, Breakpoint::new_standard()),
21641            (3, Breakpoint::new_standard()),
21642        ],
21643    );
21644
21645    editor.update_in(cx, |editor, window, cx| {
21646        editor.move_to_beginning(&MoveToBeginning, window, cx);
21647        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21648        editor.move_to_end(&MoveToEnd, window, cx);
21649        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21650        // Disabling a breakpoint that doesn't exist should do nothing
21651        editor.move_up(&MoveUp, window, cx);
21652        editor.move_up(&MoveUp, window, cx);
21653        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21654    });
21655
21656    let breakpoints = editor.update(cx, |editor, cx| {
21657        editor
21658            .breakpoint_store()
21659            .as_ref()
21660            .unwrap()
21661            .read(cx)
21662            .all_source_breakpoints(cx)
21663    });
21664
21665    let disable_breakpoint = {
21666        let mut bp = Breakpoint::new_standard();
21667        bp.state = BreakpointState::Disabled;
21668        bp
21669    };
21670
21671    assert_eq!(1, breakpoints.len());
21672    assert_breakpoint(
21673        &breakpoints,
21674        &abs_path,
21675        vec![
21676            (0, disable_breakpoint.clone()),
21677            (2, Breakpoint::new_standard()),
21678            (3, disable_breakpoint.clone()),
21679        ],
21680    );
21681
21682    editor.update_in(cx, |editor, window, cx| {
21683        editor.move_to_beginning(&MoveToBeginning, window, cx);
21684        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21685        editor.move_to_end(&MoveToEnd, window, cx);
21686        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
21687        editor.move_up(&MoveUp, window, cx);
21688        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
21689    });
21690
21691    let breakpoints = editor.update(cx, |editor, cx| {
21692        editor
21693            .breakpoint_store()
21694            .as_ref()
21695            .unwrap()
21696            .read(cx)
21697            .all_source_breakpoints(cx)
21698    });
21699
21700    assert_eq!(1, breakpoints.len());
21701    assert_breakpoint(
21702        &breakpoints,
21703        &abs_path,
21704        vec![
21705            (0, Breakpoint::new_standard()),
21706            (2, disable_breakpoint),
21707            (3, Breakpoint::new_standard()),
21708        ],
21709    );
21710}
21711
21712#[gpui::test]
21713async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
21714    init_test(cx, |_| {});
21715    let capabilities = lsp::ServerCapabilities {
21716        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
21717            prepare_provider: Some(true),
21718            work_done_progress_options: Default::default(),
21719        })),
21720        ..Default::default()
21721    };
21722    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21723
21724    cx.set_state(indoc! {"
21725        struct Fˇoo {}
21726    "});
21727
21728    cx.update_editor(|editor, _, cx| {
21729        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21730        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21731        editor.highlight_background::<DocumentHighlightRead>(
21732            &[highlight_range],
21733            |theme| theme.colors().editor_document_highlight_read_background,
21734            cx,
21735        );
21736    });
21737
21738    let mut prepare_rename_handler = cx
21739        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
21740            move |_, _, _| async move {
21741                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
21742                    start: lsp::Position {
21743                        line: 0,
21744                        character: 7,
21745                    },
21746                    end: lsp::Position {
21747                        line: 0,
21748                        character: 10,
21749                    },
21750                })))
21751            },
21752        );
21753    let prepare_rename_task = cx
21754        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21755        .expect("Prepare rename was not started");
21756    prepare_rename_handler.next().await.unwrap();
21757    prepare_rename_task.await.expect("Prepare rename failed");
21758
21759    let mut rename_handler =
21760        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21761            let edit = lsp::TextEdit {
21762                range: lsp::Range {
21763                    start: lsp::Position {
21764                        line: 0,
21765                        character: 7,
21766                    },
21767                    end: lsp::Position {
21768                        line: 0,
21769                        character: 10,
21770                    },
21771                },
21772                new_text: "FooRenamed".to_string(),
21773            };
21774            Ok(Some(lsp::WorkspaceEdit::new(
21775                // Specify the same edit twice
21776                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
21777            )))
21778        });
21779    let rename_task = cx
21780        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21781        .expect("Confirm rename was not started");
21782    rename_handler.next().await.unwrap();
21783    rename_task.await.expect("Confirm rename failed");
21784    cx.run_until_parked();
21785
21786    // Despite two edits, only one is actually applied as those are identical
21787    cx.assert_editor_state(indoc! {"
21788        struct FooRenamedˇ {}
21789    "});
21790}
21791
21792#[gpui::test]
21793async fn test_rename_without_prepare(cx: &mut TestAppContext) {
21794    init_test(cx, |_| {});
21795    // These capabilities indicate that the server does not support prepare rename.
21796    let capabilities = lsp::ServerCapabilities {
21797        rename_provider: Some(lsp::OneOf::Left(true)),
21798        ..Default::default()
21799    };
21800    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
21801
21802    cx.set_state(indoc! {"
21803        struct Fˇoo {}
21804    "});
21805
21806    cx.update_editor(|editor, _window, cx| {
21807        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
21808        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
21809        editor.highlight_background::<DocumentHighlightRead>(
21810            &[highlight_range],
21811            |theme| theme.colors().editor_document_highlight_read_background,
21812            cx,
21813        );
21814    });
21815
21816    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
21817        .expect("Prepare rename was not started")
21818        .await
21819        .expect("Prepare rename failed");
21820
21821    let mut rename_handler =
21822        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
21823            let edit = lsp::TextEdit {
21824                range: lsp::Range {
21825                    start: lsp::Position {
21826                        line: 0,
21827                        character: 7,
21828                    },
21829                    end: lsp::Position {
21830                        line: 0,
21831                        character: 10,
21832                    },
21833                },
21834                new_text: "FooRenamed".to_string(),
21835            };
21836            Ok(Some(lsp::WorkspaceEdit::new(
21837                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
21838            )))
21839        });
21840    let rename_task = cx
21841        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
21842        .expect("Confirm rename was not started");
21843    rename_handler.next().await.unwrap();
21844    rename_task.await.expect("Confirm rename failed");
21845    cx.run_until_parked();
21846
21847    // Correct range is renamed, as `surrounding_word` is used to find it.
21848    cx.assert_editor_state(indoc! {"
21849        struct FooRenamedˇ {}
21850    "});
21851}
21852
21853#[gpui::test]
21854async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
21855    init_test(cx, |_| {});
21856    let mut cx = EditorTestContext::new(cx).await;
21857
21858    let language = Arc::new(
21859        Language::new(
21860            LanguageConfig::default(),
21861            Some(tree_sitter_html::LANGUAGE.into()),
21862        )
21863        .with_brackets_query(
21864            r#"
21865            ("<" @open "/>" @close)
21866            ("</" @open ">" @close)
21867            ("<" @open ">" @close)
21868            ("\"" @open "\"" @close)
21869            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
21870        "#,
21871        )
21872        .unwrap(),
21873    );
21874    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
21875
21876    cx.set_state(indoc! {"
21877        <span>ˇ</span>
21878    "});
21879    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21880    cx.assert_editor_state(indoc! {"
21881        <span>
21882        ˇ
21883        </span>
21884    "});
21885
21886    cx.set_state(indoc! {"
21887        <span><span></span>ˇ</span>
21888    "});
21889    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21890    cx.assert_editor_state(indoc! {"
21891        <span><span></span>
21892        ˇ</span>
21893    "});
21894
21895    cx.set_state(indoc! {"
21896        <span>ˇ
21897        </span>
21898    "});
21899    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
21900    cx.assert_editor_state(indoc! {"
21901        <span>
21902        ˇ
21903        </span>
21904    "});
21905}
21906
21907#[gpui::test(iterations = 10)]
21908async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
21909    init_test(cx, |_| {});
21910
21911    let fs = FakeFs::new(cx.executor());
21912    fs.insert_tree(
21913        path!("/dir"),
21914        json!({
21915            "a.ts": "a",
21916        }),
21917    )
21918    .await;
21919
21920    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
21921    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21922    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21923
21924    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
21925    language_registry.add(Arc::new(Language::new(
21926        LanguageConfig {
21927            name: "TypeScript".into(),
21928            matcher: LanguageMatcher {
21929                path_suffixes: vec!["ts".to_string()],
21930                ..Default::default()
21931            },
21932            ..Default::default()
21933        },
21934        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
21935    )));
21936    let mut fake_language_servers = language_registry.register_fake_lsp(
21937        "TypeScript",
21938        FakeLspAdapter {
21939            capabilities: lsp::ServerCapabilities {
21940                code_lens_provider: Some(lsp::CodeLensOptions {
21941                    resolve_provider: Some(true),
21942                }),
21943                execute_command_provider: Some(lsp::ExecuteCommandOptions {
21944                    commands: vec!["_the/command".to_string()],
21945                    ..lsp::ExecuteCommandOptions::default()
21946                }),
21947                ..lsp::ServerCapabilities::default()
21948            },
21949            ..FakeLspAdapter::default()
21950        },
21951    );
21952
21953    let editor = workspace
21954        .update(cx, |workspace, window, cx| {
21955            workspace.open_abs_path(
21956                PathBuf::from(path!("/dir/a.ts")),
21957                OpenOptions::default(),
21958                window,
21959                cx,
21960            )
21961        })
21962        .unwrap()
21963        .await
21964        .unwrap()
21965        .downcast::<Editor>()
21966        .unwrap();
21967    cx.executor().run_until_parked();
21968
21969    let fake_server = fake_language_servers.next().await.unwrap();
21970
21971    let buffer = editor.update(cx, |editor, cx| {
21972        editor
21973            .buffer()
21974            .read(cx)
21975            .as_singleton()
21976            .expect("have opened a single file by path")
21977    });
21978
21979    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
21980    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
21981    drop(buffer_snapshot);
21982    let actions = cx
21983        .update_window(*workspace, |_, window, cx| {
21984            project.code_actions(&buffer, anchor..anchor, window, cx)
21985        })
21986        .unwrap();
21987
21988    fake_server
21989        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
21990            Ok(Some(vec![
21991                lsp::CodeLens {
21992                    range: lsp::Range::default(),
21993                    command: Some(lsp::Command {
21994                        title: "Code lens command".to_owned(),
21995                        command: "_the/command".to_owned(),
21996                        arguments: None,
21997                    }),
21998                    data: None,
21999                },
22000                lsp::CodeLens {
22001                    range: lsp::Range::default(),
22002                    command: Some(lsp::Command {
22003                        title: "Command not in capabilities".to_owned(),
22004                        command: "not in capabilities".to_owned(),
22005                        arguments: None,
22006                    }),
22007                    data: None,
22008                },
22009                lsp::CodeLens {
22010                    range: lsp::Range {
22011                        start: lsp::Position {
22012                            line: 1,
22013                            character: 1,
22014                        },
22015                        end: lsp::Position {
22016                            line: 1,
22017                            character: 1,
22018                        },
22019                    },
22020                    command: Some(lsp::Command {
22021                        title: "Command not in range".to_owned(),
22022                        command: "_the/command".to_owned(),
22023                        arguments: None,
22024                    }),
22025                    data: None,
22026                },
22027            ]))
22028        })
22029        .next()
22030        .await;
22031
22032    let actions = actions.await.unwrap();
22033    assert_eq!(
22034        actions.len(),
22035        1,
22036        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
22037    );
22038    let action = actions[0].clone();
22039    let apply = project.update(cx, |project, cx| {
22040        project.apply_code_action(buffer.clone(), action, true, cx)
22041    });
22042
22043    // Resolving the code action does not populate its edits. In absence of
22044    // edits, we must execute the given command.
22045    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
22046        |mut lens, _| async move {
22047            let lens_command = lens.command.as_mut().expect("should have a command");
22048            assert_eq!(lens_command.title, "Code lens command");
22049            lens_command.arguments = Some(vec![json!("the-argument")]);
22050            Ok(lens)
22051        },
22052    );
22053
22054    // While executing the command, the language server sends the editor
22055    // a `workspaceEdit` request.
22056    fake_server
22057        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
22058            let fake = fake_server.clone();
22059            move |params, _| {
22060                assert_eq!(params.command, "_the/command");
22061                let fake = fake.clone();
22062                async move {
22063                    fake.server
22064                        .request::<lsp::request::ApplyWorkspaceEdit>(
22065                            lsp::ApplyWorkspaceEditParams {
22066                                label: None,
22067                                edit: lsp::WorkspaceEdit {
22068                                    changes: Some(
22069                                        [(
22070                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
22071                                            vec![lsp::TextEdit {
22072                                                range: lsp::Range::new(
22073                                                    lsp::Position::new(0, 0),
22074                                                    lsp::Position::new(0, 0),
22075                                                ),
22076                                                new_text: "X".into(),
22077                                            }],
22078                                        )]
22079                                        .into_iter()
22080                                        .collect(),
22081                                    ),
22082                                    ..lsp::WorkspaceEdit::default()
22083                                },
22084                            },
22085                        )
22086                        .await
22087                        .into_response()
22088                        .unwrap();
22089                    Ok(Some(json!(null)))
22090                }
22091            }
22092        })
22093        .next()
22094        .await;
22095
22096    // Applying the code lens command returns a project transaction containing the edits
22097    // sent by the language server in its `workspaceEdit` request.
22098    let transaction = apply.await.unwrap();
22099    assert!(transaction.0.contains_key(&buffer));
22100    buffer.update(cx, |buffer, cx| {
22101        assert_eq!(buffer.text(), "Xa");
22102        buffer.undo(cx);
22103        assert_eq!(buffer.text(), "a");
22104    });
22105
22106    let actions_after_edits = cx
22107        .update_window(*workspace, |_, window, cx| {
22108            project.code_actions(&buffer, anchor..anchor, window, cx)
22109        })
22110        .unwrap()
22111        .await
22112        .unwrap();
22113    assert_eq!(
22114        actions, actions_after_edits,
22115        "For the same selection, same code lens actions should be returned"
22116    );
22117
22118    let _responses =
22119        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
22120            panic!("No more code lens requests are expected");
22121        });
22122    editor.update_in(cx, |editor, window, cx| {
22123        editor.select_all(&SelectAll, window, cx);
22124    });
22125    cx.executor().run_until_parked();
22126    let new_actions = cx
22127        .update_window(*workspace, |_, window, cx| {
22128            project.code_actions(&buffer, anchor..anchor, window, cx)
22129        })
22130        .unwrap()
22131        .await
22132        .unwrap();
22133    assert_eq!(
22134        actions, new_actions,
22135        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
22136    );
22137}
22138
22139#[gpui::test]
22140async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
22141    init_test(cx, |_| {});
22142
22143    let fs = FakeFs::new(cx.executor());
22144    let main_text = r#"fn main() {
22145println!("1");
22146println!("2");
22147println!("3");
22148println!("4");
22149println!("5");
22150}"#;
22151    let lib_text = "mod foo {}";
22152    fs.insert_tree(
22153        path!("/a"),
22154        json!({
22155            "lib.rs": lib_text,
22156            "main.rs": main_text,
22157        }),
22158    )
22159    .await;
22160
22161    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22162    let (workspace, cx) =
22163        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22164    let worktree_id = workspace.update(cx, |workspace, cx| {
22165        workspace.project().update(cx, |project, cx| {
22166            project.worktrees(cx).next().unwrap().read(cx).id()
22167        })
22168    });
22169
22170    let expected_ranges = vec![
22171        Point::new(0, 0)..Point::new(0, 0),
22172        Point::new(1, 0)..Point::new(1, 1),
22173        Point::new(2, 0)..Point::new(2, 2),
22174        Point::new(3, 0)..Point::new(3, 3),
22175    ];
22176
22177    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22178    let editor_1 = workspace
22179        .update_in(cx, |workspace, window, cx| {
22180            workspace.open_path(
22181                (worktree_id, "main.rs"),
22182                Some(pane_1.downgrade()),
22183                true,
22184                window,
22185                cx,
22186            )
22187        })
22188        .unwrap()
22189        .await
22190        .downcast::<Editor>()
22191        .unwrap();
22192    pane_1.update(cx, |pane, cx| {
22193        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22194        open_editor.update(cx, |editor, cx| {
22195            assert_eq!(
22196                editor.display_text(cx),
22197                main_text,
22198                "Original main.rs text on initial open",
22199            );
22200            assert_eq!(
22201                editor
22202                    .selections
22203                    .all::<Point>(cx)
22204                    .into_iter()
22205                    .map(|s| s.range())
22206                    .collect::<Vec<_>>(),
22207                vec![Point::zero()..Point::zero()],
22208                "Default selections on initial open",
22209            );
22210        })
22211    });
22212    editor_1.update_in(cx, |editor, window, cx| {
22213        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22214            s.select_ranges(expected_ranges.clone());
22215        });
22216    });
22217
22218    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
22219        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
22220    });
22221    let editor_2 = workspace
22222        .update_in(cx, |workspace, window, cx| {
22223            workspace.open_path(
22224                (worktree_id, "main.rs"),
22225                Some(pane_2.downgrade()),
22226                true,
22227                window,
22228                cx,
22229            )
22230        })
22231        .unwrap()
22232        .await
22233        .downcast::<Editor>()
22234        .unwrap();
22235    pane_2.update(cx, |pane, cx| {
22236        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22237        open_editor.update(cx, |editor, cx| {
22238            assert_eq!(
22239                editor.display_text(cx),
22240                main_text,
22241                "Original main.rs text on initial open in another panel",
22242            );
22243            assert_eq!(
22244                editor
22245                    .selections
22246                    .all::<Point>(cx)
22247                    .into_iter()
22248                    .map(|s| s.range())
22249                    .collect::<Vec<_>>(),
22250                vec![Point::zero()..Point::zero()],
22251                "Default selections on initial open in another panel",
22252            );
22253        })
22254    });
22255
22256    editor_2.update_in(cx, |editor, window, cx| {
22257        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
22258    });
22259
22260    let _other_editor_1 = workspace
22261        .update_in(cx, |workspace, window, cx| {
22262            workspace.open_path(
22263                (worktree_id, "lib.rs"),
22264                Some(pane_1.downgrade()),
22265                true,
22266                window,
22267                cx,
22268            )
22269        })
22270        .unwrap()
22271        .await
22272        .downcast::<Editor>()
22273        .unwrap();
22274    pane_1
22275        .update_in(cx, |pane, window, cx| {
22276            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22277        })
22278        .await
22279        .unwrap();
22280    drop(editor_1);
22281    pane_1.update(cx, |pane, cx| {
22282        pane.active_item()
22283            .unwrap()
22284            .downcast::<Editor>()
22285            .unwrap()
22286            .update(cx, |editor, cx| {
22287                assert_eq!(
22288                    editor.display_text(cx),
22289                    lib_text,
22290                    "Other file should be open and active",
22291                );
22292            });
22293        assert_eq!(pane.items().count(), 1, "No other editors should be open");
22294    });
22295
22296    let _other_editor_2 = workspace
22297        .update_in(cx, |workspace, window, cx| {
22298            workspace.open_path(
22299                (worktree_id, "lib.rs"),
22300                Some(pane_2.downgrade()),
22301                true,
22302                window,
22303                cx,
22304            )
22305        })
22306        .unwrap()
22307        .await
22308        .downcast::<Editor>()
22309        .unwrap();
22310    pane_2
22311        .update_in(cx, |pane, window, cx| {
22312            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22313        })
22314        .await
22315        .unwrap();
22316    drop(editor_2);
22317    pane_2.update(cx, |pane, cx| {
22318        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22319        open_editor.update(cx, |editor, cx| {
22320            assert_eq!(
22321                editor.display_text(cx),
22322                lib_text,
22323                "Other file should be open and active in another panel too",
22324            );
22325        });
22326        assert_eq!(
22327            pane.items().count(),
22328            1,
22329            "No other editors should be open in another pane",
22330        );
22331    });
22332
22333    let _editor_1_reopened = workspace
22334        .update_in(cx, |workspace, window, cx| {
22335            workspace.open_path(
22336                (worktree_id, "main.rs"),
22337                Some(pane_1.downgrade()),
22338                true,
22339                window,
22340                cx,
22341            )
22342        })
22343        .unwrap()
22344        .await
22345        .downcast::<Editor>()
22346        .unwrap();
22347    let _editor_2_reopened = workspace
22348        .update_in(cx, |workspace, window, cx| {
22349            workspace.open_path(
22350                (worktree_id, "main.rs"),
22351                Some(pane_2.downgrade()),
22352                true,
22353                window,
22354                cx,
22355            )
22356        })
22357        .unwrap()
22358        .await
22359        .downcast::<Editor>()
22360        .unwrap();
22361    pane_1.update(cx, |pane, cx| {
22362        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22363        open_editor.update(cx, |editor, cx| {
22364            assert_eq!(
22365                editor.display_text(cx),
22366                main_text,
22367                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
22368            );
22369            assert_eq!(
22370                editor
22371                    .selections
22372                    .all::<Point>(cx)
22373                    .into_iter()
22374                    .map(|s| s.range())
22375                    .collect::<Vec<_>>(),
22376                expected_ranges,
22377                "Previous editor in the 1st panel had selections and should get them restored on reopen",
22378            );
22379        })
22380    });
22381    pane_2.update(cx, |pane, cx| {
22382        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22383        open_editor.update(cx, |editor, cx| {
22384            assert_eq!(
22385                editor.display_text(cx),
22386                r#"fn main() {
22387⋯rintln!("1");
22388⋯intln!("2");
22389⋯ntln!("3");
22390println!("4");
22391println!("5");
22392}"#,
22393                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
22394            );
22395            assert_eq!(
22396                editor
22397                    .selections
22398                    .all::<Point>(cx)
22399                    .into_iter()
22400                    .map(|s| s.range())
22401                    .collect::<Vec<_>>(),
22402                vec![Point::zero()..Point::zero()],
22403                "Previous editor in the 2nd pane had no selections changed hence should restore none",
22404            );
22405        })
22406    });
22407}
22408
22409#[gpui::test]
22410async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
22411    init_test(cx, |_| {});
22412
22413    let fs = FakeFs::new(cx.executor());
22414    let main_text = r#"fn main() {
22415println!("1");
22416println!("2");
22417println!("3");
22418println!("4");
22419println!("5");
22420}"#;
22421    let lib_text = "mod foo {}";
22422    fs.insert_tree(
22423        path!("/a"),
22424        json!({
22425            "lib.rs": lib_text,
22426            "main.rs": main_text,
22427        }),
22428    )
22429    .await;
22430
22431    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22432    let (workspace, cx) =
22433        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22434    let worktree_id = workspace.update(cx, |workspace, cx| {
22435        workspace.project().update(cx, |project, cx| {
22436            project.worktrees(cx).next().unwrap().read(cx).id()
22437        })
22438    });
22439
22440    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22441    let editor = workspace
22442        .update_in(cx, |workspace, window, cx| {
22443            workspace.open_path(
22444                (worktree_id, "main.rs"),
22445                Some(pane.downgrade()),
22446                true,
22447                window,
22448                cx,
22449            )
22450        })
22451        .unwrap()
22452        .await
22453        .downcast::<Editor>()
22454        .unwrap();
22455    pane.update(cx, |pane, cx| {
22456        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22457        open_editor.update(cx, |editor, cx| {
22458            assert_eq!(
22459                editor.display_text(cx),
22460                main_text,
22461                "Original main.rs text on initial open",
22462            );
22463        })
22464    });
22465    editor.update_in(cx, |editor, window, cx| {
22466        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
22467    });
22468
22469    cx.update_global(|store: &mut SettingsStore, cx| {
22470        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22471            s.restore_on_file_reopen = Some(false);
22472        });
22473    });
22474    editor.update_in(cx, |editor, window, cx| {
22475        editor.fold_ranges(
22476            vec![
22477                Point::new(1, 0)..Point::new(1, 1),
22478                Point::new(2, 0)..Point::new(2, 2),
22479                Point::new(3, 0)..Point::new(3, 3),
22480            ],
22481            false,
22482            window,
22483            cx,
22484        );
22485    });
22486    pane.update_in(cx, |pane, window, cx| {
22487        pane.close_all_items(&CloseAllItems::default(), window, cx)
22488    })
22489    .await
22490    .unwrap();
22491    pane.update(cx, |pane, _| {
22492        assert!(pane.active_item().is_none());
22493    });
22494    cx.update_global(|store: &mut SettingsStore, cx| {
22495        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22496            s.restore_on_file_reopen = Some(true);
22497        });
22498    });
22499
22500    let _editor_reopened = workspace
22501        .update_in(cx, |workspace, window, cx| {
22502            workspace.open_path(
22503                (worktree_id, "main.rs"),
22504                Some(pane.downgrade()),
22505                true,
22506                window,
22507                cx,
22508            )
22509        })
22510        .unwrap()
22511        .await
22512        .downcast::<Editor>()
22513        .unwrap();
22514    pane.update(cx, |pane, cx| {
22515        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22516        open_editor.update(cx, |editor, cx| {
22517            assert_eq!(
22518                editor.display_text(cx),
22519                main_text,
22520                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
22521            );
22522        })
22523    });
22524}
22525
22526#[gpui::test]
22527async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
22528    struct EmptyModalView {
22529        focus_handle: gpui::FocusHandle,
22530    }
22531    impl EventEmitter<DismissEvent> for EmptyModalView {}
22532    impl Render for EmptyModalView {
22533        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
22534            div()
22535        }
22536    }
22537    impl Focusable for EmptyModalView {
22538        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
22539            self.focus_handle.clone()
22540        }
22541    }
22542    impl workspace::ModalView for EmptyModalView {}
22543    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
22544        EmptyModalView {
22545            focus_handle: cx.focus_handle(),
22546        }
22547    }
22548
22549    init_test(cx, |_| {});
22550
22551    let fs = FakeFs::new(cx.executor());
22552    let project = Project::test(fs, [], cx).await;
22553    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22554    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
22555    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22556    let editor = cx.new_window_entity(|window, cx| {
22557        Editor::new(
22558            EditorMode::full(),
22559            buffer,
22560            Some(project.clone()),
22561            window,
22562            cx,
22563        )
22564    });
22565    workspace
22566        .update(cx, |workspace, window, cx| {
22567            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
22568        })
22569        .unwrap();
22570    editor.update_in(cx, |editor, window, cx| {
22571        editor.open_context_menu(&OpenContextMenu, window, cx);
22572        assert!(editor.mouse_context_menu.is_some());
22573    });
22574    workspace
22575        .update(cx, |workspace, window, cx| {
22576            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
22577        })
22578        .unwrap();
22579    cx.read(|cx| {
22580        assert!(editor.read(cx).mouse_context_menu.is_none());
22581    });
22582}
22583
22584#[gpui::test]
22585async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
22586    init_test(cx, |_| {});
22587
22588    let fs = FakeFs::new(cx.executor());
22589    fs.insert_file(path!("/file.html"), Default::default())
22590        .await;
22591
22592    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
22593
22594    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22595    let html_language = Arc::new(Language::new(
22596        LanguageConfig {
22597            name: "HTML".into(),
22598            matcher: LanguageMatcher {
22599                path_suffixes: vec!["html".to_string()],
22600                ..LanguageMatcher::default()
22601            },
22602            brackets: BracketPairConfig {
22603                pairs: vec![BracketPair {
22604                    start: "<".into(),
22605                    end: ">".into(),
22606                    close: true,
22607                    ..Default::default()
22608                }],
22609                ..Default::default()
22610            },
22611            ..Default::default()
22612        },
22613        Some(tree_sitter_html::LANGUAGE.into()),
22614    ));
22615    language_registry.add(html_language);
22616    let mut fake_servers = language_registry.register_fake_lsp(
22617        "HTML",
22618        FakeLspAdapter {
22619            capabilities: lsp::ServerCapabilities {
22620                completion_provider: Some(lsp::CompletionOptions {
22621                    resolve_provider: Some(true),
22622                    ..Default::default()
22623                }),
22624                ..Default::default()
22625            },
22626            ..Default::default()
22627        },
22628    );
22629
22630    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22631    let cx = &mut VisualTestContext::from_window(*workspace, cx);
22632
22633    let worktree_id = workspace
22634        .update(cx, |workspace, _window, cx| {
22635            workspace.project().update(cx, |project, cx| {
22636                project.worktrees(cx).next().unwrap().read(cx).id()
22637            })
22638        })
22639        .unwrap();
22640    project
22641        .update(cx, |project, cx| {
22642            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
22643        })
22644        .await
22645        .unwrap();
22646    let editor = workspace
22647        .update(cx, |workspace, window, cx| {
22648            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
22649        })
22650        .unwrap()
22651        .await
22652        .unwrap()
22653        .downcast::<Editor>()
22654        .unwrap();
22655
22656    let fake_server = fake_servers.next().await.unwrap();
22657    editor.update_in(cx, |editor, window, cx| {
22658        editor.set_text("<ad></ad>", window, cx);
22659        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22660            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
22661        });
22662        let Some((buffer, _)) = editor
22663            .buffer
22664            .read(cx)
22665            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
22666        else {
22667            panic!("Failed to get buffer for selection position");
22668        };
22669        let buffer = buffer.read(cx);
22670        let buffer_id = buffer.remote_id();
22671        let opening_range =
22672            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
22673        let closing_range =
22674            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
22675        let mut linked_ranges = HashMap::default();
22676        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
22677        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
22678    });
22679    let mut completion_handle =
22680        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
22681            Ok(Some(lsp::CompletionResponse::Array(vec![
22682                lsp::CompletionItem {
22683                    label: "head".to_string(),
22684                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
22685                        lsp::InsertReplaceEdit {
22686                            new_text: "head".to_string(),
22687                            insert: lsp::Range::new(
22688                                lsp::Position::new(0, 1),
22689                                lsp::Position::new(0, 3),
22690                            ),
22691                            replace: lsp::Range::new(
22692                                lsp::Position::new(0, 1),
22693                                lsp::Position::new(0, 3),
22694                            ),
22695                        },
22696                    )),
22697                    ..Default::default()
22698                },
22699            ])))
22700        });
22701    editor.update_in(cx, |editor, window, cx| {
22702        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
22703    });
22704    cx.run_until_parked();
22705    completion_handle.next().await.unwrap();
22706    editor.update(cx, |editor, _| {
22707        assert!(
22708            editor.context_menu_visible(),
22709            "Completion menu should be visible"
22710        );
22711    });
22712    editor.update_in(cx, |editor, window, cx| {
22713        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
22714    });
22715    cx.executor().run_until_parked();
22716    editor.update(cx, |editor, cx| {
22717        assert_eq!(editor.text(cx), "<head></head>");
22718    });
22719}
22720
22721#[gpui::test]
22722async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
22723    init_test(cx, |_| {});
22724
22725    let fs = FakeFs::new(cx.executor());
22726    fs.insert_tree(
22727        path!("/root"),
22728        json!({
22729            "a": {
22730                "main.rs": "fn main() {}",
22731            },
22732            "foo": {
22733                "bar": {
22734                    "external_file.rs": "pub mod external {}",
22735                }
22736            }
22737        }),
22738    )
22739    .await;
22740
22741    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
22742    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22743    language_registry.add(rust_lang());
22744    let _fake_servers = language_registry.register_fake_lsp(
22745        "Rust",
22746        FakeLspAdapter {
22747            ..FakeLspAdapter::default()
22748        },
22749    );
22750    let (workspace, cx) =
22751        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22752    let worktree_id = workspace.update(cx, |workspace, cx| {
22753        workspace.project().update(cx, |project, cx| {
22754            project.worktrees(cx).next().unwrap().read(cx).id()
22755        })
22756    });
22757
22758    let assert_language_servers_count =
22759        |expected: usize, context: &str, cx: &mut VisualTestContext| {
22760            project.update(cx, |project, cx| {
22761                let current = project
22762                    .lsp_store()
22763                    .read(cx)
22764                    .as_local()
22765                    .unwrap()
22766                    .language_servers
22767                    .len();
22768                assert_eq!(expected, current, "{context}");
22769            });
22770        };
22771
22772    assert_language_servers_count(
22773        0,
22774        "No servers should be running before any file is open",
22775        cx,
22776    );
22777    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22778    let main_editor = workspace
22779        .update_in(cx, |workspace, window, cx| {
22780            workspace.open_path(
22781                (worktree_id, "main.rs"),
22782                Some(pane.downgrade()),
22783                true,
22784                window,
22785                cx,
22786            )
22787        })
22788        .unwrap()
22789        .await
22790        .downcast::<Editor>()
22791        .unwrap();
22792    pane.update(cx, |pane, cx| {
22793        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22794        open_editor.update(cx, |editor, cx| {
22795            assert_eq!(
22796                editor.display_text(cx),
22797                "fn main() {}",
22798                "Original main.rs text on initial open",
22799            );
22800        });
22801        assert_eq!(open_editor, main_editor);
22802    });
22803    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
22804
22805    let external_editor = workspace
22806        .update_in(cx, |workspace, window, cx| {
22807            workspace.open_abs_path(
22808                PathBuf::from("/root/foo/bar/external_file.rs"),
22809                OpenOptions::default(),
22810                window,
22811                cx,
22812            )
22813        })
22814        .await
22815        .expect("opening external file")
22816        .downcast::<Editor>()
22817        .expect("downcasted external file's open element to editor");
22818    pane.update(cx, |pane, cx| {
22819        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22820        open_editor.update(cx, |editor, cx| {
22821            assert_eq!(
22822                editor.display_text(cx),
22823                "pub mod external {}",
22824                "External file is open now",
22825            );
22826        });
22827        assert_eq!(open_editor, external_editor);
22828    });
22829    assert_language_servers_count(
22830        1,
22831        "Second, external, *.rs file should join the existing server",
22832        cx,
22833    );
22834
22835    pane.update_in(cx, |pane, window, cx| {
22836        pane.close_active_item(&CloseActiveItem::default(), window, cx)
22837    })
22838    .await
22839    .unwrap();
22840    pane.update_in(cx, |pane, window, cx| {
22841        pane.navigate_backward(&Default::default(), window, cx);
22842    });
22843    cx.run_until_parked();
22844    pane.update(cx, |pane, cx| {
22845        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22846        open_editor.update(cx, |editor, cx| {
22847            assert_eq!(
22848                editor.display_text(cx),
22849                "pub mod external {}",
22850                "External file is open now",
22851            );
22852        });
22853    });
22854    assert_language_servers_count(
22855        1,
22856        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
22857        cx,
22858    );
22859
22860    cx.update(|_, cx| {
22861        workspace::reload(cx);
22862    });
22863    assert_language_servers_count(
22864        1,
22865        "After reloading the worktree with local and external files opened, only one project should be started",
22866        cx,
22867    );
22868}
22869
22870#[gpui::test]
22871async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
22872    init_test(cx, |_| {});
22873
22874    let mut cx = EditorTestContext::new(cx).await;
22875    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22876    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22877
22878    // test cursor move to start of each line on tab
22879    // for `if`, `elif`, `else`, `while`, `with` and `for`
22880    cx.set_state(indoc! {"
22881        def main():
22882        ˇ    for item in items:
22883        ˇ        while item.active:
22884        ˇ            if item.value > 10:
22885        ˇ                continue
22886        ˇ            elif item.value < 0:
22887        ˇ                break
22888        ˇ            else:
22889        ˇ                with item.context() as ctx:
22890        ˇ                    yield count
22891        ˇ        else:
22892        ˇ            log('while else')
22893        ˇ    else:
22894        ˇ        log('for else')
22895    "});
22896    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22897    cx.assert_editor_state(indoc! {"
22898        def main():
22899            ˇfor item in items:
22900                ˇwhile item.active:
22901                    ˇif item.value > 10:
22902                        ˇcontinue
22903                    ˇelif item.value < 0:
22904                        ˇbreak
22905                    ˇelse:
22906                        ˇwith item.context() as ctx:
22907                            ˇyield count
22908                ˇelse:
22909                    ˇlog('while else')
22910            ˇelse:
22911                ˇlog('for else')
22912    "});
22913    // test relative indent is preserved when tab
22914    // for `if`, `elif`, `else`, `while`, `with` and `for`
22915    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22916    cx.assert_editor_state(indoc! {"
22917        def main():
22918                ˇfor item in items:
22919                    ˇwhile item.active:
22920                        ˇif item.value > 10:
22921                            ˇcontinue
22922                        ˇelif item.value < 0:
22923                            ˇbreak
22924                        ˇelse:
22925                            ˇwith item.context() as ctx:
22926                                ˇyield count
22927                    ˇelse:
22928                        ˇlog('while else')
22929                ˇelse:
22930                    ˇlog('for else')
22931    "});
22932
22933    // test cursor move to start of each line on tab
22934    // for `try`, `except`, `else`, `finally`, `match` and `def`
22935    cx.set_state(indoc! {"
22936        def main():
22937        ˇ    try:
22938        ˇ        fetch()
22939        ˇ    except ValueError:
22940        ˇ        handle_error()
22941        ˇ    else:
22942        ˇ        match value:
22943        ˇ            case _:
22944        ˇ    finally:
22945        ˇ        def status():
22946        ˇ            return 0
22947    "});
22948    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22949    cx.assert_editor_state(indoc! {"
22950        def main():
22951            ˇtry:
22952                ˇfetch()
22953            ˇexcept ValueError:
22954                ˇhandle_error()
22955            ˇelse:
22956                ˇmatch value:
22957                    ˇcase _:
22958            ˇfinally:
22959                ˇdef status():
22960                    ˇreturn 0
22961    "});
22962    // test relative indent is preserved when tab
22963    // for `try`, `except`, `else`, `finally`, `match` and `def`
22964    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
22965    cx.assert_editor_state(indoc! {"
22966        def main():
22967                ˇtry:
22968                    ˇfetch()
22969                ˇexcept ValueError:
22970                    ˇhandle_error()
22971                ˇelse:
22972                    ˇmatch value:
22973                        ˇcase _:
22974                ˇfinally:
22975                    ˇdef status():
22976                        ˇreturn 0
22977    "});
22978}
22979
22980#[gpui::test]
22981async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
22982    init_test(cx, |_| {});
22983
22984    let mut cx = EditorTestContext::new(cx).await;
22985    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
22986    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22987
22988    // test `else` auto outdents when typed inside `if` block
22989    cx.set_state(indoc! {"
22990        def main():
22991            if i == 2:
22992                return
22993                ˇ
22994    "});
22995    cx.update_editor(|editor, window, cx| {
22996        editor.handle_input("else:", window, cx);
22997    });
22998    cx.assert_editor_state(indoc! {"
22999        def main():
23000            if i == 2:
23001                return
23002            else:ˇ
23003    "});
23004
23005    // test `except` auto outdents when typed inside `try` block
23006    cx.set_state(indoc! {"
23007        def main():
23008            try:
23009                i = 2
23010                ˇ
23011    "});
23012    cx.update_editor(|editor, window, cx| {
23013        editor.handle_input("except:", window, cx);
23014    });
23015    cx.assert_editor_state(indoc! {"
23016        def main():
23017            try:
23018                i = 2
23019            except:ˇ
23020    "});
23021
23022    // test `else` auto outdents when typed inside `except` block
23023    cx.set_state(indoc! {"
23024        def main():
23025            try:
23026                i = 2
23027            except:
23028                j = 2
23029                ˇ
23030    "});
23031    cx.update_editor(|editor, window, cx| {
23032        editor.handle_input("else:", window, cx);
23033    });
23034    cx.assert_editor_state(indoc! {"
23035        def main():
23036            try:
23037                i = 2
23038            except:
23039                j = 2
23040            else:ˇ
23041    "});
23042
23043    // test `finally` auto outdents when typed inside `else` block
23044    cx.set_state(indoc! {"
23045        def main():
23046            try:
23047                i = 2
23048            except:
23049                j = 2
23050            else:
23051                k = 2
23052                ˇ
23053    "});
23054    cx.update_editor(|editor, window, cx| {
23055        editor.handle_input("finally:", window, cx);
23056    });
23057    cx.assert_editor_state(indoc! {"
23058        def main():
23059            try:
23060                i = 2
23061            except:
23062                j = 2
23063            else:
23064                k = 2
23065            finally:ˇ
23066    "});
23067
23068    // test `else` does not outdents when typed inside `except` block right after for block
23069    cx.set_state(indoc! {"
23070        def main():
23071            try:
23072                i = 2
23073            except:
23074                for i in range(n):
23075                    pass
23076                ˇ
23077    "});
23078    cx.update_editor(|editor, window, cx| {
23079        editor.handle_input("else:", window, cx);
23080    });
23081    cx.assert_editor_state(indoc! {"
23082        def main():
23083            try:
23084                i = 2
23085            except:
23086                for i in range(n):
23087                    pass
23088                else:ˇ
23089    "});
23090
23091    // test `finally` auto outdents when typed inside `else` block right after for block
23092    cx.set_state(indoc! {"
23093        def main():
23094            try:
23095                i = 2
23096            except:
23097                j = 2
23098            else:
23099                for i in range(n):
23100                    pass
23101                ˇ
23102    "});
23103    cx.update_editor(|editor, window, cx| {
23104        editor.handle_input("finally:", window, cx);
23105    });
23106    cx.assert_editor_state(indoc! {"
23107        def main():
23108            try:
23109                i = 2
23110            except:
23111                j = 2
23112            else:
23113                for i in range(n):
23114                    pass
23115            finally:ˇ
23116    "});
23117
23118    // test `except` outdents to inner "try" block
23119    cx.set_state(indoc! {"
23120        def main():
23121            try:
23122                i = 2
23123                if i == 2:
23124                    try:
23125                        i = 3
23126                        ˇ
23127    "});
23128    cx.update_editor(|editor, window, cx| {
23129        editor.handle_input("except:", window, cx);
23130    });
23131    cx.assert_editor_state(indoc! {"
23132        def main():
23133            try:
23134                i = 2
23135                if i == 2:
23136                    try:
23137                        i = 3
23138                    except:ˇ
23139    "});
23140
23141    // test `except` outdents to outer "try" block
23142    cx.set_state(indoc! {"
23143        def main():
23144            try:
23145                i = 2
23146                if i == 2:
23147                    try:
23148                        i = 3
23149                ˇ
23150    "});
23151    cx.update_editor(|editor, window, cx| {
23152        editor.handle_input("except:", window, cx);
23153    });
23154    cx.assert_editor_state(indoc! {"
23155        def main():
23156            try:
23157                i = 2
23158                if i == 2:
23159                    try:
23160                        i = 3
23161            except:ˇ
23162    "});
23163
23164    // test `else` stays at correct indent when typed after `for` block
23165    cx.set_state(indoc! {"
23166        def main():
23167            for i in range(10):
23168                if i == 3:
23169                    break
23170            ˇ
23171    "});
23172    cx.update_editor(|editor, window, cx| {
23173        editor.handle_input("else:", window, cx);
23174    });
23175    cx.assert_editor_state(indoc! {"
23176        def main():
23177            for i in range(10):
23178                if i == 3:
23179                    break
23180            else:ˇ
23181    "});
23182
23183    // test does not outdent on typing after line with square brackets
23184    cx.set_state(indoc! {"
23185        def f() -> list[str]:
23186            ˇ
23187    "});
23188    cx.update_editor(|editor, window, cx| {
23189        editor.handle_input("a", window, cx);
23190    });
23191    cx.assert_editor_state(indoc! {"
23192        def f() -> list[str]:
2319323194    "});
23195
23196    // test does not outdent on typing : after case keyword
23197    cx.set_state(indoc! {"
23198        match 1:
23199            caseˇ
23200    "});
23201    cx.update_editor(|editor, window, cx| {
23202        editor.handle_input(":", window, cx);
23203    });
23204    cx.assert_editor_state(indoc! {"
23205        match 1:
23206            case:ˇ
23207    "});
23208}
23209
23210#[gpui::test]
23211async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
23212    init_test(cx, |_| {});
23213    update_test_language_settings(cx, |settings| {
23214        settings.defaults.extend_comment_on_newline = Some(false);
23215    });
23216    let mut cx = EditorTestContext::new(cx).await;
23217    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23218    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23219
23220    // test correct indent after newline on comment
23221    cx.set_state(indoc! {"
23222        # COMMENT:ˇ
23223    "});
23224    cx.update_editor(|editor, window, cx| {
23225        editor.newline(&Newline, window, cx);
23226    });
23227    cx.assert_editor_state(indoc! {"
23228        # COMMENT:
23229        ˇ
23230    "});
23231
23232    // test correct indent after newline in brackets
23233    cx.set_state(indoc! {"
23234        {ˇ}
23235    "});
23236    cx.update_editor(|editor, window, cx| {
23237        editor.newline(&Newline, window, cx);
23238    });
23239    cx.run_until_parked();
23240    cx.assert_editor_state(indoc! {"
23241        {
23242            ˇ
23243        }
23244    "});
23245
23246    cx.set_state(indoc! {"
23247        (ˇ)
23248    "});
23249    cx.update_editor(|editor, window, cx| {
23250        editor.newline(&Newline, window, cx);
23251    });
23252    cx.run_until_parked();
23253    cx.assert_editor_state(indoc! {"
23254        (
23255            ˇ
23256        )
23257    "});
23258
23259    // do not indent after empty lists or dictionaries
23260    cx.set_state(indoc! {"
23261        a = []ˇ
23262    "});
23263    cx.update_editor(|editor, window, cx| {
23264        editor.newline(&Newline, window, cx);
23265    });
23266    cx.run_until_parked();
23267    cx.assert_editor_state(indoc! {"
23268        a = []
23269        ˇ
23270    "});
23271}
23272
23273#[gpui::test]
23274async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
23275    init_test(cx, |_| {});
23276
23277    let mut cx = EditorTestContext::new(cx).await;
23278    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23279    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23280
23281    // test cursor move to start of each line on tab
23282    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
23283    cx.set_state(indoc! {"
23284        function main() {
23285        ˇ    for item in $items; do
23286        ˇ        while [ -n \"$item\" ]; do
23287        ˇ            if [ \"$value\" -gt 10 ]; then
23288        ˇ                continue
23289        ˇ            elif [ \"$value\" -lt 0 ]; then
23290        ˇ                break
23291        ˇ            else
23292        ˇ                echo \"$item\"
23293        ˇ            fi
23294        ˇ        done
23295        ˇ    done
23296        ˇ}
23297    "});
23298    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23299    cx.assert_editor_state(indoc! {"
23300        function main() {
23301            ˇfor item in $items; do
23302                ˇwhile [ -n \"$item\" ]; do
23303                    ˇif [ \"$value\" -gt 10 ]; then
23304                        ˇcontinue
23305                    ˇelif [ \"$value\" -lt 0 ]; then
23306                        ˇbreak
23307                    ˇelse
23308                        ˇecho \"$item\"
23309                    ˇfi
23310                ˇdone
23311            ˇdone
23312        ˇ}
23313    "});
23314    // test relative indent is preserved when tab
23315    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23316    cx.assert_editor_state(indoc! {"
23317        function main() {
23318                ˇfor item in $items; do
23319                    ˇwhile [ -n \"$item\" ]; do
23320                        ˇif [ \"$value\" -gt 10 ]; then
23321                            ˇcontinue
23322                        ˇelif [ \"$value\" -lt 0 ]; then
23323                            ˇbreak
23324                        ˇelse
23325                            ˇecho \"$item\"
23326                        ˇfi
23327                    ˇdone
23328                ˇdone
23329            ˇ}
23330    "});
23331
23332    // test cursor move to start of each line on tab
23333    // for `case` statement with patterns
23334    cx.set_state(indoc! {"
23335        function handle() {
23336        ˇ    case \"$1\" in
23337        ˇ        start)
23338        ˇ            echo \"a\"
23339        ˇ            ;;
23340        ˇ        stop)
23341        ˇ            echo \"b\"
23342        ˇ            ;;
23343        ˇ        *)
23344        ˇ            echo \"c\"
23345        ˇ            ;;
23346        ˇ    esac
23347        ˇ}
23348    "});
23349    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23350    cx.assert_editor_state(indoc! {"
23351        function handle() {
23352            ˇcase \"$1\" in
23353                ˇstart)
23354                    ˇecho \"a\"
23355                    ˇ;;
23356                ˇstop)
23357                    ˇecho \"b\"
23358                    ˇ;;
23359                ˇ*)
23360                    ˇecho \"c\"
23361                    ˇ;;
23362            ˇesac
23363        ˇ}
23364    "});
23365}
23366
23367#[gpui::test]
23368async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
23369    init_test(cx, |_| {});
23370
23371    let mut cx = EditorTestContext::new(cx).await;
23372    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23373    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23374
23375    // test indents on comment insert
23376    cx.set_state(indoc! {"
23377        function main() {
23378        ˇ    for item in $items; do
23379        ˇ        while [ -n \"$item\" ]; do
23380        ˇ            if [ \"$value\" -gt 10 ]; then
23381        ˇ                continue
23382        ˇ            elif [ \"$value\" -lt 0 ]; then
23383        ˇ                break
23384        ˇ            else
23385        ˇ                echo \"$item\"
23386        ˇ            fi
23387        ˇ        done
23388        ˇ    done
23389        ˇ}
23390    "});
23391    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
23392    cx.assert_editor_state(indoc! {"
23393        function main() {
23394        #ˇ    for item in $items; do
23395        #ˇ        while [ -n \"$item\" ]; do
23396        #ˇ            if [ \"$value\" -gt 10 ]; then
23397        #ˇ                continue
23398        #ˇ            elif [ \"$value\" -lt 0 ]; then
23399        #ˇ                break
23400        #ˇ            else
23401        #ˇ                echo \"$item\"
23402        #ˇ            fi
23403        #ˇ        done
23404        #ˇ    done
23405        #ˇ}
23406    "});
23407}
23408
23409#[gpui::test]
23410async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
23411    init_test(cx, |_| {});
23412
23413    let mut cx = EditorTestContext::new(cx).await;
23414    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23415    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23416
23417    // test `else` auto outdents when typed inside `if` block
23418    cx.set_state(indoc! {"
23419        if [ \"$1\" = \"test\" ]; then
23420            echo \"foo bar\"
23421            ˇ
23422    "});
23423    cx.update_editor(|editor, window, cx| {
23424        editor.handle_input("else", window, cx);
23425    });
23426    cx.assert_editor_state(indoc! {"
23427        if [ \"$1\" = \"test\" ]; then
23428            echo \"foo bar\"
23429        elseˇ
23430    "});
23431
23432    // test `elif` auto outdents when typed inside `if` block
23433    cx.set_state(indoc! {"
23434        if [ \"$1\" = \"test\" ]; then
23435            echo \"foo bar\"
23436            ˇ
23437    "});
23438    cx.update_editor(|editor, window, cx| {
23439        editor.handle_input("elif", window, cx);
23440    });
23441    cx.assert_editor_state(indoc! {"
23442        if [ \"$1\" = \"test\" ]; then
23443            echo \"foo bar\"
23444        elifˇ
23445    "});
23446
23447    // test `fi` auto outdents when typed inside `else` block
23448    cx.set_state(indoc! {"
23449        if [ \"$1\" = \"test\" ]; then
23450            echo \"foo bar\"
23451        else
23452            echo \"bar baz\"
23453            ˇ
23454    "});
23455    cx.update_editor(|editor, window, cx| {
23456        editor.handle_input("fi", window, cx);
23457    });
23458    cx.assert_editor_state(indoc! {"
23459        if [ \"$1\" = \"test\" ]; then
23460            echo \"foo bar\"
23461        else
23462            echo \"bar baz\"
23463        fiˇ
23464    "});
23465
23466    // test `done` auto outdents when typed inside `while` block
23467    cx.set_state(indoc! {"
23468        while read line; do
23469            echo \"$line\"
23470            ˇ
23471    "});
23472    cx.update_editor(|editor, window, cx| {
23473        editor.handle_input("done", window, cx);
23474    });
23475    cx.assert_editor_state(indoc! {"
23476        while read line; do
23477            echo \"$line\"
23478        doneˇ
23479    "});
23480
23481    // test `done` auto outdents when typed inside `for` block
23482    cx.set_state(indoc! {"
23483        for file in *.txt; do
23484            cat \"$file\"
23485            ˇ
23486    "});
23487    cx.update_editor(|editor, window, cx| {
23488        editor.handle_input("done", window, cx);
23489    });
23490    cx.assert_editor_state(indoc! {"
23491        for file in *.txt; do
23492            cat \"$file\"
23493        doneˇ
23494    "});
23495
23496    // test `esac` auto outdents when typed inside `case` block
23497    cx.set_state(indoc! {"
23498        case \"$1\" in
23499            start)
23500                echo \"foo bar\"
23501                ;;
23502            stop)
23503                echo \"bar baz\"
23504                ;;
23505            ˇ
23506    "});
23507    cx.update_editor(|editor, window, cx| {
23508        editor.handle_input("esac", window, cx);
23509    });
23510    cx.assert_editor_state(indoc! {"
23511        case \"$1\" in
23512            start)
23513                echo \"foo bar\"
23514                ;;
23515            stop)
23516                echo \"bar baz\"
23517                ;;
23518        esacˇ
23519    "});
23520
23521    // test `*)` auto outdents when typed inside `case` block
23522    cx.set_state(indoc! {"
23523        case \"$1\" in
23524            start)
23525                echo \"foo bar\"
23526                ;;
23527                ˇ
23528    "});
23529    cx.update_editor(|editor, window, cx| {
23530        editor.handle_input("*)", window, cx);
23531    });
23532    cx.assert_editor_state(indoc! {"
23533        case \"$1\" in
23534            start)
23535                echo \"foo bar\"
23536                ;;
23537            *)ˇ
23538    "});
23539
23540    // test `fi` outdents to correct level with nested if blocks
23541    cx.set_state(indoc! {"
23542        if [ \"$1\" = \"test\" ]; then
23543            echo \"outer if\"
23544            if [ \"$2\" = \"debug\" ]; then
23545                echo \"inner if\"
23546                ˇ
23547    "});
23548    cx.update_editor(|editor, window, cx| {
23549        editor.handle_input("fi", window, cx);
23550    });
23551    cx.assert_editor_state(indoc! {"
23552        if [ \"$1\" = \"test\" ]; then
23553            echo \"outer if\"
23554            if [ \"$2\" = \"debug\" ]; then
23555                echo \"inner if\"
23556            fiˇ
23557    "});
23558}
23559
23560#[gpui::test]
23561async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
23562    init_test(cx, |_| {});
23563    update_test_language_settings(cx, |settings| {
23564        settings.defaults.extend_comment_on_newline = Some(false);
23565    });
23566    let mut cx = EditorTestContext::new(cx).await;
23567    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23568    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23569
23570    // test correct indent after newline on comment
23571    cx.set_state(indoc! {"
23572        # COMMENT:ˇ
23573    "});
23574    cx.update_editor(|editor, window, cx| {
23575        editor.newline(&Newline, window, cx);
23576    });
23577    cx.assert_editor_state(indoc! {"
23578        # COMMENT:
23579        ˇ
23580    "});
23581
23582    // test correct indent after newline after `then`
23583    cx.set_state(indoc! {"
23584
23585        if [ \"$1\" = \"test\" ]; thenˇ
23586    "});
23587    cx.update_editor(|editor, window, cx| {
23588        editor.newline(&Newline, window, cx);
23589    });
23590    cx.run_until_parked();
23591    cx.assert_editor_state(indoc! {"
23592
23593        if [ \"$1\" = \"test\" ]; then
23594            ˇ
23595    "});
23596
23597    // test correct indent after newline after `else`
23598    cx.set_state(indoc! {"
23599        if [ \"$1\" = \"test\" ]; then
23600        elseˇ
23601    "});
23602    cx.update_editor(|editor, window, cx| {
23603        editor.newline(&Newline, window, cx);
23604    });
23605    cx.run_until_parked();
23606    cx.assert_editor_state(indoc! {"
23607        if [ \"$1\" = \"test\" ]; then
23608        else
23609            ˇ
23610    "});
23611
23612    // test correct indent after newline after `elif`
23613    cx.set_state(indoc! {"
23614        if [ \"$1\" = \"test\" ]; then
23615        elifˇ
23616    "});
23617    cx.update_editor(|editor, window, cx| {
23618        editor.newline(&Newline, window, cx);
23619    });
23620    cx.run_until_parked();
23621    cx.assert_editor_state(indoc! {"
23622        if [ \"$1\" = \"test\" ]; then
23623        elif
23624            ˇ
23625    "});
23626
23627    // test correct indent after newline after `do`
23628    cx.set_state(indoc! {"
23629        for file in *.txt; doˇ
23630    "});
23631    cx.update_editor(|editor, window, cx| {
23632        editor.newline(&Newline, window, cx);
23633    });
23634    cx.run_until_parked();
23635    cx.assert_editor_state(indoc! {"
23636        for file in *.txt; do
23637            ˇ
23638    "});
23639
23640    // test correct indent after newline after case pattern
23641    cx.set_state(indoc! {"
23642        case \"$1\" in
23643            start)ˇ
23644    "});
23645    cx.update_editor(|editor, window, cx| {
23646        editor.newline(&Newline, window, cx);
23647    });
23648    cx.run_until_parked();
23649    cx.assert_editor_state(indoc! {"
23650        case \"$1\" in
23651            start)
23652                ˇ
23653    "});
23654
23655    // test correct indent after newline after case pattern
23656    cx.set_state(indoc! {"
23657        case \"$1\" in
23658            start)
23659                ;;
23660            *)ˇ
23661    "});
23662    cx.update_editor(|editor, window, cx| {
23663        editor.newline(&Newline, window, cx);
23664    });
23665    cx.run_until_parked();
23666    cx.assert_editor_state(indoc! {"
23667        case \"$1\" in
23668            start)
23669                ;;
23670            *)
23671                ˇ
23672    "});
23673
23674    // test correct indent after newline after function opening brace
23675    cx.set_state(indoc! {"
23676        function test() {ˇ}
23677    "});
23678    cx.update_editor(|editor, window, cx| {
23679        editor.newline(&Newline, window, cx);
23680    });
23681    cx.run_until_parked();
23682    cx.assert_editor_state(indoc! {"
23683        function test() {
23684            ˇ
23685        }
23686    "});
23687
23688    // test no extra indent after semicolon on same line
23689    cx.set_state(indoc! {"
23690        echo \"test\"23691    "});
23692    cx.update_editor(|editor, window, cx| {
23693        editor.newline(&Newline, window, cx);
23694    });
23695    cx.run_until_parked();
23696    cx.assert_editor_state(indoc! {"
23697        echo \"test\";
23698        ˇ
23699    "});
23700}
23701
23702fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
23703    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
23704    point..point
23705}
23706
23707#[track_caller]
23708fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
23709    let (text, ranges) = marked_text_ranges(marked_text, true);
23710    assert_eq!(editor.text(cx), text);
23711    assert_eq!(
23712        editor.selections.ranges(cx),
23713        ranges,
23714        "Assert selections are {}",
23715        marked_text
23716    );
23717}
23718
23719pub fn handle_signature_help_request(
23720    cx: &mut EditorLspTestContext,
23721    mocked_response: lsp::SignatureHelp,
23722) -> impl Future<Output = ()> + use<> {
23723    let mut request =
23724        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
23725            let mocked_response = mocked_response.clone();
23726            async move { Ok(Some(mocked_response)) }
23727        });
23728
23729    async move {
23730        request.next().await;
23731    }
23732}
23733
23734#[track_caller]
23735pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
23736    cx.update_editor(|editor, _, _| {
23737        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
23738            let entries = menu.entries.borrow();
23739            let entries = entries
23740                .iter()
23741                .map(|entry| entry.string.as_str())
23742                .collect::<Vec<_>>();
23743            assert_eq!(entries, expected);
23744        } else {
23745            panic!("Expected completions menu");
23746        }
23747    });
23748}
23749
23750/// Handle completion request passing a marked string specifying where the completion
23751/// should be triggered from using '|' character, what range should be replaced, and what completions
23752/// should be returned using '<' and '>' to delimit the range.
23753///
23754/// Also see `handle_completion_request_with_insert_and_replace`.
23755#[track_caller]
23756pub fn handle_completion_request(
23757    marked_string: &str,
23758    completions: Vec<&'static str>,
23759    is_incomplete: bool,
23760    counter: Arc<AtomicUsize>,
23761    cx: &mut EditorLspTestContext,
23762) -> impl Future<Output = ()> {
23763    let complete_from_marker: TextRangeMarker = '|'.into();
23764    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23765    let (_, mut marked_ranges) = marked_text_ranges_by(
23766        marked_string,
23767        vec![complete_from_marker.clone(), replace_range_marker.clone()],
23768    );
23769
23770    let complete_from_position =
23771        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23772    let replace_range =
23773        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23774
23775    let mut request =
23776        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23777            let completions = completions.clone();
23778            counter.fetch_add(1, atomic::Ordering::Release);
23779            async move {
23780                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23781                assert_eq!(
23782                    params.text_document_position.position,
23783                    complete_from_position
23784                );
23785                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
23786                    is_incomplete,
23787                    item_defaults: None,
23788                    items: completions
23789                        .iter()
23790                        .map(|completion_text| lsp::CompletionItem {
23791                            label: completion_text.to_string(),
23792                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
23793                                range: replace_range,
23794                                new_text: completion_text.to_string(),
23795                            })),
23796                            ..Default::default()
23797                        })
23798                        .collect(),
23799                })))
23800            }
23801        });
23802
23803    async move {
23804        request.next().await;
23805    }
23806}
23807
23808/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
23809/// given instead, which also contains an `insert` range.
23810///
23811/// This function uses markers to define ranges:
23812/// - `|` marks the cursor position
23813/// - `<>` marks the replace range
23814/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
23815pub fn handle_completion_request_with_insert_and_replace(
23816    cx: &mut EditorLspTestContext,
23817    marked_string: &str,
23818    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
23819    counter: Arc<AtomicUsize>,
23820) -> impl Future<Output = ()> {
23821    let complete_from_marker: TextRangeMarker = '|'.into();
23822    let replace_range_marker: TextRangeMarker = ('<', '>').into();
23823    let insert_range_marker: TextRangeMarker = ('{', '}').into();
23824
23825    let (_, mut marked_ranges) = marked_text_ranges_by(
23826        marked_string,
23827        vec![
23828            complete_from_marker.clone(),
23829            replace_range_marker.clone(),
23830            insert_range_marker.clone(),
23831        ],
23832    );
23833
23834    let complete_from_position =
23835        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
23836    let replace_range =
23837        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
23838
23839    let insert_range = match marked_ranges.remove(&insert_range_marker) {
23840        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
23841        _ => lsp::Range {
23842            start: replace_range.start,
23843            end: complete_from_position,
23844        },
23845    };
23846
23847    let mut request =
23848        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
23849            let completions = completions.clone();
23850            counter.fetch_add(1, atomic::Ordering::Release);
23851            async move {
23852                assert_eq!(params.text_document_position.text_document.uri, url.clone());
23853                assert_eq!(
23854                    params.text_document_position.position, complete_from_position,
23855                    "marker `|` position doesn't match",
23856                );
23857                Ok(Some(lsp::CompletionResponse::Array(
23858                    completions
23859                        .iter()
23860                        .map(|(label, new_text)| lsp::CompletionItem {
23861                            label: label.to_string(),
23862                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23863                                lsp::InsertReplaceEdit {
23864                                    insert: insert_range,
23865                                    replace: replace_range,
23866                                    new_text: new_text.to_string(),
23867                                },
23868                            )),
23869                            ..Default::default()
23870                        })
23871                        .collect(),
23872                )))
23873            }
23874        });
23875
23876    async move {
23877        request.next().await;
23878    }
23879}
23880
23881fn handle_resolve_completion_request(
23882    cx: &mut EditorLspTestContext,
23883    edits: Option<Vec<(&'static str, &'static str)>>,
23884) -> impl Future<Output = ()> {
23885    let edits = edits.map(|edits| {
23886        edits
23887            .iter()
23888            .map(|(marked_string, new_text)| {
23889                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
23890                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
23891                lsp::TextEdit::new(replace_range, new_text.to_string())
23892            })
23893            .collect::<Vec<_>>()
23894    });
23895
23896    let mut request =
23897        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
23898            let edits = edits.clone();
23899            async move {
23900                Ok(lsp::CompletionItem {
23901                    additional_text_edits: edits,
23902                    ..Default::default()
23903                })
23904            }
23905        });
23906
23907    async move {
23908        request.next().await;
23909    }
23910}
23911
23912pub(crate) fn update_test_language_settings(
23913    cx: &mut TestAppContext,
23914    f: impl Fn(&mut AllLanguageSettingsContent),
23915) {
23916    cx.update(|cx| {
23917        SettingsStore::update_global(cx, |store, cx| {
23918            store.update_user_settings::<AllLanguageSettings>(cx, f);
23919        });
23920    });
23921}
23922
23923pub(crate) fn update_test_project_settings(
23924    cx: &mut TestAppContext,
23925    f: impl Fn(&mut ProjectSettings),
23926) {
23927    cx.update(|cx| {
23928        SettingsStore::update_global(cx, |store, cx| {
23929            store.update_user_settings::<ProjectSettings>(cx, f);
23930        });
23931    });
23932}
23933
23934pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
23935    cx.update(|cx| {
23936        assets::Assets.load_test_fonts(cx);
23937        let store = SettingsStore::test(cx);
23938        cx.set_global(store);
23939        theme::init(theme::LoadThemes::JustBase, cx);
23940        release_channel::init(SemanticVersion::default(), cx);
23941        client::init_settings(cx);
23942        language::init(cx);
23943        Project::init_settings(cx);
23944        workspace::init_settings(cx);
23945        crate::init(cx);
23946    });
23947    zlog::init_test();
23948    update_test_language_settings(cx, f);
23949}
23950
23951#[track_caller]
23952fn assert_hunk_revert(
23953    not_reverted_text_with_selections: &str,
23954    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
23955    expected_reverted_text_with_selections: &str,
23956    base_text: &str,
23957    cx: &mut EditorLspTestContext,
23958) {
23959    cx.set_state(not_reverted_text_with_selections);
23960    cx.set_head_text(base_text);
23961    cx.executor().run_until_parked();
23962
23963    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
23964        let snapshot = editor.snapshot(window, cx);
23965        let reverted_hunk_statuses = snapshot
23966            .buffer_snapshot
23967            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
23968            .map(|hunk| hunk.status().kind)
23969            .collect::<Vec<_>>();
23970
23971        editor.git_restore(&Default::default(), window, cx);
23972        reverted_hunk_statuses
23973    });
23974    cx.executor().run_until_parked();
23975    cx.assert_editor_state(expected_reverted_text_with_selections);
23976    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
23977}
23978
23979#[gpui::test(iterations = 10)]
23980async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
23981    init_test(cx, |_| {});
23982
23983    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
23984    let counter = diagnostic_requests.clone();
23985
23986    let fs = FakeFs::new(cx.executor());
23987    fs.insert_tree(
23988        path!("/a"),
23989        json!({
23990            "first.rs": "fn main() { let a = 5; }",
23991            "second.rs": "// Test file",
23992        }),
23993    )
23994    .await;
23995
23996    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23997    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23998    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23999
24000    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24001    language_registry.add(rust_lang());
24002    let mut fake_servers = language_registry.register_fake_lsp(
24003        "Rust",
24004        FakeLspAdapter {
24005            capabilities: lsp::ServerCapabilities {
24006                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
24007                    lsp::DiagnosticOptions {
24008                        identifier: None,
24009                        inter_file_dependencies: true,
24010                        workspace_diagnostics: true,
24011                        work_done_progress_options: Default::default(),
24012                    },
24013                )),
24014                ..Default::default()
24015            },
24016            ..Default::default()
24017        },
24018    );
24019
24020    let editor = workspace
24021        .update(cx, |workspace, window, cx| {
24022            workspace.open_abs_path(
24023                PathBuf::from(path!("/a/first.rs")),
24024                OpenOptions::default(),
24025                window,
24026                cx,
24027            )
24028        })
24029        .unwrap()
24030        .await
24031        .unwrap()
24032        .downcast::<Editor>()
24033        .unwrap();
24034    let fake_server = fake_servers.next().await.unwrap();
24035    let server_id = fake_server.server.server_id();
24036    let mut first_request = fake_server
24037        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
24038            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
24039            let result_id = Some(new_result_id.to_string());
24040            assert_eq!(
24041                params.text_document.uri,
24042                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
24043            );
24044            async move {
24045                Ok(lsp::DocumentDiagnosticReportResult::Report(
24046                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
24047                        related_documents: None,
24048                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
24049                            items: Vec::new(),
24050                            result_id,
24051                        },
24052                    }),
24053                ))
24054            }
24055        });
24056
24057    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
24058        project.update(cx, |project, cx| {
24059            let buffer_id = editor
24060                .read(cx)
24061                .buffer()
24062                .read(cx)
24063                .as_singleton()
24064                .expect("created a singleton buffer")
24065                .read(cx)
24066                .remote_id();
24067            let buffer_result_id = project
24068                .lsp_store()
24069                .read(cx)
24070                .result_id(server_id, buffer_id, cx);
24071            assert_eq!(expected, buffer_result_id);
24072        });
24073    };
24074
24075    ensure_result_id(None, cx);
24076    cx.executor().advance_clock(Duration::from_millis(60));
24077    cx.executor().run_until_parked();
24078    assert_eq!(
24079        diagnostic_requests.load(atomic::Ordering::Acquire),
24080        1,
24081        "Opening file should trigger diagnostic request"
24082    );
24083    first_request
24084        .next()
24085        .await
24086        .expect("should have sent the first diagnostics pull request");
24087    ensure_result_id(Some("1".to_string()), cx);
24088
24089    // Editing should trigger diagnostics
24090    editor.update_in(cx, |editor, window, cx| {
24091        editor.handle_input("2", window, cx)
24092    });
24093    cx.executor().advance_clock(Duration::from_millis(60));
24094    cx.executor().run_until_parked();
24095    assert_eq!(
24096        diagnostic_requests.load(atomic::Ordering::Acquire),
24097        2,
24098        "Editing should trigger diagnostic request"
24099    );
24100    ensure_result_id(Some("2".to_string()), cx);
24101
24102    // Moving cursor should not trigger diagnostic request
24103    editor.update_in(cx, |editor, window, cx| {
24104        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24105            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
24106        });
24107    });
24108    cx.executor().advance_clock(Duration::from_millis(60));
24109    cx.executor().run_until_parked();
24110    assert_eq!(
24111        diagnostic_requests.load(atomic::Ordering::Acquire),
24112        2,
24113        "Cursor movement should not trigger diagnostic request"
24114    );
24115    ensure_result_id(Some("2".to_string()), cx);
24116    // Multiple rapid edits should be debounced
24117    for _ in 0..5 {
24118        editor.update_in(cx, |editor, window, cx| {
24119            editor.handle_input("x", window, cx)
24120        });
24121    }
24122    cx.executor().advance_clock(Duration::from_millis(60));
24123    cx.executor().run_until_parked();
24124
24125    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
24126    assert!(
24127        final_requests <= 4,
24128        "Multiple rapid edits should be debounced (got {final_requests} requests)",
24129    );
24130    ensure_result_id(Some(final_requests.to_string()), cx);
24131}
24132
24133#[gpui::test]
24134async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
24135    // Regression test for issue #11671
24136    // Previously, adding a cursor after moving multiple cursors would reset
24137    // the cursor count instead of adding to the existing cursors.
24138    init_test(cx, |_| {});
24139    let mut cx = EditorTestContext::new(cx).await;
24140
24141    // Create a simple buffer with cursor at start
24142    cx.set_state(indoc! {"
24143        ˇaaaa
24144        bbbb
24145        cccc
24146        dddd
24147        eeee
24148        ffff
24149        gggg
24150        hhhh"});
24151
24152    // Add 2 cursors below (so we have 3 total)
24153    cx.update_editor(|editor, window, cx| {
24154        editor.add_selection_below(&Default::default(), window, cx);
24155        editor.add_selection_below(&Default::default(), window, cx);
24156    });
24157
24158    // Verify we have 3 cursors
24159    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
24160    assert_eq!(
24161        initial_count, 3,
24162        "Should have 3 cursors after adding 2 below"
24163    );
24164
24165    // Move down one line
24166    cx.update_editor(|editor, window, cx| {
24167        editor.move_down(&MoveDown, window, cx);
24168    });
24169
24170    // Add another cursor below
24171    cx.update_editor(|editor, window, cx| {
24172        editor.add_selection_below(&Default::default(), window, cx);
24173    });
24174
24175    // Should now have 4 cursors (3 original + 1 new)
24176    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
24177    assert_eq!(
24178        final_count, 4,
24179        "Should have 4 cursors after moving and adding another"
24180    );
24181}
24182
24183#[gpui::test(iterations = 10)]
24184async fn test_document_colors(cx: &mut TestAppContext) {
24185    let expected_color = Rgba {
24186        r: 0.33,
24187        g: 0.33,
24188        b: 0.33,
24189        a: 0.33,
24190    };
24191
24192    init_test(cx, |_| {});
24193
24194    let fs = FakeFs::new(cx.executor());
24195    fs.insert_tree(
24196        path!("/a"),
24197        json!({
24198            "first.rs": "fn main() { let a = 5; }",
24199        }),
24200    )
24201    .await;
24202
24203    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24204    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24205    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24206
24207    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24208    language_registry.add(rust_lang());
24209    let mut fake_servers = language_registry.register_fake_lsp(
24210        "Rust",
24211        FakeLspAdapter {
24212            capabilities: lsp::ServerCapabilities {
24213                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
24214                ..lsp::ServerCapabilities::default()
24215            },
24216            name: "rust-analyzer",
24217            ..FakeLspAdapter::default()
24218        },
24219    );
24220    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
24221        "Rust",
24222        FakeLspAdapter {
24223            capabilities: lsp::ServerCapabilities {
24224                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
24225                ..lsp::ServerCapabilities::default()
24226            },
24227            name: "not-rust-analyzer",
24228            ..FakeLspAdapter::default()
24229        },
24230    );
24231
24232    let editor = workspace
24233        .update(cx, |workspace, window, cx| {
24234            workspace.open_abs_path(
24235                PathBuf::from(path!("/a/first.rs")),
24236                OpenOptions::default(),
24237                window,
24238                cx,
24239            )
24240        })
24241        .unwrap()
24242        .await
24243        .unwrap()
24244        .downcast::<Editor>()
24245        .unwrap();
24246    let fake_language_server = fake_servers.next().await.unwrap();
24247    let fake_language_server_without_capabilities =
24248        fake_servers_without_capabilities.next().await.unwrap();
24249    let requests_made = Arc::new(AtomicUsize::new(0));
24250    let closure_requests_made = Arc::clone(&requests_made);
24251    let mut color_request_handle = fake_language_server
24252        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
24253            let requests_made = Arc::clone(&closure_requests_made);
24254            async move {
24255                assert_eq!(
24256                    params.text_document.uri,
24257                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
24258                );
24259                requests_made.fetch_add(1, atomic::Ordering::Release);
24260                Ok(vec![
24261                    lsp::ColorInformation {
24262                        range: lsp::Range {
24263                            start: lsp::Position {
24264                                line: 0,
24265                                character: 0,
24266                            },
24267                            end: lsp::Position {
24268                                line: 0,
24269                                character: 1,
24270                            },
24271                        },
24272                        color: lsp::Color {
24273                            red: 0.33,
24274                            green: 0.33,
24275                            blue: 0.33,
24276                            alpha: 0.33,
24277                        },
24278                    },
24279                    lsp::ColorInformation {
24280                        range: lsp::Range {
24281                            start: lsp::Position {
24282                                line: 0,
24283                                character: 0,
24284                            },
24285                            end: lsp::Position {
24286                                line: 0,
24287                                character: 1,
24288                            },
24289                        },
24290                        color: lsp::Color {
24291                            red: 0.33,
24292                            green: 0.33,
24293                            blue: 0.33,
24294                            alpha: 0.33,
24295                        },
24296                    },
24297                ])
24298            }
24299        });
24300
24301    let _handle = fake_language_server_without_capabilities
24302        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
24303            panic!("Should not be called");
24304        });
24305    cx.executor().advance_clock(Duration::from_millis(100));
24306    color_request_handle.next().await.unwrap();
24307    cx.run_until_parked();
24308    assert_eq!(
24309        1,
24310        requests_made.load(atomic::Ordering::Acquire),
24311        "Should query for colors once per editor open"
24312    );
24313    editor.update_in(cx, |editor, _, cx| {
24314        assert_eq!(
24315            vec![expected_color],
24316            extract_color_inlays(editor, cx),
24317            "Should have an initial inlay"
24318        );
24319    });
24320
24321    // opening another file in a split should not influence the LSP query counter
24322    workspace
24323        .update(cx, |workspace, window, cx| {
24324            assert_eq!(
24325                workspace.panes().len(),
24326                1,
24327                "Should have one pane with one editor"
24328            );
24329            workspace.move_item_to_pane_in_direction(
24330                &MoveItemToPaneInDirection {
24331                    direction: SplitDirection::Right,
24332                    focus: false,
24333                    clone: true,
24334                },
24335                window,
24336                cx,
24337            );
24338        })
24339        .unwrap();
24340    cx.run_until_parked();
24341    workspace
24342        .update(cx, |workspace, _, cx| {
24343            let panes = workspace.panes();
24344            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
24345            for pane in panes {
24346                let editor = pane
24347                    .read(cx)
24348                    .active_item()
24349                    .and_then(|item| item.downcast::<Editor>())
24350                    .expect("Should have opened an editor in each split");
24351                let editor_file = editor
24352                    .read(cx)
24353                    .buffer()
24354                    .read(cx)
24355                    .as_singleton()
24356                    .expect("test deals with singleton buffers")
24357                    .read(cx)
24358                    .file()
24359                    .expect("test buffese should have a file")
24360                    .path();
24361                assert_eq!(
24362                    editor_file.as_ref(),
24363                    Path::new("first.rs"),
24364                    "Both editors should be opened for the same file"
24365                )
24366            }
24367        })
24368        .unwrap();
24369
24370    cx.executor().advance_clock(Duration::from_millis(500));
24371    let save = editor.update_in(cx, |editor, window, cx| {
24372        editor.move_to_end(&MoveToEnd, window, cx);
24373        editor.handle_input("dirty", window, cx);
24374        editor.save(
24375            SaveOptions {
24376                format: true,
24377                autosave: true,
24378            },
24379            project.clone(),
24380            window,
24381            cx,
24382        )
24383    });
24384    save.await.unwrap();
24385
24386    color_request_handle.next().await.unwrap();
24387    cx.run_until_parked();
24388    assert_eq!(
24389        3,
24390        requests_made.load(atomic::Ordering::Acquire),
24391        "Should query for colors once per save and once per formatting after save"
24392    );
24393
24394    drop(editor);
24395    let close = workspace
24396        .update(cx, |workspace, window, cx| {
24397            workspace.active_pane().update(cx, |pane, cx| {
24398                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24399            })
24400        })
24401        .unwrap();
24402    close.await.unwrap();
24403    let close = workspace
24404        .update(cx, |workspace, window, cx| {
24405            workspace.active_pane().update(cx, |pane, cx| {
24406                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24407            })
24408        })
24409        .unwrap();
24410    close.await.unwrap();
24411    assert_eq!(
24412        3,
24413        requests_made.load(atomic::Ordering::Acquire),
24414        "After saving and closing all editors, no extra requests should be made"
24415    );
24416    workspace
24417        .update(cx, |workspace, _, cx| {
24418            assert!(
24419                workspace.active_item(cx).is_none(),
24420                "Should close all editors"
24421            )
24422        })
24423        .unwrap();
24424
24425    workspace
24426        .update(cx, |workspace, window, cx| {
24427            workspace.active_pane().update(cx, |pane, cx| {
24428                pane.navigate_backward(&Default::default(), window, cx);
24429            })
24430        })
24431        .unwrap();
24432    cx.executor().advance_clock(Duration::from_millis(100));
24433    cx.run_until_parked();
24434    let editor = workspace
24435        .update(cx, |workspace, _, cx| {
24436            workspace
24437                .active_item(cx)
24438                .expect("Should have reopened the editor again after navigating back")
24439                .downcast::<Editor>()
24440                .expect("Should be an editor")
24441        })
24442        .unwrap();
24443    color_request_handle.next().await.unwrap();
24444    assert_eq!(
24445        3,
24446        requests_made.load(atomic::Ordering::Acquire),
24447        "Cache should be reused on buffer close and reopen"
24448    );
24449    editor.update(cx, |editor, cx| {
24450        assert_eq!(
24451            vec![expected_color],
24452            extract_color_inlays(editor, cx),
24453            "Should have an initial inlay"
24454        );
24455    });
24456}
24457
24458#[gpui::test]
24459async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
24460    init_test(cx, |_| {});
24461    let (editor, cx) = cx.add_window_view(Editor::single_line);
24462    editor.update_in(cx, |editor, window, cx| {
24463        editor.set_text("oops\n\nwow\n", window, cx)
24464    });
24465    cx.run_until_parked();
24466    editor.update(cx, |editor, cx| {
24467        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
24468    });
24469    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
24470    cx.run_until_parked();
24471    editor.update(cx, |editor, cx| {
24472        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
24473    });
24474}
24475
24476#[gpui::test]
24477async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
24478    init_test(cx, |_| {});
24479
24480    cx.update(|cx| {
24481        register_project_item::<Editor>(cx);
24482    });
24483
24484    let fs = FakeFs::new(cx.executor());
24485    fs.insert_tree("/root1", json!({})).await;
24486    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
24487        .await;
24488
24489    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
24490    let (workspace, cx) =
24491        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24492
24493    let worktree_id = project.update(cx, |project, cx| {
24494        project.worktrees(cx).next().unwrap().read(cx).id()
24495    });
24496
24497    let handle = workspace
24498        .update_in(cx, |workspace, window, cx| {
24499            let project_path = (worktree_id, "one.pdf");
24500            workspace.open_path(project_path, None, true, window, cx)
24501        })
24502        .await
24503        .unwrap();
24504
24505    assert_eq!(
24506        handle.to_any().entity_type(),
24507        TypeId::of::<InvalidBufferView>()
24508    );
24509}
24510
24511#[track_caller]
24512fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
24513    editor
24514        .all_inlays(cx)
24515        .into_iter()
24516        .filter_map(|inlay| inlay.get_color())
24517        .map(Rgba::from)
24518        .collect()
24519}