editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    invalid_buffer_view::InvalidBufferView,
   61    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   62    register_project_item,
   63};
   64
   65#[gpui::test]
   66fn test_edit_events(cx: &mut TestAppContext) {
   67    init_test(cx, |_| {});
   68
   69    let buffer = cx.new(|cx| {
   70        let mut buffer = language::Buffer::local("123456", cx);
   71        buffer.set_group_interval(Duration::from_secs(1));
   72        buffer
   73    });
   74
   75    let events = Rc::new(RefCell::new(Vec::new()));
   76    let editor1 = cx.add_window({
   77        let events = events.clone();
   78        |window, cx| {
   79            let entity = cx.entity();
   80            cx.subscribe_in(
   81                &entity,
   82                window,
   83                move |_, _, event: &EditorEvent, _, _| match event {
   84                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   85                    EditorEvent::BufferEdited => {
   86                        events.borrow_mut().push(("editor1", "buffer edited"))
   87                    }
   88                    _ => {}
   89                },
   90            )
   91            .detach();
   92            Editor::for_buffer(buffer.clone(), None, window, cx)
   93        }
   94    });
   95
   96    let editor2 = cx.add_window({
   97        let events = events.clone();
   98        |window, cx| {
   99            cx.subscribe_in(
  100                &cx.entity(),
  101                window,
  102                move |_, _, event: &EditorEvent, _, _| match event {
  103                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  104                    EditorEvent::BufferEdited => {
  105                        events.borrow_mut().push(("editor2", "buffer edited"))
  106                    }
  107                    _ => {}
  108                },
  109            )
  110            .detach();
  111            Editor::for_buffer(buffer.clone(), None, window, cx)
  112        }
  113    });
  114
  115    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  116
  117    // Mutating editor 1 will emit an `Edited` event only for that editor.
  118    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  119    assert_eq!(
  120        mem::take(&mut *events.borrow_mut()),
  121        [
  122            ("editor1", "edited"),
  123            ("editor1", "buffer edited"),
  124            ("editor2", "buffer edited"),
  125        ]
  126    );
  127
  128    // Mutating editor 2 will emit an `Edited` event only for that editor.
  129    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  130    assert_eq!(
  131        mem::take(&mut *events.borrow_mut()),
  132        [
  133            ("editor2", "edited"),
  134            ("editor1", "buffer edited"),
  135            ("editor2", "buffer edited"),
  136        ]
  137    );
  138
  139    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  140    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  141    assert_eq!(
  142        mem::take(&mut *events.borrow_mut()),
  143        [
  144            ("editor1", "edited"),
  145            ("editor1", "buffer edited"),
  146            ("editor2", "buffer edited"),
  147        ]
  148    );
  149
  150    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  151    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  152    assert_eq!(
  153        mem::take(&mut *events.borrow_mut()),
  154        [
  155            ("editor1", "edited"),
  156            ("editor1", "buffer edited"),
  157            ("editor2", "buffer edited"),
  158        ]
  159    );
  160
  161    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  162    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  163    assert_eq!(
  164        mem::take(&mut *events.borrow_mut()),
  165        [
  166            ("editor2", "edited"),
  167            ("editor1", "buffer edited"),
  168            ("editor2", "buffer edited"),
  169        ]
  170    );
  171
  172    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  173    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  174    assert_eq!(
  175        mem::take(&mut *events.borrow_mut()),
  176        [
  177            ("editor2", "edited"),
  178            ("editor1", "buffer edited"),
  179            ("editor2", "buffer edited"),
  180        ]
  181    );
  182
  183    // No event is emitted when the mutation is a no-op.
  184    _ = editor2.update(cx, |editor, window, cx| {
  185        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  186            s.select_ranges([0..0])
  187        });
  188
  189        editor.backspace(&Backspace, window, cx);
  190    });
  191    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  192}
  193
  194#[gpui::test]
  195fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  196    init_test(cx, |_| {});
  197
  198    let mut now = Instant::now();
  199    let group_interval = Duration::from_millis(1);
  200    let buffer = cx.new(|cx| {
  201        let mut buf = language::Buffer::local("123456", cx);
  202        buf.set_group_interval(group_interval);
  203        buf
  204    });
  205    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  206    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  207
  208    _ = editor.update(cx, |editor, window, cx| {
  209        editor.start_transaction_at(now, window, cx);
  210        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  211            s.select_ranges([2..4])
  212        });
  213
  214        editor.insert("cd", window, cx);
  215        editor.end_transaction_at(now, cx);
  216        assert_eq!(editor.text(cx), "12cd56");
  217        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  218
  219        editor.start_transaction_at(now, window, cx);
  220        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  221            s.select_ranges([4..5])
  222        });
  223        editor.insert("e", window, cx);
  224        editor.end_transaction_at(now, cx);
  225        assert_eq!(editor.text(cx), "12cde6");
  226        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  227
  228        now += group_interval + Duration::from_millis(1);
  229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  230            s.select_ranges([2..2])
  231        });
  232
  233        // Simulate an edit in another editor
  234        buffer.update(cx, |buffer, cx| {
  235            buffer.start_transaction_at(now, cx);
  236            buffer.edit([(0..1, "a")], None, cx);
  237            buffer.edit([(1..1, "b")], None, cx);
  238            buffer.end_transaction_at(now, cx);
  239        });
  240
  241        assert_eq!(editor.text(cx), "ab2cde6");
  242        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  243
  244        // Last transaction happened past the group interval in a different editor.
  245        // Undo it individually and don't restore selections.
  246        editor.undo(&Undo, window, cx);
  247        assert_eq!(editor.text(cx), "12cde6");
  248        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  249
  250        // First two transactions happened within the group interval in this editor.
  251        // Undo them together and restore selections.
  252        editor.undo(&Undo, window, cx);
  253        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  254        assert_eq!(editor.text(cx), "123456");
  255        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  256
  257        // Redo the first two transactions together.
  258        editor.redo(&Redo, window, cx);
  259        assert_eq!(editor.text(cx), "12cde6");
  260        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  261
  262        // Redo the last transaction on its own.
  263        editor.redo(&Redo, window, cx);
  264        assert_eq!(editor.text(cx), "ab2cde6");
  265        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  266
  267        // Test empty transactions.
  268        editor.start_transaction_at(now, window, cx);
  269        editor.end_transaction_at(now, cx);
  270        editor.undo(&Undo, window, cx);
  271        assert_eq!(editor.text(cx), "12cde6");
  272    });
  273}
  274
  275#[gpui::test]
  276fn test_ime_composition(cx: &mut TestAppContext) {
  277    init_test(cx, |_| {});
  278
  279    let buffer = cx.new(|cx| {
  280        let mut buffer = language::Buffer::local("abcde", cx);
  281        // Ensure automatic grouping doesn't occur.
  282        buffer.set_group_interval(Duration::ZERO);
  283        buffer
  284    });
  285
  286    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  287    cx.add_window(|window, cx| {
  288        let mut editor = build_editor(buffer.clone(), window, cx);
  289
  290        // Start a new IME composition.
  291        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  292        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  293        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  294        assert_eq!(editor.text(cx), "äbcde");
  295        assert_eq!(
  296            editor.marked_text_ranges(cx),
  297            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  298        );
  299
  300        // Finalize IME composition.
  301        editor.replace_text_in_range(None, "ā", window, cx);
  302        assert_eq!(editor.text(cx), "ābcde");
  303        assert_eq!(editor.marked_text_ranges(cx), None);
  304
  305        // IME composition edits are grouped and are undone/redone at once.
  306        editor.undo(&Default::default(), window, cx);
  307        assert_eq!(editor.text(cx), "abcde");
  308        assert_eq!(editor.marked_text_ranges(cx), None);
  309        editor.redo(&Default::default(), window, cx);
  310        assert_eq!(editor.text(cx), "ābcde");
  311        assert_eq!(editor.marked_text_ranges(cx), None);
  312
  313        // Start a new IME composition.
  314        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  315        assert_eq!(
  316            editor.marked_text_ranges(cx),
  317            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  318        );
  319
  320        // Undoing during an IME composition cancels it.
  321        editor.undo(&Default::default(), window, cx);
  322        assert_eq!(editor.text(cx), "ābcde");
  323        assert_eq!(editor.marked_text_ranges(cx), None);
  324
  325        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  326        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  327        assert_eq!(editor.text(cx), "ābcdè");
  328        assert_eq!(
  329            editor.marked_text_ranges(cx),
  330            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  331        );
  332
  333        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  334        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  335        assert_eq!(editor.text(cx), "ābcdę");
  336        assert_eq!(editor.marked_text_ranges(cx), None);
  337
  338        // Start a new IME composition with multiple cursors.
  339        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  340            s.select_ranges([
  341                OffsetUtf16(1)..OffsetUtf16(1),
  342                OffsetUtf16(3)..OffsetUtf16(3),
  343                OffsetUtf16(5)..OffsetUtf16(5),
  344            ])
  345        });
  346        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  347        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  348        assert_eq!(
  349            editor.marked_text_ranges(cx),
  350            Some(vec![
  351                OffsetUtf16(0)..OffsetUtf16(3),
  352                OffsetUtf16(4)..OffsetUtf16(7),
  353                OffsetUtf16(8)..OffsetUtf16(11)
  354            ])
  355        );
  356
  357        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  358        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  359        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  360        assert_eq!(
  361            editor.marked_text_ranges(cx),
  362            Some(vec![
  363                OffsetUtf16(1)..OffsetUtf16(2),
  364                OffsetUtf16(5)..OffsetUtf16(6),
  365                OffsetUtf16(9)..OffsetUtf16(10)
  366            ])
  367        );
  368
  369        // Finalize IME composition with multiple cursors.
  370        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  371        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  372        assert_eq!(editor.marked_text_ranges(cx), None);
  373
  374        editor
  375    });
  376}
  377
  378#[gpui::test]
  379fn test_selection_with_mouse(cx: &mut TestAppContext) {
  380    init_test(cx, |_| {});
  381
  382    let editor = cx.add_window(|window, cx| {
  383        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  384        build_editor(buffer, window, cx)
  385    });
  386
  387    _ = editor.update(cx, |editor, window, cx| {
  388        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  389    });
  390    assert_eq!(
  391        editor
  392            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  393            .unwrap(),
  394        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  395    );
  396
  397    _ = editor.update(cx, |editor, window, cx| {
  398        editor.update_selection(
  399            DisplayPoint::new(DisplayRow(3), 3),
  400            0,
  401            gpui::Point::<f32>::default(),
  402            window,
  403            cx,
  404        );
  405    });
  406
  407    assert_eq!(
  408        editor
  409            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  410            .unwrap(),
  411        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  412    );
  413
  414    _ = editor.update(cx, |editor, window, cx| {
  415        editor.update_selection(
  416            DisplayPoint::new(DisplayRow(1), 1),
  417            0,
  418            gpui::Point::<f32>::default(),
  419            window,
  420            cx,
  421        );
  422    });
  423
  424    assert_eq!(
  425        editor
  426            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  427            .unwrap(),
  428        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  429    );
  430
  431    _ = editor.update(cx, |editor, window, cx| {
  432        editor.end_selection(window, cx);
  433        editor.update_selection(
  434            DisplayPoint::new(DisplayRow(3), 3),
  435            0,
  436            gpui::Point::<f32>::default(),
  437            window,
  438            cx,
  439        );
  440    });
  441
  442    assert_eq!(
  443        editor
  444            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  445            .unwrap(),
  446        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  447    );
  448
  449    _ = editor.update(cx, |editor, window, cx| {
  450        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  451        editor.update_selection(
  452            DisplayPoint::new(DisplayRow(0), 0),
  453            0,
  454            gpui::Point::<f32>::default(),
  455            window,
  456            cx,
  457        );
  458    });
  459
  460    assert_eq!(
  461        editor
  462            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  463            .unwrap(),
  464        [
  465            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  466            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  467        ]
  468    );
  469
  470    _ = editor.update(cx, |editor, window, cx| {
  471        editor.end_selection(window, cx);
  472    });
  473
  474    assert_eq!(
  475        editor
  476            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  477            .unwrap(),
  478        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  479    );
  480}
  481
  482#[gpui::test]
  483fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  484    init_test(cx, |_| {});
  485
  486    let editor = cx.add_window(|window, cx| {
  487        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  488        build_editor(buffer, window, cx)
  489    });
  490
  491    _ = editor.update(cx, |editor, window, cx| {
  492        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  493    });
  494
  495    _ = editor.update(cx, |editor, window, cx| {
  496        editor.end_selection(window, cx);
  497    });
  498
  499    _ = editor.update(cx, |editor, window, cx| {
  500        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  501    });
  502
  503    _ = editor.update(cx, |editor, window, cx| {
  504        editor.end_selection(window, cx);
  505    });
  506
  507    assert_eq!(
  508        editor
  509            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  510            .unwrap(),
  511        [
  512            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  513            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  514        ]
  515    );
  516
  517    _ = editor.update(cx, |editor, window, cx| {
  518        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  519    });
  520
  521    _ = editor.update(cx, |editor, window, cx| {
  522        editor.end_selection(window, cx);
  523    });
  524
  525    assert_eq!(
  526        editor
  527            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  528            .unwrap(),
  529        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  530    );
  531}
  532
  533#[gpui::test]
  534fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  535    init_test(cx, |_| {});
  536
  537    let editor = cx.add_window(|window, cx| {
  538        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  539        build_editor(buffer, window, cx)
  540    });
  541
  542    _ = editor.update(cx, |editor, window, cx| {
  543        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  544        assert_eq!(
  545            editor.selections.display_ranges(cx),
  546            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  547        );
  548    });
  549
  550    _ = editor.update(cx, |editor, window, cx| {
  551        editor.update_selection(
  552            DisplayPoint::new(DisplayRow(3), 3),
  553            0,
  554            gpui::Point::<f32>::default(),
  555            window,
  556            cx,
  557        );
  558        assert_eq!(
  559            editor.selections.display_ranges(cx),
  560            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  561        );
  562    });
  563
  564    _ = editor.update(cx, |editor, window, cx| {
  565        editor.cancel(&Cancel, window, cx);
  566        editor.update_selection(
  567            DisplayPoint::new(DisplayRow(1), 1),
  568            0,
  569            gpui::Point::<f32>::default(),
  570            window,
  571            cx,
  572        );
  573        assert_eq!(
  574            editor.selections.display_ranges(cx),
  575            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  576        );
  577    });
  578}
  579
  580#[gpui::test]
  581fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  582    init_test(cx, |_| {});
  583
  584    let editor = cx.add_window(|window, cx| {
  585        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  586        build_editor(buffer, window, cx)
  587    });
  588
  589    _ = editor.update(cx, |editor, window, cx| {
  590        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  591        assert_eq!(
  592            editor.selections.display_ranges(cx),
  593            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  594        );
  595
  596        editor.move_down(&Default::default(), window, cx);
  597        assert_eq!(
  598            editor.selections.display_ranges(cx),
  599            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  600        );
  601
  602        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  603        assert_eq!(
  604            editor.selections.display_ranges(cx),
  605            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  606        );
  607
  608        editor.move_up(&Default::default(), window, cx);
  609        assert_eq!(
  610            editor.selections.display_ranges(cx),
  611            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  612        );
  613    });
  614}
  615
  616#[gpui::test]
  617fn test_clone(cx: &mut TestAppContext) {
  618    init_test(cx, |_| {});
  619
  620    let (text, selection_ranges) = marked_text_ranges(
  621        indoc! {"
  622            one
  623            two
  624            threeˇ
  625            four
  626            fiveˇ
  627        "},
  628        true,
  629    );
  630
  631    let editor = cx.add_window(|window, cx| {
  632        let buffer = MultiBuffer::build_simple(&text, cx);
  633        build_editor(buffer, window, cx)
  634    });
  635
  636    _ = editor.update(cx, |editor, window, cx| {
  637        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  638            s.select_ranges(selection_ranges.clone())
  639        });
  640        editor.fold_creases(
  641            vec![
  642                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  643                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  644            ],
  645            true,
  646            window,
  647            cx,
  648        );
  649    });
  650
  651    let cloned_editor = editor
  652        .update(cx, |editor, _, cx| {
  653            cx.open_window(Default::default(), |window, cx| {
  654                cx.new(|cx| editor.clone(window, cx))
  655            })
  656        })
  657        .unwrap()
  658        .unwrap();
  659
  660    let snapshot = editor
  661        .update(cx, |e, window, cx| e.snapshot(window, cx))
  662        .unwrap();
  663    let cloned_snapshot = cloned_editor
  664        .update(cx, |e, window, cx| e.snapshot(window, cx))
  665        .unwrap();
  666
  667    assert_eq!(
  668        cloned_editor
  669            .update(cx, |e, _, cx| e.display_text(cx))
  670            .unwrap(),
  671        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  672    );
  673    assert_eq!(
  674        cloned_snapshot
  675            .folds_in_range(0..text.len())
  676            .collect::<Vec<_>>(),
  677        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  678    );
  679    assert_set_eq!(
  680        cloned_editor
  681            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  682            .unwrap(),
  683        editor
  684            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  685            .unwrap()
  686    );
  687    assert_set_eq!(
  688        cloned_editor
  689            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  690            .unwrap(),
  691        editor
  692            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  693            .unwrap()
  694    );
  695}
  696
  697#[gpui::test]
  698async fn test_navigation_history(cx: &mut TestAppContext) {
  699    init_test(cx, |_| {});
  700
  701    use workspace::item::Item;
  702
  703    let fs = FakeFs::new(cx.executor());
  704    let project = Project::test(fs, [], cx).await;
  705    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  706    let pane = workspace
  707        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  708        .unwrap();
  709
  710    _ = workspace.update(cx, |_v, window, cx| {
  711        cx.new(|cx| {
  712            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  713            let mut editor = build_editor(buffer, window, cx);
  714            let handle = cx.entity();
  715            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  716
  717            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  718                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  719            }
  720
  721            // Move the cursor a small distance.
  722            // Nothing is added to the navigation history.
  723            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  724                s.select_display_ranges([
  725                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  726                ])
  727            });
  728            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  729                s.select_display_ranges([
  730                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  731                ])
  732            });
  733            assert!(pop_history(&mut editor, cx).is_none());
  734
  735            // Move the cursor a large distance.
  736            // The history can jump back to the previous position.
  737            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  738                s.select_display_ranges([
  739                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  740                ])
  741            });
  742            let nav_entry = pop_history(&mut editor, cx).unwrap();
  743            editor.navigate(nav_entry.data.unwrap(), window, cx);
  744            assert_eq!(nav_entry.item.id(), cx.entity_id());
  745            assert_eq!(
  746                editor.selections.display_ranges(cx),
  747                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  748            );
  749            assert!(pop_history(&mut editor, cx).is_none());
  750
  751            // Move the cursor a small distance via the mouse.
  752            // Nothing is added to the navigation history.
  753            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  754            editor.end_selection(window, cx);
  755            assert_eq!(
  756                editor.selections.display_ranges(cx),
  757                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  758            );
  759            assert!(pop_history(&mut editor, cx).is_none());
  760
  761            // Move the cursor a large distance via the mouse.
  762            // The history can jump back to the previous position.
  763            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  764            editor.end_selection(window, cx);
  765            assert_eq!(
  766                editor.selections.display_ranges(cx),
  767                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  768            );
  769            let nav_entry = pop_history(&mut editor, cx).unwrap();
  770            editor.navigate(nav_entry.data.unwrap(), window, cx);
  771            assert_eq!(nav_entry.item.id(), cx.entity_id());
  772            assert_eq!(
  773                editor.selections.display_ranges(cx),
  774                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  775            );
  776            assert!(pop_history(&mut editor, cx).is_none());
  777
  778            // Set scroll position to check later
  779            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  780            let original_scroll_position = editor.scroll_manager.anchor();
  781
  782            // Jump to the end of the document and adjust scroll
  783            editor.move_to_end(&MoveToEnd, window, cx);
  784            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  785            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  786
  787            let nav_entry = pop_history(&mut editor, cx).unwrap();
  788            editor.navigate(nav_entry.data.unwrap(), window, cx);
  789            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  790
  791            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  792            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  793            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  794            let invalid_point = Point::new(9999, 0);
  795            editor.navigate(
  796                Box::new(NavigationData {
  797                    cursor_anchor: invalid_anchor,
  798                    cursor_position: invalid_point,
  799                    scroll_anchor: ScrollAnchor {
  800                        anchor: invalid_anchor,
  801                        offset: Default::default(),
  802                    },
  803                    scroll_top_row: invalid_point.row,
  804                }),
  805                window,
  806                cx,
  807            );
  808            assert_eq!(
  809                editor.selections.display_ranges(cx),
  810                &[editor.max_point(cx)..editor.max_point(cx)]
  811            );
  812            assert_eq!(
  813                editor.scroll_position(cx),
  814                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  815            );
  816
  817            editor
  818        })
  819    });
  820}
  821
  822#[gpui::test]
  823fn test_cancel(cx: &mut TestAppContext) {
  824    init_test(cx, |_| {});
  825
  826    let editor = cx.add_window(|window, cx| {
  827        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  828        build_editor(buffer, window, cx)
  829    });
  830
  831    _ = editor.update(cx, |editor, window, cx| {
  832        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  833        editor.update_selection(
  834            DisplayPoint::new(DisplayRow(1), 1),
  835            0,
  836            gpui::Point::<f32>::default(),
  837            window,
  838            cx,
  839        );
  840        editor.end_selection(window, cx);
  841
  842        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  843        editor.update_selection(
  844            DisplayPoint::new(DisplayRow(0), 3),
  845            0,
  846            gpui::Point::<f32>::default(),
  847            window,
  848            cx,
  849        );
  850        editor.end_selection(window, cx);
  851        assert_eq!(
  852            editor.selections.display_ranges(cx),
  853            [
  854                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  855                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  856            ]
  857        );
  858    });
  859
  860    _ = editor.update(cx, |editor, window, cx| {
  861        editor.cancel(&Cancel, window, cx);
  862        assert_eq!(
  863            editor.selections.display_ranges(cx),
  864            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  865        );
  866    });
  867
  868    _ = editor.update(cx, |editor, window, cx| {
  869        editor.cancel(&Cancel, window, cx);
  870        assert_eq!(
  871            editor.selections.display_ranges(cx),
  872            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  873        );
  874    });
  875}
  876
  877#[gpui::test]
  878fn test_fold_action(cx: &mut TestAppContext) {
  879    init_test(cx, |_| {});
  880
  881    let editor = cx.add_window(|window, cx| {
  882        let buffer = MultiBuffer::build_simple(
  883            &"
  884                impl Foo {
  885                    // Hello!
  886
  887                    fn a() {
  888                        1
  889                    }
  890
  891                    fn b() {
  892                        2
  893                    }
  894
  895                    fn c() {
  896                        3
  897                    }
  898                }
  899            "
  900            .unindent(),
  901            cx,
  902        );
  903        build_editor(buffer, window, cx)
  904    });
  905
  906    _ = editor.update(cx, |editor, window, cx| {
  907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  908            s.select_display_ranges([
  909                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  910            ]);
  911        });
  912        editor.fold(&Fold, window, cx);
  913        assert_eq!(
  914            editor.display_text(cx),
  915            "
  916                impl Foo {
  917                    // Hello!
  918
  919                    fn a() {
  920                        1
  921                    }
  922
  923                    fn b() {⋯
  924                    }
  925
  926                    fn c() {⋯
  927                    }
  928                }
  929            "
  930            .unindent(),
  931        );
  932
  933        editor.fold(&Fold, window, cx);
  934        assert_eq!(
  935            editor.display_text(cx),
  936            "
  937                impl Foo {⋯
  938                }
  939            "
  940            .unindent(),
  941        );
  942
  943        editor.unfold_lines(&UnfoldLines, window, cx);
  944        assert_eq!(
  945            editor.display_text(cx),
  946            "
  947                impl Foo {
  948                    // Hello!
  949
  950                    fn a() {
  951                        1
  952                    }
  953
  954                    fn b() {⋯
  955                    }
  956
  957                    fn c() {⋯
  958                    }
  959                }
  960            "
  961            .unindent(),
  962        );
  963
  964        editor.unfold_lines(&UnfoldLines, window, cx);
  965        assert_eq!(
  966            editor.display_text(cx),
  967            editor.buffer.read(cx).read(cx).text()
  968        );
  969    });
  970}
  971
  972#[gpui::test]
  973fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  974    init_test(cx, |_| {});
  975
  976    let editor = cx.add_window(|window, cx| {
  977        let buffer = MultiBuffer::build_simple(
  978            &"
  979                class Foo:
  980                    # Hello!
  981
  982                    def a():
  983                        print(1)
  984
  985                    def b():
  986                        print(2)
  987
  988                    def c():
  989                        print(3)
  990            "
  991            .unindent(),
  992            cx,
  993        );
  994        build_editor(buffer, window, cx)
  995    });
  996
  997    _ = editor.update(cx, |editor, window, cx| {
  998        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  999            s.select_display_ranges([
 1000                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1001            ]);
 1002        });
 1003        editor.fold(&Fold, window, cx);
 1004        assert_eq!(
 1005            editor.display_text(cx),
 1006            "
 1007                class Foo:
 1008                    # Hello!
 1009
 1010                    def a():
 1011                        print(1)
 1012
 1013                    def b():⋯
 1014
 1015                    def c():⋯
 1016            "
 1017            .unindent(),
 1018        );
 1019
 1020        editor.fold(&Fold, window, cx);
 1021        assert_eq!(
 1022            editor.display_text(cx),
 1023            "
 1024                class Foo:⋯
 1025            "
 1026            .unindent(),
 1027        );
 1028
 1029        editor.unfold_lines(&UnfoldLines, window, cx);
 1030        assert_eq!(
 1031            editor.display_text(cx),
 1032            "
 1033                class Foo:
 1034                    # Hello!
 1035
 1036                    def a():
 1037                        print(1)
 1038
 1039                    def b():⋯
 1040
 1041                    def c():⋯
 1042            "
 1043            .unindent(),
 1044        );
 1045
 1046        editor.unfold_lines(&UnfoldLines, window, cx);
 1047        assert_eq!(
 1048            editor.display_text(cx),
 1049            editor.buffer.read(cx).read(cx).text()
 1050        );
 1051    });
 1052}
 1053
 1054#[gpui::test]
 1055fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1056    init_test(cx, |_| {});
 1057
 1058    let editor = cx.add_window(|window, cx| {
 1059        let buffer = MultiBuffer::build_simple(
 1060            &"
 1061                class Foo:
 1062                    # Hello!
 1063
 1064                    def a():
 1065                        print(1)
 1066
 1067                    def b():
 1068                        print(2)
 1069
 1070
 1071                    def c():
 1072                        print(3)
 1073
 1074
 1075            "
 1076            .unindent(),
 1077            cx,
 1078        );
 1079        build_editor(buffer, window, cx)
 1080    });
 1081
 1082    _ = editor.update(cx, |editor, window, cx| {
 1083        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1084            s.select_display_ranges([
 1085                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1086            ]);
 1087        });
 1088        editor.fold(&Fold, window, cx);
 1089        assert_eq!(
 1090            editor.display_text(cx),
 1091            "
 1092                class Foo:
 1093                    # Hello!
 1094
 1095                    def a():
 1096                        print(1)
 1097
 1098                    def b():⋯
 1099
 1100
 1101                    def c():⋯
 1102
 1103
 1104            "
 1105            .unindent(),
 1106        );
 1107
 1108        editor.fold(&Fold, window, cx);
 1109        assert_eq!(
 1110            editor.display_text(cx),
 1111            "
 1112                class Foo:⋯
 1113
 1114
 1115            "
 1116            .unindent(),
 1117        );
 1118
 1119        editor.unfold_lines(&UnfoldLines, window, cx);
 1120        assert_eq!(
 1121            editor.display_text(cx),
 1122            "
 1123                class Foo:
 1124                    # Hello!
 1125
 1126                    def a():
 1127                        print(1)
 1128
 1129                    def b():⋯
 1130
 1131
 1132                    def c():⋯
 1133
 1134
 1135            "
 1136            .unindent(),
 1137        );
 1138
 1139        editor.unfold_lines(&UnfoldLines, window, cx);
 1140        assert_eq!(
 1141            editor.display_text(cx),
 1142            editor.buffer.read(cx).read(cx).text()
 1143        );
 1144    });
 1145}
 1146
 1147#[gpui::test]
 1148fn test_fold_at_level(cx: &mut TestAppContext) {
 1149    init_test(cx, |_| {});
 1150
 1151    let editor = cx.add_window(|window, cx| {
 1152        let buffer = MultiBuffer::build_simple(
 1153            &"
 1154                class Foo:
 1155                    # Hello!
 1156
 1157                    def a():
 1158                        print(1)
 1159
 1160                    def b():
 1161                        print(2)
 1162
 1163
 1164                class Bar:
 1165                    # World!
 1166
 1167                    def a():
 1168                        print(1)
 1169
 1170                    def b():
 1171                        print(2)
 1172
 1173
 1174            "
 1175            .unindent(),
 1176            cx,
 1177        );
 1178        build_editor(buffer, window, cx)
 1179    });
 1180
 1181    _ = editor.update(cx, |editor, window, cx| {
 1182        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1183        assert_eq!(
 1184            editor.display_text(cx),
 1185            "
 1186                class Foo:
 1187                    # Hello!
 1188
 1189                    def a():⋯
 1190
 1191                    def b():⋯
 1192
 1193
 1194                class Bar:
 1195                    # World!
 1196
 1197                    def a():⋯
 1198
 1199                    def b():⋯
 1200
 1201
 1202            "
 1203            .unindent(),
 1204        );
 1205
 1206        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1207        assert_eq!(
 1208            editor.display_text(cx),
 1209            "
 1210                class Foo:⋯
 1211
 1212
 1213                class Bar:⋯
 1214
 1215
 1216            "
 1217            .unindent(),
 1218        );
 1219
 1220        editor.unfold_all(&UnfoldAll, window, cx);
 1221        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1222        assert_eq!(
 1223            editor.display_text(cx),
 1224            "
 1225                class Foo:
 1226                    # Hello!
 1227
 1228                    def a():
 1229                        print(1)
 1230
 1231                    def b():
 1232                        print(2)
 1233
 1234
 1235                class Bar:
 1236                    # World!
 1237
 1238                    def a():
 1239                        print(1)
 1240
 1241                    def b():
 1242                        print(2)
 1243
 1244
 1245            "
 1246            .unindent(),
 1247        );
 1248
 1249        assert_eq!(
 1250            editor.display_text(cx),
 1251            editor.buffer.read(cx).read(cx).text()
 1252        );
 1253    });
 1254}
 1255
 1256#[gpui::test]
 1257fn test_move_cursor(cx: &mut TestAppContext) {
 1258    init_test(cx, |_| {});
 1259
 1260    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1261    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1262
 1263    buffer.update(cx, |buffer, cx| {
 1264        buffer.edit(
 1265            vec![
 1266                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1267                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1268            ],
 1269            None,
 1270            cx,
 1271        );
 1272    });
 1273    _ = editor.update(cx, |editor, window, cx| {
 1274        assert_eq!(
 1275            editor.selections.display_ranges(cx),
 1276            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1277        );
 1278
 1279        editor.move_down(&MoveDown, window, cx);
 1280        assert_eq!(
 1281            editor.selections.display_ranges(cx),
 1282            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1283        );
 1284
 1285        editor.move_right(&MoveRight, window, cx);
 1286        assert_eq!(
 1287            editor.selections.display_ranges(cx),
 1288            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1289        );
 1290
 1291        editor.move_left(&MoveLeft, window, cx);
 1292        assert_eq!(
 1293            editor.selections.display_ranges(cx),
 1294            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1295        );
 1296
 1297        editor.move_up(&MoveUp, window, cx);
 1298        assert_eq!(
 1299            editor.selections.display_ranges(cx),
 1300            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1301        );
 1302
 1303        editor.move_to_end(&MoveToEnd, window, cx);
 1304        assert_eq!(
 1305            editor.selections.display_ranges(cx),
 1306            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1307        );
 1308
 1309        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1310        assert_eq!(
 1311            editor.selections.display_ranges(cx),
 1312            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1313        );
 1314
 1315        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1316            s.select_display_ranges([
 1317                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1318            ]);
 1319        });
 1320        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1321        assert_eq!(
 1322            editor.selections.display_ranges(cx),
 1323            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1324        );
 1325
 1326        editor.select_to_end(&SelectToEnd, window, cx);
 1327        assert_eq!(
 1328            editor.selections.display_ranges(cx),
 1329            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1330        );
 1331    });
 1332}
 1333
 1334#[gpui::test]
 1335fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1336    init_test(cx, |_| {});
 1337
 1338    let editor = cx.add_window(|window, cx| {
 1339        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1340        build_editor(buffer, window, cx)
 1341    });
 1342
 1343    assert_eq!('🟥'.len_utf8(), 4);
 1344    assert_eq!('α'.len_utf8(), 2);
 1345
 1346    _ = editor.update(cx, |editor, window, cx| {
 1347        editor.fold_creases(
 1348            vec![
 1349                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1350                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1351                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1352            ],
 1353            true,
 1354            window,
 1355            cx,
 1356        );
 1357        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1358
 1359        editor.move_right(&MoveRight, window, cx);
 1360        assert_eq!(
 1361            editor.selections.display_ranges(cx),
 1362            &[empty_range(0, "🟥".len())]
 1363        );
 1364        editor.move_right(&MoveRight, window, cx);
 1365        assert_eq!(
 1366            editor.selections.display_ranges(cx),
 1367            &[empty_range(0, "🟥🟧".len())]
 1368        );
 1369        editor.move_right(&MoveRight, window, cx);
 1370        assert_eq!(
 1371            editor.selections.display_ranges(cx),
 1372            &[empty_range(0, "🟥🟧⋯".len())]
 1373        );
 1374
 1375        editor.move_down(&MoveDown, window, cx);
 1376        assert_eq!(
 1377            editor.selections.display_ranges(cx),
 1378            &[empty_range(1, "ab⋯e".len())]
 1379        );
 1380        editor.move_left(&MoveLeft, window, cx);
 1381        assert_eq!(
 1382            editor.selections.display_ranges(cx),
 1383            &[empty_range(1, "ab⋯".len())]
 1384        );
 1385        editor.move_left(&MoveLeft, window, cx);
 1386        assert_eq!(
 1387            editor.selections.display_ranges(cx),
 1388            &[empty_range(1, "ab".len())]
 1389        );
 1390        editor.move_left(&MoveLeft, window, cx);
 1391        assert_eq!(
 1392            editor.selections.display_ranges(cx),
 1393            &[empty_range(1, "a".len())]
 1394        );
 1395
 1396        editor.move_down(&MoveDown, window, cx);
 1397        assert_eq!(
 1398            editor.selections.display_ranges(cx),
 1399            &[empty_range(2, "α".len())]
 1400        );
 1401        editor.move_right(&MoveRight, window, cx);
 1402        assert_eq!(
 1403            editor.selections.display_ranges(cx),
 1404            &[empty_range(2, "αβ".len())]
 1405        );
 1406        editor.move_right(&MoveRight, window, cx);
 1407        assert_eq!(
 1408            editor.selections.display_ranges(cx),
 1409            &[empty_range(2, "αβ⋯".len())]
 1410        );
 1411        editor.move_right(&MoveRight, window, cx);
 1412        assert_eq!(
 1413            editor.selections.display_ranges(cx),
 1414            &[empty_range(2, "αβ⋯ε".len())]
 1415        );
 1416
 1417        editor.move_up(&MoveUp, window, cx);
 1418        assert_eq!(
 1419            editor.selections.display_ranges(cx),
 1420            &[empty_range(1, "ab⋯e".len())]
 1421        );
 1422        editor.move_down(&MoveDown, window, cx);
 1423        assert_eq!(
 1424            editor.selections.display_ranges(cx),
 1425            &[empty_range(2, "αβ⋯ε".len())]
 1426        );
 1427        editor.move_up(&MoveUp, window, cx);
 1428        assert_eq!(
 1429            editor.selections.display_ranges(cx),
 1430            &[empty_range(1, "ab⋯e".len())]
 1431        );
 1432
 1433        editor.move_up(&MoveUp, window, cx);
 1434        assert_eq!(
 1435            editor.selections.display_ranges(cx),
 1436            &[empty_range(0, "🟥🟧".len())]
 1437        );
 1438        editor.move_left(&MoveLeft, window, cx);
 1439        assert_eq!(
 1440            editor.selections.display_ranges(cx),
 1441            &[empty_range(0, "🟥".len())]
 1442        );
 1443        editor.move_left(&MoveLeft, window, cx);
 1444        assert_eq!(
 1445            editor.selections.display_ranges(cx),
 1446            &[empty_range(0, "".len())]
 1447        );
 1448    });
 1449}
 1450
 1451#[gpui::test]
 1452fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1453    init_test(cx, |_| {});
 1454
 1455    let editor = cx.add_window(|window, cx| {
 1456        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1457        build_editor(buffer, window, cx)
 1458    });
 1459    _ = editor.update(cx, |editor, window, cx| {
 1460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1461            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1462        });
 1463
 1464        // moving above start of document should move selection to start of document,
 1465        // but the next move down should still be at the original goal_x
 1466        editor.move_up(&MoveUp, window, cx);
 1467        assert_eq!(
 1468            editor.selections.display_ranges(cx),
 1469            &[empty_range(0, "".len())]
 1470        );
 1471
 1472        editor.move_down(&MoveDown, window, cx);
 1473        assert_eq!(
 1474            editor.selections.display_ranges(cx),
 1475            &[empty_range(1, "abcd".len())]
 1476        );
 1477
 1478        editor.move_down(&MoveDown, window, cx);
 1479        assert_eq!(
 1480            editor.selections.display_ranges(cx),
 1481            &[empty_range(2, "αβγ".len())]
 1482        );
 1483
 1484        editor.move_down(&MoveDown, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[empty_range(3, "abcd".len())]
 1488        );
 1489
 1490        editor.move_down(&MoveDown, window, cx);
 1491        assert_eq!(
 1492            editor.selections.display_ranges(cx),
 1493            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1494        );
 1495
 1496        // moving past end of document should not change goal_x
 1497        editor.move_down(&MoveDown, window, cx);
 1498        assert_eq!(
 1499            editor.selections.display_ranges(cx),
 1500            &[empty_range(5, "".len())]
 1501        );
 1502
 1503        editor.move_down(&MoveDown, window, cx);
 1504        assert_eq!(
 1505            editor.selections.display_ranges(cx),
 1506            &[empty_range(5, "".len())]
 1507        );
 1508
 1509        editor.move_up(&MoveUp, window, cx);
 1510        assert_eq!(
 1511            editor.selections.display_ranges(cx),
 1512            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1513        );
 1514
 1515        editor.move_up(&MoveUp, window, cx);
 1516        assert_eq!(
 1517            editor.selections.display_ranges(cx),
 1518            &[empty_range(3, "abcd".len())]
 1519        );
 1520
 1521        editor.move_up(&MoveUp, window, cx);
 1522        assert_eq!(
 1523            editor.selections.display_ranges(cx),
 1524            &[empty_range(2, "αβγ".len())]
 1525        );
 1526    });
 1527}
 1528
 1529#[gpui::test]
 1530fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1531    init_test(cx, |_| {});
 1532    let move_to_beg = MoveToBeginningOfLine {
 1533        stop_at_soft_wraps: true,
 1534        stop_at_indent: true,
 1535    };
 1536
 1537    let delete_to_beg = DeleteToBeginningOfLine {
 1538        stop_at_indent: false,
 1539    };
 1540
 1541    let move_to_end = MoveToEndOfLine {
 1542        stop_at_soft_wraps: true,
 1543    };
 1544
 1545    let editor = cx.add_window(|window, cx| {
 1546        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1547        build_editor(buffer, window, cx)
 1548    });
 1549    _ = editor.update(cx, |editor, window, cx| {
 1550        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1551            s.select_display_ranges([
 1552                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1553                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1554            ]);
 1555        });
 1556    });
 1557
 1558    _ = editor.update(cx, |editor, window, cx| {
 1559        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1560        assert_eq!(
 1561            editor.selections.display_ranges(cx),
 1562            &[
 1563                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1564                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1565            ]
 1566        );
 1567    });
 1568
 1569    _ = editor.update(cx, |editor, window, cx| {
 1570        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1571        assert_eq!(
 1572            editor.selections.display_ranges(cx),
 1573            &[
 1574                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1575                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1576            ]
 1577        );
 1578    });
 1579
 1580    _ = editor.update(cx, |editor, window, cx| {
 1581        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1582        assert_eq!(
 1583            editor.selections.display_ranges(cx),
 1584            &[
 1585                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1586                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1587            ]
 1588        );
 1589    });
 1590
 1591    _ = editor.update(cx, |editor, window, cx| {
 1592        editor.move_to_end_of_line(&move_to_end, window, cx);
 1593        assert_eq!(
 1594            editor.selections.display_ranges(cx),
 1595            &[
 1596                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1597                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1598            ]
 1599        );
 1600    });
 1601
 1602    // Moving to the end of line again is a no-op.
 1603    _ = editor.update(cx, |editor, window, cx| {
 1604        editor.move_to_end_of_line(&move_to_end, window, cx);
 1605        assert_eq!(
 1606            editor.selections.display_ranges(cx),
 1607            &[
 1608                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1609                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1610            ]
 1611        );
 1612    });
 1613
 1614    _ = editor.update(cx, |editor, window, cx| {
 1615        editor.move_left(&MoveLeft, window, cx);
 1616        editor.select_to_beginning_of_line(
 1617            &SelectToBeginningOfLine {
 1618                stop_at_soft_wraps: true,
 1619                stop_at_indent: true,
 1620            },
 1621            window,
 1622            cx,
 1623        );
 1624        assert_eq!(
 1625            editor.selections.display_ranges(cx),
 1626            &[
 1627                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1628                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1629            ]
 1630        );
 1631    });
 1632
 1633    _ = editor.update(cx, |editor, window, cx| {
 1634        editor.select_to_beginning_of_line(
 1635            &SelectToBeginningOfLine {
 1636                stop_at_soft_wraps: true,
 1637                stop_at_indent: true,
 1638            },
 1639            window,
 1640            cx,
 1641        );
 1642        assert_eq!(
 1643            editor.selections.display_ranges(cx),
 1644            &[
 1645                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1646                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1647            ]
 1648        );
 1649    });
 1650
 1651    _ = editor.update(cx, |editor, window, cx| {
 1652        editor.select_to_beginning_of_line(
 1653            &SelectToBeginningOfLine {
 1654                stop_at_soft_wraps: true,
 1655                stop_at_indent: true,
 1656            },
 1657            window,
 1658            cx,
 1659        );
 1660        assert_eq!(
 1661            editor.selections.display_ranges(cx),
 1662            &[
 1663                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1664                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1665            ]
 1666        );
 1667    });
 1668
 1669    _ = editor.update(cx, |editor, window, cx| {
 1670        editor.select_to_end_of_line(
 1671            &SelectToEndOfLine {
 1672                stop_at_soft_wraps: true,
 1673            },
 1674            window,
 1675            cx,
 1676        );
 1677        assert_eq!(
 1678            editor.selections.display_ranges(cx),
 1679            &[
 1680                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1681                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1682            ]
 1683        );
 1684    });
 1685
 1686    _ = editor.update(cx, |editor, window, cx| {
 1687        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1688        assert_eq!(editor.display_text(cx), "ab\n  de");
 1689        assert_eq!(
 1690            editor.selections.display_ranges(cx),
 1691            &[
 1692                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1693                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1694            ]
 1695        );
 1696    });
 1697
 1698    _ = editor.update(cx, |editor, window, cx| {
 1699        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1700        assert_eq!(editor.display_text(cx), "\n");
 1701        assert_eq!(
 1702            editor.selections.display_ranges(cx),
 1703            &[
 1704                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1705                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1706            ]
 1707        );
 1708    });
 1709}
 1710
 1711#[gpui::test]
 1712fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1713    init_test(cx, |_| {});
 1714    let move_to_beg = MoveToBeginningOfLine {
 1715        stop_at_soft_wraps: false,
 1716        stop_at_indent: false,
 1717    };
 1718
 1719    let move_to_end = MoveToEndOfLine {
 1720        stop_at_soft_wraps: false,
 1721    };
 1722
 1723    let editor = cx.add_window(|window, cx| {
 1724        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1725        build_editor(buffer, window, cx)
 1726    });
 1727
 1728    _ = editor.update(cx, |editor, window, cx| {
 1729        editor.set_wrap_width(Some(140.0.into()), cx);
 1730
 1731        // We expect the following lines after wrapping
 1732        // ```
 1733        // thequickbrownfox
 1734        // jumpedoverthelazydo
 1735        // gs
 1736        // ```
 1737        // The final `gs` was soft-wrapped onto a new line.
 1738        assert_eq!(
 1739            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1740            editor.display_text(cx),
 1741        );
 1742
 1743        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1744        // Start the cursor at the `k` on the first line
 1745        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1746            s.select_display_ranges([
 1747                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1748            ]);
 1749        });
 1750
 1751        // Moving to the beginning of the line should put us at the beginning of the line.
 1752        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1753        assert_eq!(
 1754            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1755            editor.selections.display_ranges(cx)
 1756        );
 1757
 1758        // Moving to the end of the line should put us at the end of the line.
 1759        editor.move_to_end_of_line(&move_to_end, window, cx);
 1760        assert_eq!(
 1761            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1762            editor.selections.display_ranges(cx)
 1763        );
 1764
 1765        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1766        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1767        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1768            s.select_display_ranges([
 1769                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1770            ]);
 1771        });
 1772
 1773        // Moving to the beginning of the line should put us at the start of the second line of
 1774        // display text, i.e., the `j`.
 1775        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1776        assert_eq!(
 1777            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1778            editor.selections.display_ranges(cx)
 1779        );
 1780
 1781        // Moving to the beginning of the line again should be a no-op.
 1782        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1783        assert_eq!(
 1784            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1785            editor.selections.display_ranges(cx)
 1786        );
 1787
 1788        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1789        // next display line.
 1790        editor.move_to_end_of_line(&move_to_end, window, cx);
 1791        assert_eq!(
 1792            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1793            editor.selections.display_ranges(cx)
 1794        );
 1795
 1796        // Moving to the end of the line again should be a no-op.
 1797        editor.move_to_end_of_line(&move_to_end, window, cx);
 1798        assert_eq!(
 1799            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1800            editor.selections.display_ranges(cx)
 1801        );
 1802    });
 1803}
 1804
 1805#[gpui::test]
 1806fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1807    init_test(cx, |_| {});
 1808
 1809    let move_to_beg = MoveToBeginningOfLine {
 1810        stop_at_soft_wraps: true,
 1811        stop_at_indent: true,
 1812    };
 1813
 1814    let select_to_beg = SelectToBeginningOfLine {
 1815        stop_at_soft_wraps: true,
 1816        stop_at_indent: true,
 1817    };
 1818
 1819    let delete_to_beg = DeleteToBeginningOfLine {
 1820        stop_at_indent: true,
 1821    };
 1822
 1823    let move_to_end = MoveToEndOfLine {
 1824        stop_at_soft_wraps: false,
 1825    };
 1826
 1827    let editor = cx.add_window(|window, cx| {
 1828        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1829        build_editor(buffer, window, cx)
 1830    });
 1831
 1832    _ = editor.update(cx, |editor, window, cx| {
 1833        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1834            s.select_display_ranges([
 1835                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1836                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1837            ]);
 1838        });
 1839
 1840        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1841        // and the second cursor at the first non-whitespace character in the line.
 1842        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1843        assert_eq!(
 1844            editor.selections.display_ranges(cx),
 1845            &[
 1846                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1847                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1848            ]
 1849        );
 1850
 1851        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1852        // and should move the second cursor to the beginning of the line.
 1853        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1854        assert_eq!(
 1855            editor.selections.display_ranges(cx),
 1856            &[
 1857                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1858                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1859            ]
 1860        );
 1861
 1862        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1863        // and should move the second cursor back to the first non-whitespace character in the line.
 1864        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1865        assert_eq!(
 1866            editor.selections.display_ranges(cx),
 1867            &[
 1868                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1869                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1870            ]
 1871        );
 1872
 1873        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1874        // and to the first non-whitespace character in the line for the second cursor.
 1875        editor.move_to_end_of_line(&move_to_end, window, cx);
 1876        editor.move_left(&MoveLeft, window, cx);
 1877        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1878        assert_eq!(
 1879            editor.selections.display_ranges(cx),
 1880            &[
 1881                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1882                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1883            ]
 1884        );
 1885
 1886        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1887        // and should select to the beginning of the line for the second cursor.
 1888        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1889        assert_eq!(
 1890            editor.selections.display_ranges(cx),
 1891            &[
 1892                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1893                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1894            ]
 1895        );
 1896
 1897        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1898        // and should delete to the first non-whitespace character in the line for the second cursor.
 1899        editor.move_to_end_of_line(&move_to_end, window, cx);
 1900        editor.move_left(&MoveLeft, window, cx);
 1901        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1902        assert_eq!(editor.text(cx), "c\n  f");
 1903    });
 1904}
 1905
 1906#[gpui::test]
 1907fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1908    init_test(cx, |_| {});
 1909
 1910    let move_to_beg = MoveToBeginningOfLine {
 1911        stop_at_soft_wraps: true,
 1912        stop_at_indent: true,
 1913    };
 1914
 1915    let editor = cx.add_window(|window, cx| {
 1916        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1917        build_editor(buffer, window, cx)
 1918    });
 1919
 1920    _ = editor.update(cx, |editor, window, cx| {
 1921        // test cursor between line_start and indent_start
 1922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1923            s.select_display_ranges([
 1924                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1925            ]);
 1926        });
 1927
 1928        // cursor should move to line_start
 1929        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1930        assert_eq!(
 1931            editor.selections.display_ranges(cx),
 1932            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1933        );
 1934
 1935        // cursor should move to indent_start
 1936        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1937        assert_eq!(
 1938            editor.selections.display_ranges(cx),
 1939            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 1940        );
 1941
 1942        // cursor should move to back to line_start
 1943        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1944        assert_eq!(
 1945            editor.selections.display_ranges(cx),
 1946            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1947        );
 1948    });
 1949}
 1950
 1951#[gpui::test]
 1952fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1953    init_test(cx, |_| {});
 1954
 1955    let editor = cx.add_window(|window, cx| {
 1956        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1957        build_editor(buffer, window, cx)
 1958    });
 1959    _ = editor.update(cx, |editor, window, cx| {
 1960        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1961            s.select_display_ranges([
 1962                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1963                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1964            ])
 1965        });
 1966        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1967        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1968
 1969        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1970        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1971
 1972        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1973        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1974
 1975        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1976        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1977
 1978        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1979        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1980
 1981        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1982        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1983
 1984        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1985        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1986
 1987        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1988        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1989
 1990        editor.move_right(&MoveRight, window, cx);
 1991        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1992        assert_selection_ranges(
 1993            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1994            editor,
 1995            cx,
 1996        );
 1997
 1998        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1999        assert_selection_ranges(
 2000            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2001            editor,
 2002            cx,
 2003        );
 2004
 2005        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2006        assert_selection_ranges(
 2007            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2008            editor,
 2009            cx,
 2010        );
 2011    });
 2012}
 2013
 2014#[gpui::test]
 2015fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2016    init_test(cx, |_| {});
 2017
 2018    let editor = cx.add_window(|window, cx| {
 2019        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2020        build_editor(buffer, window, cx)
 2021    });
 2022
 2023    _ = editor.update(cx, |editor, window, cx| {
 2024        editor.set_wrap_width(Some(140.0.into()), cx);
 2025        assert_eq!(
 2026            editor.display_text(cx),
 2027            "use one::{\n    two::three::\n    four::five\n};"
 2028        );
 2029
 2030        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2031            s.select_display_ranges([
 2032                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2033            ]);
 2034        });
 2035
 2036        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2037        assert_eq!(
 2038            editor.selections.display_ranges(cx),
 2039            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2040        );
 2041
 2042        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2043        assert_eq!(
 2044            editor.selections.display_ranges(cx),
 2045            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2046        );
 2047
 2048        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2049        assert_eq!(
 2050            editor.selections.display_ranges(cx),
 2051            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2052        );
 2053
 2054        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2055        assert_eq!(
 2056            editor.selections.display_ranges(cx),
 2057            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2058        );
 2059
 2060        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2061        assert_eq!(
 2062            editor.selections.display_ranges(cx),
 2063            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2064        );
 2065
 2066        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2067        assert_eq!(
 2068            editor.selections.display_ranges(cx),
 2069            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2070        );
 2071    });
 2072}
 2073
 2074#[gpui::test]
 2075async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2076    init_test(cx, |_| {});
 2077    let mut cx = EditorTestContext::new(cx).await;
 2078
 2079    let line_height = cx.editor(|editor, window, _| {
 2080        editor
 2081            .style()
 2082            .unwrap()
 2083            .text
 2084            .line_height_in_pixels(window.rem_size())
 2085    });
 2086    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2087
 2088    cx.set_state(
 2089        &r#"ˇone
 2090        two
 2091
 2092        three
 2093        fourˇ
 2094        five
 2095
 2096        six"#
 2097            .unindent(),
 2098    );
 2099
 2100    cx.update_editor(|editor, window, cx| {
 2101        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2102    });
 2103    cx.assert_editor_state(
 2104        &r#"one
 2105        two
 2106        ˇ
 2107        three
 2108        four
 2109        five
 2110        ˇ
 2111        six"#
 2112            .unindent(),
 2113    );
 2114
 2115    cx.update_editor(|editor, window, cx| {
 2116        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2117    });
 2118    cx.assert_editor_state(
 2119        &r#"one
 2120        two
 2121
 2122        three
 2123        four
 2124        five
 2125        ˇ
 2126        sixˇ"#
 2127            .unindent(),
 2128    );
 2129
 2130    cx.update_editor(|editor, window, cx| {
 2131        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2132    });
 2133    cx.assert_editor_state(
 2134        &r#"one
 2135        two
 2136
 2137        three
 2138        four
 2139        five
 2140
 2141        sixˇ"#
 2142            .unindent(),
 2143    );
 2144
 2145    cx.update_editor(|editor, window, cx| {
 2146        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2147    });
 2148    cx.assert_editor_state(
 2149        &r#"one
 2150        two
 2151
 2152        three
 2153        four
 2154        five
 2155        ˇ
 2156        six"#
 2157            .unindent(),
 2158    );
 2159
 2160    cx.update_editor(|editor, window, cx| {
 2161        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2162    });
 2163    cx.assert_editor_state(
 2164        &r#"one
 2165        two
 2166        ˇ
 2167        three
 2168        four
 2169        five
 2170
 2171        six"#
 2172            .unindent(),
 2173    );
 2174
 2175    cx.update_editor(|editor, window, cx| {
 2176        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2177    });
 2178    cx.assert_editor_state(
 2179        &r#"ˇone
 2180        two
 2181
 2182        three
 2183        four
 2184        five
 2185
 2186        six"#
 2187            .unindent(),
 2188    );
 2189}
 2190
 2191#[gpui::test]
 2192async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2193    init_test(cx, |_| {});
 2194    let mut cx = EditorTestContext::new(cx).await;
 2195    let line_height = cx.editor(|editor, window, _| {
 2196        editor
 2197            .style()
 2198            .unwrap()
 2199            .text
 2200            .line_height_in_pixels(window.rem_size())
 2201    });
 2202    let window = cx.window;
 2203    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2204
 2205    cx.set_state(
 2206        r#"ˇone
 2207        two
 2208        three
 2209        four
 2210        five
 2211        six
 2212        seven
 2213        eight
 2214        nine
 2215        ten
 2216        "#,
 2217    );
 2218
 2219    cx.update_editor(|editor, window, cx| {
 2220        assert_eq!(
 2221            editor.snapshot(window, cx).scroll_position(),
 2222            gpui::Point::new(0., 0.)
 2223        );
 2224        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2225        assert_eq!(
 2226            editor.snapshot(window, cx).scroll_position(),
 2227            gpui::Point::new(0., 3.)
 2228        );
 2229        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2230        assert_eq!(
 2231            editor.snapshot(window, cx).scroll_position(),
 2232            gpui::Point::new(0., 6.)
 2233        );
 2234        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2235        assert_eq!(
 2236            editor.snapshot(window, cx).scroll_position(),
 2237            gpui::Point::new(0., 3.)
 2238        );
 2239
 2240        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2241        assert_eq!(
 2242            editor.snapshot(window, cx).scroll_position(),
 2243            gpui::Point::new(0., 1.)
 2244        );
 2245        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2246        assert_eq!(
 2247            editor.snapshot(window, cx).scroll_position(),
 2248            gpui::Point::new(0., 3.)
 2249        );
 2250    });
 2251}
 2252
 2253#[gpui::test]
 2254async fn test_autoscroll(cx: &mut TestAppContext) {
 2255    init_test(cx, |_| {});
 2256    let mut cx = EditorTestContext::new(cx).await;
 2257
 2258    let line_height = cx.update_editor(|editor, window, cx| {
 2259        editor.set_vertical_scroll_margin(2, cx);
 2260        editor
 2261            .style()
 2262            .unwrap()
 2263            .text
 2264            .line_height_in_pixels(window.rem_size())
 2265    });
 2266    let window = cx.window;
 2267    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2268
 2269    cx.set_state(
 2270        r#"ˇone
 2271            two
 2272            three
 2273            four
 2274            five
 2275            six
 2276            seven
 2277            eight
 2278            nine
 2279            ten
 2280        "#,
 2281    );
 2282    cx.update_editor(|editor, window, cx| {
 2283        assert_eq!(
 2284            editor.snapshot(window, cx).scroll_position(),
 2285            gpui::Point::new(0., 0.0)
 2286        );
 2287    });
 2288
 2289    // Add a cursor below the visible area. Since both cursors cannot fit
 2290    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2291    // allows the vertical scroll margin below that cursor.
 2292    cx.update_editor(|editor, window, cx| {
 2293        editor.change_selections(Default::default(), window, cx, |selections| {
 2294            selections.select_ranges([
 2295                Point::new(0, 0)..Point::new(0, 0),
 2296                Point::new(6, 0)..Point::new(6, 0),
 2297            ]);
 2298        })
 2299    });
 2300    cx.update_editor(|editor, window, cx| {
 2301        assert_eq!(
 2302            editor.snapshot(window, cx).scroll_position(),
 2303            gpui::Point::new(0., 3.0)
 2304        );
 2305    });
 2306
 2307    // Move down. The editor cursor scrolls down to track the newest cursor.
 2308    cx.update_editor(|editor, window, cx| {
 2309        editor.move_down(&Default::default(), window, cx);
 2310    });
 2311    cx.update_editor(|editor, window, cx| {
 2312        assert_eq!(
 2313            editor.snapshot(window, cx).scroll_position(),
 2314            gpui::Point::new(0., 4.0)
 2315        );
 2316    });
 2317
 2318    // Add a cursor above the visible area. Since both cursors fit on screen,
 2319    // the editor scrolls to show both.
 2320    cx.update_editor(|editor, window, cx| {
 2321        editor.change_selections(Default::default(), window, cx, |selections| {
 2322            selections.select_ranges([
 2323                Point::new(1, 0)..Point::new(1, 0),
 2324                Point::new(6, 0)..Point::new(6, 0),
 2325            ]);
 2326        })
 2327    });
 2328    cx.update_editor(|editor, window, cx| {
 2329        assert_eq!(
 2330            editor.snapshot(window, cx).scroll_position(),
 2331            gpui::Point::new(0., 1.0)
 2332        );
 2333    });
 2334}
 2335
 2336#[gpui::test]
 2337async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2338    init_test(cx, |_| {});
 2339    let mut cx = EditorTestContext::new(cx).await;
 2340
 2341    let line_height = cx.editor(|editor, window, _cx| {
 2342        editor
 2343            .style()
 2344            .unwrap()
 2345            .text
 2346            .line_height_in_pixels(window.rem_size())
 2347    });
 2348    let window = cx.window;
 2349    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2350    cx.set_state(
 2351        &r#"
 2352        ˇone
 2353        two
 2354        threeˇ
 2355        four
 2356        five
 2357        six
 2358        seven
 2359        eight
 2360        nine
 2361        ten
 2362        "#
 2363        .unindent(),
 2364    );
 2365
 2366    cx.update_editor(|editor, window, cx| {
 2367        editor.move_page_down(&MovePageDown::default(), window, cx)
 2368    });
 2369    cx.assert_editor_state(
 2370        &r#"
 2371        one
 2372        two
 2373        three
 2374        ˇfour
 2375        five
 2376        sixˇ
 2377        seven
 2378        eight
 2379        nine
 2380        ten
 2381        "#
 2382        .unindent(),
 2383    );
 2384
 2385    cx.update_editor(|editor, window, cx| {
 2386        editor.move_page_down(&MovePageDown::default(), window, cx)
 2387    });
 2388    cx.assert_editor_state(
 2389        &r#"
 2390        one
 2391        two
 2392        three
 2393        four
 2394        five
 2395        six
 2396        ˇseven
 2397        eight
 2398        nineˇ
 2399        ten
 2400        "#
 2401        .unindent(),
 2402    );
 2403
 2404    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2405    cx.assert_editor_state(
 2406        &r#"
 2407        one
 2408        two
 2409        three
 2410        ˇfour
 2411        five
 2412        sixˇ
 2413        seven
 2414        eight
 2415        nine
 2416        ten
 2417        "#
 2418        .unindent(),
 2419    );
 2420
 2421    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2422    cx.assert_editor_state(
 2423        &r#"
 2424        ˇone
 2425        two
 2426        threeˇ
 2427        four
 2428        five
 2429        six
 2430        seven
 2431        eight
 2432        nine
 2433        ten
 2434        "#
 2435        .unindent(),
 2436    );
 2437
 2438    // Test select collapsing
 2439    cx.update_editor(|editor, window, cx| {
 2440        editor.move_page_down(&MovePageDown::default(), window, cx);
 2441        editor.move_page_down(&MovePageDown::default(), window, cx);
 2442        editor.move_page_down(&MovePageDown::default(), window, cx);
 2443    });
 2444    cx.assert_editor_state(
 2445        &r#"
 2446        one
 2447        two
 2448        three
 2449        four
 2450        five
 2451        six
 2452        seven
 2453        eight
 2454        nine
 2455        ˇten
 2456        ˇ"#
 2457        .unindent(),
 2458    );
 2459}
 2460
 2461#[gpui::test]
 2462async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2463    init_test(cx, |_| {});
 2464    let mut cx = EditorTestContext::new(cx).await;
 2465    cx.set_state("one «two threeˇ» four");
 2466    cx.update_editor(|editor, window, cx| {
 2467        editor.delete_to_beginning_of_line(
 2468            &DeleteToBeginningOfLine {
 2469                stop_at_indent: false,
 2470            },
 2471            window,
 2472            cx,
 2473        );
 2474        assert_eq!(editor.text(cx), " four");
 2475    });
 2476}
 2477
 2478#[gpui::test]
 2479async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2480    init_test(cx, |_| {});
 2481
 2482    let mut cx = EditorTestContext::new(cx).await;
 2483
 2484    // For an empty selection, the preceding word fragment is deleted.
 2485    // For non-empty selections, only selected characters are deleted.
 2486    cx.set_state("onˇe two t«hreˇ»e four");
 2487    cx.update_editor(|editor, window, cx| {
 2488        editor.delete_to_previous_word_start(
 2489            &DeleteToPreviousWordStart {
 2490                ignore_newlines: false,
 2491                ignore_brackets: false,
 2492            },
 2493            window,
 2494            cx,
 2495        );
 2496    });
 2497    cx.assert_editor_state("ˇe two tˇe four");
 2498
 2499    cx.set_state("e tˇwo te «fˇ»our");
 2500    cx.update_editor(|editor, window, cx| {
 2501        editor.delete_to_next_word_end(
 2502            &DeleteToNextWordEnd {
 2503                ignore_newlines: false,
 2504                ignore_brackets: false,
 2505            },
 2506            window,
 2507            cx,
 2508        );
 2509    });
 2510    cx.assert_editor_state("e tˇ te ˇour");
 2511}
 2512
 2513#[gpui::test]
 2514async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2515    init_test(cx, |_| {});
 2516
 2517    let mut cx = EditorTestContext::new(cx).await;
 2518
 2519    cx.set_state("here is some text    ˇwith a space");
 2520    cx.update_editor(|editor, window, cx| {
 2521        editor.delete_to_previous_word_start(
 2522            &DeleteToPreviousWordStart {
 2523                ignore_newlines: false,
 2524                ignore_brackets: true,
 2525            },
 2526            window,
 2527            cx,
 2528        );
 2529    });
 2530    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2531    cx.assert_editor_state("here is some textˇwith a space");
 2532
 2533    cx.set_state("here is some text    ˇwith a space");
 2534    cx.update_editor(|editor, window, cx| {
 2535        editor.delete_to_previous_word_start(
 2536            &DeleteToPreviousWordStart {
 2537                ignore_newlines: false,
 2538                ignore_brackets: false,
 2539            },
 2540            window,
 2541            cx,
 2542        );
 2543    });
 2544    cx.assert_editor_state("here is some textˇwith a space");
 2545
 2546    cx.set_state("here is some textˇ    with a space");
 2547    cx.update_editor(|editor, window, cx| {
 2548        editor.delete_to_next_word_end(
 2549            &DeleteToNextWordEnd {
 2550                ignore_newlines: false,
 2551                ignore_brackets: true,
 2552            },
 2553            window,
 2554            cx,
 2555        );
 2556    });
 2557    // Same happens in the other direction.
 2558    cx.assert_editor_state("here is some textˇwith a space");
 2559
 2560    cx.set_state("here is some textˇ    with a space");
 2561    cx.update_editor(|editor, window, cx| {
 2562        editor.delete_to_next_word_end(
 2563            &DeleteToNextWordEnd {
 2564                ignore_newlines: false,
 2565                ignore_brackets: false,
 2566            },
 2567            window,
 2568            cx,
 2569        );
 2570    });
 2571    cx.assert_editor_state("here is some textˇwith a space");
 2572
 2573    cx.set_state("here is some textˇ    with a space");
 2574    cx.update_editor(|editor, window, cx| {
 2575        editor.delete_to_next_word_end(
 2576            &DeleteToNextWordEnd {
 2577                ignore_newlines: true,
 2578                ignore_brackets: false,
 2579            },
 2580            window,
 2581            cx,
 2582        );
 2583    });
 2584    cx.assert_editor_state("here is some textˇwith a space");
 2585    cx.update_editor(|editor, window, cx| {
 2586        editor.delete_to_previous_word_start(
 2587            &DeleteToPreviousWordStart {
 2588                ignore_newlines: true,
 2589                ignore_brackets: false,
 2590            },
 2591            window,
 2592            cx,
 2593        );
 2594    });
 2595    cx.assert_editor_state("here is some ˇwith a space");
 2596    cx.update_editor(|editor, window, cx| {
 2597        editor.delete_to_previous_word_start(
 2598            &DeleteToPreviousWordStart {
 2599                ignore_newlines: true,
 2600                ignore_brackets: false,
 2601            },
 2602            window,
 2603            cx,
 2604        );
 2605    });
 2606    // Single whitespaces are removed with the word behind them.
 2607    cx.assert_editor_state("here is ˇwith a space");
 2608    cx.update_editor(|editor, window, cx| {
 2609        editor.delete_to_previous_word_start(
 2610            &DeleteToPreviousWordStart {
 2611                ignore_newlines: true,
 2612                ignore_brackets: false,
 2613            },
 2614            window,
 2615            cx,
 2616        );
 2617    });
 2618    cx.assert_editor_state("here ˇwith a space");
 2619    cx.update_editor(|editor, window, cx| {
 2620        editor.delete_to_previous_word_start(
 2621            &DeleteToPreviousWordStart {
 2622                ignore_newlines: true,
 2623                ignore_brackets: false,
 2624            },
 2625            window,
 2626            cx,
 2627        );
 2628    });
 2629    cx.assert_editor_state("ˇwith a space");
 2630    cx.update_editor(|editor, window, cx| {
 2631        editor.delete_to_previous_word_start(
 2632            &DeleteToPreviousWordStart {
 2633                ignore_newlines: true,
 2634                ignore_brackets: false,
 2635            },
 2636            window,
 2637            cx,
 2638        );
 2639    });
 2640    cx.assert_editor_state("ˇwith a space");
 2641    cx.update_editor(|editor, window, cx| {
 2642        editor.delete_to_next_word_end(
 2643            &DeleteToNextWordEnd {
 2644                ignore_newlines: true,
 2645                ignore_brackets: false,
 2646            },
 2647            window,
 2648            cx,
 2649        );
 2650    });
 2651    // Same happens in the other direction.
 2652    cx.assert_editor_state("ˇ a space");
 2653    cx.update_editor(|editor, window, cx| {
 2654        editor.delete_to_next_word_end(
 2655            &DeleteToNextWordEnd {
 2656                ignore_newlines: true,
 2657                ignore_brackets: false,
 2658            },
 2659            window,
 2660            cx,
 2661        );
 2662    });
 2663    cx.assert_editor_state("ˇ space");
 2664    cx.update_editor(|editor, window, cx| {
 2665        editor.delete_to_next_word_end(
 2666            &DeleteToNextWordEnd {
 2667                ignore_newlines: true,
 2668                ignore_brackets: false,
 2669            },
 2670            window,
 2671            cx,
 2672        );
 2673    });
 2674    cx.assert_editor_state("ˇ");
 2675    cx.update_editor(|editor, window, cx| {
 2676        editor.delete_to_next_word_end(
 2677            &DeleteToNextWordEnd {
 2678                ignore_newlines: true,
 2679                ignore_brackets: false,
 2680            },
 2681            window,
 2682            cx,
 2683        );
 2684    });
 2685    cx.assert_editor_state("ˇ");
 2686    cx.update_editor(|editor, window, cx| {
 2687        editor.delete_to_previous_word_start(
 2688            &DeleteToPreviousWordStart {
 2689                ignore_newlines: true,
 2690                ignore_brackets: false,
 2691            },
 2692            window,
 2693            cx,
 2694        );
 2695    });
 2696    cx.assert_editor_state("ˇ");
 2697}
 2698
 2699#[gpui::test]
 2700async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2701    init_test(cx, |_| {});
 2702
 2703    let language = Arc::new(
 2704        Language::new(
 2705            LanguageConfig {
 2706                brackets: BracketPairConfig {
 2707                    pairs: vec![
 2708                        BracketPair {
 2709                            start: "\"".to_string(),
 2710                            end: "\"".to_string(),
 2711                            close: true,
 2712                            surround: true,
 2713                            newline: false,
 2714                        },
 2715                        BracketPair {
 2716                            start: "(".to_string(),
 2717                            end: ")".to_string(),
 2718                            close: true,
 2719                            surround: true,
 2720                            newline: true,
 2721                        },
 2722                    ],
 2723                    ..BracketPairConfig::default()
 2724                },
 2725                ..LanguageConfig::default()
 2726            },
 2727            Some(tree_sitter_rust::LANGUAGE.into()),
 2728        )
 2729        .with_brackets_query(
 2730            r#"
 2731                ("(" @open ")" @close)
 2732                ("\"" @open "\"" @close)
 2733            "#,
 2734        )
 2735        .unwrap(),
 2736    );
 2737
 2738    let mut cx = EditorTestContext::new(cx).await;
 2739    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2740
 2741    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2742    cx.update_editor(|editor, window, cx| {
 2743        editor.delete_to_previous_word_start(
 2744            &DeleteToPreviousWordStart {
 2745                ignore_newlines: true,
 2746                ignore_brackets: false,
 2747            },
 2748            window,
 2749            cx,
 2750        );
 2751    });
 2752    // Deletion stops before brackets if asked to not ignore them.
 2753    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2754    cx.update_editor(|editor, window, cx| {
 2755        editor.delete_to_previous_word_start(
 2756            &DeleteToPreviousWordStart {
 2757                ignore_newlines: true,
 2758                ignore_brackets: false,
 2759            },
 2760            window,
 2761            cx,
 2762        );
 2763    });
 2764    // Deletion has to remove a single bracket and then stop again.
 2765    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2766
 2767    cx.update_editor(|editor, window, cx| {
 2768        editor.delete_to_previous_word_start(
 2769            &DeleteToPreviousWordStart {
 2770                ignore_newlines: true,
 2771                ignore_brackets: false,
 2772            },
 2773            window,
 2774            cx,
 2775        );
 2776    });
 2777    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2778
 2779    cx.update_editor(|editor, window, cx| {
 2780        editor.delete_to_previous_word_start(
 2781            &DeleteToPreviousWordStart {
 2782                ignore_newlines: true,
 2783                ignore_brackets: false,
 2784            },
 2785            window,
 2786            cx,
 2787        );
 2788    });
 2789    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2790
 2791    cx.update_editor(|editor, window, cx| {
 2792        editor.delete_to_previous_word_start(
 2793            &DeleteToPreviousWordStart {
 2794                ignore_newlines: true,
 2795                ignore_brackets: false,
 2796            },
 2797            window,
 2798            cx,
 2799        );
 2800    });
 2801    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2802
 2803    cx.update_editor(|editor, window, cx| {
 2804        editor.delete_to_next_word_end(
 2805            &DeleteToNextWordEnd {
 2806                ignore_newlines: true,
 2807                ignore_brackets: false,
 2808            },
 2809            window,
 2810            cx,
 2811        );
 2812    });
 2813    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2814    cx.assert_editor_state(r#"ˇ");"#);
 2815
 2816    cx.update_editor(|editor, window, cx| {
 2817        editor.delete_to_next_word_end(
 2818            &DeleteToNextWordEnd {
 2819                ignore_newlines: true,
 2820                ignore_brackets: false,
 2821            },
 2822            window,
 2823            cx,
 2824        );
 2825    });
 2826    cx.assert_editor_state(r#"ˇ"#);
 2827
 2828    cx.update_editor(|editor, window, cx| {
 2829        editor.delete_to_next_word_end(
 2830            &DeleteToNextWordEnd {
 2831                ignore_newlines: true,
 2832                ignore_brackets: false,
 2833            },
 2834            window,
 2835            cx,
 2836        );
 2837    });
 2838    cx.assert_editor_state(r#"ˇ"#);
 2839
 2840    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2841    cx.update_editor(|editor, window, cx| {
 2842        editor.delete_to_previous_word_start(
 2843            &DeleteToPreviousWordStart {
 2844                ignore_newlines: true,
 2845                ignore_brackets: true,
 2846            },
 2847            window,
 2848            cx,
 2849        );
 2850    });
 2851    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2852}
 2853
 2854#[gpui::test]
 2855fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2856    init_test(cx, |_| {});
 2857
 2858    let editor = cx.add_window(|window, cx| {
 2859        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2860        build_editor(buffer, window, cx)
 2861    });
 2862    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2863        ignore_newlines: false,
 2864        ignore_brackets: false,
 2865    };
 2866    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2867        ignore_newlines: true,
 2868        ignore_brackets: false,
 2869    };
 2870
 2871    _ = editor.update(cx, |editor, window, cx| {
 2872        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2873            s.select_display_ranges([
 2874                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2875            ])
 2876        });
 2877        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2878        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2879        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2880        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2881        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2882        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2883        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2884        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2885        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2886        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2887        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2888        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2889    });
 2890}
 2891
 2892#[gpui::test]
 2893fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2894    init_test(cx, |_| {});
 2895
 2896    let editor = cx.add_window(|window, cx| {
 2897        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2898        build_editor(buffer, window, cx)
 2899    });
 2900    let del_to_next_word_end = DeleteToNextWordEnd {
 2901        ignore_newlines: false,
 2902        ignore_brackets: false,
 2903    };
 2904    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2905        ignore_newlines: true,
 2906        ignore_brackets: false,
 2907    };
 2908
 2909    _ = editor.update(cx, |editor, window, cx| {
 2910        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2911            s.select_display_ranges([
 2912                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2913            ])
 2914        });
 2915        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2916        assert_eq!(
 2917            editor.buffer.read(cx).read(cx).text(),
 2918            "one\n   two\nthree\n   four"
 2919        );
 2920        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2921        assert_eq!(
 2922            editor.buffer.read(cx).read(cx).text(),
 2923            "\n   two\nthree\n   four"
 2924        );
 2925        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2926        assert_eq!(
 2927            editor.buffer.read(cx).read(cx).text(),
 2928            "two\nthree\n   four"
 2929        );
 2930        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2931        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2932        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2933        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2934        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2935        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 2936        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2937        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2938    });
 2939}
 2940
 2941#[gpui::test]
 2942fn test_newline(cx: &mut TestAppContext) {
 2943    init_test(cx, |_| {});
 2944
 2945    let editor = cx.add_window(|window, cx| {
 2946        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2947        build_editor(buffer, window, cx)
 2948    });
 2949
 2950    _ = editor.update(cx, |editor, window, cx| {
 2951        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2952            s.select_display_ranges([
 2953                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2954                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2955                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2956            ])
 2957        });
 2958
 2959        editor.newline(&Newline, window, cx);
 2960        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2961    });
 2962}
 2963
 2964#[gpui::test]
 2965fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2966    init_test(cx, |_| {});
 2967
 2968    let editor = cx.add_window(|window, cx| {
 2969        let buffer = MultiBuffer::build_simple(
 2970            "
 2971                a
 2972                b(
 2973                    X
 2974                )
 2975                c(
 2976                    X
 2977                )
 2978            "
 2979            .unindent()
 2980            .as_str(),
 2981            cx,
 2982        );
 2983        let mut editor = build_editor(buffer, window, cx);
 2984        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2985            s.select_ranges([
 2986                Point::new(2, 4)..Point::new(2, 5),
 2987                Point::new(5, 4)..Point::new(5, 5),
 2988            ])
 2989        });
 2990        editor
 2991    });
 2992
 2993    _ = editor.update(cx, |editor, window, cx| {
 2994        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2995        editor.buffer.update(cx, |buffer, cx| {
 2996            buffer.edit(
 2997                [
 2998                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2999                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3000                ],
 3001                None,
 3002                cx,
 3003            );
 3004            assert_eq!(
 3005                buffer.read(cx).text(),
 3006                "
 3007                    a
 3008                    b()
 3009                    c()
 3010                "
 3011                .unindent()
 3012            );
 3013        });
 3014        assert_eq!(
 3015            editor.selections.ranges(cx),
 3016            &[
 3017                Point::new(1, 2)..Point::new(1, 2),
 3018                Point::new(2, 2)..Point::new(2, 2),
 3019            ],
 3020        );
 3021
 3022        editor.newline(&Newline, window, cx);
 3023        assert_eq!(
 3024            editor.text(cx),
 3025            "
 3026                a
 3027                b(
 3028                )
 3029                c(
 3030                )
 3031            "
 3032            .unindent()
 3033        );
 3034
 3035        // The selections are moved after the inserted newlines
 3036        assert_eq!(
 3037            editor.selections.ranges(cx),
 3038            &[
 3039                Point::new(2, 0)..Point::new(2, 0),
 3040                Point::new(4, 0)..Point::new(4, 0),
 3041            ],
 3042        );
 3043    });
 3044}
 3045
 3046#[gpui::test]
 3047async fn test_newline_above(cx: &mut TestAppContext) {
 3048    init_test(cx, |settings| {
 3049        settings.defaults.tab_size = NonZeroU32::new(4)
 3050    });
 3051
 3052    let language = Arc::new(
 3053        Language::new(
 3054            LanguageConfig::default(),
 3055            Some(tree_sitter_rust::LANGUAGE.into()),
 3056        )
 3057        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3058        .unwrap(),
 3059    );
 3060
 3061    let mut cx = EditorTestContext::new(cx).await;
 3062    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3063    cx.set_state(indoc! {"
 3064        const a: ˇA = (
 3065 3066                «const_functionˇ»(ˇ),
 3067                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3068 3069        ˇ);ˇ
 3070    "});
 3071
 3072    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3073    cx.assert_editor_state(indoc! {"
 3074        ˇ
 3075        const a: A = (
 3076            ˇ
 3077            (
 3078                ˇ
 3079                ˇ
 3080                const_function(),
 3081                ˇ
 3082                ˇ
 3083                ˇ
 3084                ˇ
 3085                something_else,
 3086                ˇ
 3087            )
 3088            ˇ
 3089            ˇ
 3090        );
 3091    "});
 3092}
 3093
 3094#[gpui::test]
 3095async fn test_newline_below(cx: &mut TestAppContext) {
 3096    init_test(cx, |settings| {
 3097        settings.defaults.tab_size = NonZeroU32::new(4)
 3098    });
 3099
 3100    let language = Arc::new(
 3101        Language::new(
 3102            LanguageConfig::default(),
 3103            Some(tree_sitter_rust::LANGUAGE.into()),
 3104        )
 3105        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3106        .unwrap(),
 3107    );
 3108
 3109    let mut cx = EditorTestContext::new(cx).await;
 3110    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3111    cx.set_state(indoc! {"
 3112        const a: ˇA = (
 3113 3114                «const_functionˇ»(ˇ),
 3115                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3116 3117        ˇ);ˇ
 3118    "});
 3119
 3120    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3121    cx.assert_editor_state(indoc! {"
 3122        const a: A = (
 3123            ˇ
 3124            (
 3125                ˇ
 3126                const_function(),
 3127                ˇ
 3128                ˇ
 3129                something_else,
 3130                ˇ
 3131                ˇ
 3132                ˇ
 3133                ˇ
 3134            )
 3135            ˇ
 3136        );
 3137        ˇ
 3138        ˇ
 3139    "});
 3140}
 3141
 3142#[gpui::test]
 3143async fn test_newline_comments(cx: &mut TestAppContext) {
 3144    init_test(cx, |settings| {
 3145        settings.defaults.tab_size = NonZeroU32::new(4)
 3146    });
 3147
 3148    let language = Arc::new(Language::new(
 3149        LanguageConfig {
 3150            line_comments: vec!["// ".into()],
 3151            ..LanguageConfig::default()
 3152        },
 3153        None,
 3154    ));
 3155    {
 3156        let mut cx = EditorTestContext::new(cx).await;
 3157        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3158        cx.set_state(indoc! {"
 3159        // Fooˇ
 3160    "});
 3161
 3162        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3163        cx.assert_editor_state(indoc! {"
 3164        // Foo
 3165        // ˇ
 3166    "});
 3167        // Ensure that we add comment prefix when existing line contains space
 3168        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3169        cx.assert_editor_state(
 3170            indoc! {"
 3171        // Foo
 3172        //s
 3173        // ˇ
 3174    "}
 3175            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3176            .as_str(),
 3177        );
 3178        // Ensure that we add comment prefix when existing line does not contain space
 3179        cx.set_state(indoc! {"
 3180        // Foo
 3181        //ˇ
 3182    "});
 3183        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3184        cx.assert_editor_state(indoc! {"
 3185        // Foo
 3186        //
 3187        // ˇ
 3188    "});
 3189        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3190        cx.set_state(indoc! {"
 3191        ˇ// Foo
 3192    "});
 3193        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3194        cx.assert_editor_state(indoc! {"
 3195
 3196        ˇ// Foo
 3197    "});
 3198    }
 3199    // Ensure that comment continuations can be disabled.
 3200    update_test_language_settings(cx, |settings| {
 3201        settings.defaults.extend_comment_on_newline = Some(false);
 3202    });
 3203    let mut cx = EditorTestContext::new(cx).await;
 3204    cx.set_state(indoc! {"
 3205        // Fooˇ
 3206    "});
 3207    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3208    cx.assert_editor_state(indoc! {"
 3209        // Foo
 3210        ˇ
 3211    "});
 3212}
 3213
 3214#[gpui::test]
 3215async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3216    init_test(cx, |settings| {
 3217        settings.defaults.tab_size = NonZeroU32::new(4)
 3218    });
 3219
 3220    let language = Arc::new(Language::new(
 3221        LanguageConfig {
 3222            line_comments: vec!["// ".into(), "/// ".into()],
 3223            ..LanguageConfig::default()
 3224        },
 3225        None,
 3226    ));
 3227    {
 3228        let mut cx = EditorTestContext::new(cx).await;
 3229        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3230        cx.set_state(indoc! {"
 3231        //ˇ
 3232    "});
 3233        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3234        cx.assert_editor_state(indoc! {"
 3235        //
 3236        // ˇ
 3237    "});
 3238
 3239        cx.set_state(indoc! {"
 3240        ///ˇ
 3241    "});
 3242        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3243        cx.assert_editor_state(indoc! {"
 3244        ///
 3245        /// ˇ
 3246    "});
 3247    }
 3248}
 3249
 3250#[gpui::test]
 3251async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3252    init_test(cx, |settings| {
 3253        settings.defaults.tab_size = NonZeroU32::new(4)
 3254    });
 3255
 3256    let language = Arc::new(
 3257        Language::new(
 3258            LanguageConfig {
 3259                documentation_comment: Some(language::BlockCommentConfig {
 3260                    start: "/**".into(),
 3261                    end: "*/".into(),
 3262                    prefix: "* ".into(),
 3263                    tab_size: 1,
 3264                }),
 3265
 3266                ..LanguageConfig::default()
 3267            },
 3268            Some(tree_sitter_rust::LANGUAGE.into()),
 3269        )
 3270        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3271        .unwrap(),
 3272    );
 3273
 3274    {
 3275        let mut cx = EditorTestContext::new(cx).await;
 3276        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3277        cx.set_state(indoc! {"
 3278        /**ˇ
 3279    "});
 3280
 3281        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3282        cx.assert_editor_state(indoc! {"
 3283        /**
 3284         * ˇ
 3285    "});
 3286        // Ensure that if cursor is before the comment start,
 3287        // we do not actually insert a comment prefix.
 3288        cx.set_state(indoc! {"
 3289        ˇ/**
 3290    "});
 3291        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3292        cx.assert_editor_state(indoc! {"
 3293
 3294        ˇ/**
 3295    "});
 3296        // Ensure that if cursor is between it doesn't add comment prefix.
 3297        cx.set_state(indoc! {"
 3298        /*ˇ*
 3299    "});
 3300        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3301        cx.assert_editor_state(indoc! {"
 3302        /*
 3303        ˇ*
 3304    "});
 3305        // Ensure that if suffix exists on same line after cursor it adds new line.
 3306        cx.set_state(indoc! {"
 3307        /**ˇ*/
 3308    "});
 3309        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3310        cx.assert_editor_state(indoc! {"
 3311        /**
 3312         * ˇ
 3313         */
 3314    "});
 3315        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3316        cx.set_state(indoc! {"
 3317        /**ˇ */
 3318    "});
 3319        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3320        cx.assert_editor_state(indoc! {"
 3321        /**
 3322         * ˇ
 3323         */
 3324    "});
 3325        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3326        cx.set_state(indoc! {"
 3327        /** ˇ*/
 3328    "});
 3329        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3330        cx.assert_editor_state(
 3331            indoc! {"
 3332        /**s
 3333         * ˇ
 3334         */
 3335    "}
 3336            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3337            .as_str(),
 3338        );
 3339        // Ensure that delimiter space is preserved when newline on already
 3340        // spaced delimiter.
 3341        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3342        cx.assert_editor_state(
 3343            indoc! {"
 3344        /**s
 3345         *s
 3346         * ˇ
 3347         */
 3348    "}
 3349            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3350            .as_str(),
 3351        );
 3352        // Ensure that delimiter space is preserved when space is not
 3353        // on existing delimiter.
 3354        cx.set_state(indoc! {"
 3355        /**
 3356 3357         */
 3358    "});
 3359        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3360        cx.assert_editor_state(indoc! {"
 3361        /**
 3362         *
 3363         * ˇ
 3364         */
 3365    "});
 3366        // Ensure that if suffix exists on same line after cursor it
 3367        // doesn't add extra new line if prefix is not on same line.
 3368        cx.set_state(indoc! {"
 3369        /**
 3370        ˇ*/
 3371    "});
 3372        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3373        cx.assert_editor_state(indoc! {"
 3374        /**
 3375
 3376        ˇ*/
 3377    "});
 3378        // Ensure that it detects suffix after existing prefix.
 3379        cx.set_state(indoc! {"
 3380        /**ˇ/
 3381    "});
 3382        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3383        cx.assert_editor_state(indoc! {"
 3384        /**
 3385        ˇ/
 3386    "});
 3387        // Ensure that if suffix exists on same line before
 3388        // cursor it does not add comment prefix.
 3389        cx.set_state(indoc! {"
 3390        /** */ˇ
 3391    "});
 3392        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3393        cx.assert_editor_state(indoc! {"
 3394        /** */
 3395        ˇ
 3396    "});
 3397        // Ensure that if suffix exists on same line before
 3398        // cursor it does not add comment prefix.
 3399        cx.set_state(indoc! {"
 3400        /**
 3401         *
 3402         */ˇ
 3403    "});
 3404        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3405        cx.assert_editor_state(indoc! {"
 3406        /**
 3407         *
 3408         */
 3409         ˇ
 3410    "});
 3411
 3412        // Ensure that inline comment followed by code
 3413        // doesn't add comment prefix on newline
 3414        cx.set_state(indoc! {"
 3415        /** */ textˇ
 3416    "});
 3417        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3418        cx.assert_editor_state(indoc! {"
 3419        /** */ text
 3420        ˇ
 3421    "});
 3422
 3423        // Ensure that text after comment end tag
 3424        // doesn't add comment prefix on newline
 3425        cx.set_state(indoc! {"
 3426        /**
 3427         *
 3428         */ˇtext
 3429    "});
 3430        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3431        cx.assert_editor_state(indoc! {"
 3432        /**
 3433         *
 3434         */
 3435         ˇtext
 3436    "});
 3437
 3438        // Ensure if not comment block it doesn't
 3439        // add comment prefix on newline
 3440        cx.set_state(indoc! {"
 3441        * textˇ
 3442    "});
 3443        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3444        cx.assert_editor_state(indoc! {"
 3445        * text
 3446        ˇ
 3447    "});
 3448    }
 3449    // Ensure that comment continuations can be disabled.
 3450    update_test_language_settings(cx, |settings| {
 3451        settings.defaults.extend_comment_on_newline = Some(false);
 3452    });
 3453    let mut cx = EditorTestContext::new(cx).await;
 3454    cx.set_state(indoc! {"
 3455        /**ˇ
 3456    "});
 3457    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3458    cx.assert_editor_state(indoc! {"
 3459        /**
 3460        ˇ
 3461    "});
 3462}
 3463
 3464#[gpui::test]
 3465async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3466    init_test(cx, |settings| {
 3467        settings.defaults.tab_size = NonZeroU32::new(4)
 3468    });
 3469
 3470    let lua_language = Arc::new(Language::new(
 3471        LanguageConfig {
 3472            line_comments: vec!["--".into()],
 3473            block_comment: Some(language::BlockCommentConfig {
 3474                start: "--[[".into(),
 3475                prefix: "".into(),
 3476                end: "]]".into(),
 3477                tab_size: 0,
 3478            }),
 3479            ..LanguageConfig::default()
 3480        },
 3481        None,
 3482    ));
 3483
 3484    let mut cx = EditorTestContext::new(cx).await;
 3485    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3486
 3487    // Line with line comment should extend
 3488    cx.set_state(indoc! {"
 3489        --ˇ
 3490    "});
 3491    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3492    cx.assert_editor_state(indoc! {"
 3493        --
 3494        --ˇ
 3495    "});
 3496
 3497    // Line with block comment that matches line comment should not extend
 3498    cx.set_state(indoc! {"
 3499        --[[ˇ
 3500    "});
 3501    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3502    cx.assert_editor_state(indoc! {"
 3503        --[[
 3504        ˇ
 3505    "});
 3506}
 3507
 3508#[gpui::test]
 3509fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3510    init_test(cx, |_| {});
 3511
 3512    let editor = cx.add_window(|window, cx| {
 3513        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3514        let mut editor = build_editor(buffer, window, cx);
 3515        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3516            s.select_ranges([3..4, 11..12, 19..20])
 3517        });
 3518        editor
 3519    });
 3520
 3521    _ = editor.update(cx, |editor, window, cx| {
 3522        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3523        editor.buffer.update(cx, |buffer, cx| {
 3524            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3525            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3526        });
 3527        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3528
 3529        editor.insert("Z", window, cx);
 3530        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3531
 3532        // The selections are moved after the inserted characters
 3533        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3534    });
 3535}
 3536
 3537#[gpui::test]
 3538async fn test_tab(cx: &mut TestAppContext) {
 3539    init_test(cx, |settings| {
 3540        settings.defaults.tab_size = NonZeroU32::new(3)
 3541    });
 3542
 3543    let mut cx = EditorTestContext::new(cx).await;
 3544    cx.set_state(indoc! {"
 3545        ˇabˇc
 3546        ˇ🏀ˇ🏀ˇefg
 3547 3548    "});
 3549    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3550    cx.assert_editor_state(indoc! {"
 3551           ˇab ˇc
 3552           ˇ🏀  ˇ🏀  ˇefg
 3553        d  ˇ
 3554    "});
 3555
 3556    cx.set_state(indoc! {"
 3557        a
 3558        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3559    "});
 3560    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3561    cx.assert_editor_state(indoc! {"
 3562        a
 3563           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3564    "});
 3565}
 3566
 3567#[gpui::test]
 3568async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3569    init_test(cx, |_| {});
 3570
 3571    let mut cx = EditorTestContext::new(cx).await;
 3572    let language = Arc::new(
 3573        Language::new(
 3574            LanguageConfig::default(),
 3575            Some(tree_sitter_rust::LANGUAGE.into()),
 3576        )
 3577        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3578        .unwrap(),
 3579    );
 3580    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3581
 3582    // test when all cursors are not at suggested indent
 3583    // then simply move to their suggested indent location
 3584    cx.set_state(indoc! {"
 3585        const a: B = (
 3586            c(
 3587        ˇ
 3588        ˇ    )
 3589        );
 3590    "});
 3591    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3592    cx.assert_editor_state(indoc! {"
 3593        const a: B = (
 3594            c(
 3595                ˇ
 3596            ˇ)
 3597        );
 3598    "});
 3599
 3600    // test cursor already at suggested indent not moving when
 3601    // other cursors are yet to reach their suggested indents
 3602    cx.set_state(indoc! {"
 3603        ˇ
 3604        const a: B = (
 3605            c(
 3606                d(
 3607        ˇ
 3608                )
 3609        ˇ
 3610        ˇ    )
 3611        );
 3612    "});
 3613    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3614    cx.assert_editor_state(indoc! {"
 3615        ˇ
 3616        const a: B = (
 3617            c(
 3618                d(
 3619                    ˇ
 3620                )
 3621                ˇ
 3622            ˇ)
 3623        );
 3624    "});
 3625    // test when all cursors are at suggested indent then tab is inserted
 3626    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3627    cx.assert_editor_state(indoc! {"
 3628            ˇ
 3629        const a: B = (
 3630            c(
 3631                d(
 3632                        ˇ
 3633                )
 3634                    ˇ
 3635                ˇ)
 3636        );
 3637    "});
 3638
 3639    // test when current indent is less than suggested indent,
 3640    // we adjust line to match suggested indent and move cursor to it
 3641    //
 3642    // when no other cursor is at word boundary, all of them should move
 3643    cx.set_state(indoc! {"
 3644        const a: B = (
 3645            c(
 3646                d(
 3647        ˇ
 3648        ˇ   )
 3649        ˇ   )
 3650        );
 3651    "});
 3652    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3653    cx.assert_editor_state(indoc! {"
 3654        const a: B = (
 3655            c(
 3656                d(
 3657                    ˇ
 3658                ˇ)
 3659            ˇ)
 3660        );
 3661    "});
 3662
 3663    // test when current indent is less than suggested indent,
 3664    // we adjust line to match suggested indent and move cursor to it
 3665    //
 3666    // when some other cursor is at word boundary, it should not move
 3667    cx.set_state(indoc! {"
 3668        const a: B = (
 3669            c(
 3670                d(
 3671        ˇ
 3672        ˇ   )
 3673           ˇ)
 3674        );
 3675    "});
 3676    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3677    cx.assert_editor_state(indoc! {"
 3678        const a: B = (
 3679            c(
 3680                d(
 3681                    ˇ
 3682                ˇ)
 3683            ˇ)
 3684        );
 3685    "});
 3686
 3687    // test when current indent is more than suggested indent,
 3688    // we just move cursor to current indent instead of suggested indent
 3689    //
 3690    // when no other cursor is at word boundary, all of them should move
 3691    cx.set_state(indoc! {"
 3692        const a: B = (
 3693            c(
 3694                d(
 3695        ˇ
 3696        ˇ                )
 3697        ˇ   )
 3698        );
 3699    "});
 3700    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3701    cx.assert_editor_state(indoc! {"
 3702        const a: B = (
 3703            c(
 3704                d(
 3705                    ˇ
 3706                        ˇ)
 3707            ˇ)
 3708        );
 3709    "});
 3710    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3711    cx.assert_editor_state(indoc! {"
 3712        const a: B = (
 3713            c(
 3714                d(
 3715                        ˇ
 3716                            ˇ)
 3717                ˇ)
 3718        );
 3719    "});
 3720
 3721    // test when current indent is more than suggested indent,
 3722    // we just move cursor to current indent instead of suggested indent
 3723    //
 3724    // when some other cursor is at word boundary, it doesn't move
 3725    cx.set_state(indoc! {"
 3726        const a: B = (
 3727            c(
 3728                d(
 3729        ˇ
 3730        ˇ                )
 3731            ˇ)
 3732        );
 3733    "});
 3734    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3735    cx.assert_editor_state(indoc! {"
 3736        const a: B = (
 3737            c(
 3738                d(
 3739                    ˇ
 3740                        ˇ)
 3741            ˇ)
 3742        );
 3743    "});
 3744
 3745    // handle auto-indent when there are multiple cursors on the same line
 3746    cx.set_state(indoc! {"
 3747        const a: B = (
 3748            c(
 3749        ˇ    ˇ
 3750        ˇ    )
 3751        );
 3752    "});
 3753    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3754    cx.assert_editor_state(indoc! {"
 3755        const a: B = (
 3756            c(
 3757                ˇ
 3758            ˇ)
 3759        );
 3760    "});
 3761}
 3762
 3763#[gpui::test]
 3764async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3765    init_test(cx, |settings| {
 3766        settings.defaults.tab_size = NonZeroU32::new(3)
 3767    });
 3768
 3769    let mut cx = EditorTestContext::new(cx).await;
 3770    cx.set_state(indoc! {"
 3771         ˇ
 3772        \t ˇ
 3773        \t  ˇ
 3774        \t   ˇ
 3775         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3776    "});
 3777
 3778    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3779    cx.assert_editor_state(indoc! {"
 3780           ˇ
 3781        \t   ˇ
 3782        \t   ˇ
 3783        \t      ˇ
 3784         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3785    "});
 3786}
 3787
 3788#[gpui::test]
 3789async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3790    init_test(cx, |settings| {
 3791        settings.defaults.tab_size = NonZeroU32::new(4)
 3792    });
 3793
 3794    let language = Arc::new(
 3795        Language::new(
 3796            LanguageConfig::default(),
 3797            Some(tree_sitter_rust::LANGUAGE.into()),
 3798        )
 3799        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3800        .unwrap(),
 3801    );
 3802
 3803    let mut cx = EditorTestContext::new(cx).await;
 3804    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3805    cx.set_state(indoc! {"
 3806        fn a() {
 3807            if b {
 3808        \t ˇc
 3809            }
 3810        }
 3811    "});
 3812
 3813    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3814    cx.assert_editor_state(indoc! {"
 3815        fn a() {
 3816            if b {
 3817                ˇc
 3818            }
 3819        }
 3820    "});
 3821}
 3822
 3823#[gpui::test]
 3824async fn test_indent_outdent(cx: &mut TestAppContext) {
 3825    init_test(cx, |settings| {
 3826        settings.defaults.tab_size = NonZeroU32::new(4);
 3827    });
 3828
 3829    let mut cx = EditorTestContext::new(cx).await;
 3830
 3831    cx.set_state(indoc! {"
 3832          «oneˇ» «twoˇ»
 3833        three
 3834         four
 3835    "});
 3836    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3837    cx.assert_editor_state(indoc! {"
 3838            «oneˇ» «twoˇ»
 3839        three
 3840         four
 3841    "});
 3842
 3843    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3844    cx.assert_editor_state(indoc! {"
 3845        «oneˇ» «twoˇ»
 3846        three
 3847         four
 3848    "});
 3849
 3850    // select across line ending
 3851    cx.set_state(indoc! {"
 3852        one two
 3853        t«hree
 3854        ˇ» four
 3855    "});
 3856    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3857    cx.assert_editor_state(indoc! {"
 3858        one two
 3859            t«hree
 3860        ˇ» four
 3861    "});
 3862
 3863    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3864    cx.assert_editor_state(indoc! {"
 3865        one two
 3866        t«hree
 3867        ˇ» four
 3868    "});
 3869
 3870    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3871    cx.set_state(indoc! {"
 3872        one two
 3873        ˇthree
 3874            four
 3875    "});
 3876    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3877    cx.assert_editor_state(indoc! {"
 3878        one two
 3879            ˇthree
 3880            four
 3881    "});
 3882
 3883    cx.set_state(indoc! {"
 3884        one two
 3885        ˇ    three
 3886            four
 3887    "});
 3888    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3889    cx.assert_editor_state(indoc! {"
 3890        one two
 3891        ˇthree
 3892            four
 3893    "});
 3894}
 3895
 3896#[gpui::test]
 3897async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3898    // This is a regression test for issue #33761
 3899    init_test(cx, |_| {});
 3900
 3901    let mut cx = EditorTestContext::new(cx).await;
 3902    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3903    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3904
 3905    cx.set_state(
 3906        r#"ˇ#     ingress:
 3907ˇ#         api:
 3908ˇ#             enabled: false
 3909ˇ#             pathType: Prefix
 3910ˇ#           console:
 3911ˇ#               enabled: false
 3912ˇ#               pathType: Prefix
 3913"#,
 3914    );
 3915
 3916    // Press tab to indent all lines
 3917    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3918
 3919    cx.assert_editor_state(
 3920        r#"    ˇ#     ingress:
 3921    ˇ#         api:
 3922    ˇ#             enabled: false
 3923    ˇ#             pathType: Prefix
 3924    ˇ#           console:
 3925    ˇ#               enabled: false
 3926    ˇ#               pathType: Prefix
 3927"#,
 3928    );
 3929}
 3930
 3931#[gpui::test]
 3932async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3933    // This is a test to make sure our fix for issue #33761 didn't break anything
 3934    init_test(cx, |_| {});
 3935
 3936    let mut cx = EditorTestContext::new(cx).await;
 3937    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3938    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3939
 3940    cx.set_state(
 3941        r#"ˇingress:
 3942ˇ  api:
 3943ˇ    enabled: false
 3944ˇ    pathType: Prefix
 3945"#,
 3946    );
 3947
 3948    // Press tab to indent all lines
 3949    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3950
 3951    cx.assert_editor_state(
 3952        r#"ˇingress:
 3953    ˇapi:
 3954        ˇenabled: false
 3955        ˇpathType: Prefix
 3956"#,
 3957    );
 3958}
 3959
 3960#[gpui::test]
 3961async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3962    init_test(cx, |settings| {
 3963        settings.defaults.hard_tabs = Some(true);
 3964    });
 3965
 3966    let mut cx = EditorTestContext::new(cx).await;
 3967
 3968    // select two ranges on one line
 3969    cx.set_state(indoc! {"
 3970        «oneˇ» «twoˇ»
 3971        three
 3972        four
 3973    "});
 3974    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3975    cx.assert_editor_state(indoc! {"
 3976        \t«oneˇ» «twoˇ»
 3977        three
 3978        four
 3979    "});
 3980    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3981    cx.assert_editor_state(indoc! {"
 3982        \t\t«oneˇ» «twoˇ»
 3983        three
 3984        four
 3985    "});
 3986    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3987    cx.assert_editor_state(indoc! {"
 3988        \t«oneˇ» «twoˇ»
 3989        three
 3990        four
 3991    "});
 3992    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3993    cx.assert_editor_state(indoc! {"
 3994        «oneˇ» «twoˇ»
 3995        three
 3996        four
 3997    "});
 3998
 3999    // select across a line ending
 4000    cx.set_state(indoc! {"
 4001        one two
 4002        t«hree
 4003        ˇ»four
 4004    "});
 4005    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4006    cx.assert_editor_state(indoc! {"
 4007        one two
 4008        \tt«hree
 4009        ˇ»four
 4010    "});
 4011    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4012    cx.assert_editor_state(indoc! {"
 4013        one two
 4014        \t\tt«hree
 4015        ˇ»four
 4016    "});
 4017    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4018    cx.assert_editor_state(indoc! {"
 4019        one two
 4020        \tt«hree
 4021        ˇ»four
 4022    "});
 4023    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4024    cx.assert_editor_state(indoc! {"
 4025        one two
 4026        t«hree
 4027        ˇ»four
 4028    "});
 4029
 4030    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4031    cx.set_state(indoc! {"
 4032        one two
 4033        ˇthree
 4034        four
 4035    "});
 4036    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4037    cx.assert_editor_state(indoc! {"
 4038        one two
 4039        ˇthree
 4040        four
 4041    "});
 4042    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4043    cx.assert_editor_state(indoc! {"
 4044        one two
 4045        \tˇthree
 4046        four
 4047    "});
 4048    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4049    cx.assert_editor_state(indoc! {"
 4050        one two
 4051        ˇthree
 4052        four
 4053    "});
 4054}
 4055
 4056#[gpui::test]
 4057fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4058    init_test(cx, |settings| {
 4059        settings.languages.0.extend([
 4060            (
 4061                "TOML".into(),
 4062                LanguageSettingsContent {
 4063                    tab_size: NonZeroU32::new(2),
 4064                    ..Default::default()
 4065                },
 4066            ),
 4067            (
 4068                "Rust".into(),
 4069                LanguageSettingsContent {
 4070                    tab_size: NonZeroU32::new(4),
 4071                    ..Default::default()
 4072                },
 4073            ),
 4074        ]);
 4075    });
 4076
 4077    let toml_language = Arc::new(Language::new(
 4078        LanguageConfig {
 4079            name: "TOML".into(),
 4080            ..Default::default()
 4081        },
 4082        None,
 4083    ));
 4084    let rust_language = Arc::new(Language::new(
 4085        LanguageConfig {
 4086            name: "Rust".into(),
 4087            ..Default::default()
 4088        },
 4089        None,
 4090    ));
 4091
 4092    let toml_buffer =
 4093        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4094    let rust_buffer =
 4095        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4096    let multibuffer = cx.new(|cx| {
 4097        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4098        multibuffer.push_excerpts(
 4099            toml_buffer.clone(),
 4100            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4101            cx,
 4102        );
 4103        multibuffer.push_excerpts(
 4104            rust_buffer.clone(),
 4105            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4106            cx,
 4107        );
 4108        multibuffer
 4109    });
 4110
 4111    cx.add_window(|window, cx| {
 4112        let mut editor = build_editor(multibuffer, window, cx);
 4113
 4114        assert_eq!(
 4115            editor.text(cx),
 4116            indoc! {"
 4117                a = 1
 4118                b = 2
 4119
 4120                const c: usize = 3;
 4121            "}
 4122        );
 4123
 4124        select_ranges(
 4125            &mut editor,
 4126            indoc! {"
 4127                «aˇ» = 1
 4128                b = 2
 4129
 4130                «const c:ˇ» usize = 3;
 4131            "},
 4132            window,
 4133            cx,
 4134        );
 4135
 4136        editor.tab(&Tab, window, cx);
 4137        assert_text_with_selections(
 4138            &mut editor,
 4139            indoc! {"
 4140                  «aˇ» = 1
 4141                b = 2
 4142
 4143                    «const c:ˇ» usize = 3;
 4144            "},
 4145            cx,
 4146        );
 4147        editor.backtab(&Backtab, window, cx);
 4148        assert_text_with_selections(
 4149            &mut editor,
 4150            indoc! {"
 4151                «aˇ» = 1
 4152                b = 2
 4153
 4154                «const c:ˇ» usize = 3;
 4155            "},
 4156            cx,
 4157        );
 4158
 4159        editor
 4160    });
 4161}
 4162
 4163#[gpui::test]
 4164async fn test_backspace(cx: &mut TestAppContext) {
 4165    init_test(cx, |_| {});
 4166
 4167    let mut cx = EditorTestContext::new(cx).await;
 4168
 4169    // Basic backspace
 4170    cx.set_state(indoc! {"
 4171        onˇe two three
 4172        fou«rˇ» five six
 4173        seven «ˇeight nine
 4174        »ten
 4175    "});
 4176    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4177    cx.assert_editor_state(indoc! {"
 4178        oˇe two three
 4179        fouˇ five six
 4180        seven ˇten
 4181    "});
 4182
 4183    // Test backspace inside and around indents
 4184    cx.set_state(indoc! {"
 4185        zero
 4186            ˇone
 4187                ˇtwo
 4188            ˇ ˇ ˇ  three
 4189        ˇ  ˇ  four
 4190    "});
 4191    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4192    cx.assert_editor_state(indoc! {"
 4193        zero
 4194        ˇone
 4195            ˇtwo
 4196        ˇ  threeˇ  four
 4197    "});
 4198}
 4199
 4200#[gpui::test]
 4201async fn test_delete(cx: &mut TestAppContext) {
 4202    init_test(cx, |_| {});
 4203
 4204    let mut cx = EditorTestContext::new(cx).await;
 4205    cx.set_state(indoc! {"
 4206        onˇe two three
 4207        fou«rˇ» five six
 4208        seven «ˇeight nine
 4209        »ten
 4210    "});
 4211    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4212    cx.assert_editor_state(indoc! {"
 4213        onˇ two three
 4214        fouˇ five six
 4215        seven ˇten
 4216    "});
 4217}
 4218
 4219#[gpui::test]
 4220fn test_delete_line(cx: &mut TestAppContext) {
 4221    init_test(cx, |_| {});
 4222
 4223    let editor = cx.add_window(|window, cx| {
 4224        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4225        build_editor(buffer, window, cx)
 4226    });
 4227    _ = editor.update(cx, |editor, window, cx| {
 4228        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4229            s.select_display_ranges([
 4230                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4231                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4232                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4233            ])
 4234        });
 4235        editor.delete_line(&DeleteLine, window, cx);
 4236        assert_eq!(editor.display_text(cx), "ghi");
 4237        assert_eq!(
 4238            editor.selections.display_ranges(cx),
 4239            vec![
 4240                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4241                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 4242            ]
 4243        );
 4244    });
 4245
 4246    let editor = cx.add_window(|window, cx| {
 4247        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4248        build_editor(buffer, window, cx)
 4249    });
 4250    _ = editor.update(cx, |editor, window, cx| {
 4251        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4252            s.select_display_ranges([
 4253                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4254            ])
 4255        });
 4256        editor.delete_line(&DeleteLine, window, cx);
 4257        assert_eq!(editor.display_text(cx), "ghi\n");
 4258        assert_eq!(
 4259            editor.selections.display_ranges(cx),
 4260            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4261        );
 4262    });
 4263}
 4264
 4265#[gpui::test]
 4266fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4267    init_test(cx, |_| {});
 4268
 4269    cx.add_window(|window, cx| {
 4270        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4271        let mut editor = build_editor(buffer.clone(), window, cx);
 4272        let buffer = buffer.read(cx).as_singleton().unwrap();
 4273
 4274        assert_eq!(
 4275            editor.selections.ranges::<Point>(cx),
 4276            &[Point::new(0, 0)..Point::new(0, 0)]
 4277        );
 4278
 4279        // When on single line, replace newline at end by space
 4280        editor.join_lines(&JoinLines, window, cx);
 4281        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4282        assert_eq!(
 4283            editor.selections.ranges::<Point>(cx),
 4284            &[Point::new(0, 3)..Point::new(0, 3)]
 4285        );
 4286
 4287        // When multiple lines are selected, remove newlines that are spanned by the selection
 4288        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4289            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4290        });
 4291        editor.join_lines(&JoinLines, window, cx);
 4292        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4293        assert_eq!(
 4294            editor.selections.ranges::<Point>(cx),
 4295            &[Point::new(0, 11)..Point::new(0, 11)]
 4296        );
 4297
 4298        // Undo should be transactional
 4299        editor.undo(&Undo, window, cx);
 4300        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4301        assert_eq!(
 4302            editor.selections.ranges::<Point>(cx),
 4303            &[Point::new(0, 5)..Point::new(2, 2)]
 4304        );
 4305
 4306        // When joining an empty line don't insert a space
 4307        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4308            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4309        });
 4310        editor.join_lines(&JoinLines, window, cx);
 4311        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4312        assert_eq!(
 4313            editor.selections.ranges::<Point>(cx),
 4314            [Point::new(2, 3)..Point::new(2, 3)]
 4315        );
 4316
 4317        // We can remove trailing newlines
 4318        editor.join_lines(&JoinLines, window, cx);
 4319        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4320        assert_eq!(
 4321            editor.selections.ranges::<Point>(cx),
 4322            [Point::new(2, 3)..Point::new(2, 3)]
 4323        );
 4324
 4325        // We don't blow up on the last line
 4326        editor.join_lines(&JoinLines, window, cx);
 4327        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4328        assert_eq!(
 4329            editor.selections.ranges::<Point>(cx),
 4330            [Point::new(2, 3)..Point::new(2, 3)]
 4331        );
 4332
 4333        // reset to test indentation
 4334        editor.buffer.update(cx, |buffer, cx| {
 4335            buffer.edit(
 4336                [
 4337                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4338                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4339                ],
 4340                None,
 4341                cx,
 4342            )
 4343        });
 4344
 4345        // We remove any leading spaces
 4346        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4347        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4348            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4349        });
 4350        editor.join_lines(&JoinLines, window, cx);
 4351        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4352
 4353        // We don't insert a space for a line containing only spaces
 4354        editor.join_lines(&JoinLines, window, cx);
 4355        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4356
 4357        // We ignore any leading tabs
 4358        editor.join_lines(&JoinLines, window, cx);
 4359        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4360
 4361        editor
 4362    });
 4363}
 4364
 4365#[gpui::test]
 4366fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4367    init_test(cx, |_| {});
 4368
 4369    cx.add_window(|window, cx| {
 4370        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4371        let mut editor = build_editor(buffer.clone(), window, cx);
 4372        let buffer = buffer.read(cx).as_singleton().unwrap();
 4373
 4374        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4375            s.select_ranges([
 4376                Point::new(0, 2)..Point::new(1, 1),
 4377                Point::new(1, 2)..Point::new(1, 2),
 4378                Point::new(3, 1)..Point::new(3, 2),
 4379            ])
 4380        });
 4381
 4382        editor.join_lines(&JoinLines, window, cx);
 4383        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4384
 4385        assert_eq!(
 4386            editor.selections.ranges::<Point>(cx),
 4387            [
 4388                Point::new(0, 7)..Point::new(0, 7),
 4389                Point::new(1, 3)..Point::new(1, 3)
 4390            ]
 4391        );
 4392        editor
 4393    });
 4394}
 4395
 4396#[gpui::test]
 4397async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4398    init_test(cx, |_| {});
 4399
 4400    let mut cx = EditorTestContext::new(cx).await;
 4401
 4402    let diff_base = r#"
 4403        Line 0
 4404        Line 1
 4405        Line 2
 4406        Line 3
 4407        "#
 4408    .unindent();
 4409
 4410    cx.set_state(
 4411        &r#"
 4412        ˇLine 0
 4413        Line 1
 4414        Line 2
 4415        Line 3
 4416        "#
 4417        .unindent(),
 4418    );
 4419
 4420    cx.set_head_text(&diff_base);
 4421    executor.run_until_parked();
 4422
 4423    // Join lines
 4424    cx.update_editor(|editor, window, cx| {
 4425        editor.join_lines(&JoinLines, window, cx);
 4426    });
 4427    executor.run_until_parked();
 4428
 4429    cx.assert_editor_state(
 4430        &r#"
 4431        Line 0ˇ Line 1
 4432        Line 2
 4433        Line 3
 4434        "#
 4435        .unindent(),
 4436    );
 4437    // Join again
 4438    cx.update_editor(|editor, window, cx| {
 4439        editor.join_lines(&JoinLines, window, cx);
 4440    });
 4441    executor.run_until_parked();
 4442
 4443    cx.assert_editor_state(
 4444        &r#"
 4445        Line 0 Line 1ˇ Line 2
 4446        Line 3
 4447        "#
 4448        .unindent(),
 4449    );
 4450}
 4451
 4452#[gpui::test]
 4453async fn test_custom_newlines_cause_no_false_positive_diffs(
 4454    executor: BackgroundExecutor,
 4455    cx: &mut TestAppContext,
 4456) {
 4457    init_test(cx, |_| {});
 4458    let mut cx = EditorTestContext::new(cx).await;
 4459    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4460    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4461    executor.run_until_parked();
 4462
 4463    cx.update_editor(|editor, window, cx| {
 4464        let snapshot = editor.snapshot(window, cx);
 4465        assert_eq!(
 4466            snapshot
 4467                .buffer_snapshot
 4468                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4469                .collect::<Vec<_>>(),
 4470            Vec::new(),
 4471            "Should not have any diffs for files with custom newlines"
 4472        );
 4473    });
 4474}
 4475
 4476#[gpui::test]
 4477async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4478    init_test(cx, |_| {});
 4479
 4480    let mut cx = EditorTestContext::new(cx).await;
 4481
 4482    // Test sort_lines_case_insensitive()
 4483    cx.set_state(indoc! {"
 4484        «z
 4485        y
 4486        x
 4487        Z
 4488        Y
 4489        Xˇ»
 4490    "});
 4491    cx.update_editor(|e, window, cx| {
 4492        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4493    });
 4494    cx.assert_editor_state(indoc! {"
 4495        «x
 4496        X
 4497        y
 4498        Y
 4499        z
 4500        Zˇ»
 4501    "});
 4502
 4503    // Test sort_lines_by_length()
 4504    //
 4505    // Demonstrates:
 4506    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4507    // - sort is stable
 4508    cx.set_state(indoc! {"
 4509        «123
 4510        æ
 4511        12
 4512 4513        1
 4514        æˇ»
 4515    "});
 4516    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4517    cx.assert_editor_state(indoc! {"
 4518        «æ
 4519 4520        1
 4521        æ
 4522        12
 4523        123ˇ»
 4524    "});
 4525
 4526    // Test reverse_lines()
 4527    cx.set_state(indoc! {"
 4528        «5
 4529        4
 4530        3
 4531        2
 4532        1ˇ»
 4533    "});
 4534    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4535    cx.assert_editor_state(indoc! {"
 4536        «1
 4537        2
 4538        3
 4539        4
 4540        5ˇ»
 4541    "});
 4542
 4543    // Skip testing shuffle_line()
 4544
 4545    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4546    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4547
 4548    // Don't manipulate when cursor is on single line, but expand the selection
 4549    cx.set_state(indoc! {"
 4550        ddˇdd
 4551        ccc
 4552        bb
 4553        a
 4554    "});
 4555    cx.update_editor(|e, window, cx| {
 4556        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4557    });
 4558    cx.assert_editor_state(indoc! {"
 4559        «ddddˇ»
 4560        ccc
 4561        bb
 4562        a
 4563    "});
 4564
 4565    // Basic manipulate case
 4566    // Start selection moves to column 0
 4567    // End of selection shrinks to fit shorter line
 4568    cx.set_state(indoc! {"
 4569        dd«d
 4570        ccc
 4571        bb
 4572        aaaaaˇ»
 4573    "});
 4574    cx.update_editor(|e, window, cx| {
 4575        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4576    });
 4577    cx.assert_editor_state(indoc! {"
 4578        «aaaaa
 4579        bb
 4580        ccc
 4581        dddˇ»
 4582    "});
 4583
 4584    // Manipulate case with newlines
 4585    cx.set_state(indoc! {"
 4586        dd«d
 4587        ccc
 4588
 4589        bb
 4590        aaaaa
 4591
 4592        ˇ»
 4593    "});
 4594    cx.update_editor(|e, window, cx| {
 4595        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4596    });
 4597    cx.assert_editor_state(indoc! {"
 4598        «
 4599
 4600        aaaaa
 4601        bb
 4602        ccc
 4603        dddˇ»
 4604
 4605    "});
 4606
 4607    // Adding new line
 4608    cx.set_state(indoc! {"
 4609        aa«a
 4610        bbˇ»b
 4611    "});
 4612    cx.update_editor(|e, window, cx| {
 4613        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4614    });
 4615    cx.assert_editor_state(indoc! {"
 4616        «aaa
 4617        bbb
 4618        added_lineˇ»
 4619    "});
 4620
 4621    // Removing line
 4622    cx.set_state(indoc! {"
 4623        aa«a
 4624        bbbˇ»
 4625    "});
 4626    cx.update_editor(|e, window, cx| {
 4627        e.manipulate_immutable_lines(window, cx, |lines| {
 4628            lines.pop();
 4629        })
 4630    });
 4631    cx.assert_editor_state(indoc! {"
 4632        «aaaˇ»
 4633    "});
 4634
 4635    // Removing all lines
 4636    cx.set_state(indoc! {"
 4637        aa«a
 4638        bbbˇ»
 4639    "});
 4640    cx.update_editor(|e, window, cx| {
 4641        e.manipulate_immutable_lines(window, cx, |lines| {
 4642            lines.drain(..);
 4643        })
 4644    });
 4645    cx.assert_editor_state(indoc! {"
 4646        ˇ
 4647    "});
 4648}
 4649
 4650#[gpui::test]
 4651async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4652    init_test(cx, |_| {});
 4653
 4654    let mut cx = EditorTestContext::new(cx).await;
 4655
 4656    // Consider continuous selection as single selection
 4657    cx.set_state(indoc! {"
 4658        Aaa«aa
 4659        cˇ»c«c
 4660        bb
 4661        aaaˇ»aa
 4662    "});
 4663    cx.update_editor(|e, window, cx| {
 4664        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4665    });
 4666    cx.assert_editor_state(indoc! {"
 4667        «Aaaaa
 4668        ccc
 4669        bb
 4670        aaaaaˇ»
 4671    "});
 4672
 4673    cx.set_state(indoc! {"
 4674        Aaa«aa
 4675        cˇ»c«c
 4676        bb
 4677        aaaˇ»aa
 4678    "});
 4679    cx.update_editor(|e, window, cx| {
 4680        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4681    });
 4682    cx.assert_editor_state(indoc! {"
 4683        «Aaaaa
 4684        ccc
 4685        bbˇ»
 4686    "});
 4687
 4688    // Consider non continuous selection as distinct dedup operations
 4689    cx.set_state(indoc! {"
 4690        «aaaaa
 4691        bb
 4692        aaaaa
 4693        aaaaaˇ»
 4694
 4695        aaa«aaˇ»
 4696    "});
 4697    cx.update_editor(|e, window, cx| {
 4698        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4699    });
 4700    cx.assert_editor_state(indoc! {"
 4701        «aaaaa
 4702        bbˇ»
 4703
 4704        «aaaaaˇ»
 4705    "});
 4706}
 4707
 4708#[gpui::test]
 4709async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4710    init_test(cx, |_| {});
 4711
 4712    let mut cx = EditorTestContext::new(cx).await;
 4713
 4714    cx.set_state(indoc! {"
 4715        «Aaa
 4716        aAa
 4717        Aaaˇ»
 4718    "});
 4719    cx.update_editor(|e, window, cx| {
 4720        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4721    });
 4722    cx.assert_editor_state(indoc! {"
 4723        «Aaa
 4724        aAaˇ»
 4725    "});
 4726
 4727    cx.set_state(indoc! {"
 4728        «Aaa
 4729        aAa
 4730        aaAˇ»
 4731    "});
 4732    cx.update_editor(|e, window, cx| {
 4733        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4734    });
 4735    cx.assert_editor_state(indoc! {"
 4736        «Aaaˇ»
 4737    "});
 4738}
 4739
 4740#[gpui::test]
 4741async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4742    init_test(cx, |_| {});
 4743
 4744    let mut cx = EditorTestContext::new(cx).await;
 4745
 4746    let js_language = Arc::new(Language::new(
 4747        LanguageConfig {
 4748            name: "JavaScript".into(),
 4749            wrap_characters: Some(language::WrapCharactersConfig {
 4750                start_prefix: "<".into(),
 4751                start_suffix: ">".into(),
 4752                end_prefix: "</".into(),
 4753                end_suffix: ">".into(),
 4754            }),
 4755            ..LanguageConfig::default()
 4756        },
 4757        None,
 4758    ));
 4759
 4760    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4761
 4762    cx.set_state(indoc! {"
 4763        «testˇ»
 4764    "});
 4765    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4766    cx.assert_editor_state(indoc! {"
 4767        <«ˇ»>test</«ˇ»>
 4768    "});
 4769
 4770    cx.set_state(indoc! {"
 4771        «test
 4772         testˇ»
 4773    "});
 4774    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4775    cx.assert_editor_state(indoc! {"
 4776        <«ˇ»>test
 4777         test</«ˇ»>
 4778    "});
 4779
 4780    cx.set_state(indoc! {"
 4781        teˇst
 4782    "});
 4783    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4784    cx.assert_editor_state(indoc! {"
 4785        te<«ˇ»></«ˇ»>st
 4786    "});
 4787}
 4788
 4789#[gpui::test]
 4790async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4791    init_test(cx, |_| {});
 4792
 4793    let mut cx = EditorTestContext::new(cx).await;
 4794
 4795    let js_language = Arc::new(Language::new(
 4796        LanguageConfig {
 4797            name: "JavaScript".into(),
 4798            wrap_characters: Some(language::WrapCharactersConfig {
 4799                start_prefix: "<".into(),
 4800                start_suffix: ">".into(),
 4801                end_prefix: "</".into(),
 4802                end_suffix: ">".into(),
 4803            }),
 4804            ..LanguageConfig::default()
 4805        },
 4806        None,
 4807    ));
 4808
 4809    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4810
 4811    cx.set_state(indoc! {"
 4812        «testˇ»
 4813        «testˇ» «testˇ»
 4814        «testˇ»
 4815    "});
 4816    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4817    cx.assert_editor_state(indoc! {"
 4818        <«ˇ»>test</«ˇ»>
 4819        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4820        <«ˇ»>test</«ˇ»>
 4821    "});
 4822
 4823    cx.set_state(indoc! {"
 4824        «test
 4825         testˇ»
 4826        «test
 4827         testˇ»
 4828    "});
 4829    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4830    cx.assert_editor_state(indoc! {"
 4831        <«ˇ»>test
 4832         test</«ˇ»>
 4833        <«ˇ»>test
 4834         test</«ˇ»>
 4835    "});
 4836}
 4837
 4838#[gpui::test]
 4839async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4840    init_test(cx, |_| {});
 4841
 4842    let mut cx = EditorTestContext::new(cx).await;
 4843
 4844    let plaintext_language = Arc::new(Language::new(
 4845        LanguageConfig {
 4846            name: "Plain Text".into(),
 4847            ..LanguageConfig::default()
 4848        },
 4849        None,
 4850    ));
 4851
 4852    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4853
 4854    cx.set_state(indoc! {"
 4855        «testˇ»
 4856    "});
 4857    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4858    cx.assert_editor_state(indoc! {"
 4859      «testˇ»
 4860    "});
 4861}
 4862
 4863#[gpui::test]
 4864async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4865    init_test(cx, |_| {});
 4866
 4867    let mut cx = EditorTestContext::new(cx).await;
 4868
 4869    // Manipulate with multiple selections on a single line
 4870    cx.set_state(indoc! {"
 4871        dd«dd
 4872        cˇ»c«c
 4873        bb
 4874        aaaˇ»aa
 4875    "});
 4876    cx.update_editor(|e, window, cx| {
 4877        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4878    });
 4879    cx.assert_editor_state(indoc! {"
 4880        «aaaaa
 4881        bb
 4882        ccc
 4883        ddddˇ»
 4884    "});
 4885
 4886    // Manipulate with multiple disjoin selections
 4887    cx.set_state(indoc! {"
 4888 4889        4
 4890        3
 4891        2
 4892        1ˇ»
 4893
 4894        dd«dd
 4895        ccc
 4896        bb
 4897        aaaˇ»aa
 4898    "});
 4899    cx.update_editor(|e, window, cx| {
 4900        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4901    });
 4902    cx.assert_editor_state(indoc! {"
 4903        «1
 4904        2
 4905        3
 4906        4
 4907        5ˇ»
 4908
 4909        «aaaaa
 4910        bb
 4911        ccc
 4912        ddddˇ»
 4913    "});
 4914
 4915    // Adding lines on each selection
 4916    cx.set_state(indoc! {"
 4917 4918        1ˇ»
 4919
 4920        bb«bb
 4921        aaaˇ»aa
 4922    "});
 4923    cx.update_editor(|e, window, cx| {
 4924        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4925    });
 4926    cx.assert_editor_state(indoc! {"
 4927        «2
 4928        1
 4929        added lineˇ»
 4930
 4931        «bbbb
 4932        aaaaa
 4933        added lineˇ»
 4934    "});
 4935
 4936    // Removing lines on each selection
 4937    cx.set_state(indoc! {"
 4938 4939        1ˇ»
 4940
 4941        bb«bb
 4942        aaaˇ»aa
 4943    "});
 4944    cx.update_editor(|e, window, cx| {
 4945        e.manipulate_immutable_lines(window, cx, |lines| {
 4946            lines.pop();
 4947        })
 4948    });
 4949    cx.assert_editor_state(indoc! {"
 4950        «2ˇ»
 4951
 4952        «bbbbˇ»
 4953    "});
 4954}
 4955
 4956#[gpui::test]
 4957async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4958    init_test(cx, |settings| {
 4959        settings.defaults.tab_size = NonZeroU32::new(3)
 4960    });
 4961
 4962    let mut cx = EditorTestContext::new(cx).await;
 4963
 4964    // MULTI SELECTION
 4965    // Ln.1 "«" tests empty lines
 4966    // Ln.9 tests just leading whitespace
 4967    cx.set_state(indoc! {"
 4968        «
 4969        abc                 // No indentationˇ»
 4970        «\tabc              // 1 tabˇ»
 4971        \t\tabc «      ˇ»   // 2 tabs
 4972        \t ab«c             // Tab followed by space
 4973         \tabc              // Space followed by tab (3 spaces should be the result)
 4974        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4975           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4976        \t
 4977        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4978    "});
 4979    cx.update_editor(|e, window, cx| {
 4980        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4981    });
 4982    cx.assert_editor_state(
 4983        indoc! {"
 4984            «
 4985            abc                 // No indentation
 4986               abc              // 1 tab
 4987                  abc          // 2 tabs
 4988                abc             // Tab followed by space
 4989               abc              // Space followed by tab (3 spaces should be the result)
 4990                           abc   // Mixed indentation (tab conversion depends on the column)
 4991               abc         // Already space indented
 4992               ·
 4993               abc\tdef          // Only the leading tab is manipulatedˇ»
 4994        "}
 4995        .replace("·", "")
 4996        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4997    );
 4998
 4999    // Test on just a few lines, the others should remain unchanged
 5000    // Only lines (3, 5, 10, 11) should change
 5001    cx.set_state(
 5002        indoc! {"
 5003            ·
 5004            abc                 // No indentation
 5005            \tabcˇ               // 1 tab
 5006            \t\tabc             // 2 tabs
 5007            \t abcˇ              // Tab followed by space
 5008             \tabc              // Space followed by tab (3 spaces should be the result)
 5009            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5010               abc              // Already space indented
 5011            «\t
 5012            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5013        "}
 5014        .replace("·", "")
 5015        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5016    );
 5017    cx.update_editor(|e, window, cx| {
 5018        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5019    });
 5020    cx.assert_editor_state(
 5021        indoc! {"
 5022            ·
 5023            abc                 // No indentation
 5024            «   abc               // 1 tabˇ»
 5025            \t\tabc             // 2 tabs
 5026            «    abc              // Tab followed by spaceˇ»
 5027             \tabc              // Space followed by tab (3 spaces should be the result)
 5028            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5029               abc              // Already space indented
 5030            «   ·
 5031               abc\tdef          // Only the leading tab is manipulatedˇ»
 5032        "}
 5033        .replace("·", "")
 5034        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5035    );
 5036
 5037    // SINGLE SELECTION
 5038    // Ln.1 "«" tests empty lines
 5039    // Ln.9 tests just leading whitespace
 5040    cx.set_state(indoc! {"
 5041        «
 5042        abc                 // No indentation
 5043        \tabc               // 1 tab
 5044        \t\tabc             // 2 tabs
 5045        \t abc              // Tab followed by space
 5046         \tabc              // Space followed by tab (3 spaces should be the result)
 5047        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5048           abc              // Already space indented
 5049        \t
 5050        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5051    "});
 5052    cx.update_editor(|e, window, cx| {
 5053        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5054    });
 5055    cx.assert_editor_state(
 5056        indoc! {"
 5057            «
 5058            abc                 // No indentation
 5059               abc               // 1 tab
 5060                  abc             // 2 tabs
 5061                abc              // Tab followed by space
 5062               abc              // Space followed by tab (3 spaces should be the result)
 5063                           abc   // Mixed indentation (tab conversion depends on the column)
 5064               abc              // Already space indented
 5065               ·
 5066               abc\tdef          // Only the leading tab is manipulatedˇ»
 5067        "}
 5068        .replace("·", "")
 5069        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5070    );
 5071}
 5072
 5073#[gpui::test]
 5074async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5075    init_test(cx, |settings| {
 5076        settings.defaults.tab_size = NonZeroU32::new(3)
 5077    });
 5078
 5079    let mut cx = EditorTestContext::new(cx).await;
 5080
 5081    // MULTI SELECTION
 5082    // Ln.1 "«" tests empty lines
 5083    // Ln.11 tests just leading whitespace
 5084    cx.set_state(indoc! {"
 5085        «
 5086        abˇ»ˇc                 // No indentation
 5087         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5088          abc  «             // 2 spaces (< 3 so dont convert)
 5089           abc              // 3 spaces (convert)
 5090             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5091        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5092        «\t abc              // Tab followed by space
 5093         \tabc              // Space followed by tab (should be consumed due to tab)
 5094        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5095           \tˇ»  «\t
 5096           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5097    "});
 5098    cx.update_editor(|e, window, cx| {
 5099        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5100    });
 5101    cx.assert_editor_state(indoc! {"
 5102        «
 5103        abc                 // No indentation
 5104         abc                // 1 space (< 3 so dont convert)
 5105          abc               // 2 spaces (< 3 so dont convert)
 5106        \tabc              // 3 spaces (convert)
 5107        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5108        \t\t\tabc           // Already tab indented
 5109        \t abc              // Tab followed by space
 5110        \tabc              // Space followed by tab (should be consumed due to tab)
 5111        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5112        \t\t\t
 5113        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5114    "});
 5115
 5116    // Test on just a few lines, the other should remain unchanged
 5117    // Only lines (4, 8, 11, 12) should change
 5118    cx.set_state(
 5119        indoc! {"
 5120            ·
 5121            abc                 // No indentation
 5122             abc                // 1 space (< 3 so dont convert)
 5123              abc               // 2 spaces (< 3 so dont convert)
 5124            «   abc              // 3 spaces (convert)ˇ»
 5125                 abc            // 5 spaces (1 tab + 2 spaces)
 5126            \t\t\tabc           // Already tab indented
 5127            \t abc              // Tab followed by space
 5128             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5129               \t\t  \tabc      // Mixed indentation
 5130            \t \t  \t   \tabc   // Mixed indentation
 5131               \t  \tˇ
 5132            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5133        "}
 5134        .replace("·", "")
 5135        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5136    );
 5137    cx.update_editor(|e, window, cx| {
 5138        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5139    });
 5140    cx.assert_editor_state(
 5141        indoc! {"
 5142            ·
 5143            abc                 // No indentation
 5144             abc                // 1 space (< 3 so dont convert)
 5145              abc               // 2 spaces (< 3 so dont convert)
 5146            «\tabc              // 3 spaces (convert)ˇ»
 5147                 abc            // 5 spaces (1 tab + 2 spaces)
 5148            \t\t\tabc           // Already tab indented
 5149            \t abc              // Tab followed by space
 5150            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5151               \t\t  \tabc      // Mixed indentation
 5152            \t \t  \t   \tabc   // Mixed indentation
 5153            «\t\t\t
 5154            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5155        "}
 5156        .replace("·", "")
 5157        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5158    );
 5159
 5160    // SINGLE SELECTION
 5161    // Ln.1 "«" tests empty lines
 5162    // Ln.11 tests just leading whitespace
 5163    cx.set_state(indoc! {"
 5164        «
 5165        abc                 // No indentation
 5166         abc                // 1 space (< 3 so dont convert)
 5167          abc               // 2 spaces (< 3 so dont convert)
 5168           abc              // 3 spaces (convert)
 5169             abc            // 5 spaces (1 tab + 2 spaces)
 5170        \t\t\tabc           // Already tab indented
 5171        \t abc              // Tab followed by space
 5172         \tabc              // Space followed by tab (should be consumed due to tab)
 5173        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5174           \t  \t
 5175           abc   \t         // Only the leading spaces should be convertedˇ»
 5176    "});
 5177    cx.update_editor(|e, window, cx| {
 5178        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5179    });
 5180    cx.assert_editor_state(indoc! {"
 5181        «
 5182        abc                 // No indentation
 5183         abc                // 1 space (< 3 so dont convert)
 5184          abc               // 2 spaces (< 3 so dont convert)
 5185        \tabc              // 3 spaces (convert)
 5186        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5187        \t\t\tabc           // Already tab indented
 5188        \t abc              // Tab followed by space
 5189        \tabc              // Space followed by tab (should be consumed due to tab)
 5190        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5191        \t\t\t
 5192        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5193    "});
 5194}
 5195
 5196#[gpui::test]
 5197async fn test_toggle_case(cx: &mut TestAppContext) {
 5198    init_test(cx, |_| {});
 5199
 5200    let mut cx = EditorTestContext::new(cx).await;
 5201
 5202    // If all lower case -> upper case
 5203    cx.set_state(indoc! {"
 5204        «hello worldˇ»
 5205    "});
 5206    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5207    cx.assert_editor_state(indoc! {"
 5208        «HELLO WORLDˇ»
 5209    "});
 5210
 5211    // If all upper case -> lower case
 5212    cx.set_state(indoc! {"
 5213        «HELLO WORLDˇ»
 5214    "});
 5215    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5216    cx.assert_editor_state(indoc! {"
 5217        «hello worldˇ»
 5218    "});
 5219
 5220    // If any upper case characters are identified -> lower case
 5221    // This matches JetBrains IDEs
 5222    cx.set_state(indoc! {"
 5223        «hEllo worldˇ»
 5224    "});
 5225    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5226    cx.assert_editor_state(indoc! {"
 5227        «hello worldˇ»
 5228    "});
 5229}
 5230
 5231#[gpui::test]
 5232async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5233    init_test(cx, |_| {});
 5234
 5235    let mut cx = EditorTestContext::new(cx).await;
 5236
 5237    cx.set_state(indoc! {"
 5238        «implement-windows-supportˇ»
 5239    "});
 5240    cx.update_editor(|e, window, cx| {
 5241        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5242    });
 5243    cx.assert_editor_state(indoc! {"
 5244        «Implement windows supportˇ»
 5245    "});
 5246}
 5247
 5248#[gpui::test]
 5249async fn test_manipulate_text(cx: &mut TestAppContext) {
 5250    init_test(cx, |_| {});
 5251
 5252    let mut cx = EditorTestContext::new(cx).await;
 5253
 5254    // Test convert_to_upper_case()
 5255    cx.set_state(indoc! {"
 5256        «hello worldˇ»
 5257    "});
 5258    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5259    cx.assert_editor_state(indoc! {"
 5260        «HELLO WORLDˇ»
 5261    "});
 5262
 5263    // Test convert_to_lower_case()
 5264    cx.set_state(indoc! {"
 5265        «HELLO WORLDˇ»
 5266    "});
 5267    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5268    cx.assert_editor_state(indoc! {"
 5269        «hello worldˇ»
 5270    "});
 5271
 5272    // Test multiple line, single selection case
 5273    cx.set_state(indoc! {"
 5274        «The quick brown
 5275        fox jumps over
 5276        the lazy dogˇ»
 5277    "});
 5278    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5279    cx.assert_editor_state(indoc! {"
 5280        «The Quick Brown
 5281        Fox Jumps Over
 5282        The Lazy Dogˇ»
 5283    "});
 5284
 5285    // Test multiple line, single selection case
 5286    cx.set_state(indoc! {"
 5287        «The quick brown
 5288        fox jumps over
 5289        the lazy dogˇ»
 5290    "});
 5291    cx.update_editor(|e, window, cx| {
 5292        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5293    });
 5294    cx.assert_editor_state(indoc! {"
 5295        «TheQuickBrown
 5296        FoxJumpsOver
 5297        TheLazyDogˇ»
 5298    "});
 5299
 5300    // From here on out, test more complex cases of manipulate_text()
 5301
 5302    // Test no selection case - should affect words cursors are in
 5303    // Cursor at beginning, middle, and end of word
 5304    cx.set_state(indoc! {"
 5305        ˇhello big beauˇtiful worldˇ
 5306    "});
 5307    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5308    cx.assert_editor_state(indoc! {"
 5309        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5310    "});
 5311
 5312    // Test multiple selections on a single line and across multiple lines
 5313    cx.set_state(indoc! {"
 5314        «Theˇ» quick «brown
 5315        foxˇ» jumps «overˇ»
 5316        the «lazyˇ» dog
 5317    "});
 5318    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5319    cx.assert_editor_state(indoc! {"
 5320        «THEˇ» quick «BROWN
 5321        FOXˇ» jumps «OVERˇ»
 5322        the «LAZYˇ» dog
 5323    "});
 5324
 5325    // Test case where text length grows
 5326    cx.set_state(indoc! {"
 5327        «tschüߡ»
 5328    "});
 5329    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5330    cx.assert_editor_state(indoc! {"
 5331        «TSCHÜSSˇ»
 5332    "});
 5333
 5334    // Test to make sure we don't crash when text shrinks
 5335    cx.set_state(indoc! {"
 5336        aaa_bbbˇ
 5337    "});
 5338    cx.update_editor(|e, window, cx| {
 5339        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5340    });
 5341    cx.assert_editor_state(indoc! {"
 5342        «aaaBbbˇ»
 5343    "});
 5344
 5345    // Test to make sure we all aware of the fact that each word can grow and shrink
 5346    // Final selections should be aware of this fact
 5347    cx.set_state(indoc! {"
 5348        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5349    "});
 5350    cx.update_editor(|e, window, cx| {
 5351        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5352    });
 5353    cx.assert_editor_state(indoc! {"
 5354        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5355    "});
 5356
 5357    cx.set_state(indoc! {"
 5358        «hElLo, WoRld!ˇ»
 5359    "});
 5360    cx.update_editor(|e, window, cx| {
 5361        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5362    });
 5363    cx.assert_editor_state(indoc! {"
 5364        «HeLlO, wOrLD!ˇ»
 5365    "});
 5366}
 5367
 5368#[gpui::test]
 5369fn test_duplicate_line(cx: &mut TestAppContext) {
 5370    init_test(cx, |_| {});
 5371
 5372    let editor = cx.add_window(|window, cx| {
 5373        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5374        build_editor(buffer, window, cx)
 5375    });
 5376    _ = editor.update(cx, |editor, window, cx| {
 5377        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5378            s.select_display_ranges([
 5379                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5380                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5381                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5382                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5383            ])
 5384        });
 5385        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5386        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5387        assert_eq!(
 5388            editor.selections.display_ranges(cx),
 5389            vec![
 5390                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5391                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5392                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5393                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5394            ]
 5395        );
 5396    });
 5397
 5398    let editor = cx.add_window(|window, cx| {
 5399        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5400        build_editor(buffer, window, cx)
 5401    });
 5402    _ = editor.update(cx, |editor, window, cx| {
 5403        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5404            s.select_display_ranges([
 5405                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5406                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5407            ])
 5408        });
 5409        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5410        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5411        assert_eq!(
 5412            editor.selections.display_ranges(cx),
 5413            vec![
 5414                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5415                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5416            ]
 5417        );
 5418    });
 5419
 5420    // With `move_upwards` the selections stay in place, except for
 5421    // the lines inserted above them
 5422    let editor = cx.add_window(|window, cx| {
 5423        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5424        build_editor(buffer, window, cx)
 5425    });
 5426    _ = editor.update(cx, |editor, window, cx| {
 5427        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5428            s.select_display_ranges([
 5429                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5430                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5431                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5432                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5433            ])
 5434        });
 5435        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5436        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5437        assert_eq!(
 5438            editor.selections.display_ranges(cx),
 5439            vec![
 5440                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5441                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5442                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5443                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5444            ]
 5445        );
 5446    });
 5447
 5448    let editor = cx.add_window(|window, cx| {
 5449        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5450        build_editor(buffer, window, cx)
 5451    });
 5452    _ = editor.update(cx, |editor, window, cx| {
 5453        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5454            s.select_display_ranges([
 5455                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5456                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5457            ])
 5458        });
 5459        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5460        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5461        assert_eq!(
 5462            editor.selections.display_ranges(cx),
 5463            vec![
 5464                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5465                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5466            ]
 5467        );
 5468    });
 5469
 5470    let editor = cx.add_window(|window, cx| {
 5471        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5472        build_editor(buffer, window, cx)
 5473    });
 5474    _ = editor.update(cx, |editor, window, cx| {
 5475        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5476            s.select_display_ranges([
 5477                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5478                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5479            ])
 5480        });
 5481        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5482        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5483        assert_eq!(
 5484            editor.selections.display_ranges(cx),
 5485            vec![
 5486                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5487                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5488            ]
 5489        );
 5490    });
 5491}
 5492
 5493#[gpui::test]
 5494fn test_move_line_up_down(cx: &mut TestAppContext) {
 5495    init_test(cx, |_| {});
 5496
 5497    let editor = cx.add_window(|window, cx| {
 5498        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5499        build_editor(buffer, window, cx)
 5500    });
 5501    _ = editor.update(cx, |editor, window, cx| {
 5502        editor.fold_creases(
 5503            vec![
 5504                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5505                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5506                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5507            ],
 5508            true,
 5509            window,
 5510            cx,
 5511        );
 5512        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5513            s.select_display_ranges([
 5514                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5515                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5516                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5517                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5518            ])
 5519        });
 5520        assert_eq!(
 5521            editor.display_text(cx),
 5522            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5523        );
 5524
 5525        editor.move_line_up(&MoveLineUp, window, cx);
 5526        assert_eq!(
 5527            editor.display_text(cx),
 5528            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5529        );
 5530        assert_eq!(
 5531            editor.selections.display_ranges(cx),
 5532            vec![
 5533                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5534                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5535                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5536                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5537            ]
 5538        );
 5539    });
 5540
 5541    _ = editor.update(cx, |editor, window, cx| {
 5542        editor.move_line_down(&MoveLineDown, window, cx);
 5543        assert_eq!(
 5544            editor.display_text(cx),
 5545            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5546        );
 5547        assert_eq!(
 5548            editor.selections.display_ranges(cx),
 5549            vec![
 5550                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5551                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5552                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5553                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5554            ]
 5555        );
 5556    });
 5557
 5558    _ = editor.update(cx, |editor, window, cx| {
 5559        editor.move_line_down(&MoveLineDown, window, cx);
 5560        assert_eq!(
 5561            editor.display_text(cx),
 5562            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5563        );
 5564        assert_eq!(
 5565            editor.selections.display_ranges(cx),
 5566            vec![
 5567                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5568                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5569                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5570                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5571            ]
 5572        );
 5573    });
 5574
 5575    _ = editor.update(cx, |editor, window, cx| {
 5576        editor.move_line_up(&MoveLineUp, window, cx);
 5577        assert_eq!(
 5578            editor.display_text(cx),
 5579            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5580        );
 5581        assert_eq!(
 5582            editor.selections.display_ranges(cx),
 5583            vec![
 5584                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5585                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5586                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5587                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5588            ]
 5589        );
 5590    });
 5591}
 5592
 5593#[gpui::test]
 5594fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5595    init_test(cx, |_| {});
 5596    let editor = cx.add_window(|window, cx| {
 5597        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5598        build_editor(buffer, window, cx)
 5599    });
 5600    _ = editor.update(cx, |editor, window, cx| {
 5601        editor.fold_creases(
 5602            vec![Crease::simple(
 5603                Point::new(6, 4)..Point::new(7, 4),
 5604                FoldPlaceholder::test(),
 5605            )],
 5606            true,
 5607            window,
 5608            cx,
 5609        );
 5610        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5611            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5612        });
 5613        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5614        editor.move_line_up(&MoveLineUp, window, cx);
 5615        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5616        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5617    });
 5618}
 5619
 5620#[gpui::test]
 5621fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5622    init_test(cx, |_| {});
 5623
 5624    let editor = cx.add_window(|window, cx| {
 5625        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5626        build_editor(buffer, window, cx)
 5627    });
 5628    _ = editor.update(cx, |editor, window, cx| {
 5629        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5630        editor.insert_blocks(
 5631            [BlockProperties {
 5632                style: BlockStyle::Fixed,
 5633                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5634                height: Some(1),
 5635                render: Arc::new(|_| div().into_any()),
 5636                priority: 0,
 5637            }],
 5638            Some(Autoscroll::fit()),
 5639            cx,
 5640        );
 5641        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5642            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5643        });
 5644        editor.move_line_down(&MoveLineDown, window, cx);
 5645    });
 5646}
 5647
 5648#[gpui::test]
 5649async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5650    init_test(cx, |_| {});
 5651
 5652    let mut cx = EditorTestContext::new(cx).await;
 5653    cx.set_state(
 5654        &"
 5655            ˇzero
 5656            one
 5657            two
 5658            three
 5659            four
 5660            five
 5661        "
 5662        .unindent(),
 5663    );
 5664
 5665    // Create a four-line block that replaces three lines of text.
 5666    cx.update_editor(|editor, window, cx| {
 5667        let snapshot = editor.snapshot(window, cx);
 5668        let snapshot = &snapshot.buffer_snapshot;
 5669        let placement = BlockPlacement::Replace(
 5670            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5671        );
 5672        editor.insert_blocks(
 5673            [BlockProperties {
 5674                placement,
 5675                height: Some(4),
 5676                style: BlockStyle::Sticky,
 5677                render: Arc::new(|_| gpui::div().into_any_element()),
 5678                priority: 0,
 5679            }],
 5680            None,
 5681            cx,
 5682        );
 5683    });
 5684
 5685    // Move down so that the cursor touches the block.
 5686    cx.update_editor(|editor, window, cx| {
 5687        editor.move_down(&Default::default(), window, cx);
 5688    });
 5689    cx.assert_editor_state(
 5690        &"
 5691            zero
 5692            «one
 5693            two
 5694            threeˇ»
 5695            four
 5696            five
 5697        "
 5698        .unindent(),
 5699    );
 5700
 5701    // Move down past the block.
 5702    cx.update_editor(|editor, window, cx| {
 5703        editor.move_down(&Default::default(), window, cx);
 5704    });
 5705    cx.assert_editor_state(
 5706        &"
 5707            zero
 5708            one
 5709            two
 5710            three
 5711            ˇfour
 5712            five
 5713        "
 5714        .unindent(),
 5715    );
 5716}
 5717
 5718#[gpui::test]
 5719fn test_transpose(cx: &mut TestAppContext) {
 5720    init_test(cx, |_| {});
 5721
 5722    _ = cx.add_window(|window, cx| {
 5723        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5724        editor.set_style(EditorStyle::default(), window, cx);
 5725        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5726            s.select_ranges([1..1])
 5727        });
 5728        editor.transpose(&Default::default(), window, cx);
 5729        assert_eq!(editor.text(cx), "bac");
 5730        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5731
 5732        editor.transpose(&Default::default(), window, cx);
 5733        assert_eq!(editor.text(cx), "bca");
 5734        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5735
 5736        editor.transpose(&Default::default(), window, cx);
 5737        assert_eq!(editor.text(cx), "bac");
 5738        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5739
 5740        editor
 5741    });
 5742
 5743    _ = cx.add_window(|window, cx| {
 5744        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5745        editor.set_style(EditorStyle::default(), window, cx);
 5746        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5747            s.select_ranges([3..3])
 5748        });
 5749        editor.transpose(&Default::default(), window, cx);
 5750        assert_eq!(editor.text(cx), "acb\nde");
 5751        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5752
 5753        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5754            s.select_ranges([4..4])
 5755        });
 5756        editor.transpose(&Default::default(), window, cx);
 5757        assert_eq!(editor.text(cx), "acbd\ne");
 5758        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5759
 5760        editor.transpose(&Default::default(), window, cx);
 5761        assert_eq!(editor.text(cx), "acbde\n");
 5762        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5763
 5764        editor.transpose(&Default::default(), window, cx);
 5765        assert_eq!(editor.text(cx), "acbd\ne");
 5766        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5767
 5768        editor
 5769    });
 5770
 5771    _ = cx.add_window(|window, cx| {
 5772        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5773        editor.set_style(EditorStyle::default(), window, cx);
 5774        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5775            s.select_ranges([1..1, 2..2, 4..4])
 5776        });
 5777        editor.transpose(&Default::default(), window, cx);
 5778        assert_eq!(editor.text(cx), "bacd\ne");
 5779        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5780
 5781        editor.transpose(&Default::default(), window, cx);
 5782        assert_eq!(editor.text(cx), "bcade\n");
 5783        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5784
 5785        editor.transpose(&Default::default(), window, cx);
 5786        assert_eq!(editor.text(cx), "bcda\ne");
 5787        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5788
 5789        editor.transpose(&Default::default(), window, cx);
 5790        assert_eq!(editor.text(cx), "bcade\n");
 5791        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5792
 5793        editor.transpose(&Default::default(), window, cx);
 5794        assert_eq!(editor.text(cx), "bcaed\n");
 5795        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5796
 5797        editor
 5798    });
 5799
 5800    _ = cx.add_window(|window, cx| {
 5801        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5802        editor.set_style(EditorStyle::default(), window, cx);
 5803        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5804            s.select_ranges([4..4])
 5805        });
 5806        editor.transpose(&Default::default(), window, cx);
 5807        assert_eq!(editor.text(cx), "🏀🍐✋");
 5808        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5809
 5810        editor.transpose(&Default::default(), window, cx);
 5811        assert_eq!(editor.text(cx), "🏀✋🍐");
 5812        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5813
 5814        editor.transpose(&Default::default(), window, cx);
 5815        assert_eq!(editor.text(cx), "🏀🍐✋");
 5816        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5817
 5818        editor
 5819    });
 5820}
 5821
 5822#[gpui::test]
 5823async fn test_rewrap(cx: &mut TestAppContext) {
 5824    init_test(cx, |settings| {
 5825        settings.languages.0.extend([
 5826            (
 5827                "Markdown".into(),
 5828                LanguageSettingsContent {
 5829                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5830                    preferred_line_length: Some(40),
 5831                    ..Default::default()
 5832                },
 5833            ),
 5834            (
 5835                "Plain Text".into(),
 5836                LanguageSettingsContent {
 5837                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5838                    preferred_line_length: Some(40),
 5839                    ..Default::default()
 5840                },
 5841            ),
 5842            (
 5843                "C++".into(),
 5844                LanguageSettingsContent {
 5845                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5846                    preferred_line_length: Some(40),
 5847                    ..Default::default()
 5848                },
 5849            ),
 5850            (
 5851                "Python".into(),
 5852                LanguageSettingsContent {
 5853                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5854                    preferred_line_length: Some(40),
 5855                    ..Default::default()
 5856                },
 5857            ),
 5858            (
 5859                "Rust".into(),
 5860                LanguageSettingsContent {
 5861                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5862                    preferred_line_length: Some(40),
 5863                    ..Default::default()
 5864                },
 5865            ),
 5866        ])
 5867    });
 5868
 5869    let mut cx = EditorTestContext::new(cx).await;
 5870
 5871    let cpp_language = Arc::new(Language::new(
 5872        LanguageConfig {
 5873            name: "C++".into(),
 5874            line_comments: vec!["// ".into()],
 5875            ..LanguageConfig::default()
 5876        },
 5877        None,
 5878    ));
 5879    let python_language = Arc::new(Language::new(
 5880        LanguageConfig {
 5881            name: "Python".into(),
 5882            line_comments: vec!["# ".into()],
 5883            ..LanguageConfig::default()
 5884        },
 5885        None,
 5886    ));
 5887    let markdown_language = Arc::new(Language::new(
 5888        LanguageConfig {
 5889            name: "Markdown".into(),
 5890            rewrap_prefixes: vec![
 5891                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5892                regex::Regex::new("[-*+]\\s+").unwrap(),
 5893            ],
 5894            ..LanguageConfig::default()
 5895        },
 5896        None,
 5897    ));
 5898    let rust_language = Arc::new(
 5899        Language::new(
 5900            LanguageConfig {
 5901                name: "Rust".into(),
 5902                line_comments: vec!["// ".into(), "/// ".into()],
 5903                ..LanguageConfig::default()
 5904            },
 5905            Some(tree_sitter_rust::LANGUAGE.into()),
 5906        )
 5907        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 5908        .unwrap(),
 5909    );
 5910
 5911    let plaintext_language = Arc::new(Language::new(
 5912        LanguageConfig {
 5913            name: "Plain Text".into(),
 5914            ..LanguageConfig::default()
 5915        },
 5916        None,
 5917    ));
 5918
 5919    // Test basic rewrapping of a long line with a cursor
 5920    assert_rewrap(
 5921        indoc! {"
 5922            // ˇThis is a long comment that needs to be wrapped.
 5923        "},
 5924        indoc! {"
 5925            // ˇThis is a long comment that needs to
 5926            // be wrapped.
 5927        "},
 5928        cpp_language.clone(),
 5929        &mut cx,
 5930    );
 5931
 5932    // Test rewrapping a full selection
 5933    assert_rewrap(
 5934        indoc! {"
 5935            «// This selected long comment needs to be wrapped.ˇ»"
 5936        },
 5937        indoc! {"
 5938            «// This selected long comment needs to
 5939            // be wrapped.ˇ»"
 5940        },
 5941        cpp_language.clone(),
 5942        &mut cx,
 5943    );
 5944
 5945    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5946    assert_rewrap(
 5947        indoc! {"
 5948            // ˇThis is the first line.
 5949            // Thisˇ is the second line.
 5950            // This is the thirdˇ line, all part of one paragraph.
 5951         "},
 5952        indoc! {"
 5953            // ˇThis is the first line. Thisˇ is the
 5954            // second line. This is the thirdˇ line,
 5955            // all part of one paragraph.
 5956         "},
 5957        cpp_language.clone(),
 5958        &mut cx,
 5959    );
 5960
 5961    // Test multiple cursors in different paragraphs trigger separate rewraps
 5962    assert_rewrap(
 5963        indoc! {"
 5964            // ˇThis is the first paragraph, first line.
 5965            // ˇThis is the first paragraph, second line.
 5966
 5967            // ˇThis is the second paragraph, first line.
 5968            // ˇThis is the second paragraph, second line.
 5969        "},
 5970        indoc! {"
 5971            // ˇThis is the first paragraph, first
 5972            // line. ˇThis is the first paragraph,
 5973            // second line.
 5974
 5975            // ˇThis is the second paragraph, first
 5976            // line. ˇThis is the second paragraph,
 5977            // second line.
 5978        "},
 5979        cpp_language.clone(),
 5980        &mut cx,
 5981    );
 5982
 5983    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5984    assert_rewrap(
 5985        indoc! {"
 5986            «// A regular long long comment to be wrapped.
 5987            /// A documentation long comment to be wrapped.ˇ»
 5988          "},
 5989        indoc! {"
 5990            «// A regular long long comment to be
 5991            // wrapped.
 5992            /// A documentation long comment to be
 5993            /// wrapped.ˇ»
 5994          "},
 5995        rust_language.clone(),
 5996        &mut cx,
 5997    );
 5998
 5999    // Test that change in indentation level trigger seperate rewraps
 6000    assert_rewrap(
 6001        indoc! {"
 6002            fn foo() {
 6003                «// This is a long comment at the base indent.
 6004                    // This is a long comment at the next indent.ˇ»
 6005            }
 6006        "},
 6007        indoc! {"
 6008            fn foo() {
 6009                «// This is a long comment at the
 6010                // base indent.
 6011                    // This is a long comment at the
 6012                    // next indent.ˇ»
 6013            }
 6014        "},
 6015        rust_language.clone(),
 6016        &mut cx,
 6017    );
 6018
 6019    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6020    assert_rewrap(
 6021        indoc! {"
 6022            # ˇThis is a long comment using a pound sign.
 6023        "},
 6024        indoc! {"
 6025            # ˇThis is a long comment using a pound
 6026            # sign.
 6027        "},
 6028        python_language,
 6029        &mut cx,
 6030    );
 6031
 6032    // Test rewrapping only affects comments, not code even when selected
 6033    assert_rewrap(
 6034        indoc! {"
 6035            «/// This doc comment is long and should be wrapped.
 6036            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6037        "},
 6038        indoc! {"
 6039            «/// This doc comment is long and should
 6040            /// be wrapped.
 6041            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6042        "},
 6043        rust_language.clone(),
 6044        &mut cx,
 6045    );
 6046
 6047    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6048    assert_rewrap(
 6049        indoc! {"
 6050            # Header
 6051
 6052            A long long long line of markdown text to wrap.ˇ
 6053         "},
 6054        indoc! {"
 6055            # Header
 6056
 6057            A long long long line of markdown text
 6058            to wrap.ˇ
 6059         "},
 6060        markdown_language.clone(),
 6061        &mut cx,
 6062    );
 6063
 6064    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6065    assert_rewrap(
 6066        indoc! {"
 6067            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6068            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6069            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6070        "},
 6071        indoc! {"
 6072            «1. This is a numbered list item that is
 6073               very long and needs to be wrapped
 6074               properly.
 6075            2. This is a numbered list item that is
 6076               very long and needs to be wrapped
 6077               properly.
 6078            - This is an unordered list item that is
 6079              also very long and should not merge
 6080              with the numbered item.ˇ»
 6081        "},
 6082        markdown_language.clone(),
 6083        &mut cx,
 6084    );
 6085
 6086    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6087    assert_rewrap(
 6088        indoc! {"
 6089            «1. This is a numbered list item that is
 6090            very long and needs to be wrapped
 6091            properly.
 6092            2. This is a numbered list item that is
 6093            very long and needs to be wrapped
 6094            properly.
 6095            - This is an unordered list item that is
 6096            also very long and should not merge with
 6097            the numbered item.ˇ»
 6098        "},
 6099        indoc! {"
 6100            «1. This is a numbered list item that is
 6101               very long and needs to be wrapped
 6102               properly.
 6103            2. This is a numbered list item that is
 6104               very long and needs to be wrapped
 6105               properly.
 6106            - This is an unordered list item that is
 6107              also very long and should not merge
 6108              with the numbered item.ˇ»
 6109        "},
 6110        markdown_language.clone(),
 6111        &mut cx,
 6112    );
 6113
 6114    // Test that rewrapping maintain indents even when they already exists.
 6115    assert_rewrap(
 6116        indoc! {"
 6117            «1. This is a numbered list
 6118               item that is very long and needs to be wrapped properly.
 6119            2. This is a numbered list
 6120               item that is very long and needs to be wrapped properly.
 6121            - This is an unordered list item that is also very long and
 6122              should not merge with the numbered item.ˇ»
 6123        "},
 6124        indoc! {"
 6125            «1. This is a numbered list item that is
 6126               very long and needs to be wrapped
 6127               properly.
 6128            2. This is a numbered list item that is
 6129               very long and needs to be wrapped
 6130               properly.
 6131            - This is an unordered list item that is
 6132              also very long and should not merge
 6133              with the numbered item.ˇ»
 6134        "},
 6135        markdown_language,
 6136        &mut cx,
 6137    );
 6138
 6139    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6140    assert_rewrap(
 6141        indoc! {"
 6142            ˇThis is a very long line of plain text that will be wrapped.
 6143        "},
 6144        indoc! {"
 6145            ˇThis is a very long line of plain text
 6146            that will be wrapped.
 6147        "},
 6148        plaintext_language.clone(),
 6149        &mut cx,
 6150    );
 6151
 6152    // Test that non-commented code acts as a paragraph boundary within a selection
 6153    assert_rewrap(
 6154        indoc! {"
 6155               «// This is the first long comment block to be wrapped.
 6156               fn my_func(a: u32);
 6157               // This is the second long comment block to be wrapped.ˇ»
 6158           "},
 6159        indoc! {"
 6160               «// This is the first long comment block
 6161               // to be wrapped.
 6162               fn my_func(a: u32);
 6163               // This is the second long comment block
 6164               // to be wrapped.ˇ»
 6165           "},
 6166        rust_language,
 6167        &mut cx,
 6168    );
 6169
 6170    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6171    assert_rewrap(
 6172        indoc! {"
 6173            «ˇThis is a very long line that will be wrapped.
 6174
 6175            This is another paragraph in the same selection.»
 6176
 6177            «\tThis is a very long indented line that will be wrapped.ˇ»
 6178         "},
 6179        indoc! {"
 6180            «ˇThis is a very long line that will be
 6181            wrapped.
 6182
 6183            This is another paragraph in the same
 6184            selection.»
 6185
 6186            «\tThis is a very long indented line
 6187            \tthat will be wrapped.ˇ»
 6188         "},
 6189        plaintext_language,
 6190        &mut cx,
 6191    );
 6192
 6193    // Test that an empty comment line acts as a paragraph boundary
 6194    assert_rewrap(
 6195        indoc! {"
 6196            // ˇThis is a long comment that will be wrapped.
 6197            //
 6198            // And this is another long comment that will also be wrapped.ˇ
 6199         "},
 6200        indoc! {"
 6201            // ˇThis is a long comment that will be
 6202            // wrapped.
 6203            //
 6204            // And this is another long comment that
 6205            // will also be wrapped.ˇ
 6206         "},
 6207        cpp_language,
 6208        &mut cx,
 6209    );
 6210
 6211    #[track_caller]
 6212    fn assert_rewrap(
 6213        unwrapped_text: &str,
 6214        wrapped_text: &str,
 6215        language: Arc<Language>,
 6216        cx: &mut EditorTestContext,
 6217    ) {
 6218        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6219        cx.set_state(unwrapped_text);
 6220        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6221        cx.assert_editor_state(wrapped_text);
 6222    }
 6223}
 6224
 6225#[gpui::test]
 6226async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6227    init_test(cx, |settings| {
 6228        settings.languages.0.extend([(
 6229            "Rust".into(),
 6230            LanguageSettingsContent {
 6231                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6232                preferred_line_length: Some(40),
 6233                ..Default::default()
 6234            },
 6235        )])
 6236    });
 6237
 6238    let mut cx = EditorTestContext::new(cx).await;
 6239
 6240    let rust_lang = Arc::new(
 6241        Language::new(
 6242            LanguageConfig {
 6243                name: "Rust".into(),
 6244                line_comments: vec!["// ".into()],
 6245                block_comment: Some(BlockCommentConfig {
 6246                    start: "/*".into(),
 6247                    end: "*/".into(),
 6248                    prefix: "* ".into(),
 6249                    tab_size: 1,
 6250                }),
 6251                documentation_comment: Some(BlockCommentConfig {
 6252                    start: "/**".into(),
 6253                    end: "*/".into(),
 6254                    prefix: "* ".into(),
 6255                    tab_size: 1,
 6256                }),
 6257
 6258                ..LanguageConfig::default()
 6259            },
 6260            Some(tree_sitter_rust::LANGUAGE.into()),
 6261        )
 6262        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6263        .unwrap(),
 6264    );
 6265
 6266    // regular block comment
 6267    assert_rewrap(
 6268        indoc! {"
 6269            /*
 6270             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6271             */
 6272            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6273        "},
 6274        indoc! {"
 6275            /*
 6276             *ˇ Lorem ipsum dolor sit amet,
 6277             * consectetur adipiscing elit.
 6278             */
 6279            /*
 6280             *ˇ Lorem ipsum dolor sit amet,
 6281             * consectetur adipiscing elit.
 6282             */
 6283        "},
 6284        rust_lang.clone(),
 6285        &mut cx,
 6286    );
 6287
 6288    // indent is respected
 6289    assert_rewrap(
 6290        indoc! {"
 6291            {}
 6292                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6293        "},
 6294        indoc! {"
 6295            {}
 6296                /*
 6297                 *ˇ Lorem ipsum dolor sit amet,
 6298                 * consectetur adipiscing elit.
 6299                 */
 6300        "},
 6301        rust_lang.clone(),
 6302        &mut cx,
 6303    );
 6304
 6305    // short block comments with inline delimiters
 6306    assert_rewrap(
 6307        indoc! {"
 6308            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6309            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6310             */
 6311            /*
 6312             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6313        "},
 6314        indoc! {"
 6315            /*
 6316             *ˇ Lorem ipsum dolor sit amet,
 6317             * consectetur adipiscing elit.
 6318             */
 6319            /*
 6320             *ˇ Lorem ipsum dolor sit amet,
 6321             * consectetur adipiscing elit.
 6322             */
 6323            /*
 6324             *ˇ Lorem ipsum dolor sit amet,
 6325             * consectetur adipiscing elit.
 6326             */
 6327        "},
 6328        rust_lang.clone(),
 6329        &mut cx,
 6330    );
 6331
 6332    // multiline block comment with inline start/end delimiters
 6333    assert_rewrap(
 6334        indoc! {"
 6335            /*ˇ Lorem ipsum dolor sit amet,
 6336             * consectetur adipiscing elit. */
 6337        "},
 6338        indoc! {"
 6339            /*
 6340             *ˇ Lorem ipsum dolor sit amet,
 6341             * consectetur adipiscing elit.
 6342             */
 6343        "},
 6344        rust_lang.clone(),
 6345        &mut cx,
 6346    );
 6347
 6348    // block comment rewrap still respects paragraph bounds
 6349    assert_rewrap(
 6350        indoc! {"
 6351            /*
 6352             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6353             *
 6354             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6355             */
 6356        "},
 6357        indoc! {"
 6358            /*
 6359             *ˇ Lorem ipsum dolor sit amet,
 6360             * consectetur adipiscing elit.
 6361             *
 6362             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6363             */
 6364        "},
 6365        rust_lang.clone(),
 6366        &mut cx,
 6367    );
 6368
 6369    // documentation comments
 6370    assert_rewrap(
 6371        indoc! {"
 6372            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6373            /**
 6374             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6375             */
 6376        "},
 6377        indoc! {"
 6378            /**
 6379             *ˇ Lorem ipsum dolor sit amet,
 6380             * consectetur adipiscing elit.
 6381             */
 6382            /**
 6383             *ˇ Lorem ipsum dolor sit amet,
 6384             * consectetur adipiscing elit.
 6385             */
 6386        "},
 6387        rust_lang.clone(),
 6388        &mut cx,
 6389    );
 6390
 6391    // different, adjacent comments
 6392    assert_rewrap(
 6393        indoc! {"
 6394            /**
 6395             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6396             */
 6397            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6398            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6399        "},
 6400        indoc! {"
 6401            /**
 6402             *ˇ Lorem ipsum dolor sit amet,
 6403             * consectetur adipiscing elit.
 6404             */
 6405            /*
 6406             *ˇ Lorem ipsum dolor sit amet,
 6407             * consectetur adipiscing elit.
 6408             */
 6409            //ˇ Lorem ipsum dolor sit amet,
 6410            // consectetur adipiscing elit.
 6411        "},
 6412        rust_lang.clone(),
 6413        &mut cx,
 6414    );
 6415
 6416    // selection w/ single short block comment
 6417    assert_rewrap(
 6418        indoc! {"
 6419            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6420        "},
 6421        indoc! {"
 6422            «/*
 6423             * Lorem ipsum dolor sit amet,
 6424             * consectetur adipiscing elit.
 6425             */ˇ»
 6426        "},
 6427        rust_lang.clone(),
 6428        &mut cx,
 6429    );
 6430
 6431    // rewrapping a single comment w/ abutting comments
 6432    assert_rewrap(
 6433        indoc! {"
 6434            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6435            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6436        "},
 6437        indoc! {"
 6438            /*
 6439             * ˇLorem ipsum dolor sit amet,
 6440             * consectetur adipiscing elit.
 6441             */
 6442            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6443        "},
 6444        rust_lang.clone(),
 6445        &mut cx,
 6446    );
 6447
 6448    // selection w/ non-abutting short block comments
 6449    assert_rewrap(
 6450        indoc! {"
 6451            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6452
 6453            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6454        "},
 6455        indoc! {"
 6456            «/*
 6457             * Lorem ipsum dolor sit amet,
 6458             * consectetur adipiscing elit.
 6459             */
 6460
 6461            /*
 6462             * Lorem ipsum dolor sit amet,
 6463             * consectetur adipiscing elit.
 6464             */ˇ»
 6465        "},
 6466        rust_lang.clone(),
 6467        &mut cx,
 6468    );
 6469
 6470    // selection of multiline block comments
 6471    assert_rewrap(
 6472        indoc! {"
 6473            «/* Lorem ipsum dolor sit amet,
 6474             * consectetur adipiscing elit. */ˇ»
 6475        "},
 6476        indoc! {"
 6477            «/*
 6478             * Lorem ipsum dolor sit amet,
 6479             * consectetur adipiscing elit.
 6480             */ˇ»
 6481        "},
 6482        rust_lang.clone(),
 6483        &mut cx,
 6484    );
 6485
 6486    // partial selection of multiline block comments
 6487    assert_rewrap(
 6488        indoc! {"
 6489            «/* Lorem ipsum dolor sit amet,ˇ»
 6490             * consectetur adipiscing elit. */
 6491            /* Lorem ipsum dolor sit amet,
 6492             «* consectetur adipiscing elit. */ˇ»
 6493        "},
 6494        indoc! {"
 6495            «/*
 6496             * Lorem ipsum dolor sit amet,ˇ»
 6497             * consectetur adipiscing elit. */
 6498            /* Lorem ipsum dolor sit amet,
 6499             «* consectetur adipiscing elit.
 6500             */ˇ»
 6501        "},
 6502        rust_lang.clone(),
 6503        &mut cx,
 6504    );
 6505
 6506    // selection w/ abutting short block comments
 6507    // TODO: should not be combined; should rewrap as 2 comments
 6508    assert_rewrap(
 6509        indoc! {"
 6510            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6511            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6512        "},
 6513        // desired behavior:
 6514        // indoc! {"
 6515        //     «/*
 6516        //      * Lorem ipsum dolor sit amet,
 6517        //      * consectetur adipiscing elit.
 6518        //      */
 6519        //     /*
 6520        //      * Lorem ipsum dolor sit amet,
 6521        //      * consectetur adipiscing elit.
 6522        //      */ˇ»
 6523        // "},
 6524        // actual behaviour:
 6525        indoc! {"
 6526            «/*
 6527             * Lorem ipsum dolor sit amet,
 6528             * consectetur adipiscing elit. Lorem
 6529             * ipsum dolor sit amet, consectetur
 6530             * adipiscing elit.
 6531             */ˇ»
 6532        "},
 6533        rust_lang.clone(),
 6534        &mut cx,
 6535    );
 6536
 6537    // TODO: same as above, but with delimiters on separate line
 6538    // assert_rewrap(
 6539    //     indoc! {"
 6540    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6541    //          */
 6542    //         /*
 6543    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6544    //     "},
 6545    //     // desired:
 6546    //     // indoc! {"
 6547    //     //     «/*
 6548    //     //      * Lorem ipsum dolor sit amet,
 6549    //     //      * consectetur adipiscing elit.
 6550    //     //      */
 6551    //     //     /*
 6552    //     //      * Lorem ipsum dolor sit amet,
 6553    //     //      * consectetur adipiscing elit.
 6554    //     //      */ˇ»
 6555    //     // "},
 6556    //     // actual: (but with trailing w/s on the empty lines)
 6557    //     indoc! {"
 6558    //         «/*
 6559    //          * Lorem ipsum dolor sit amet,
 6560    //          * consectetur adipiscing elit.
 6561    //          *
 6562    //          */
 6563    //         /*
 6564    //          *
 6565    //          * Lorem ipsum dolor sit amet,
 6566    //          * consectetur adipiscing elit.
 6567    //          */ˇ»
 6568    //     "},
 6569    //     rust_lang.clone(),
 6570    //     &mut cx,
 6571    // );
 6572
 6573    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6574    assert_rewrap(
 6575        indoc! {"
 6576            /*
 6577             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6578             */
 6579            /*
 6580             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6581            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6582        "},
 6583        // desired:
 6584        // indoc! {"
 6585        //     /*
 6586        //      *ˇ Lorem ipsum dolor sit amet,
 6587        //      * consectetur adipiscing elit.
 6588        //      */
 6589        //     /*
 6590        //      *ˇ Lorem ipsum dolor sit amet,
 6591        //      * consectetur adipiscing elit.
 6592        //      */
 6593        //     /*
 6594        //      *ˇ Lorem ipsum dolor sit amet
 6595        //      */ /* consectetur adipiscing elit. */
 6596        // "},
 6597        // actual:
 6598        indoc! {"
 6599            /*
 6600             //ˇ Lorem ipsum dolor sit amet,
 6601             // consectetur adipiscing elit.
 6602             */
 6603            /*
 6604             * //ˇ Lorem ipsum dolor sit amet,
 6605             * consectetur adipiscing elit.
 6606             */
 6607            /*
 6608             *ˇ Lorem ipsum dolor sit amet */ /*
 6609             * consectetur adipiscing elit.
 6610             */
 6611        "},
 6612        rust_lang,
 6613        &mut cx,
 6614    );
 6615
 6616    #[track_caller]
 6617    fn assert_rewrap(
 6618        unwrapped_text: &str,
 6619        wrapped_text: &str,
 6620        language: Arc<Language>,
 6621        cx: &mut EditorTestContext,
 6622    ) {
 6623        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6624        cx.set_state(unwrapped_text);
 6625        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6626        cx.assert_editor_state(wrapped_text);
 6627    }
 6628}
 6629
 6630#[gpui::test]
 6631async fn test_hard_wrap(cx: &mut TestAppContext) {
 6632    init_test(cx, |_| {});
 6633    let mut cx = EditorTestContext::new(cx).await;
 6634
 6635    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6636    cx.update_editor(|editor, _, cx| {
 6637        editor.set_hard_wrap(Some(14), cx);
 6638    });
 6639
 6640    cx.set_state(indoc!(
 6641        "
 6642        one two three ˇ
 6643        "
 6644    ));
 6645    cx.simulate_input("four");
 6646    cx.run_until_parked();
 6647
 6648    cx.assert_editor_state(indoc!(
 6649        "
 6650        one two three
 6651        fourˇ
 6652        "
 6653    ));
 6654
 6655    cx.update_editor(|editor, window, cx| {
 6656        editor.newline(&Default::default(), window, cx);
 6657    });
 6658    cx.run_until_parked();
 6659    cx.assert_editor_state(indoc!(
 6660        "
 6661        one two three
 6662        four
 6663        ˇ
 6664        "
 6665    ));
 6666
 6667    cx.simulate_input("five");
 6668    cx.run_until_parked();
 6669    cx.assert_editor_state(indoc!(
 6670        "
 6671        one two three
 6672        four
 6673        fiveˇ
 6674        "
 6675    ));
 6676
 6677    cx.update_editor(|editor, window, cx| {
 6678        editor.newline(&Default::default(), window, cx);
 6679    });
 6680    cx.run_until_parked();
 6681    cx.simulate_input("# ");
 6682    cx.run_until_parked();
 6683    cx.assert_editor_state(indoc!(
 6684        "
 6685        one two three
 6686        four
 6687        five
 6688        # ˇ
 6689        "
 6690    ));
 6691
 6692    cx.update_editor(|editor, window, cx| {
 6693        editor.newline(&Default::default(), window, cx);
 6694    });
 6695    cx.run_until_parked();
 6696    cx.assert_editor_state(indoc!(
 6697        "
 6698        one two three
 6699        four
 6700        five
 6701        #\x20
 6702 6703        "
 6704    ));
 6705
 6706    cx.simulate_input(" 6");
 6707    cx.run_until_parked();
 6708    cx.assert_editor_state(indoc!(
 6709        "
 6710        one two three
 6711        four
 6712        five
 6713        #
 6714        # 6ˇ
 6715        "
 6716    ));
 6717}
 6718
 6719#[gpui::test]
 6720async fn test_clipboard(cx: &mut TestAppContext) {
 6721    init_test(cx, |_| {});
 6722
 6723    let mut cx = EditorTestContext::new(cx).await;
 6724
 6725    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6726    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6727    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6728
 6729    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6730    cx.set_state("two ˇfour ˇsix ˇ");
 6731    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6732    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6733
 6734    // Paste again but with only two cursors. Since the number of cursors doesn't
 6735    // match the number of slices in the clipboard, the entire clipboard text
 6736    // is pasted at each cursor.
 6737    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6738    cx.update_editor(|e, window, cx| {
 6739        e.handle_input("( ", window, cx);
 6740        e.paste(&Paste, window, cx);
 6741        e.handle_input(") ", window, cx);
 6742    });
 6743    cx.assert_editor_state(
 6744        &([
 6745            "( one✅ ",
 6746            "three ",
 6747            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6748            "three ",
 6749            "five ) ˇ",
 6750        ]
 6751        .join("\n")),
 6752    );
 6753
 6754    // Cut with three selections, one of which is full-line.
 6755    cx.set_state(indoc! {"
 6756        1«2ˇ»3
 6757        4ˇ567
 6758        «8ˇ»9"});
 6759    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6760    cx.assert_editor_state(indoc! {"
 6761        1ˇ3
 6762        ˇ9"});
 6763
 6764    // Paste with three selections, noticing how the copied selection that was full-line
 6765    // gets inserted before the second cursor.
 6766    cx.set_state(indoc! {"
 6767        1ˇ3
 6768 6769        «oˇ»ne"});
 6770    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6771    cx.assert_editor_state(indoc! {"
 6772        12ˇ3
 6773        4567
 6774 6775        8ˇne"});
 6776
 6777    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6778    cx.set_state(indoc! {"
 6779        The quick brown
 6780        fox juˇmps over
 6781        the lazy dog"});
 6782    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6783    assert_eq!(
 6784        cx.read_from_clipboard()
 6785            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6786        Some("fox jumps over\n".to_string())
 6787    );
 6788
 6789    // Paste with three selections, noticing how the copied full-line selection is inserted
 6790    // before the empty selections but replaces the selection that is non-empty.
 6791    cx.set_state(indoc! {"
 6792        Tˇhe quick brown
 6793        «foˇ»x jumps over
 6794        tˇhe lazy dog"});
 6795    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6796    cx.assert_editor_state(indoc! {"
 6797        fox jumps over
 6798        Tˇhe quick brown
 6799        fox jumps over
 6800        ˇx jumps over
 6801        fox jumps over
 6802        tˇhe lazy dog"});
 6803}
 6804
 6805#[gpui::test]
 6806async fn test_copy_trim(cx: &mut TestAppContext) {
 6807    init_test(cx, |_| {});
 6808
 6809    let mut cx = EditorTestContext::new(cx).await;
 6810    cx.set_state(
 6811        r#"            «for selection in selections.iter() {
 6812            let mut start = selection.start;
 6813            let mut end = selection.end;
 6814            let is_entire_line = selection.is_empty();
 6815            if is_entire_line {
 6816                start = Point::new(start.row, 0);ˇ»
 6817                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6818            }
 6819        "#,
 6820    );
 6821    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6822    assert_eq!(
 6823        cx.read_from_clipboard()
 6824            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6825        Some(
 6826            "for selection in selections.iter() {
 6827            let mut start = selection.start;
 6828            let mut end = selection.end;
 6829            let is_entire_line = selection.is_empty();
 6830            if is_entire_line {
 6831                start = Point::new(start.row, 0);"
 6832                .to_string()
 6833        ),
 6834        "Regular copying preserves all indentation selected",
 6835    );
 6836    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6837    assert_eq!(
 6838        cx.read_from_clipboard()
 6839            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6840        Some(
 6841            "for selection in selections.iter() {
 6842let mut start = selection.start;
 6843let mut end = selection.end;
 6844let is_entire_line = selection.is_empty();
 6845if is_entire_line {
 6846    start = Point::new(start.row, 0);"
 6847                .to_string()
 6848        ),
 6849        "Copying with stripping should strip all leading whitespaces"
 6850    );
 6851
 6852    cx.set_state(
 6853        r#"       «     for selection in selections.iter() {
 6854            let mut start = selection.start;
 6855            let mut end = selection.end;
 6856            let is_entire_line = selection.is_empty();
 6857            if is_entire_line {
 6858                start = Point::new(start.row, 0);ˇ»
 6859                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6860            }
 6861        "#,
 6862    );
 6863    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6864    assert_eq!(
 6865        cx.read_from_clipboard()
 6866            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6867        Some(
 6868            "     for selection in selections.iter() {
 6869            let mut start = selection.start;
 6870            let mut end = selection.end;
 6871            let is_entire_line = selection.is_empty();
 6872            if is_entire_line {
 6873                start = Point::new(start.row, 0);"
 6874                .to_string()
 6875        ),
 6876        "Regular copying preserves all indentation selected",
 6877    );
 6878    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6879    assert_eq!(
 6880        cx.read_from_clipboard()
 6881            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6882        Some(
 6883            "for selection in selections.iter() {
 6884let mut start = selection.start;
 6885let mut end = selection.end;
 6886let is_entire_line = selection.is_empty();
 6887if is_entire_line {
 6888    start = Point::new(start.row, 0);"
 6889                .to_string()
 6890        ),
 6891        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6892    );
 6893
 6894    cx.set_state(
 6895        r#"       «ˇ     for selection in selections.iter() {
 6896            let mut start = selection.start;
 6897            let mut end = selection.end;
 6898            let is_entire_line = selection.is_empty();
 6899            if is_entire_line {
 6900                start = Point::new(start.row, 0);»
 6901                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6902            }
 6903        "#,
 6904    );
 6905    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6906    assert_eq!(
 6907        cx.read_from_clipboard()
 6908            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6909        Some(
 6910            "     for selection in selections.iter() {
 6911            let mut start = selection.start;
 6912            let mut end = selection.end;
 6913            let is_entire_line = selection.is_empty();
 6914            if is_entire_line {
 6915                start = Point::new(start.row, 0);"
 6916                .to_string()
 6917        ),
 6918        "Regular copying for reverse selection works the same",
 6919    );
 6920    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6921    assert_eq!(
 6922        cx.read_from_clipboard()
 6923            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6924        Some(
 6925            "for selection in selections.iter() {
 6926let mut start = selection.start;
 6927let mut end = selection.end;
 6928let is_entire_line = selection.is_empty();
 6929if is_entire_line {
 6930    start = Point::new(start.row, 0);"
 6931                .to_string()
 6932        ),
 6933        "Copying with stripping for reverse selection works the same"
 6934    );
 6935
 6936    cx.set_state(
 6937        r#"            for selection «in selections.iter() {
 6938            let mut start = selection.start;
 6939            let mut end = selection.end;
 6940            let is_entire_line = selection.is_empty();
 6941            if is_entire_line {
 6942                start = Point::new(start.row, 0);ˇ»
 6943                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6944            }
 6945        "#,
 6946    );
 6947    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6948    assert_eq!(
 6949        cx.read_from_clipboard()
 6950            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6951        Some(
 6952            "in selections.iter() {
 6953            let mut start = selection.start;
 6954            let mut end = selection.end;
 6955            let is_entire_line = selection.is_empty();
 6956            if is_entire_line {
 6957                start = Point::new(start.row, 0);"
 6958                .to_string()
 6959        ),
 6960        "When selecting past the indent, the copying works as usual",
 6961    );
 6962    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6963    assert_eq!(
 6964        cx.read_from_clipboard()
 6965            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6966        Some(
 6967            "in selections.iter() {
 6968            let mut start = selection.start;
 6969            let mut end = selection.end;
 6970            let is_entire_line = selection.is_empty();
 6971            if is_entire_line {
 6972                start = Point::new(start.row, 0);"
 6973                .to_string()
 6974        ),
 6975        "When selecting past the indent, nothing is trimmed"
 6976    );
 6977
 6978    cx.set_state(
 6979        r#"            «for selection in selections.iter() {
 6980            let mut start = selection.start;
 6981
 6982            let mut end = selection.end;
 6983            let is_entire_line = selection.is_empty();
 6984            if is_entire_line {
 6985                start = Point::new(start.row, 0);
 6986ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6987            }
 6988        "#,
 6989    );
 6990    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6991    assert_eq!(
 6992        cx.read_from_clipboard()
 6993            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6994        Some(
 6995            "for selection in selections.iter() {
 6996let mut start = selection.start;
 6997
 6998let mut end = selection.end;
 6999let is_entire_line = selection.is_empty();
 7000if is_entire_line {
 7001    start = Point::new(start.row, 0);
 7002"
 7003            .to_string()
 7004        ),
 7005        "Copying with stripping should ignore empty lines"
 7006    );
 7007}
 7008
 7009#[gpui::test]
 7010async fn test_paste_multiline(cx: &mut TestAppContext) {
 7011    init_test(cx, |_| {});
 7012
 7013    let mut cx = EditorTestContext::new(cx).await;
 7014    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7015
 7016    // Cut an indented block, without the leading whitespace.
 7017    cx.set_state(indoc! {"
 7018        const a: B = (
 7019            c(),
 7020            «d(
 7021                e,
 7022                f
 7023            )ˇ»
 7024        );
 7025    "});
 7026    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7027    cx.assert_editor_state(indoc! {"
 7028        const a: B = (
 7029            c(),
 7030            ˇ
 7031        );
 7032    "});
 7033
 7034    // Paste it at the same position.
 7035    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7036    cx.assert_editor_state(indoc! {"
 7037        const a: B = (
 7038            c(),
 7039            d(
 7040                e,
 7041                f
 7042 7043        );
 7044    "});
 7045
 7046    // Paste it at a line with a lower indent level.
 7047    cx.set_state(indoc! {"
 7048        ˇ
 7049        const a: B = (
 7050            c(),
 7051        );
 7052    "});
 7053    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7054    cx.assert_editor_state(indoc! {"
 7055        d(
 7056            e,
 7057            f
 7058 7059        const a: B = (
 7060            c(),
 7061        );
 7062    "});
 7063
 7064    // Cut an indented block, with the leading whitespace.
 7065    cx.set_state(indoc! {"
 7066        const a: B = (
 7067            c(),
 7068        «    d(
 7069                e,
 7070                f
 7071            )
 7072        ˇ»);
 7073    "});
 7074    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7075    cx.assert_editor_state(indoc! {"
 7076        const a: B = (
 7077            c(),
 7078        ˇ);
 7079    "});
 7080
 7081    // Paste it at the same position.
 7082    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7083    cx.assert_editor_state(indoc! {"
 7084        const a: B = (
 7085            c(),
 7086            d(
 7087                e,
 7088                f
 7089            )
 7090        ˇ);
 7091    "});
 7092
 7093    // Paste it at a line with a higher indent level.
 7094    cx.set_state(indoc! {"
 7095        const a: B = (
 7096            c(),
 7097            d(
 7098                e,
 7099 7100            )
 7101        );
 7102    "});
 7103    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7104    cx.assert_editor_state(indoc! {"
 7105        const a: B = (
 7106            c(),
 7107            d(
 7108                e,
 7109                f    d(
 7110                    e,
 7111                    f
 7112                )
 7113        ˇ
 7114            )
 7115        );
 7116    "});
 7117
 7118    // Copy an indented block, starting mid-line
 7119    cx.set_state(indoc! {"
 7120        const a: B = (
 7121            c(),
 7122            somethin«g(
 7123                e,
 7124                f
 7125            )ˇ»
 7126        );
 7127    "});
 7128    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7129
 7130    // Paste it on a line with a lower indent level
 7131    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7132    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7133    cx.assert_editor_state(indoc! {"
 7134        const a: B = (
 7135            c(),
 7136            something(
 7137                e,
 7138                f
 7139            )
 7140        );
 7141        g(
 7142            e,
 7143            f
 7144"});
 7145}
 7146
 7147#[gpui::test]
 7148async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7149    init_test(cx, |_| {});
 7150
 7151    cx.write_to_clipboard(ClipboardItem::new_string(
 7152        "    d(\n        e\n    );\n".into(),
 7153    ));
 7154
 7155    let mut cx = EditorTestContext::new(cx).await;
 7156    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7157
 7158    cx.set_state(indoc! {"
 7159        fn a() {
 7160            b();
 7161            if c() {
 7162                ˇ
 7163            }
 7164        }
 7165    "});
 7166
 7167    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7168    cx.assert_editor_state(indoc! {"
 7169        fn a() {
 7170            b();
 7171            if c() {
 7172                d(
 7173                    e
 7174                );
 7175        ˇ
 7176            }
 7177        }
 7178    "});
 7179
 7180    cx.set_state(indoc! {"
 7181        fn a() {
 7182            b();
 7183            ˇ
 7184        }
 7185    "});
 7186
 7187    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7188    cx.assert_editor_state(indoc! {"
 7189        fn a() {
 7190            b();
 7191            d(
 7192                e
 7193            );
 7194        ˇ
 7195        }
 7196    "});
 7197}
 7198
 7199#[gpui::test]
 7200fn test_select_all(cx: &mut TestAppContext) {
 7201    init_test(cx, |_| {});
 7202
 7203    let editor = cx.add_window(|window, cx| {
 7204        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7205        build_editor(buffer, window, cx)
 7206    });
 7207    _ = editor.update(cx, |editor, window, cx| {
 7208        editor.select_all(&SelectAll, window, cx);
 7209        assert_eq!(
 7210            editor.selections.display_ranges(cx),
 7211            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7212        );
 7213    });
 7214}
 7215
 7216#[gpui::test]
 7217fn test_select_line(cx: &mut TestAppContext) {
 7218    init_test(cx, |_| {});
 7219
 7220    let editor = cx.add_window(|window, cx| {
 7221        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7222        build_editor(buffer, window, cx)
 7223    });
 7224    _ = editor.update(cx, |editor, window, cx| {
 7225        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7226            s.select_display_ranges([
 7227                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7228                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7229                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7230                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7231            ])
 7232        });
 7233        editor.select_line(&SelectLine, window, cx);
 7234        assert_eq!(
 7235            editor.selections.display_ranges(cx),
 7236            vec![
 7237                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7238                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7239            ]
 7240        );
 7241    });
 7242
 7243    _ = editor.update(cx, |editor, window, cx| {
 7244        editor.select_line(&SelectLine, window, cx);
 7245        assert_eq!(
 7246            editor.selections.display_ranges(cx),
 7247            vec![
 7248                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7249                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7250            ]
 7251        );
 7252    });
 7253
 7254    _ = editor.update(cx, |editor, window, cx| {
 7255        editor.select_line(&SelectLine, window, cx);
 7256        assert_eq!(
 7257            editor.selections.display_ranges(cx),
 7258            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7259        );
 7260    });
 7261}
 7262
 7263#[gpui::test]
 7264async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7265    init_test(cx, |_| {});
 7266    let mut cx = EditorTestContext::new(cx).await;
 7267
 7268    #[track_caller]
 7269    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7270        cx.set_state(initial_state);
 7271        cx.update_editor(|e, window, cx| {
 7272            e.split_selection_into_lines(&Default::default(), window, cx)
 7273        });
 7274        cx.assert_editor_state(expected_state);
 7275    }
 7276
 7277    // Selection starts and ends at the middle of lines, left-to-right
 7278    test(
 7279        &mut cx,
 7280        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7281        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7282    );
 7283    // Same thing, right-to-left
 7284    test(
 7285        &mut cx,
 7286        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7287        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7288    );
 7289
 7290    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7291    test(
 7292        &mut cx,
 7293        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7294        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7295    );
 7296    // Same thing, right-to-left
 7297    test(
 7298        &mut cx,
 7299        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7300        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7301    );
 7302
 7303    // Whole buffer, left-to-right, last line ends with newline
 7304    test(
 7305        &mut cx,
 7306        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7307        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7308    );
 7309    // Same thing, right-to-left
 7310    test(
 7311        &mut cx,
 7312        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7313        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7314    );
 7315
 7316    // Starts at the end of a line, ends at the start of another
 7317    test(
 7318        &mut cx,
 7319        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7320        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7321    );
 7322}
 7323
 7324#[gpui::test]
 7325async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7326    init_test(cx, |_| {});
 7327
 7328    let editor = cx.add_window(|window, cx| {
 7329        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7330        build_editor(buffer, window, cx)
 7331    });
 7332
 7333    // setup
 7334    _ = editor.update(cx, |editor, window, cx| {
 7335        editor.fold_creases(
 7336            vec![
 7337                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7338                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7339                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7340            ],
 7341            true,
 7342            window,
 7343            cx,
 7344        );
 7345        assert_eq!(
 7346            editor.display_text(cx),
 7347            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7348        );
 7349    });
 7350
 7351    _ = editor.update(cx, |editor, window, cx| {
 7352        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7353            s.select_display_ranges([
 7354                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7355                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7356                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7357                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7358            ])
 7359        });
 7360        editor.split_selection_into_lines(&Default::default(), window, cx);
 7361        assert_eq!(
 7362            editor.display_text(cx),
 7363            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7364        );
 7365    });
 7366    EditorTestContext::for_editor(editor, cx)
 7367        .await
 7368        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7369
 7370    _ = editor.update(cx, |editor, window, cx| {
 7371        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7372            s.select_display_ranges([
 7373                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7374            ])
 7375        });
 7376        editor.split_selection_into_lines(&Default::default(), window, cx);
 7377        assert_eq!(
 7378            editor.display_text(cx),
 7379            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7380        );
 7381        assert_eq!(
 7382            editor.selections.display_ranges(cx),
 7383            [
 7384                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7385                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7386                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7387                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7388                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7389                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7390                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7391            ]
 7392        );
 7393    });
 7394    EditorTestContext::for_editor(editor, cx)
 7395        .await
 7396        .assert_editor_state(
 7397            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7398        );
 7399}
 7400
 7401#[gpui::test]
 7402async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7403    init_test(cx, |_| {});
 7404
 7405    let mut cx = EditorTestContext::new(cx).await;
 7406
 7407    cx.set_state(indoc!(
 7408        r#"abc
 7409           defˇghi
 7410
 7411           jk
 7412           nlmo
 7413           "#
 7414    ));
 7415
 7416    cx.update_editor(|editor, window, cx| {
 7417        editor.add_selection_above(&Default::default(), window, cx);
 7418    });
 7419
 7420    cx.assert_editor_state(indoc!(
 7421        r#"abcˇ
 7422           defˇghi
 7423
 7424           jk
 7425           nlmo
 7426           "#
 7427    ));
 7428
 7429    cx.update_editor(|editor, window, cx| {
 7430        editor.add_selection_above(&Default::default(), window, cx);
 7431    });
 7432
 7433    cx.assert_editor_state(indoc!(
 7434        r#"abcˇ
 7435            defˇghi
 7436
 7437            jk
 7438            nlmo
 7439            "#
 7440    ));
 7441
 7442    cx.update_editor(|editor, window, cx| {
 7443        editor.add_selection_below(&Default::default(), window, cx);
 7444    });
 7445
 7446    cx.assert_editor_state(indoc!(
 7447        r#"abc
 7448           defˇghi
 7449
 7450           jk
 7451           nlmo
 7452           "#
 7453    ));
 7454
 7455    cx.update_editor(|editor, window, cx| {
 7456        editor.undo_selection(&Default::default(), window, cx);
 7457    });
 7458
 7459    cx.assert_editor_state(indoc!(
 7460        r#"abcˇ
 7461           defˇghi
 7462
 7463           jk
 7464           nlmo
 7465           "#
 7466    ));
 7467
 7468    cx.update_editor(|editor, window, cx| {
 7469        editor.redo_selection(&Default::default(), window, cx);
 7470    });
 7471
 7472    cx.assert_editor_state(indoc!(
 7473        r#"abc
 7474           defˇghi
 7475
 7476           jk
 7477           nlmo
 7478           "#
 7479    ));
 7480
 7481    cx.update_editor(|editor, window, cx| {
 7482        editor.add_selection_below(&Default::default(), window, cx);
 7483    });
 7484
 7485    cx.assert_editor_state(indoc!(
 7486        r#"abc
 7487           defˇghi
 7488           ˇ
 7489           jk
 7490           nlmo
 7491           "#
 7492    ));
 7493
 7494    cx.update_editor(|editor, window, cx| {
 7495        editor.add_selection_below(&Default::default(), window, cx);
 7496    });
 7497
 7498    cx.assert_editor_state(indoc!(
 7499        r#"abc
 7500           defˇghi
 7501           ˇ
 7502           jkˇ
 7503           nlmo
 7504           "#
 7505    ));
 7506
 7507    cx.update_editor(|editor, window, cx| {
 7508        editor.add_selection_below(&Default::default(), window, cx);
 7509    });
 7510
 7511    cx.assert_editor_state(indoc!(
 7512        r#"abc
 7513           defˇghi
 7514           ˇ
 7515           jkˇ
 7516           nlmˇo
 7517           "#
 7518    ));
 7519
 7520    cx.update_editor(|editor, window, cx| {
 7521        editor.add_selection_below(&Default::default(), window, cx);
 7522    });
 7523
 7524    cx.assert_editor_state(indoc!(
 7525        r#"abc
 7526           defˇghi
 7527           ˇ
 7528           jkˇ
 7529           nlmˇo
 7530           ˇ"#
 7531    ));
 7532
 7533    // change selections
 7534    cx.set_state(indoc!(
 7535        r#"abc
 7536           def«ˇg»hi
 7537
 7538           jk
 7539           nlmo
 7540           "#
 7541    ));
 7542
 7543    cx.update_editor(|editor, window, cx| {
 7544        editor.add_selection_below(&Default::default(), window, cx);
 7545    });
 7546
 7547    cx.assert_editor_state(indoc!(
 7548        r#"abc
 7549           def«ˇg»hi
 7550
 7551           jk
 7552           nlm«ˇo»
 7553           "#
 7554    ));
 7555
 7556    cx.update_editor(|editor, window, cx| {
 7557        editor.add_selection_below(&Default::default(), window, cx);
 7558    });
 7559
 7560    cx.assert_editor_state(indoc!(
 7561        r#"abc
 7562           def«ˇg»hi
 7563
 7564           jk
 7565           nlm«ˇo»
 7566           "#
 7567    ));
 7568
 7569    cx.update_editor(|editor, window, cx| {
 7570        editor.add_selection_above(&Default::default(), window, cx);
 7571    });
 7572
 7573    cx.assert_editor_state(indoc!(
 7574        r#"abc
 7575           def«ˇg»hi
 7576
 7577           jk
 7578           nlmo
 7579           "#
 7580    ));
 7581
 7582    cx.update_editor(|editor, window, cx| {
 7583        editor.add_selection_above(&Default::default(), window, cx);
 7584    });
 7585
 7586    cx.assert_editor_state(indoc!(
 7587        r#"abc
 7588           def«ˇg»hi
 7589
 7590           jk
 7591           nlmo
 7592           "#
 7593    ));
 7594
 7595    // Change selections again
 7596    cx.set_state(indoc!(
 7597        r#"a«bc
 7598           defgˇ»hi
 7599
 7600           jk
 7601           nlmo
 7602           "#
 7603    ));
 7604
 7605    cx.update_editor(|editor, window, cx| {
 7606        editor.add_selection_below(&Default::default(), window, cx);
 7607    });
 7608
 7609    cx.assert_editor_state(indoc!(
 7610        r#"a«bcˇ»
 7611           d«efgˇ»hi
 7612
 7613           j«kˇ»
 7614           nlmo
 7615           "#
 7616    ));
 7617
 7618    cx.update_editor(|editor, window, cx| {
 7619        editor.add_selection_below(&Default::default(), window, cx);
 7620    });
 7621    cx.assert_editor_state(indoc!(
 7622        r#"a«bcˇ»
 7623           d«efgˇ»hi
 7624
 7625           j«kˇ»
 7626           n«lmoˇ»
 7627           "#
 7628    ));
 7629    cx.update_editor(|editor, window, cx| {
 7630        editor.add_selection_above(&Default::default(), window, cx);
 7631    });
 7632
 7633    cx.assert_editor_state(indoc!(
 7634        r#"a«bcˇ»
 7635           d«efgˇ»hi
 7636
 7637           j«kˇ»
 7638           nlmo
 7639           "#
 7640    ));
 7641
 7642    // Change selections again
 7643    cx.set_state(indoc!(
 7644        r#"abc
 7645           d«ˇefghi
 7646
 7647           jk
 7648           nlm»o
 7649           "#
 7650    ));
 7651
 7652    cx.update_editor(|editor, window, cx| {
 7653        editor.add_selection_above(&Default::default(), window, cx);
 7654    });
 7655
 7656    cx.assert_editor_state(indoc!(
 7657        r#"a«ˇbc»
 7658           d«ˇef»ghi
 7659
 7660           j«ˇk»
 7661           n«ˇlm»o
 7662           "#
 7663    ));
 7664
 7665    cx.update_editor(|editor, window, cx| {
 7666        editor.add_selection_below(&Default::default(), window, cx);
 7667    });
 7668
 7669    cx.assert_editor_state(indoc!(
 7670        r#"abc
 7671           d«ˇef»ghi
 7672
 7673           j«ˇk»
 7674           n«ˇlm»o
 7675           "#
 7676    ));
 7677}
 7678
 7679#[gpui::test]
 7680async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7681    init_test(cx, |_| {});
 7682    let mut cx = EditorTestContext::new(cx).await;
 7683
 7684    cx.set_state(indoc!(
 7685        r#"line onˇe
 7686           liˇne two
 7687           line three
 7688           line four"#
 7689    ));
 7690
 7691    cx.update_editor(|editor, window, cx| {
 7692        editor.add_selection_below(&Default::default(), window, cx);
 7693    });
 7694
 7695    // test multiple cursors expand in the same direction
 7696    cx.assert_editor_state(indoc!(
 7697        r#"line onˇe
 7698           liˇne twˇo
 7699           liˇne three
 7700           line four"#
 7701    ));
 7702
 7703    cx.update_editor(|editor, window, cx| {
 7704        editor.add_selection_below(&Default::default(), window, cx);
 7705    });
 7706
 7707    cx.update_editor(|editor, window, cx| {
 7708        editor.add_selection_below(&Default::default(), window, cx);
 7709    });
 7710
 7711    // test multiple cursors expand below overflow
 7712    cx.assert_editor_state(indoc!(
 7713        r#"line onˇe
 7714           liˇne twˇo
 7715           liˇne thˇree
 7716           liˇne foˇur"#
 7717    ));
 7718
 7719    cx.update_editor(|editor, window, cx| {
 7720        editor.add_selection_above(&Default::default(), window, cx);
 7721    });
 7722
 7723    // test multiple cursors retrieves back correctly
 7724    cx.assert_editor_state(indoc!(
 7725        r#"line onˇe
 7726           liˇne twˇo
 7727           liˇne thˇree
 7728           line four"#
 7729    ));
 7730
 7731    cx.update_editor(|editor, window, cx| {
 7732        editor.add_selection_above(&Default::default(), window, cx);
 7733    });
 7734
 7735    cx.update_editor(|editor, window, cx| {
 7736        editor.add_selection_above(&Default::default(), window, cx);
 7737    });
 7738
 7739    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7740    cx.assert_editor_state(indoc!(
 7741        r#"liˇne onˇe
 7742           liˇne two
 7743           line three
 7744           line four"#
 7745    ));
 7746
 7747    cx.update_editor(|editor, window, cx| {
 7748        editor.undo_selection(&Default::default(), window, cx);
 7749    });
 7750
 7751    // test undo
 7752    cx.assert_editor_state(indoc!(
 7753        r#"line onˇe
 7754           liˇne twˇo
 7755           line three
 7756           line four"#
 7757    ));
 7758
 7759    cx.update_editor(|editor, window, cx| {
 7760        editor.redo_selection(&Default::default(), window, cx);
 7761    });
 7762
 7763    // test redo
 7764    cx.assert_editor_state(indoc!(
 7765        r#"liˇne onˇe
 7766           liˇne two
 7767           line three
 7768           line four"#
 7769    ));
 7770
 7771    cx.set_state(indoc!(
 7772        r#"abcd
 7773           ef«ghˇ»
 7774           ijkl
 7775           «mˇ»nop"#
 7776    ));
 7777
 7778    cx.update_editor(|editor, window, cx| {
 7779        editor.add_selection_above(&Default::default(), window, cx);
 7780    });
 7781
 7782    // test multiple selections expand in the same direction
 7783    cx.assert_editor_state(indoc!(
 7784        r#"ab«cdˇ»
 7785           ef«ghˇ»
 7786           «iˇ»jkl
 7787           «mˇ»nop"#
 7788    ));
 7789
 7790    cx.update_editor(|editor, window, cx| {
 7791        editor.add_selection_above(&Default::default(), window, cx);
 7792    });
 7793
 7794    // test multiple selection upward overflow
 7795    cx.assert_editor_state(indoc!(
 7796        r#"ab«cdˇ»
 7797           «eˇ»f«ghˇ»
 7798           «iˇ»jkl
 7799           «mˇ»nop"#
 7800    ));
 7801
 7802    cx.update_editor(|editor, window, cx| {
 7803        editor.add_selection_below(&Default::default(), window, cx);
 7804    });
 7805
 7806    // test multiple selection retrieves back correctly
 7807    cx.assert_editor_state(indoc!(
 7808        r#"abcd
 7809           ef«ghˇ»
 7810           «iˇ»jkl
 7811           «mˇ»nop"#
 7812    ));
 7813
 7814    cx.update_editor(|editor, window, cx| {
 7815        editor.add_selection_below(&Default::default(), window, cx);
 7816    });
 7817
 7818    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7819    cx.assert_editor_state(indoc!(
 7820        r#"abcd
 7821           ef«ghˇ»
 7822           ij«klˇ»
 7823           «mˇ»nop"#
 7824    ));
 7825
 7826    cx.update_editor(|editor, window, cx| {
 7827        editor.undo_selection(&Default::default(), window, cx);
 7828    });
 7829
 7830    // test undo
 7831    cx.assert_editor_state(indoc!(
 7832        r#"abcd
 7833           ef«ghˇ»
 7834           «iˇ»jkl
 7835           «mˇ»nop"#
 7836    ));
 7837
 7838    cx.update_editor(|editor, window, cx| {
 7839        editor.redo_selection(&Default::default(), window, cx);
 7840    });
 7841
 7842    // test redo
 7843    cx.assert_editor_state(indoc!(
 7844        r#"abcd
 7845           ef«ghˇ»
 7846           ij«klˇ»
 7847           «mˇ»nop"#
 7848    ));
 7849}
 7850
 7851#[gpui::test]
 7852async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7853    init_test(cx, |_| {});
 7854    let mut cx = EditorTestContext::new(cx).await;
 7855
 7856    cx.set_state(indoc!(
 7857        r#"line onˇe
 7858           liˇne two
 7859           line three
 7860           line four"#
 7861    ));
 7862
 7863    cx.update_editor(|editor, window, cx| {
 7864        editor.add_selection_below(&Default::default(), window, cx);
 7865        editor.add_selection_below(&Default::default(), window, cx);
 7866        editor.add_selection_below(&Default::default(), window, cx);
 7867    });
 7868
 7869    // initial state with two multi cursor groups
 7870    cx.assert_editor_state(indoc!(
 7871        r#"line onˇe
 7872           liˇne twˇo
 7873           liˇne thˇree
 7874           liˇne foˇur"#
 7875    ));
 7876
 7877    // add single cursor in middle - simulate opt click
 7878    cx.update_editor(|editor, window, cx| {
 7879        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7880        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7881        editor.end_selection(window, cx);
 7882    });
 7883
 7884    cx.assert_editor_state(indoc!(
 7885        r#"line onˇe
 7886           liˇne twˇo
 7887           liˇneˇ thˇree
 7888           liˇne foˇur"#
 7889    ));
 7890
 7891    cx.update_editor(|editor, window, cx| {
 7892        editor.add_selection_above(&Default::default(), window, cx);
 7893    });
 7894
 7895    // test new added selection expands above and existing selection shrinks
 7896    cx.assert_editor_state(indoc!(
 7897        r#"line onˇe
 7898           liˇneˇ twˇo
 7899           liˇneˇ thˇree
 7900           line four"#
 7901    ));
 7902
 7903    cx.update_editor(|editor, window, cx| {
 7904        editor.add_selection_above(&Default::default(), window, cx);
 7905    });
 7906
 7907    // test new added selection expands above and existing selection shrinks
 7908    cx.assert_editor_state(indoc!(
 7909        r#"lineˇ onˇe
 7910           liˇneˇ twˇo
 7911           lineˇ three
 7912           line four"#
 7913    ));
 7914
 7915    // intial state with two selection groups
 7916    cx.set_state(indoc!(
 7917        r#"abcd
 7918           ef«ghˇ»
 7919           ijkl
 7920           «mˇ»nop"#
 7921    ));
 7922
 7923    cx.update_editor(|editor, window, cx| {
 7924        editor.add_selection_above(&Default::default(), window, cx);
 7925        editor.add_selection_above(&Default::default(), window, cx);
 7926    });
 7927
 7928    cx.assert_editor_state(indoc!(
 7929        r#"ab«cdˇ»
 7930           «eˇ»f«ghˇ»
 7931           «iˇ»jkl
 7932           «mˇ»nop"#
 7933    ));
 7934
 7935    // add single selection in middle - simulate opt drag
 7936    cx.update_editor(|editor, window, cx| {
 7937        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7938        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7939        editor.update_selection(
 7940            DisplayPoint::new(DisplayRow(2), 4),
 7941            0,
 7942            gpui::Point::<f32>::default(),
 7943            window,
 7944            cx,
 7945        );
 7946        editor.end_selection(window, cx);
 7947    });
 7948
 7949    cx.assert_editor_state(indoc!(
 7950        r#"ab«cdˇ»
 7951           «eˇ»f«ghˇ»
 7952           «iˇ»jk«lˇ»
 7953           «mˇ»nop"#
 7954    ));
 7955
 7956    cx.update_editor(|editor, window, cx| {
 7957        editor.add_selection_below(&Default::default(), window, cx);
 7958    });
 7959
 7960    // test new added selection expands below, others shrinks from above
 7961    cx.assert_editor_state(indoc!(
 7962        r#"abcd
 7963           ef«ghˇ»
 7964           «iˇ»jk«lˇ»
 7965           «mˇ»no«pˇ»"#
 7966    ));
 7967}
 7968
 7969#[gpui::test]
 7970async fn test_select_next(cx: &mut TestAppContext) {
 7971    init_test(cx, |_| {});
 7972
 7973    let mut cx = EditorTestContext::new(cx).await;
 7974    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7975
 7976    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7977        .unwrap();
 7978    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7979
 7980    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7981        .unwrap();
 7982    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7983
 7984    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7985    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7986
 7987    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7988    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7989
 7990    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7991        .unwrap();
 7992    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7993
 7994    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7995        .unwrap();
 7996    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7997
 7998    // Test selection direction should be preserved
 7999    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8000
 8001    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8002        .unwrap();
 8003    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8004}
 8005
 8006#[gpui::test]
 8007async fn test_select_all_matches(cx: &mut TestAppContext) {
 8008    init_test(cx, |_| {});
 8009
 8010    let mut cx = EditorTestContext::new(cx).await;
 8011
 8012    // Test caret-only selections
 8013    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8014    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8015        .unwrap();
 8016    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8017
 8018    // Test left-to-right selections
 8019    cx.set_state("abc\n«abcˇ»\nabc");
 8020    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8021        .unwrap();
 8022    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8023
 8024    // Test right-to-left selections
 8025    cx.set_state("abc\n«ˇabc»\nabc");
 8026    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8027        .unwrap();
 8028    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8029
 8030    // Test selecting whitespace with caret selection
 8031    cx.set_state("abc\nˇ   abc\nabc");
 8032    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8033        .unwrap();
 8034    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8035
 8036    // Test selecting whitespace with left-to-right selection
 8037    cx.set_state("abc\n«ˇ  »abc\nabc");
 8038    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8039        .unwrap();
 8040    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8041
 8042    // Test no matches with right-to-left selection
 8043    cx.set_state("abc\n«  ˇ»abc\nabc");
 8044    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8045        .unwrap();
 8046    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8047
 8048    // Test with a single word and clip_at_line_ends=true (#29823)
 8049    cx.set_state("aˇbc");
 8050    cx.update_editor(|e, window, cx| {
 8051        e.set_clip_at_line_ends(true, cx);
 8052        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8053        e.set_clip_at_line_ends(false, cx);
 8054    });
 8055    cx.assert_editor_state("«abcˇ»");
 8056}
 8057
 8058#[gpui::test]
 8059async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8060    init_test(cx, |_| {});
 8061
 8062    let mut cx = EditorTestContext::new(cx).await;
 8063
 8064    let large_body_1 = "\nd".repeat(200);
 8065    let large_body_2 = "\ne".repeat(200);
 8066
 8067    cx.set_state(&format!(
 8068        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8069    ));
 8070    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8071        let scroll_position = editor.scroll_position(cx);
 8072        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8073        scroll_position
 8074    });
 8075
 8076    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8077        .unwrap();
 8078    cx.assert_editor_state(&format!(
 8079        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8080    ));
 8081    let scroll_position_after_selection =
 8082        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8083    assert_eq!(
 8084        initial_scroll_position, scroll_position_after_selection,
 8085        "Scroll position should not change after selecting all matches"
 8086    );
 8087}
 8088
 8089#[gpui::test]
 8090async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8091    init_test(cx, |_| {});
 8092
 8093    let mut cx = EditorLspTestContext::new_rust(
 8094        lsp::ServerCapabilities {
 8095            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8096            ..Default::default()
 8097        },
 8098        cx,
 8099    )
 8100    .await;
 8101
 8102    cx.set_state(indoc! {"
 8103        line 1
 8104        line 2
 8105        linˇe 3
 8106        line 4
 8107        line 5
 8108    "});
 8109
 8110    // Make an edit
 8111    cx.update_editor(|editor, window, cx| {
 8112        editor.handle_input("X", window, cx);
 8113    });
 8114
 8115    // Move cursor to a different position
 8116    cx.update_editor(|editor, window, cx| {
 8117        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8118            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8119        });
 8120    });
 8121
 8122    cx.assert_editor_state(indoc! {"
 8123        line 1
 8124        line 2
 8125        linXe 3
 8126        line 4
 8127        liˇne 5
 8128    "});
 8129
 8130    cx.lsp
 8131        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8132            Ok(Some(vec![lsp::TextEdit::new(
 8133                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8134                "PREFIX ".to_string(),
 8135            )]))
 8136        });
 8137
 8138    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8139        .unwrap()
 8140        .await
 8141        .unwrap();
 8142
 8143    cx.assert_editor_state(indoc! {"
 8144        PREFIX line 1
 8145        line 2
 8146        linXe 3
 8147        line 4
 8148        liˇne 5
 8149    "});
 8150
 8151    // Undo formatting
 8152    cx.update_editor(|editor, window, cx| {
 8153        editor.undo(&Default::default(), window, cx);
 8154    });
 8155
 8156    // Verify cursor moved back to position after edit
 8157    cx.assert_editor_state(indoc! {"
 8158        line 1
 8159        line 2
 8160        linXˇe 3
 8161        line 4
 8162        line 5
 8163    "});
 8164}
 8165
 8166#[gpui::test]
 8167async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8168    init_test(cx, |_| {});
 8169
 8170    let mut cx = EditorTestContext::new(cx).await;
 8171
 8172    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8173    cx.update_editor(|editor, window, cx| {
 8174        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8175    });
 8176
 8177    cx.set_state(indoc! {"
 8178        line 1
 8179        line 2
 8180        linˇe 3
 8181        line 4
 8182        line 5
 8183        line 6
 8184        line 7
 8185        line 8
 8186        line 9
 8187        line 10
 8188    "});
 8189
 8190    let snapshot = cx.buffer_snapshot();
 8191    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8192
 8193    cx.update(|_, cx| {
 8194        provider.update(cx, |provider, _| {
 8195            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 8196                id: None,
 8197                edits: vec![(edit_position..edit_position, "X".into())],
 8198                edit_preview: None,
 8199            }))
 8200        })
 8201    });
 8202
 8203    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8204    cx.update_editor(|editor, window, cx| {
 8205        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8206    });
 8207
 8208    cx.assert_editor_state(indoc! {"
 8209        line 1
 8210        line 2
 8211        lineXˇ 3
 8212        line 4
 8213        line 5
 8214        line 6
 8215        line 7
 8216        line 8
 8217        line 9
 8218        line 10
 8219    "});
 8220
 8221    cx.update_editor(|editor, window, cx| {
 8222        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8223            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8224        });
 8225    });
 8226
 8227    cx.assert_editor_state(indoc! {"
 8228        line 1
 8229        line 2
 8230        lineX 3
 8231        line 4
 8232        line 5
 8233        line 6
 8234        line 7
 8235        line 8
 8236        line 9
 8237        liˇne 10
 8238    "});
 8239
 8240    cx.update_editor(|editor, window, cx| {
 8241        editor.undo(&Default::default(), window, cx);
 8242    });
 8243
 8244    cx.assert_editor_state(indoc! {"
 8245        line 1
 8246        line 2
 8247        lineˇ 3
 8248        line 4
 8249        line 5
 8250        line 6
 8251        line 7
 8252        line 8
 8253        line 9
 8254        line 10
 8255    "});
 8256}
 8257
 8258#[gpui::test]
 8259async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8260    init_test(cx, |_| {});
 8261
 8262    let mut cx = EditorTestContext::new(cx).await;
 8263    cx.set_state(
 8264        r#"let foo = 2;
 8265lˇet foo = 2;
 8266let fooˇ = 2;
 8267let foo = 2;
 8268let foo = ˇ2;"#,
 8269    );
 8270
 8271    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8272        .unwrap();
 8273    cx.assert_editor_state(
 8274        r#"let foo = 2;
 8275«letˇ» foo = 2;
 8276let «fooˇ» = 2;
 8277let foo = 2;
 8278let foo = «2ˇ»;"#,
 8279    );
 8280
 8281    // noop for multiple selections with different contents
 8282    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8283        .unwrap();
 8284    cx.assert_editor_state(
 8285        r#"let foo = 2;
 8286«letˇ» foo = 2;
 8287let «fooˇ» = 2;
 8288let foo = 2;
 8289let foo = «2ˇ»;"#,
 8290    );
 8291
 8292    // Test last selection direction should be preserved
 8293    cx.set_state(
 8294        r#"let foo = 2;
 8295let foo = 2;
 8296let «fooˇ» = 2;
 8297let «ˇfoo» = 2;
 8298let foo = 2;"#,
 8299    );
 8300
 8301    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8302        .unwrap();
 8303    cx.assert_editor_state(
 8304        r#"let foo = 2;
 8305let foo = 2;
 8306let «fooˇ» = 2;
 8307let «ˇfoo» = 2;
 8308let «ˇfoo» = 2;"#,
 8309    );
 8310}
 8311
 8312#[gpui::test]
 8313async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8314    init_test(cx, |_| {});
 8315
 8316    let mut cx =
 8317        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8318
 8319    cx.assert_editor_state(indoc! {"
 8320        ˇbbb
 8321        ccc
 8322
 8323        bbb
 8324        ccc
 8325        "});
 8326    cx.dispatch_action(SelectPrevious::default());
 8327    cx.assert_editor_state(indoc! {"
 8328                «bbbˇ»
 8329                ccc
 8330
 8331                bbb
 8332                ccc
 8333                "});
 8334    cx.dispatch_action(SelectPrevious::default());
 8335    cx.assert_editor_state(indoc! {"
 8336                «bbbˇ»
 8337                ccc
 8338
 8339                «bbbˇ»
 8340                ccc
 8341                "});
 8342}
 8343
 8344#[gpui::test]
 8345async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8346    init_test(cx, |_| {});
 8347
 8348    let mut cx = EditorTestContext::new(cx).await;
 8349    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8350
 8351    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8352        .unwrap();
 8353    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8354
 8355    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8356        .unwrap();
 8357    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8358
 8359    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8360    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8361
 8362    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8363    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8364
 8365    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8366        .unwrap();
 8367    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8368
 8369    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8370        .unwrap();
 8371    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8372}
 8373
 8374#[gpui::test]
 8375async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8376    init_test(cx, |_| {});
 8377
 8378    let mut cx = EditorTestContext::new(cx).await;
 8379    cx.set_state("");
 8380
 8381    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8382        .unwrap();
 8383    cx.assert_editor_state("«aˇ»");
 8384    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8385        .unwrap();
 8386    cx.assert_editor_state("«aˇ»");
 8387}
 8388
 8389#[gpui::test]
 8390async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8391    init_test(cx, |_| {});
 8392
 8393    let mut cx = EditorTestContext::new(cx).await;
 8394    cx.set_state(
 8395        r#"let foo = 2;
 8396lˇet foo = 2;
 8397let fooˇ = 2;
 8398let foo = 2;
 8399let foo = ˇ2;"#,
 8400    );
 8401
 8402    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8403        .unwrap();
 8404    cx.assert_editor_state(
 8405        r#"let foo = 2;
 8406«letˇ» foo = 2;
 8407let «fooˇ» = 2;
 8408let foo = 2;
 8409let foo = «2ˇ»;"#,
 8410    );
 8411
 8412    // noop for multiple selections with different contents
 8413    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8414        .unwrap();
 8415    cx.assert_editor_state(
 8416        r#"let foo = 2;
 8417«letˇ» foo = 2;
 8418let «fooˇ» = 2;
 8419let foo = 2;
 8420let foo = «2ˇ»;"#,
 8421    );
 8422}
 8423
 8424#[gpui::test]
 8425async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8426    init_test(cx, |_| {});
 8427
 8428    let mut cx = EditorTestContext::new(cx).await;
 8429    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8430
 8431    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8432        .unwrap();
 8433    // selection direction is preserved
 8434    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8435
 8436    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8437        .unwrap();
 8438    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8439
 8440    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8441    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8442
 8443    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8444    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8445
 8446    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8447        .unwrap();
 8448    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8449
 8450    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8451        .unwrap();
 8452    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8453}
 8454
 8455#[gpui::test]
 8456async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8457    init_test(cx, |_| {});
 8458
 8459    let language = Arc::new(Language::new(
 8460        LanguageConfig::default(),
 8461        Some(tree_sitter_rust::LANGUAGE.into()),
 8462    ));
 8463
 8464    let text = r#"
 8465        use mod1::mod2::{mod3, mod4};
 8466
 8467        fn fn_1(param1: bool, param2: &str) {
 8468            let var1 = "text";
 8469        }
 8470    "#
 8471    .unindent();
 8472
 8473    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8474    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8475    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8476
 8477    editor
 8478        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8479        .await;
 8480
 8481    editor.update_in(cx, |editor, window, cx| {
 8482        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8483            s.select_display_ranges([
 8484                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8485                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8486                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8487            ]);
 8488        });
 8489        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8490    });
 8491    editor.update(cx, |editor, cx| {
 8492        assert_text_with_selections(
 8493            editor,
 8494            indoc! {r#"
 8495                use mod1::mod2::{mod3, «mod4ˇ»};
 8496
 8497                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8498                    let var1 = "«ˇtext»";
 8499                }
 8500            "#},
 8501            cx,
 8502        );
 8503    });
 8504
 8505    editor.update_in(cx, |editor, window, cx| {
 8506        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8507    });
 8508    editor.update(cx, |editor, cx| {
 8509        assert_text_with_selections(
 8510            editor,
 8511            indoc! {r#"
 8512                use mod1::mod2::«{mod3, mod4}ˇ»;
 8513
 8514                «ˇfn fn_1(param1: bool, param2: &str) {
 8515                    let var1 = "text";
 8516 8517            "#},
 8518            cx,
 8519        );
 8520    });
 8521
 8522    editor.update_in(cx, |editor, window, cx| {
 8523        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8524    });
 8525    assert_eq!(
 8526        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8527        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8528    );
 8529
 8530    // Trying to expand the selected syntax node one more time has no effect.
 8531    editor.update_in(cx, |editor, window, cx| {
 8532        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8533    });
 8534    assert_eq!(
 8535        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8536        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8537    );
 8538
 8539    editor.update_in(cx, |editor, window, cx| {
 8540        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8541    });
 8542    editor.update(cx, |editor, cx| {
 8543        assert_text_with_selections(
 8544            editor,
 8545            indoc! {r#"
 8546                use mod1::mod2::«{mod3, mod4}ˇ»;
 8547
 8548                «ˇfn fn_1(param1: bool, param2: &str) {
 8549                    let var1 = "text";
 8550 8551            "#},
 8552            cx,
 8553        );
 8554    });
 8555
 8556    editor.update_in(cx, |editor, window, cx| {
 8557        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8558    });
 8559    editor.update(cx, |editor, cx| {
 8560        assert_text_with_selections(
 8561            editor,
 8562            indoc! {r#"
 8563                use mod1::mod2::{mod3, «mod4ˇ»};
 8564
 8565                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8566                    let var1 = "«ˇtext»";
 8567                }
 8568            "#},
 8569            cx,
 8570        );
 8571    });
 8572
 8573    editor.update_in(cx, |editor, window, cx| {
 8574        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8575    });
 8576    editor.update(cx, |editor, cx| {
 8577        assert_text_with_selections(
 8578            editor,
 8579            indoc! {r#"
 8580                use mod1::mod2::{mod3, mo«ˇ»d4};
 8581
 8582                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8583                    let var1 = "te«ˇ»xt";
 8584                }
 8585            "#},
 8586            cx,
 8587        );
 8588    });
 8589
 8590    // Trying to shrink the selected syntax node one more time has no effect.
 8591    editor.update_in(cx, |editor, window, cx| {
 8592        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8593    });
 8594    editor.update_in(cx, |editor, _, cx| {
 8595        assert_text_with_selections(
 8596            editor,
 8597            indoc! {r#"
 8598                use mod1::mod2::{mod3, mo«ˇ»d4};
 8599
 8600                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8601                    let var1 = "te«ˇ»xt";
 8602                }
 8603            "#},
 8604            cx,
 8605        );
 8606    });
 8607
 8608    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8609    // a fold.
 8610    editor.update_in(cx, |editor, window, cx| {
 8611        editor.fold_creases(
 8612            vec![
 8613                Crease::simple(
 8614                    Point::new(0, 21)..Point::new(0, 24),
 8615                    FoldPlaceholder::test(),
 8616                ),
 8617                Crease::simple(
 8618                    Point::new(3, 20)..Point::new(3, 22),
 8619                    FoldPlaceholder::test(),
 8620                ),
 8621            ],
 8622            true,
 8623            window,
 8624            cx,
 8625        );
 8626        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8627    });
 8628    editor.update(cx, |editor, cx| {
 8629        assert_text_with_selections(
 8630            editor,
 8631            indoc! {r#"
 8632                use mod1::mod2::«{mod3, mod4}ˇ»;
 8633
 8634                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8635                    let var1 = "«ˇtext»";
 8636                }
 8637            "#},
 8638            cx,
 8639        );
 8640    });
 8641}
 8642
 8643#[gpui::test]
 8644async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8645    init_test(cx, |_| {});
 8646
 8647    let language = Arc::new(Language::new(
 8648        LanguageConfig::default(),
 8649        Some(tree_sitter_rust::LANGUAGE.into()),
 8650    ));
 8651
 8652    let text = "let a = 2;";
 8653
 8654    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8655    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8656    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8657
 8658    editor
 8659        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8660        .await;
 8661
 8662    // Test case 1: Cursor at end of word
 8663    editor.update_in(cx, |editor, window, cx| {
 8664        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8665            s.select_display_ranges([
 8666                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8667            ]);
 8668        });
 8669    });
 8670    editor.update(cx, |editor, cx| {
 8671        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8672    });
 8673    editor.update_in(cx, |editor, window, cx| {
 8674        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8675    });
 8676    editor.update(cx, |editor, cx| {
 8677        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8678    });
 8679    editor.update_in(cx, |editor, window, cx| {
 8680        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8681    });
 8682    editor.update(cx, |editor, cx| {
 8683        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8684    });
 8685
 8686    // Test case 2: Cursor at end of statement
 8687    editor.update_in(cx, |editor, window, cx| {
 8688        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8689            s.select_display_ranges([
 8690                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8691            ]);
 8692        });
 8693    });
 8694    editor.update(cx, |editor, cx| {
 8695        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8696    });
 8697    editor.update_in(cx, |editor, window, cx| {
 8698        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8699    });
 8700    editor.update(cx, |editor, cx| {
 8701        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8702    });
 8703}
 8704
 8705#[gpui::test]
 8706async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8707    init_test(cx, |_| {});
 8708
 8709    let language = Arc::new(Language::new(
 8710        LanguageConfig::default(),
 8711        Some(tree_sitter_rust::LANGUAGE.into()),
 8712    ));
 8713
 8714    let text = r#"
 8715        use mod1::mod2::{mod3, mod4};
 8716
 8717        fn fn_1(param1: bool, param2: &str) {
 8718            let var1 = "hello world";
 8719        }
 8720    "#
 8721    .unindent();
 8722
 8723    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8724    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8725    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8726
 8727    editor
 8728        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8729        .await;
 8730
 8731    // Test 1: Cursor on a letter of a string word
 8732    editor.update_in(cx, |editor, window, cx| {
 8733        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8734            s.select_display_ranges([
 8735                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8736            ]);
 8737        });
 8738    });
 8739    editor.update_in(cx, |editor, window, cx| {
 8740        assert_text_with_selections(
 8741            editor,
 8742            indoc! {r#"
 8743                use mod1::mod2::{mod3, mod4};
 8744
 8745                fn fn_1(param1: bool, param2: &str) {
 8746                    let var1 = "hˇello world";
 8747                }
 8748            "#},
 8749            cx,
 8750        );
 8751        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8752        assert_text_with_selections(
 8753            editor,
 8754            indoc! {r#"
 8755                use mod1::mod2::{mod3, mod4};
 8756
 8757                fn fn_1(param1: bool, param2: &str) {
 8758                    let var1 = "«ˇhello» world";
 8759                }
 8760            "#},
 8761            cx,
 8762        );
 8763    });
 8764
 8765    // Test 2: Partial selection within a word
 8766    editor.update_in(cx, |editor, window, cx| {
 8767        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8768            s.select_display_ranges([
 8769                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 8770            ]);
 8771        });
 8772    });
 8773    editor.update_in(cx, |editor, window, cx| {
 8774        assert_text_with_selections(
 8775            editor,
 8776            indoc! {r#"
 8777                use mod1::mod2::{mod3, mod4};
 8778
 8779                fn fn_1(param1: bool, param2: &str) {
 8780                    let var1 = "h«elˇ»lo world";
 8781                }
 8782            "#},
 8783            cx,
 8784        );
 8785        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8786        assert_text_with_selections(
 8787            editor,
 8788            indoc! {r#"
 8789                use mod1::mod2::{mod3, mod4};
 8790
 8791                fn fn_1(param1: bool, param2: &str) {
 8792                    let var1 = "«ˇhello» world";
 8793                }
 8794            "#},
 8795            cx,
 8796        );
 8797    });
 8798
 8799    // Test 3: Complete word already selected
 8800    editor.update_in(cx, |editor, window, cx| {
 8801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8802            s.select_display_ranges([
 8803                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 8804            ]);
 8805        });
 8806    });
 8807    editor.update_in(cx, |editor, window, cx| {
 8808        assert_text_with_selections(
 8809            editor,
 8810            indoc! {r#"
 8811                use mod1::mod2::{mod3, mod4};
 8812
 8813                fn fn_1(param1: bool, param2: &str) {
 8814                    let var1 = "«helloˇ» world";
 8815                }
 8816            "#},
 8817            cx,
 8818        );
 8819        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8820        assert_text_with_selections(
 8821            editor,
 8822            indoc! {r#"
 8823                use mod1::mod2::{mod3, mod4};
 8824
 8825                fn fn_1(param1: bool, param2: &str) {
 8826                    let var1 = "«hello worldˇ»";
 8827                }
 8828            "#},
 8829            cx,
 8830        );
 8831    });
 8832
 8833    // Test 4: Selection spanning across words
 8834    editor.update_in(cx, |editor, window, cx| {
 8835        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8836            s.select_display_ranges([
 8837                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 8838            ]);
 8839        });
 8840    });
 8841    editor.update_in(cx, |editor, window, cx| {
 8842        assert_text_with_selections(
 8843            editor,
 8844            indoc! {r#"
 8845                use mod1::mod2::{mod3, mod4};
 8846
 8847                fn fn_1(param1: bool, param2: &str) {
 8848                    let var1 = "hel«lo woˇ»rld";
 8849                }
 8850            "#},
 8851            cx,
 8852        );
 8853        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8854        assert_text_with_selections(
 8855            editor,
 8856            indoc! {r#"
 8857                use mod1::mod2::{mod3, mod4};
 8858
 8859                fn fn_1(param1: bool, param2: &str) {
 8860                    let var1 = "«ˇhello world»";
 8861                }
 8862            "#},
 8863            cx,
 8864        );
 8865    });
 8866
 8867    // Test 5: Expansion beyond string
 8868    editor.update_in(cx, |editor, window, cx| {
 8869        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8870        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8871        assert_text_with_selections(
 8872            editor,
 8873            indoc! {r#"
 8874                use mod1::mod2::{mod3, mod4};
 8875
 8876                fn fn_1(param1: bool, param2: &str) {
 8877                    «ˇlet var1 = "hello world";»
 8878                }
 8879            "#},
 8880            cx,
 8881        );
 8882    });
 8883}
 8884
 8885#[gpui::test]
 8886async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 8887    init_test(cx, |_| {});
 8888
 8889    let mut cx = EditorTestContext::new(cx).await;
 8890
 8891    let language = Arc::new(Language::new(
 8892        LanguageConfig::default(),
 8893        Some(tree_sitter_rust::LANGUAGE.into()),
 8894    ));
 8895
 8896    cx.update_buffer(|buffer, cx| {
 8897        buffer.set_language(Some(language), cx);
 8898    });
 8899
 8900    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 8901    cx.update_editor(|editor, window, cx| {
 8902        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 8903    });
 8904
 8905    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 8906}
 8907
 8908#[gpui::test]
 8909async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 8910    init_test(cx, |_| {});
 8911
 8912    let base_text = r#"
 8913        impl A {
 8914            // this is an uncommitted comment
 8915
 8916            fn b() {
 8917                c();
 8918            }
 8919
 8920            // this is another uncommitted comment
 8921
 8922            fn d() {
 8923                // e
 8924                // f
 8925            }
 8926        }
 8927
 8928        fn g() {
 8929            // h
 8930        }
 8931    "#
 8932    .unindent();
 8933
 8934    let text = r#"
 8935        ˇimpl A {
 8936
 8937            fn b() {
 8938                c();
 8939            }
 8940
 8941            fn d() {
 8942                // e
 8943                // f
 8944            }
 8945        }
 8946
 8947        fn g() {
 8948            // h
 8949        }
 8950    "#
 8951    .unindent();
 8952
 8953    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8954    cx.set_state(&text);
 8955    cx.set_head_text(&base_text);
 8956    cx.update_editor(|editor, window, cx| {
 8957        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8958    });
 8959
 8960    cx.assert_state_with_diff(
 8961        "
 8962        ˇimpl A {
 8963      -     // this is an uncommitted comment
 8964
 8965            fn b() {
 8966                c();
 8967            }
 8968
 8969      -     // this is another uncommitted comment
 8970      -
 8971            fn d() {
 8972                // e
 8973                // f
 8974            }
 8975        }
 8976
 8977        fn g() {
 8978            // h
 8979        }
 8980    "
 8981        .unindent(),
 8982    );
 8983
 8984    let expected_display_text = "
 8985        impl A {
 8986            // this is an uncommitted comment
 8987
 8988            fn b() {
 8989 8990            }
 8991
 8992            // this is another uncommitted comment
 8993
 8994            fn d() {
 8995 8996            }
 8997        }
 8998
 8999        fn g() {
 9000 9001        }
 9002        "
 9003    .unindent();
 9004
 9005    cx.update_editor(|editor, window, cx| {
 9006        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9007        assert_eq!(editor.display_text(cx), expected_display_text);
 9008    });
 9009}
 9010
 9011#[gpui::test]
 9012async fn test_autoindent(cx: &mut TestAppContext) {
 9013    init_test(cx, |_| {});
 9014
 9015    let language = Arc::new(
 9016        Language::new(
 9017            LanguageConfig {
 9018                brackets: BracketPairConfig {
 9019                    pairs: vec![
 9020                        BracketPair {
 9021                            start: "{".to_string(),
 9022                            end: "}".to_string(),
 9023                            close: false,
 9024                            surround: false,
 9025                            newline: true,
 9026                        },
 9027                        BracketPair {
 9028                            start: "(".to_string(),
 9029                            end: ")".to_string(),
 9030                            close: false,
 9031                            surround: false,
 9032                            newline: true,
 9033                        },
 9034                    ],
 9035                    ..Default::default()
 9036                },
 9037                ..Default::default()
 9038            },
 9039            Some(tree_sitter_rust::LANGUAGE.into()),
 9040        )
 9041        .with_indents_query(
 9042            r#"
 9043                (_ "(" ")" @end) @indent
 9044                (_ "{" "}" @end) @indent
 9045            "#,
 9046        )
 9047        .unwrap(),
 9048    );
 9049
 9050    let text = "fn a() {}";
 9051
 9052    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9053    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9054    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9055    editor
 9056        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9057        .await;
 9058
 9059    editor.update_in(cx, |editor, window, cx| {
 9060        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9061            s.select_ranges([5..5, 8..8, 9..9])
 9062        });
 9063        editor.newline(&Newline, window, cx);
 9064        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9065        assert_eq!(
 9066            editor.selections.ranges(cx),
 9067            &[
 9068                Point::new(1, 4)..Point::new(1, 4),
 9069                Point::new(3, 4)..Point::new(3, 4),
 9070                Point::new(5, 0)..Point::new(5, 0)
 9071            ]
 9072        );
 9073    });
 9074}
 9075
 9076#[gpui::test]
 9077async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9078    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9079
 9080    let language = Arc::new(
 9081        Language::new(
 9082            LanguageConfig {
 9083                brackets: BracketPairConfig {
 9084                    pairs: vec![
 9085                        BracketPair {
 9086                            start: "{".to_string(),
 9087                            end: "}".to_string(),
 9088                            close: false,
 9089                            surround: false,
 9090                            newline: true,
 9091                        },
 9092                        BracketPair {
 9093                            start: "(".to_string(),
 9094                            end: ")".to_string(),
 9095                            close: false,
 9096                            surround: false,
 9097                            newline: true,
 9098                        },
 9099                    ],
 9100                    ..Default::default()
 9101                },
 9102                ..Default::default()
 9103            },
 9104            Some(tree_sitter_rust::LANGUAGE.into()),
 9105        )
 9106        .with_indents_query(
 9107            r#"
 9108                (_ "(" ")" @end) @indent
 9109                (_ "{" "}" @end) @indent
 9110            "#,
 9111        )
 9112        .unwrap(),
 9113    );
 9114
 9115    let text = "fn a() {}";
 9116
 9117    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9118    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9119    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9120    editor
 9121        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9122        .await;
 9123
 9124    editor.update_in(cx, |editor, window, cx| {
 9125        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9126            s.select_ranges([5..5, 8..8, 9..9])
 9127        });
 9128        editor.newline(&Newline, window, cx);
 9129        assert_eq!(
 9130            editor.text(cx),
 9131            indoc!(
 9132                "
 9133                fn a(
 9134
 9135                ) {
 9136
 9137                }
 9138                "
 9139            )
 9140        );
 9141        assert_eq!(
 9142            editor.selections.ranges(cx),
 9143            &[
 9144                Point::new(1, 0)..Point::new(1, 0),
 9145                Point::new(3, 0)..Point::new(3, 0),
 9146                Point::new(5, 0)..Point::new(5, 0)
 9147            ]
 9148        );
 9149    });
 9150}
 9151
 9152#[gpui::test]
 9153async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9154    init_test(cx, |settings| {
 9155        settings.defaults.auto_indent = Some(true);
 9156        settings.languages.0.insert(
 9157            "python".into(),
 9158            LanguageSettingsContent {
 9159                auto_indent: Some(false),
 9160                ..Default::default()
 9161            },
 9162        );
 9163    });
 9164
 9165    let mut cx = EditorTestContext::new(cx).await;
 9166
 9167    let injected_language = Arc::new(
 9168        Language::new(
 9169            LanguageConfig {
 9170                brackets: BracketPairConfig {
 9171                    pairs: vec![
 9172                        BracketPair {
 9173                            start: "{".to_string(),
 9174                            end: "}".to_string(),
 9175                            close: false,
 9176                            surround: false,
 9177                            newline: true,
 9178                        },
 9179                        BracketPair {
 9180                            start: "(".to_string(),
 9181                            end: ")".to_string(),
 9182                            close: true,
 9183                            surround: false,
 9184                            newline: true,
 9185                        },
 9186                    ],
 9187                    ..Default::default()
 9188                },
 9189                name: "python".into(),
 9190                ..Default::default()
 9191            },
 9192            Some(tree_sitter_python::LANGUAGE.into()),
 9193        )
 9194        .with_indents_query(
 9195            r#"
 9196                (_ "(" ")" @end) @indent
 9197                (_ "{" "}" @end) @indent
 9198            "#,
 9199        )
 9200        .unwrap(),
 9201    );
 9202
 9203    let language = Arc::new(
 9204        Language::new(
 9205            LanguageConfig {
 9206                brackets: BracketPairConfig {
 9207                    pairs: vec![
 9208                        BracketPair {
 9209                            start: "{".to_string(),
 9210                            end: "}".to_string(),
 9211                            close: false,
 9212                            surround: false,
 9213                            newline: true,
 9214                        },
 9215                        BracketPair {
 9216                            start: "(".to_string(),
 9217                            end: ")".to_string(),
 9218                            close: true,
 9219                            surround: false,
 9220                            newline: true,
 9221                        },
 9222                    ],
 9223                    ..Default::default()
 9224                },
 9225                name: LanguageName::new("rust"),
 9226                ..Default::default()
 9227            },
 9228            Some(tree_sitter_rust::LANGUAGE.into()),
 9229        )
 9230        .with_indents_query(
 9231            r#"
 9232                (_ "(" ")" @end) @indent
 9233                (_ "{" "}" @end) @indent
 9234            "#,
 9235        )
 9236        .unwrap()
 9237        .with_injection_query(
 9238            r#"
 9239            (macro_invocation
 9240                macro: (identifier) @_macro_name
 9241                (token_tree) @injection.content
 9242                (#set! injection.language "python"))
 9243           "#,
 9244        )
 9245        .unwrap(),
 9246    );
 9247
 9248    cx.language_registry().add(injected_language);
 9249    cx.language_registry().add(language.clone());
 9250
 9251    cx.update_buffer(|buffer, cx| {
 9252        buffer.set_language(Some(language), cx);
 9253    });
 9254
 9255    cx.set_state(r#"struct A {ˇ}"#);
 9256
 9257    cx.update_editor(|editor, window, cx| {
 9258        editor.newline(&Default::default(), window, cx);
 9259    });
 9260
 9261    cx.assert_editor_state(indoc!(
 9262        "struct A {
 9263            ˇ
 9264        }"
 9265    ));
 9266
 9267    cx.set_state(r#"select_biased!(ˇ)"#);
 9268
 9269    cx.update_editor(|editor, window, cx| {
 9270        editor.newline(&Default::default(), window, cx);
 9271        editor.handle_input("def ", window, cx);
 9272        editor.handle_input("(", window, cx);
 9273        editor.newline(&Default::default(), window, cx);
 9274        editor.handle_input("a", window, cx);
 9275    });
 9276
 9277    cx.assert_editor_state(indoc!(
 9278        "select_biased!(
 9279        def (
 9280 9281        )
 9282        )"
 9283    ));
 9284}
 9285
 9286#[gpui::test]
 9287async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9288    init_test(cx, |_| {});
 9289
 9290    {
 9291        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9292        cx.set_state(indoc! {"
 9293            impl A {
 9294
 9295                fn b() {}
 9296
 9297            «fn c() {
 9298
 9299            }ˇ»
 9300            }
 9301        "});
 9302
 9303        cx.update_editor(|editor, window, cx| {
 9304            editor.autoindent(&Default::default(), window, cx);
 9305        });
 9306
 9307        cx.assert_editor_state(indoc! {"
 9308            impl A {
 9309
 9310                fn b() {}
 9311
 9312                «fn c() {
 9313
 9314                }ˇ»
 9315            }
 9316        "});
 9317    }
 9318
 9319    {
 9320        let mut cx = EditorTestContext::new_multibuffer(
 9321            cx,
 9322            [indoc! { "
 9323                impl A {
 9324                «
 9325                // a
 9326                fn b(){}
 9327                »
 9328                «
 9329                    }
 9330                    fn c(){}
 9331                »
 9332            "}],
 9333        );
 9334
 9335        let buffer = cx.update_editor(|editor, _, cx| {
 9336            let buffer = editor.buffer().update(cx, |buffer, _| {
 9337                buffer.all_buffers().iter().next().unwrap().clone()
 9338            });
 9339            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9340            buffer
 9341        });
 9342
 9343        cx.run_until_parked();
 9344        cx.update_editor(|editor, window, cx| {
 9345            editor.select_all(&Default::default(), window, cx);
 9346            editor.autoindent(&Default::default(), window, cx)
 9347        });
 9348        cx.run_until_parked();
 9349
 9350        cx.update(|_, cx| {
 9351            assert_eq!(
 9352                buffer.read(cx).text(),
 9353                indoc! { "
 9354                    impl A {
 9355
 9356                        // a
 9357                        fn b(){}
 9358
 9359
 9360                    }
 9361                    fn c(){}
 9362
 9363                " }
 9364            )
 9365        });
 9366    }
 9367}
 9368
 9369#[gpui::test]
 9370async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9371    init_test(cx, |_| {});
 9372
 9373    let mut cx = EditorTestContext::new(cx).await;
 9374
 9375    let language = Arc::new(Language::new(
 9376        LanguageConfig {
 9377            brackets: BracketPairConfig {
 9378                pairs: vec![
 9379                    BracketPair {
 9380                        start: "{".to_string(),
 9381                        end: "}".to_string(),
 9382                        close: true,
 9383                        surround: true,
 9384                        newline: true,
 9385                    },
 9386                    BracketPair {
 9387                        start: "(".to_string(),
 9388                        end: ")".to_string(),
 9389                        close: true,
 9390                        surround: true,
 9391                        newline: true,
 9392                    },
 9393                    BracketPair {
 9394                        start: "/*".to_string(),
 9395                        end: " */".to_string(),
 9396                        close: true,
 9397                        surround: true,
 9398                        newline: true,
 9399                    },
 9400                    BracketPair {
 9401                        start: "[".to_string(),
 9402                        end: "]".to_string(),
 9403                        close: false,
 9404                        surround: false,
 9405                        newline: true,
 9406                    },
 9407                    BracketPair {
 9408                        start: "\"".to_string(),
 9409                        end: "\"".to_string(),
 9410                        close: true,
 9411                        surround: true,
 9412                        newline: false,
 9413                    },
 9414                    BracketPair {
 9415                        start: "<".to_string(),
 9416                        end: ">".to_string(),
 9417                        close: false,
 9418                        surround: true,
 9419                        newline: true,
 9420                    },
 9421                ],
 9422                ..Default::default()
 9423            },
 9424            autoclose_before: "})]".to_string(),
 9425            ..Default::default()
 9426        },
 9427        Some(tree_sitter_rust::LANGUAGE.into()),
 9428    ));
 9429
 9430    cx.language_registry().add(language.clone());
 9431    cx.update_buffer(|buffer, cx| {
 9432        buffer.set_language(Some(language), cx);
 9433    });
 9434
 9435    cx.set_state(
 9436        &r#"
 9437            🏀ˇ
 9438            εˇ
 9439            ❤️ˇ
 9440        "#
 9441        .unindent(),
 9442    );
 9443
 9444    // autoclose multiple nested brackets at multiple cursors
 9445    cx.update_editor(|editor, window, cx| {
 9446        editor.handle_input("{", window, cx);
 9447        editor.handle_input("{", window, cx);
 9448        editor.handle_input("{", window, cx);
 9449    });
 9450    cx.assert_editor_state(
 9451        &"
 9452            🏀{{{ˇ}}}
 9453            ε{{{ˇ}}}
 9454            ❤️{{{ˇ}}}
 9455        "
 9456        .unindent(),
 9457    );
 9458
 9459    // insert a different closing bracket
 9460    cx.update_editor(|editor, window, cx| {
 9461        editor.handle_input(")", window, cx);
 9462    });
 9463    cx.assert_editor_state(
 9464        &"
 9465            🏀{{{)ˇ}}}
 9466            ε{{{)ˇ}}}
 9467            ❤️{{{)ˇ}}}
 9468        "
 9469        .unindent(),
 9470    );
 9471
 9472    // skip over the auto-closed brackets when typing a closing bracket
 9473    cx.update_editor(|editor, window, cx| {
 9474        editor.move_right(&MoveRight, window, cx);
 9475        editor.handle_input("}", window, cx);
 9476        editor.handle_input("}", window, cx);
 9477        editor.handle_input("}", window, cx);
 9478    });
 9479    cx.assert_editor_state(
 9480        &"
 9481            🏀{{{)}}}}ˇ
 9482            ε{{{)}}}}ˇ
 9483            ❤️{{{)}}}}ˇ
 9484        "
 9485        .unindent(),
 9486    );
 9487
 9488    // autoclose multi-character pairs
 9489    cx.set_state(
 9490        &"
 9491            ˇ
 9492            ˇ
 9493        "
 9494        .unindent(),
 9495    );
 9496    cx.update_editor(|editor, window, cx| {
 9497        editor.handle_input("/", window, cx);
 9498        editor.handle_input("*", window, cx);
 9499    });
 9500    cx.assert_editor_state(
 9501        &"
 9502            /*ˇ */
 9503            /*ˇ */
 9504        "
 9505        .unindent(),
 9506    );
 9507
 9508    // one cursor autocloses a multi-character pair, one cursor
 9509    // does not autoclose.
 9510    cx.set_state(
 9511        &"
 9512 9513            ˇ
 9514        "
 9515        .unindent(),
 9516    );
 9517    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9518    cx.assert_editor_state(
 9519        &"
 9520            /*ˇ */
 9521 9522        "
 9523        .unindent(),
 9524    );
 9525
 9526    // Don't autoclose if the next character isn't whitespace and isn't
 9527    // listed in the language's "autoclose_before" section.
 9528    cx.set_state("ˇa b");
 9529    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9530    cx.assert_editor_state("{ˇa b");
 9531
 9532    // Don't autoclose if `close` is false for the bracket pair
 9533    cx.set_state("ˇ");
 9534    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9535    cx.assert_editor_state("");
 9536
 9537    // Surround with brackets if text is selected
 9538    cx.set_state("«aˇ» b");
 9539    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9540    cx.assert_editor_state("{«aˇ»} b");
 9541
 9542    // Autoclose when not immediately after a word character
 9543    cx.set_state("a ˇ");
 9544    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9545    cx.assert_editor_state("a \"ˇ\"");
 9546
 9547    // Autoclose pair where the start and end characters are the same
 9548    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9549    cx.assert_editor_state("a \"\"ˇ");
 9550
 9551    // Don't autoclose when immediately after a word character
 9552    cx.set_state("");
 9553    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9554    cx.assert_editor_state("a\"ˇ");
 9555
 9556    // Do autoclose when after a non-word character
 9557    cx.set_state("");
 9558    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9559    cx.assert_editor_state("{\"ˇ\"");
 9560
 9561    // Non identical pairs autoclose regardless of preceding character
 9562    cx.set_state("");
 9563    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9564    cx.assert_editor_state("a{ˇ}");
 9565
 9566    // Don't autoclose pair if autoclose is disabled
 9567    cx.set_state("ˇ");
 9568    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9569    cx.assert_editor_state("");
 9570
 9571    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9572    cx.set_state("«aˇ» b");
 9573    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9574    cx.assert_editor_state("<«aˇ»> b");
 9575}
 9576
 9577#[gpui::test]
 9578async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9579    init_test(cx, |settings| {
 9580        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9581    });
 9582
 9583    let mut cx = EditorTestContext::new(cx).await;
 9584
 9585    let language = Arc::new(Language::new(
 9586        LanguageConfig {
 9587            brackets: BracketPairConfig {
 9588                pairs: vec![
 9589                    BracketPair {
 9590                        start: "{".to_string(),
 9591                        end: "}".to_string(),
 9592                        close: true,
 9593                        surround: true,
 9594                        newline: true,
 9595                    },
 9596                    BracketPair {
 9597                        start: "(".to_string(),
 9598                        end: ")".to_string(),
 9599                        close: true,
 9600                        surround: true,
 9601                        newline: true,
 9602                    },
 9603                    BracketPair {
 9604                        start: "[".to_string(),
 9605                        end: "]".to_string(),
 9606                        close: false,
 9607                        surround: false,
 9608                        newline: true,
 9609                    },
 9610                ],
 9611                ..Default::default()
 9612            },
 9613            autoclose_before: "})]".to_string(),
 9614            ..Default::default()
 9615        },
 9616        Some(tree_sitter_rust::LANGUAGE.into()),
 9617    ));
 9618
 9619    cx.language_registry().add(language.clone());
 9620    cx.update_buffer(|buffer, cx| {
 9621        buffer.set_language(Some(language), cx);
 9622    });
 9623
 9624    cx.set_state(
 9625        &"
 9626            ˇ
 9627            ˇ
 9628            ˇ
 9629        "
 9630        .unindent(),
 9631    );
 9632
 9633    // ensure only matching closing brackets are skipped over
 9634    cx.update_editor(|editor, window, cx| {
 9635        editor.handle_input("}", window, cx);
 9636        editor.move_left(&MoveLeft, window, cx);
 9637        editor.handle_input(")", window, cx);
 9638        editor.move_left(&MoveLeft, window, cx);
 9639    });
 9640    cx.assert_editor_state(
 9641        &"
 9642            ˇ)}
 9643            ˇ)}
 9644            ˇ)}
 9645        "
 9646        .unindent(),
 9647    );
 9648
 9649    // skip-over closing brackets at multiple cursors
 9650    cx.update_editor(|editor, window, cx| {
 9651        editor.handle_input(")", window, cx);
 9652        editor.handle_input("}", window, cx);
 9653    });
 9654    cx.assert_editor_state(
 9655        &"
 9656            )}ˇ
 9657            )}ˇ
 9658            )}ˇ
 9659        "
 9660        .unindent(),
 9661    );
 9662
 9663    // ignore non-close brackets
 9664    cx.update_editor(|editor, window, cx| {
 9665        editor.handle_input("]", window, cx);
 9666        editor.move_left(&MoveLeft, window, cx);
 9667        editor.handle_input("]", window, cx);
 9668    });
 9669    cx.assert_editor_state(
 9670        &"
 9671            )}]ˇ]
 9672            )}]ˇ]
 9673            )}]ˇ]
 9674        "
 9675        .unindent(),
 9676    );
 9677}
 9678
 9679#[gpui::test]
 9680async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9681    init_test(cx, |_| {});
 9682
 9683    let mut cx = EditorTestContext::new(cx).await;
 9684
 9685    let html_language = Arc::new(
 9686        Language::new(
 9687            LanguageConfig {
 9688                name: "HTML".into(),
 9689                brackets: BracketPairConfig {
 9690                    pairs: vec![
 9691                        BracketPair {
 9692                            start: "<".into(),
 9693                            end: ">".into(),
 9694                            close: true,
 9695                            ..Default::default()
 9696                        },
 9697                        BracketPair {
 9698                            start: "{".into(),
 9699                            end: "}".into(),
 9700                            close: true,
 9701                            ..Default::default()
 9702                        },
 9703                        BracketPair {
 9704                            start: "(".into(),
 9705                            end: ")".into(),
 9706                            close: true,
 9707                            ..Default::default()
 9708                        },
 9709                    ],
 9710                    ..Default::default()
 9711                },
 9712                autoclose_before: "})]>".into(),
 9713                ..Default::default()
 9714            },
 9715            Some(tree_sitter_html::LANGUAGE.into()),
 9716        )
 9717        .with_injection_query(
 9718            r#"
 9719            (script_element
 9720                (raw_text) @injection.content
 9721                (#set! injection.language "javascript"))
 9722            "#,
 9723        )
 9724        .unwrap(),
 9725    );
 9726
 9727    let javascript_language = Arc::new(Language::new(
 9728        LanguageConfig {
 9729            name: "JavaScript".into(),
 9730            brackets: BracketPairConfig {
 9731                pairs: vec![
 9732                    BracketPair {
 9733                        start: "/*".into(),
 9734                        end: " */".into(),
 9735                        close: true,
 9736                        ..Default::default()
 9737                    },
 9738                    BracketPair {
 9739                        start: "{".into(),
 9740                        end: "}".into(),
 9741                        close: true,
 9742                        ..Default::default()
 9743                    },
 9744                    BracketPair {
 9745                        start: "(".into(),
 9746                        end: ")".into(),
 9747                        close: true,
 9748                        ..Default::default()
 9749                    },
 9750                ],
 9751                ..Default::default()
 9752            },
 9753            autoclose_before: "})]>".into(),
 9754            ..Default::default()
 9755        },
 9756        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9757    ));
 9758
 9759    cx.language_registry().add(html_language.clone());
 9760    cx.language_registry().add(javascript_language);
 9761    cx.executor().run_until_parked();
 9762
 9763    cx.update_buffer(|buffer, cx| {
 9764        buffer.set_language(Some(html_language), cx);
 9765    });
 9766
 9767    cx.set_state(
 9768        &r#"
 9769            <body>ˇ
 9770                <script>
 9771                    var x = 1;ˇ
 9772                </script>
 9773            </body>ˇ
 9774        "#
 9775        .unindent(),
 9776    );
 9777
 9778    // Precondition: different languages are active at different locations.
 9779    cx.update_editor(|editor, window, cx| {
 9780        let snapshot = editor.snapshot(window, cx);
 9781        let cursors = editor.selections.ranges::<usize>(cx);
 9782        let languages = cursors
 9783            .iter()
 9784            .map(|c| snapshot.language_at(c.start).unwrap().name())
 9785            .collect::<Vec<_>>();
 9786        assert_eq!(
 9787            languages,
 9788            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 9789        );
 9790    });
 9791
 9792    // Angle brackets autoclose in HTML, but not JavaScript.
 9793    cx.update_editor(|editor, window, cx| {
 9794        editor.handle_input("<", window, cx);
 9795        editor.handle_input("a", window, cx);
 9796    });
 9797    cx.assert_editor_state(
 9798        &r#"
 9799            <body><aˇ>
 9800                <script>
 9801                    var x = 1;<aˇ
 9802                </script>
 9803            </body><aˇ>
 9804        "#
 9805        .unindent(),
 9806    );
 9807
 9808    // Curly braces and parens autoclose in both HTML and JavaScript.
 9809    cx.update_editor(|editor, window, cx| {
 9810        editor.handle_input(" b=", window, cx);
 9811        editor.handle_input("{", window, cx);
 9812        editor.handle_input("c", window, cx);
 9813        editor.handle_input("(", window, cx);
 9814    });
 9815    cx.assert_editor_state(
 9816        &r#"
 9817            <body><a b={c(ˇ)}>
 9818                <script>
 9819                    var x = 1;<a b={c(ˇ)}
 9820                </script>
 9821            </body><a b={c(ˇ)}>
 9822        "#
 9823        .unindent(),
 9824    );
 9825
 9826    // Brackets that were already autoclosed are skipped.
 9827    cx.update_editor(|editor, window, cx| {
 9828        editor.handle_input(")", window, cx);
 9829        editor.handle_input("d", window, cx);
 9830        editor.handle_input("}", window, cx);
 9831    });
 9832    cx.assert_editor_state(
 9833        &r#"
 9834            <body><a b={c()d}ˇ>
 9835                <script>
 9836                    var x = 1;<a b={c()d}ˇ
 9837                </script>
 9838            </body><a b={c()d}ˇ>
 9839        "#
 9840        .unindent(),
 9841    );
 9842    cx.update_editor(|editor, window, cx| {
 9843        editor.handle_input(">", window, cx);
 9844    });
 9845    cx.assert_editor_state(
 9846        &r#"
 9847            <body><a b={c()d}>ˇ
 9848                <script>
 9849                    var x = 1;<a b={c()d}>ˇ
 9850                </script>
 9851            </body><a b={c()d}>ˇ
 9852        "#
 9853        .unindent(),
 9854    );
 9855
 9856    // Reset
 9857    cx.set_state(
 9858        &r#"
 9859            <body>ˇ
 9860                <script>
 9861                    var x = 1;ˇ
 9862                </script>
 9863            </body>ˇ
 9864        "#
 9865        .unindent(),
 9866    );
 9867
 9868    cx.update_editor(|editor, window, cx| {
 9869        editor.handle_input("<", window, cx);
 9870    });
 9871    cx.assert_editor_state(
 9872        &r#"
 9873            <body><ˇ>
 9874                <script>
 9875                    var x = 1;<ˇ
 9876                </script>
 9877            </body><ˇ>
 9878        "#
 9879        .unindent(),
 9880    );
 9881
 9882    // When backspacing, the closing angle brackets are removed.
 9883    cx.update_editor(|editor, window, cx| {
 9884        editor.backspace(&Backspace, window, cx);
 9885    });
 9886    cx.assert_editor_state(
 9887        &r#"
 9888            <body>ˇ
 9889                <script>
 9890                    var x = 1;ˇ
 9891                </script>
 9892            </body>ˇ
 9893        "#
 9894        .unindent(),
 9895    );
 9896
 9897    // Block comments autoclose in JavaScript, but not HTML.
 9898    cx.update_editor(|editor, window, cx| {
 9899        editor.handle_input("/", window, cx);
 9900        editor.handle_input("*", window, cx);
 9901    });
 9902    cx.assert_editor_state(
 9903        &r#"
 9904            <body>/*ˇ
 9905                <script>
 9906                    var x = 1;/*ˇ */
 9907                </script>
 9908            </body>/*ˇ
 9909        "#
 9910        .unindent(),
 9911    );
 9912}
 9913
 9914#[gpui::test]
 9915async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 9916    init_test(cx, |_| {});
 9917
 9918    let mut cx = EditorTestContext::new(cx).await;
 9919
 9920    let rust_language = Arc::new(
 9921        Language::new(
 9922            LanguageConfig {
 9923                name: "Rust".into(),
 9924                brackets: serde_json::from_value(json!([
 9925                    { "start": "{", "end": "}", "close": true, "newline": true },
 9926                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 9927                ]))
 9928                .unwrap(),
 9929                autoclose_before: "})]>".into(),
 9930                ..Default::default()
 9931            },
 9932            Some(tree_sitter_rust::LANGUAGE.into()),
 9933        )
 9934        .with_override_query("(string_literal) @string")
 9935        .unwrap(),
 9936    );
 9937
 9938    cx.language_registry().add(rust_language.clone());
 9939    cx.update_buffer(|buffer, cx| {
 9940        buffer.set_language(Some(rust_language), cx);
 9941    });
 9942
 9943    cx.set_state(
 9944        &r#"
 9945            let x = ˇ
 9946        "#
 9947        .unindent(),
 9948    );
 9949
 9950    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 9951    cx.update_editor(|editor, window, cx| {
 9952        editor.handle_input("\"", window, cx);
 9953    });
 9954    cx.assert_editor_state(
 9955        &r#"
 9956            let x = "ˇ"
 9957        "#
 9958        .unindent(),
 9959    );
 9960
 9961    // Inserting another quotation mark. The cursor moves across the existing
 9962    // automatically-inserted quotation mark.
 9963    cx.update_editor(|editor, window, cx| {
 9964        editor.handle_input("\"", window, cx);
 9965    });
 9966    cx.assert_editor_state(
 9967        &r#"
 9968            let x = ""ˇ
 9969        "#
 9970        .unindent(),
 9971    );
 9972
 9973    // Reset
 9974    cx.set_state(
 9975        &r#"
 9976            let x = ˇ
 9977        "#
 9978        .unindent(),
 9979    );
 9980
 9981    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 9982    cx.update_editor(|editor, window, cx| {
 9983        editor.handle_input("\"", window, cx);
 9984        editor.handle_input(" ", window, cx);
 9985        editor.move_left(&Default::default(), window, cx);
 9986        editor.handle_input("\\", window, cx);
 9987        editor.handle_input("\"", window, cx);
 9988    });
 9989    cx.assert_editor_state(
 9990        &r#"
 9991            let x = "\"ˇ "
 9992        "#
 9993        .unindent(),
 9994    );
 9995
 9996    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 9997    // mark. Nothing is inserted.
 9998    cx.update_editor(|editor, window, cx| {
 9999        editor.move_right(&Default::default(), window, cx);
10000        editor.handle_input("\"", window, cx);
10001    });
10002    cx.assert_editor_state(
10003        &r#"
10004            let x = "\" "ˇ
10005        "#
10006        .unindent(),
10007    );
10008}
10009
10010#[gpui::test]
10011async fn test_surround_with_pair(cx: &mut TestAppContext) {
10012    init_test(cx, |_| {});
10013
10014    let language = Arc::new(Language::new(
10015        LanguageConfig {
10016            brackets: BracketPairConfig {
10017                pairs: vec![
10018                    BracketPair {
10019                        start: "{".to_string(),
10020                        end: "}".to_string(),
10021                        close: true,
10022                        surround: true,
10023                        newline: true,
10024                    },
10025                    BracketPair {
10026                        start: "/* ".to_string(),
10027                        end: "*/".to_string(),
10028                        close: true,
10029                        surround: true,
10030                        ..Default::default()
10031                    },
10032                ],
10033                ..Default::default()
10034            },
10035            ..Default::default()
10036        },
10037        Some(tree_sitter_rust::LANGUAGE.into()),
10038    ));
10039
10040    let text = r#"
10041        a
10042        b
10043        c
10044    "#
10045    .unindent();
10046
10047    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10048    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10049    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10050    editor
10051        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10052        .await;
10053
10054    editor.update_in(cx, |editor, window, cx| {
10055        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10056            s.select_display_ranges([
10057                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10058                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10059                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10060            ])
10061        });
10062
10063        editor.handle_input("{", window, cx);
10064        editor.handle_input("{", window, cx);
10065        editor.handle_input("{", window, cx);
10066        assert_eq!(
10067            editor.text(cx),
10068            "
10069                {{{a}}}
10070                {{{b}}}
10071                {{{c}}}
10072            "
10073            .unindent()
10074        );
10075        assert_eq!(
10076            editor.selections.display_ranges(cx),
10077            [
10078                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10079                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10080                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10081            ]
10082        );
10083
10084        editor.undo(&Undo, window, cx);
10085        editor.undo(&Undo, window, cx);
10086        editor.undo(&Undo, window, cx);
10087        assert_eq!(
10088            editor.text(cx),
10089            "
10090                a
10091                b
10092                c
10093            "
10094            .unindent()
10095        );
10096        assert_eq!(
10097            editor.selections.display_ranges(cx),
10098            [
10099                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10100                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10101                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10102            ]
10103        );
10104
10105        // Ensure inserting the first character of a multi-byte bracket pair
10106        // doesn't surround the selections with the bracket.
10107        editor.handle_input("/", window, cx);
10108        assert_eq!(
10109            editor.text(cx),
10110            "
10111                /
10112                /
10113                /
10114            "
10115            .unindent()
10116        );
10117        assert_eq!(
10118            editor.selections.display_ranges(cx),
10119            [
10120                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10121                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10122                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10123            ]
10124        );
10125
10126        editor.undo(&Undo, window, cx);
10127        assert_eq!(
10128            editor.text(cx),
10129            "
10130                a
10131                b
10132                c
10133            "
10134            .unindent()
10135        );
10136        assert_eq!(
10137            editor.selections.display_ranges(cx),
10138            [
10139                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10140                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10141                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10142            ]
10143        );
10144
10145        // Ensure inserting the last character of a multi-byte bracket pair
10146        // doesn't surround the selections with the bracket.
10147        editor.handle_input("*", window, cx);
10148        assert_eq!(
10149            editor.text(cx),
10150            "
10151                *
10152                *
10153                *
10154            "
10155            .unindent()
10156        );
10157        assert_eq!(
10158            editor.selections.display_ranges(cx),
10159            [
10160                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10161                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10162                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10163            ]
10164        );
10165    });
10166}
10167
10168#[gpui::test]
10169async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10170    init_test(cx, |_| {});
10171
10172    let language = Arc::new(Language::new(
10173        LanguageConfig {
10174            brackets: BracketPairConfig {
10175                pairs: vec![BracketPair {
10176                    start: "{".to_string(),
10177                    end: "}".to_string(),
10178                    close: true,
10179                    surround: true,
10180                    newline: true,
10181                }],
10182                ..Default::default()
10183            },
10184            autoclose_before: "}".to_string(),
10185            ..Default::default()
10186        },
10187        Some(tree_sitter_rust::LANGUAGE.into()),
10188    ));
10189
10190    let text = r#"
10191        a
10192        b
10193        c
10194    "#
10195    .unindent();
10196
10197    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10198    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10199    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10200    editor
10201        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10202        .await;
10203
10204    editor.update_in(cx, |editor, window, cx| {
10205        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10206            s.select_ranges([
10207                Point::new(0, 1)..Point::new(0, 1),
10208                Point::new(1, 1)..Point::new(1, 1),
10209                Point::new(2, 1)..Point::new(2, 1),
10210            ])
10211        });
10212
10213        editor.handle_input("{", window, cx);
10214        editor.handle_input("{", window, cx);
10215        editor.handle_input("_", window, cx);
10216        assert_eq!(
10217            editor.text(cx),
10218            "
10219                a{{_}}
10220                b{{_}}
10221                c{{_}}
10222            "
10223            .unindent()
10224        );
10225        assert_eq!(
10226            editor.selections.ranges::<Point>(cx),
10227            [
10228                Point::new(0, 4)..Point::new(0, 4),
10229                Point::new(1, 4)..Point::new(1, 4),
10230                Point::new(2, 4)..Point::new(2, 4)
10231            ]
10232        );
10233
10234        editor.backspace(&Default::default(), window, cx);
10235        editor.backspace(&Default::default(), window, cx);
10236        assert_eq!(
10237            editor.text(cx),
10238            "
10239                a{}
10240                b{}
10241                c{}
10242            "
10243            .unindent()
10244        );
10245        assert_eq!(
10246            editor.selections.ranges::<Point>(cx),
10247            [
10248                Point::new(0, 2)..Point::new(0, 2),
10249                Point::new(1, 2)..Point::new(1, 2),
10250                Point::new(2, 2)..Point::new(2, 2)
10251            ]
10252        );
10253
10254        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10255        assert_eq!(
10256            editor.text(cx),
10257            "
10258                a
10259                b
10260                c
10261            "
10262            .unindent()
10263        );
10264        assert_eq!(
10265            editor.selections.ranges::<Point>(cx),
10266            [
10267                Point::new(0, 1)..Point::new(0, 1),
10268                Point::new(1, 1)..Point::new(1, 1),
10269                Point::new(2, 1)..Point::new(2, 1)
10270            ]
10271        );
10272    });
10273}
10274
10275#[gpui::test]
10276async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10277    init_test(cx, |settings| {
10278        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10279    });
10280
10281    let mut cx = EditorTestContext::new(cx).await;
10282
10283    let language = Arc::new(Language::new(
10284        LanguageConfig {
10285            brackets: BracketPairConfig {
10286                pairs: vec![
10287                    BracketPair {
10288                        start: "{".to_string(),
10289                        end: "}".to_string(),
10290                        close: true,
10291                        surround: true,
10292                        newline: true,
10293                    },
10294                    BracketPair {
10295                        start: "(".to_string(),
10296                        end: ")".to_string(),
10297                        close: true,
10298                        surround: true,
10299                        newline: true,
10300                    },
10301                    BracketPair {
10302                        start: "[".to_string(),
10303                        end: "]".to_string(),
10304                        close: false,
10305                        surround: true,
10306                        newline: true,
10307                    },
10308                ],
10309                ..Default::default()
10310            },
10311            autoclose_before: "})]".to_string(),
10312            ..Default::default()
10313        },
10314        Some(tree_sitter_rust::LANGUAGE.into()),
10315    ));
10316
10317    cx.language_registry().add(language.clone());
10318    cx.update_buffer(|buffer, cx| {
10319        buffer.set_language(Some(language), cx);
10320    });
10321
10322    cx.set_state(
10323        &"
10324            {(ˇ)}
10325            [[ˇ]]
10326            {(ˇ)}
10327        "
10328        .unindent(),
10329    );
10330
10331    cx.update_editor(|editor, window, cx| {
10332        editor.backspace(&Default::default(), window, cx);
10333        editor.backspace(&Default::default(), window, cx);
10334    });
10335
10336    cx.assert_editor_state(
10337        &"
10338            ˇ
10339            ˇ]]
10340            ˇ
10341        "
10342        .unindent(),
10343    );
10344
10345    cx.update_editor(|editor, window, cx| {
10346        editor.handle_input("{", window, cx);
10347        editor.handle_input("{", window, cx);
10348        editor.move_right(&MoveRight, window, cx);
10349        editor.move_right(&MoveRight, window, cx);
10350        editor.move_left(&MoveLeft, window, cx);
10351        editor.move_left(&MoveLeft, window, cx);
10352        editor.backspace(&Default::default(), window, cx);
10353    });
10354
10355    cx.assert_editor_state(
10356        &"
10357            {ˇ}
10358            {ˇ}]]
10359            {ˇ}
10360        "
10361        .unindent(),
10362    );
10363
10364    cx.update_editor(|editor, window, cx| {
10365        editor.backspace(&Default::default(), window, cx);
10366    });
10367
10368    cx.assert_editor_state(
10369        &"
10370            ˇ
10371            ˇ]]
10372            ˇ
10373        "
10374        .unindent(),
10375    );
10376}
10377
10378#[gpui::test]
10379async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10380    init_test(cx, |_| {});
10381
10382    let language = Arc::new(Language::new(
10383        LanguageConfig::default(),
10384        Some(tree_sitter_rust::LANGUAGE.into()),
10385    ));
10386
10387    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10388    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10389    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10390    editor
10391        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10392        .await;
10393
10394    editor.update_in(cx, |editor, window, cx| {
10395        editor.set_auto_replace_emoji_shortcode(true);
10396
10397        editor.handle_input("Hello ", window, cx);
10398        editor.handle_input(":wave", window, cx);
10399        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10400
10401        editor.handle_input(":", window, cx);
10402        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10403
10404        editor.handle_input(" :smile", window, cx);
10405        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10406
10407        editor.handle_input(":", window, cx);
10408        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10409
10410        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10411        editor.handle_input(":wave", window, cx);
10412        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10413
10414        editor.handle_input(":", window, cx);
10415        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10416
10417        editor.handle_input(":1", window, cx);
10418        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10419
10420        editor.handle_input(":", window, cx);
10421        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10422
10423        // Ensure shortcode does not get replaced when it is part of a word
10424        editor.handle_input(" Test:wave", window, cx);
10425        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10426
10427        editor.handle_input(":", window, cx);
10428        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10429
10430        editor.set_auto_replace_emoji_shortcode(false);
10431
10432        // Ensure shortcode does not get replaced when auto replace is off
10433        editor.handle_input(" :wave", window, cx);
10434        assert_eq!(
10435            editor.text(cx),
10436            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10437        );
10438
10439        editor.handle_input(":", window, cx);
10440        assert_eq!(
10441            editor.text(cx),
10442            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10443        );
10444    });
10445}
10446
10447#[gpui::test]
10448async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10449    init_test(cx, |_| {});
10450
10451    let (text, insertion_ranges) = marked_text_ranges(
10452        indoc! {"
10453            ˇ
10454        "},
10455        false,
10456    );
10457
10458    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10459    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10460
10461    _ = editor.update_in(cx, |editor, window, cx| {
10462        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10463
10464        editor
10465            .insert_snippet(&insertion_ranges, snippet, window, cx)
10466            .unwrap();
10467
10468        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10469            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10470            assert_eq!(editor.text(cx), expected_text);
10471            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10472        }
10473
10474        assert(
10475            editor,
10476            cx,
10477            indoc! {"
10478            type «» =•
10479            "},
10480        );
10481
10482        assert!(editor.context_menu_visible(), "There should be a matches");
10483    });
10484}
10485
10486#[gpui::test]
10487async fn test_snippets(cx: &mut TestAppContext) {
10488    init_test(cx, |_| {});
10489
10490    let mut cx = EditorTestContext::new(cx).await;
10491
10492    cx.set_state(indoc! {"
10493        a.ˇ b
10494        a.ˇ b
10495        a.ˇ b
10496    "});
10497
10498    cx.update_editor(|editor, window, cx| {
10499        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10500        let insertion_ranges = editor
10501            .selections
10502            .all(cx)
10503            .iter()
10504            .map(|s| s.range())
10505            .collect::<Vec<_>>();
10506        editor
10507            .insert_snippet(&insertion_ranges, snippet, window, cx)
10508            .unwrap();
10509    });
10510
10511    cx.assert_editor_state(indoc! {"
10512        a.f(«oneˇ», two, «threeˇ») b
10513        a.f(«oneˇ», two, «threeˇ») b
10514        a.f(«oneˇ», two, «threeˇ») b
10515    "});
10516
10517    // Can't move earlier than the first tab stop
10518    cx.update_editor(|editor, window, cx| {
10519        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10520    });
10521    cx.assert_editor_state(indoc! {"
10522        a.f(«oneˇ», two, «threeˇ») b
10523        a.f(«oneˇ», two, «threeˇ») b
10524        a.f(«oneˇ», two, «threeˇ») b
10525    "});
10526
10527    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10528    cx.assert_editor_state(indoc! {"
10529        a.f(one, «twoˇ», three) b
10530        a.f(one, «twoˇ», three) b
10531        a.f(one, «twoˇ», three) b
10532    "});
10533
10534    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10535    cx.assert_editor_state(indoc! {"
10536        a.f(«oneˇ», two, «threeˇ») b
10537        a.f(«oneˇ», two, «threeˇ») b
10538        a.f(«oneˇ», two, «threeˇ») b
10539    "});
10540
10541    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10542    cx.assert_editor_state(indoc! {"
10543        a.f(one, «twoˇ», three) b
10544        a.f(one, «twoˇ», three) b
10545        a.f(one, «twoˇ», three) b
10546    "});
10547    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10548    cx.assert_editor_state(indoc! {"
10549        a.f(one, two, three)ˇ b
10550        a.f(one, two, three)ˇ b
10551        a.f(one, two, three)ˇ b
10552    "});
10553
10554    // As soon as the last tab stop is reached, snippet state is gone
10555    cx.update_editor(|editor, window, cx| {
10556        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10557    });
10558    cx.assert_editor_state(indoc! {"
10559        a.f(one, two, three)ˇ b
10560        a.f(one, two, three)ˇ b
10561        a.f(one, two, three)ˇ b
10562    "});
10563}
10564
10565#[gpui::test]
10566async fn test_snippet_indentation(cx: &mut TestAppContext) {
10567    init_test(cx, |_| {});
10568
10569    let mut cx = EditorTestContext::new(cx).await;
10570
10571    cx.update_editor(|editor, window, cx| {
10572        let snippet = Snippet::parse(indoc! {"
10573            /*
10574             * Multiline comment with leading indentation
10575             *
10576             * $1
10577             */
10578            $0"})
10579        .unwrap();
10580        let insertion_ranges = editor
10581            .selections
10582            .all(cx)
10583            .iter()
10584            .map(|s| s.range())
10585            .collect::<Vec<_>>();
10586        editor
10587            .insert_snippet(&insertion_ranges, snippet, window, cx)
10588            .unwrap();
10589    });
10590
10591    cx.assert_editor_state(indoc! {"
10592        /*
10593         * Multiline comment with leading indentation
10594         *
10595         * ˇ
10596         */
10597    "});
10598
10599    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10600    cx.assert_editor_state(indoc! {"
10601        /*
10602         * Multiline comment with leading indentation
10603         *
10604         *•
10605         */
10606        ˇ"});
10607}
10608
10609#[gpui::test]
10610async fn test_document_format_during_save(cx: &mut TestAppContext) {
10611    init_test(cx, |_| {});
10612
10613    let fs = FakeFs::new(cx.executor());
10614    fs.insert_file(path!("/file.rs"), Default::default()).await;
10615
10616    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10617
10618    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10619    language_registry.add(rust_lang());
10620    let mut fake_servers = language_registry.register_fake_lsp(
10621        "Rust",
10622        FakeLspAdapter {
10623            capabilities: lsp::ServerCapabilities {
10624                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10625                ..Default::default()
10626            },
10627            ..Default::default()
10628        },
10629    );
10630
10631    let buffer = project
10632        .update(cx, |project, cx| {
10633            project.open_local_buffer(path!("/file.rs"), cx)
10634        })
10635        .await
10636        .unwrap();
10637
10638    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10639    let (editor, cx) = cx.add_window_view(|window, cx| {
10640        build_editor_with_project(project.clone(), buffer, window, cx)
10641    });
10642    editor.update_in(cx, |editor, window, cx| {
10643        editor.set_text("one\ntwo\nthree\n", window, cx)
10644    });
10645    assert!(cx.read(|cx| editor.is_dirty(cx)));
10646
10647    cx.executor().start_waiting();
10648    let fake_server = fake_servers.next().await.unwrap();
10649
10650    {
10651        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10652            move |params, _| async move {
10653                assert_eq!(
10654                    params.text_document.uri,
10655                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10656                );
10657                assert_eq!(params.options.tab_size, 4);
10658                Ok(Some(vec![lsp::TextEdit::new(
10659                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10660                    ", ".to_string(),
10661                )]))
10662            },
10663        );
10664        let save = editor
10665            .update_in(cx, |editor, window, cx| {
10666                editor.save(
10667                    SaveOptions {
10668                        format: true,
10669                        autosave: false,
10670                    },
10671                    project.clone(),
10672                    window,
10673                    cx,
10674                )
10675            })
10676            .unwrap();
10677        cx.executor().start_waiting();
10678        save.await;
10679
10680        assert_eq!(
10681            editor.update(cx, |editor, cx| editor.text(cx)),
10682            "one, two\nthree\n"
10683        );
10684        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10685    }
10686
10687    {
10688        editor.update_in(cx, |editor, window, cx| {
10689            editor.set_text("one\ntwo\nthree\n", window, cx)
10690        });
10691        assert!(cx.read(|cx| editor.is_dirty(cx)));
10692
10693        // Ensure we can still save even if formatting hangs.
10694        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10695            move |params, _| async move {
10696                assert_eq!(
10697                    params.text_document.uri,
10698                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10699                );
10700                futures::future::pending::<()>().await;
10701                unreachable!()
10702            },
10703        );
10704        let save = editor
10705            .update_in(cx, |editor, window, cx| {
10706                editor.save(
10707                    SaveOptions {
10708                        format: true,
10709                        autosave: false,
10710                    },
10711                    project.clone(),
10712                    window,
10713                    cx,
10714                )
10715            })
10716            .unwrap();
10717        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10718        cx.executor().start_waiting();
10719        save.await;
10720        assert_eq!(
10721            editor.update(cx, |editor, cx| editor.text(cx)),
10722            "one\ntwo\nthree\n"
10723        );
10724    }
10725
10726    // Set rust language override and assert overridden tabsize is sent to language server
10727    update_test_language_settings(cx, |settings| {
10728        settings.languages.0.insert(
10729            "Rust".into(),
10730            LanguageSettingsContent {
10731                tab_size: NonZeroU32::new(8),
10732                ..Default::default()
10733            },
10734        );
10735    });
10736
10737    {
10738        editor.update_in(cx, |editor, window, cx| {
10739            editor.set_text("somehting_new\n", window, cx)
10740        });
10741        assert!(cx.read(|cx| editor.is_dirty(cx)));
10742        let _formatting_request_signal = fake_server
10743            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10744                assert_eq!(
10745                    params.text_document.uri,
10746                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10747                );
10748                assert_eq!(params.options.tab_size, 8);
10749                Ok(Some(vec![]))
10750            });
10751        let save = editor
10752            .update_in(cx, |editor, window, cx| {
10753                editor.save(
10754                    SaveOptions {
10755                        format: true,
10756                        autosave: false,
10757                    },
10758                    project.clone(),
10759                    window,
10760                    cx,
10761                )
10762            })
10763            .unwrap();
10764        cx.executor().start_waiting();
10765        save.await;
10766    }
10767}
10768
10769#[gpui::test]
10770async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
10771    init_test(cx, |settings| {
10772        settings.defaults.ensure_final_newline_on_save = Some(false);
10773    });
10774
10775    let fs = FakeFs::new(cx.executor());
10776    fs.insert_file(path!("/file.txt"), "foo".into()).await;
10777
10778    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
10779
10780    let buffer = project
10781        .update(cx, |project, cx| {
10782            project.open_local_buffer(path!("/file.txt"), cx)
10783        })
10784        .await
10785        .unwrap();
10786
10787    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10788    let (editor, cx) = cx.add_window_view(|window, cx| {
10789        build_editor_with_project(project.clone(), buffer, window, cx)
10790    });
10791    editor.update_in(cx, |editor, window, cx| {
10792        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
10793            s.select_ranges([0..0])
10794        });
10795    });
10796    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10797
10798    editor.update_in(cx, |editor, window, cx| {
10799        editor.handle_input("\n", window, cx)
10800    });
10801    cx.run_until_parked();
10802    save(&editor, &project, cx).await;
10803    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10804
10805    editor.update_in(cx, |editor, window, cx| {
10806        editor.undo(&Default::default(), window, cx);
10807    });
10808    save(&editor, &project, cx).await;
10809    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10810
10811    editor.update_in(cx, |editor, window, cx| {
10812        editor.redo(&Default::default(), window, cx);
10813    });
10814    cx.run_until_parked();
10815    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10816
10817    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
10818        let save = editor
10819            .update_in(cx, |editor, window, cx| {
10820                editor.save(
10821                    SaveOptions {
10822                        format: true,
10823                        autosave: false,
10824                    },
10825                    project.clone(),
10826                    window,
10827                    cx,
10828                )
10829            })
10830            .unwrap();
10831        cx.executor().start_waiting();
10832        save.await;
10833        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10834    }
10835}
10836
10837#[gpui::test]
10838async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
10839    init_test(cx, |_| {});
10840
10841    let cols = 4;
10842    let rows = 10;
10843    let sample_text_1 = sample_text(rows, cols, 'a');
10844    assert_eq!(
10845        sample_text_1,
10846        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10847    );
10848    let sample_text_2 = sample_text(rows, cols, 'l');
10849    assert_eq!(
10850        sample_text_2,
10851        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10852    );
10853    let sample_text_3 = sample_text(rows, cols, 'v');
10854    assert_eq!(
10855        sample_text_3,
10856        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10857    );
10858
10859    let fs = FakeFs::new(cx.executor());
10860    fs.insert_tree(
10861        path!("/a"),
10862        json!({
10863            "main.rs": sample_text_1,
10864            "other.rs": sample_text_2,
10865            "lib.rs": sample_text_3,
10866        }),
10867    )
10868    .await;
10869
10870    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10871    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10872    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10873
10874    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10875    language_registry.add(rust_lang());
10876    let mut fake_servers = language_registry.register_fake_lsp(
10877        "Rust",
10878        FakeLspAdapter {
10879            capabilities: lsp::ServerCapabilities {
10880                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10881                ..Default::default()
10882            },
10883            ..Default::default()
10884        },
10885    );
10886
10887    let worktree = project.update(cx, |project, cx| {
10888        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
10889        assert_eq!(worktrees.len(), 1);
10890        worktrees.pop().unwrap()
10891    });
10892    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10893
10894    let buffer_1 = project
10895        .update(cx, |project, cx| {
10896            project.open_buffer((worktree_id, "main.rs"), cx)
10897        })
10898        .await
10899        .unwrap();
10900    let buffer_2 = project
10901        .update(cx, |project, cx| {
10902            project.open_buffer((worktree_id, "other.rs"), cx)
10903        })
10904        .await
10905        .unwrap();
10906    let buffer_3 = project
10907        .update(cx, |project, cx| {
10908            project.open_buffer((worktree_id, "lib.rs"), cx)
10909        })
10910        .await
10911        .unwrap();
10912
10913    let multi_buffer = cx.new(|cx| {
10914        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10915        multi_buffer.push_excerpts(
10916            buffer_1.clone(),
10917            [
10918                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10919                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10920                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10921            ],
10922            cx,
10923        );
10924        multi_buffer.push_excerpts(
10925            buffer_2.clone(),
10926            [
10927                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10928                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10929                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10930            ],
10931            cx,
10932        );
10933        multi_buffer.push_excerpts(
10934            buffer_3.clone(),
10935            [
10936                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10937                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10938                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10939            ],
10940            cx,
10941        );
10942        multi_buffer
10943    });
10944    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
10945        Editor::new(
10946            EditorMode::full(),
10947            multi_buffer,
10948            Some(project.clone()),
10949            window,
10950            cx,
10951        )
10952    });
10953
10954    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10955        editor.change_selections(
10956            SelectionEffects::scroll(Autoscroll::Next),
10957            window,
10958            cx,
10959            |s| s.select_ranges(Some(1..2)),
10960        );
10961        editor.insert("|one|two|three|", window, cx);
10962    });
10963    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10964    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10965        editor.change_selections(
10966            SelectionEffects::scroll(Autoscroll::Next),
10967            window,
10968            cx,
10969            |s| s.select_ranges(Some(60..70)),
10970        );
10971        editor.insert("|four|five|six|", window, cx);
10972    });
10973    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10974
10975    // First two buffers should be edited, but not the third one.
10976    assert_eq!(
10977        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10978        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
10979    );
10980    buffer_1.update(cx, |buffer, _| {
10981        assert!(buffer.is_dirty());
10982        assert_eq!(
10983            buffer.text(),
10984            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
10985        )
10986    });
10987    buffer_2.update(cx, |buffer, _| {
10988        assert!(buffer.is_dirty());
10989        assert_eq!(
10990            buffer.text(),
10991            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
10992        )
10993    });
10994    buffer_3.update(cx, |buffer, _| {
10995        assert!(!buffer.is_dirty());
10996        assert_eq!(buffer.text(), sample_text_3,)
10997    });
10998    cx.executor().run_until_parked();
10999
11000    cx.executor().start_waiting();
11001    let save = multi_buffer_editor
11002        .update_in(cx, |editor, window, cx| {
11003            editor.save(
11004                SaveOptions {
11005                    format: true,
11006                    autosave: false,
11007                },
11008                project.clone(),
11009                window,
11010                cx,
11011            )
11012        })
11013        .unwrap();
11014
11015    let fake_server = fake_servers.next().await.unwrap();
11016    fake_server
11017        .server
11018        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11019            Ok(Some(vec![lsp::TextEdit::new(
11020                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11021                format!("[{} formatted]", params.text_document.uri),
11022            )]))
11023        })
11024        .detach();
11025    save.await;
11026
11027    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11028    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11029    assert_eq!(
11030        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11031        uri!(
11032            "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"
11033        ),
11034    );
11035    buffer_1.update(cx, |buffer, _| {
11036        assert!(!buffer.is_dirty());
11037        assert_eq!(
11038            buffer.text(),
11039            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11040        )
11041    });
11042    buffer_2.update(cx, |buffer, _| {
11043        assert!(!buffer.is_dirty());
11044        assert_eq!(
11045            buffer.text(),
11046            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11047        )
11048    });
11049    buffer_3.update(cx, |buffer, _| {
11050        assert!(!buffer.is_dirty());
11051        assert_eq!(buffer.text(), sample_text_3,)
11052    });
11053}
11054
11055#[gpui::test]
11056async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11057    init_test(cx, |_| {});
11058
11059    let fs = FakeFs::new(cx.executor());
11060    fs.insert_tree(
11061        path!("/dir"),
11062        json!({
11063            "file1.rs": "fn main() { println!(\"hello\"); }",
11064            "file2.rs": "fn test() { println!(\"test\"); }",
11065            "file3.rs": "fn other() { println!(\"other\"); }\n",
11066        }),
11067    )
11068    .await;
11069
11070    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11071    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11072    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11073
11074    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11075    language_registry.add(rust_lang());
11076
11077    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11078    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11079
11080    // Open three buffers
11081    let buffer_1 = project
11082        .update(cx, |project, cx| {
11083            project.open_buffer((worktree_id, "file1.rs"), cx)
11084        })
11085        .await
11086        .unwrap();
11087    let buffer_2 = project
11088        .update(cx, |project, cx| {
11089            project.open_buffer((worktree_id, "file2.rs"), cx)
11090        })
11091        .await
11092        .unwrap();
11093    let buffer_3 = project
11094        .update(cx, |project, cx| {
11095            project.open_buffer((worktree_id, "file3.rs"), cx)
11096        })
11097        .await
11098        .unwrap();
11099
11100    // Create a multi-buffer with all three buffers
11101    let multi_buffer = cx.new(|cx| {
11102        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11103        multi_buffer.push_excerpts(
11104            buffer_1.clone(),
11105            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11106            cx,
11107        );
11108        multi_buffer.push_excerpts(
11109            buffer_2.clone(),
11110            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11111            cx,
11112        );
11113        multi_buffer.push_excerpts(
11114            buffer_3.clone(),
11115            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11116            cx,
11117        );
11118        multi_buffer
11119    });
11120
11121    let editor = cx.new_window_entity(|window, cx| {
11122        Editor::new(
11123            EditorMode::full(),
11124            multi_buffer,
11125            Some(project.clone()),
11126            window,
11127            cx,
11128        )
11129    });
11130
11131    // Edit only the first buffer
11132    editor.update_in(cx, |editor, window, cx| {
11133        editor.change_selections(
11134            SelectionEffects::scroll(Autoscroll::Next),
11135            window,
11136            cx,
11137            |s| s.select_ranges(Some(10..10)),
11138        );
11139        editor.insert("// edited", window, cx);
11140    });
11141
11142    // Verify that only buffer 1 is dirty
11143    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11144    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11145    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11146
11147    // Get write counts after file creation (files were created with initial content)
11148    // We expect each file to have been written once during creation
11149    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11150    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11151    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11152
11153    // Perform autosave
11154    let save_task = editor.update_in(cx, |editor, window, cx| {
11155        editor.save(
11156            SaveOptions {
11157                format: true,
11158                autosave: true,
11159            },
11160            project.clone(),
11161            window,
11162            cx,
11163        )
11164    });
11165    save_task.await.unwrap();
11166
11167    // Only the dirty buffer should have been saved
11168    assert_eq!(
11169        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11170        1,
11171        "Buffer 1 was dirty, so it should have been written once during autosave"
11172    );
11173    assert_eq!(
11174        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11175        0,
11176        "Buffer 2 was clean, so it should not have been written during autosave"
11177    );
11178    assert_eq!(
11179        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11180        0,
11181        "Buffer 3 was clean, so it should not have been written during autosave"
11182    );
11183
11184    // Verify buffer states after autosave
11185    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11186    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11187    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11188
11189    // Now perform a manual save (format = true)
11190    let save_task = editor.update_in(cx, |editor, window, cx| {
11191        editor.save(
11192            SaveOptions {
11193                format: true,
11194                autosave: false,
11195            },
11196            project.clone(),
11197            window,
11198            cx,
11199        )
11200    });
11201    save_task.await.unwrap();
11202
11203    // During manual save, clean buffers don't get written to disk
11204    // They just get did_save called for language server notifications
11205    assert_eq!(
11206        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11207        1,
11208        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11209    );
11210    assert_eq!(
11211        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11212        0,
11213        "Buffer 2 should not have been written at all"
11214    );
11215    assert_eq!(
11216        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11217        0,
11218        "Buffer 3 should not have been written at all"
11219    );
11220}
11221
11222async fn setup_range_format_test(
11223    cx: &mut TestAppContext,
11224) -> (
11225    Entity<Project>,
11226    Entity<Editor>,
11227    &mut gpui::VisualTestContext,
11228    lsp::FakeLanguageServer,
11229) {
11230    init_test(cx, |_| {});
11231
11232    let fs = FakeFs::new(cx.executor());
11233    fs.insert_file(path!("/file.rs"), Default::default()).await;
11234
11235    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11236
11237    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11238    language_registry.add(rust_lang());
11239    let mut fake_servers = language_registry.register_fake_lsp(
11240        "Rust",
11241        FakeLspAdapter {
11242            capabilities: lsp::ServerCapabilities {
11243                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11244                ..lsp::ServerCapabilities::default()
11245            },
11246            ..FakeLspAdapter::default()
11247        },
11248    );
11249
11250    let buffer = project
11251        .update(cx, |project, cx| {
11252            project.open_local_buffer(path!("/file.rs"), cx)
11253        })
11254        .await
11255        .unwrap();
11256
11257    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11258    let (editor, cx) = cx.add_window_view(|window, cx| {
11259        build_editor_with_project(project.clone(), buffer, window, cx)
11260    });
11261
11262    cx.executor().start_waiting();
11263    let fake_server = fake_servers.next().await.unwrap();
11264
11265    (project, editor, cx, fake_server)
11266}
11267
11268#[gpui::test]
11269async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11270    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11271
11272    editor.update_in(cx, |editor, window, cx| {
11273        editor.set_text("one\ntwo\nthree\n", window, cx)
11274    });
11275    assert!(cx.read(|cx| editor.is_dirty(cx)));
11276
11277    let save = editor
11278        .update_in(cx, |editor, window, cx| {
11279            editor.save(
11280                SaveOptions {
11281                    format: true,
11282                    autosave: false,
11283                },
11284                project.clone(),
11285                window,
11286                cx,
11287            )
11288        })
11289        .unwrap();
11290    fake_server
11291        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11292            assert_eq!(
11293                params.text_document.uri,
11294                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11295            );
11296            assert_eq!(params.options.tab_size, 4);
11297            Ok(Some(vec![lsp::TextEdit::new(
11298                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11299                ", ".to_string(),
11300            )]))
11301        })
11302        .next()
11303        .await;
11304    cx.executor().start_waiting();
11305    save.await;
11306    assert_eq!(
11307        editor.update(cx, |editor, cx| editor.text(cx)),
11308        "one, two\nthree\n"
11309    );
11310    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11311}
11312
11313#[gpui::test]
11314async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11315    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11316
11317    editor.update_in(cx, |editor, window, cx| {
11318        editor.set_text("one\ntwo\nthree\n", window, cx)
11319    });
11320    assert!(cx.read(|cx| editor.is_dirty(cx)));
11321
11322    // Test that save still works when formatting hangs
11323    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11324        move |params, _| async move {
11325            assert_eq!(
11326                params.text_document.uri,
11327                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11328            );
11329            futures::future::pending::<()>().await;
11330            unreachable!()
11331        },
11332    );
11333    let save = editor
11334        .update_in(cx, |editor, window, cx| {
11335            editor.save(
11336                SaveOptions {
11337                    format: true,
11338                    autosave: false,
11339                },
11340                project.clone(),
11341                window,
11342                cx,
11343            )
11344        })
11345        .unwrap();
11346    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11347    cx.executor().start_waiting();
11348    save.await;
11349    assert_eq!(
11350        editor.update(cx, |editor, cx| editor.text(cx)),
11351        "one\ntwo\nthree\n"
11352    );
11353    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11354}
11355
11356#[gpui::test]
11357async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11358    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11359
11360    // Buffer starts clean, no formatting should be requested
11361    let save = editor
11362        .update_in(cx, |editor, window, cx| {
11363            editor.save(
11364                SaveOptions {
11365                    format: false,
11366                    autosave: false,
11367                },
11368                project.clone(),
11369                window,
11370                cx,
11371            )
11372        })
11373        .unwrap();
11374    let _pending_format_request = fake_server
11375        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11376            panic!("Should not be invoked");
11377        })
11378        .next();
11379    cx.executor().start_waiting();
11380    save.await;
11381    cx.run_until_parked();
11382}
11383
11384#[gpui::test]
11385async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11386    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11387
11388    // Set Rust language override and assert overridden tabsize is sent to language server
11389    update_test_language_settings(cx, |settings| {
11390        settings.languages.0.insert(
11391            "Rust".into(),
11392            LanguageSettingsContent {
11393                tab_size: NonZeroU32::new(8),
11394                ..Default::default()
11395            },
11396        );
11397    });
11398
11399    editor.update_in(cx, |editor, window, cx| {
11400        editor.set_text("something_new\n", window, cx)
11401    });
11402    assert!(cx.read(|cx| editor.is_dirty(cx)));
11403    let save = editor
11404        .update_in(cx, |editor, window, cx| {
11405            editor.save(
11406                SaveOptions {
11407                    format: true,
11408                    autosave: false,
11409                },
11410                project.clone(),
11411                window,
11412                cx,
11413            )
11414        })
11415        .unwrap();
11416    fake_server
11417        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11418            assert_eq!(
11419                params.text_document.uri,
11420                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11421            );
11422            assert_eq!(params.options.tab_size, 8);
11423            Ok(Some(Vec::new()))
11424        })
11425        .next()
11426        .await;
11427    save.await;
11428}
11429
11430#[gpui::test]
11431async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11432    init_test(cx, |settings| {
11433        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11434            Formatter::LanguageServer { name: None },
11435        )))
11436    });
11437
11438    let fs = FakeFs::new(cx.executor());
11439    fs.insert_file(path!("/file.rs"), Default::default()).await;
11440
11441    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11442
11443    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11444    language_registry.add(Arc::new(Language::new(
11445        LanguageConfig {
11446            name: "Rust".into(),
11447            matcher: LanguageMatcher {
11448                path_suffixes: vec!["rs".to_string()],
11449                ..Default::default()
11450            },
11451            ..LanguageConfig::default()
11452        },
11453        Some(tree_sitter_rust::LANGUAGE.into()),
11454    )));
11455    update_test_language_settings(cx, |settings| {
11456        // Enable Prettier formatting for the same buffer, and ensure
11457        // LSP is called instead of Prettier.
11458        settings.defaults.prettier = Some(PrettierSettings {
11459            allowed: true,
11460            ..PrettierSettings::default()
11461        });
11462    });
11463    let mut fake_servers = language_registry.register_fake_lsp(
11464        "Rust",
11465        FakeLspAdapter {
11466            capabilities: lsp::ServerCapabilities {
11467                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11468                ..Default::default()
11469            },
11470            ..Default::default()
11471        },
11472    );
11473
11474    let buffer = project
11475        .update(cx, |project, cx| {
11476            project.open_local_buffer(path!("/file.rs"), cx)
11477        })
11478        .await
11479        .unwrap();
11480
11481    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11482    let (editor, cx) = cx.add_window_view(|window, cx| {
11483        build_editor_with_project(project.clone(), buffer, window, cx)
11484    });
11485    editor.update_in(cx, |editor, window, cx| {
11486        editor.set_text("one\ntwo\nthree\n", window, cx)
11487    });
11488
11489    cx.executor().start_waiting();
11490    let fake_server = fake_servers.next().await.unwrap();
11491
11492    let format = editor
11493        .update_in(cx, |editor, window, cx| {
11494            editor.perform_format(
11495                project.clone(),
11496                FormatTrigger::Manual,
11497                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11498                window,
11499                cx,
11500            )
11501        })
11502        .unwrap();
11503    fake_server
11504        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11505            assert_eq!(
11506                params.text_document.uri,
11507                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11508            );
11509            assert_eq!(params.options.tab_size, 4);
11510            Ok(Some(vec![lsp::TextEdit::new(
11511                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11512                ", ".to_string(),
11513            )]))
11514        })
11515        .next()
11516        .await;
11517    cx.executor().start_waiting();
11518    format.await;
11519    assert_eq!(
11520        editor.update(cx, |editor, cx| editor.text(cx)),
11521        "one, two\nthree\n"
11522    );
11523
11524    editor.update_in(cx, |editor, window, cx| {
11525        editor.set_text("one\ntwo\nthree\n", window, cx)
11526    });
11527    // Ensure we don't lock if formatting hangs.
11528    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11529        move |params, _| async move {
11530            assert_eq!(
11531                params.text_document.uri,
11532                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11533            );
11534            futures::future::pending::<()>().await;
11535            unreachable!()
11536        },
11537    );
11538    let format = editor
11539        .update_in(cx, |editor, window, cx| {
11540            editor.perform_format(
11541                project,
11542                FormatTrigger::Manual,
11543                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11544                window,
11545                cx,
11546            )
11547        })
11548        .unwrap();
11549    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11550    cx.executor().start_waiting();
11551    format.await;
11552    assert_eq!(
11553        editor.update(cx, |editor, cx| editor.text(cx)),
11554        "one\ntwo\nthree\n"
11555    );
11556}
11557
11558#[gpui::test]
11559async fn test_multiple_formatters(cx: &mut TestAppContext) {
11560    init_test(cx, |settings| {
11561        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11562        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11563            Formatter::LanguageServer { name: None },
11564            Formatter::CodeActions(
11565                [
11566                    ("code-action-1".into(), true),
11567                    ("code-action-2".into(), true),
11568                ]
11569                .into_iter()
11570                .collect(),
11571            ),
11572        ])))
11573    });
11574
11575    let fs = FakeFs::new(cx.executor());
11576    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11577        .await;
11578
11579    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11580    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11581    language_registry.add(rust_lang());
11582
11583    let mut fake_servers = language_registry.register_fake_lsp(
11584        "Rust",
11585        FakeLspAdapter {
11586            capabilities: lsp::ServerCapabilities {
11587                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11588                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11589                    commands: vec!["the-command-for-code-action-1".into()],
11590                    ..Default::default()
11591                }),
11592                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11593                ..Default::default()
11594            },
11595            ..Default::default()
11596        },
11597    );
11598
11599    let buffer = project
11600        .update(cx, |project, cx| {
11601            project.open_local_buffer(path!("/file.rs"), cx)
11602        })
11603        .await
11604        .unwrap();
11605
11606    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11607    let (editor, cx) = cx.add_window_view(|window, cx| {
11608        build_editor_with_project(project.clone(), buffer, window, cx)
11609    });
11610
11611    cx.executor().start_waiting();
11612
11613    let fake_server = fake_servers.next().await.unwrap();
11614    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11615        move |_params, _| async move {
11616            Ok(Some(vec![lsp::TextEdit::new(
11617                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11618                "applied-formatting\n".to_string(),
11619            )]))
11620        },
11621    );
11622    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11623        move |params, _| async move {
11624            assert_eq!(
11625                params.context.only,
11626                Some(vec!["code-action-1".into(), "code-action-2".into()])
11627            );
11628            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11629            Ok(Some(vec![
11630                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11631                    kind: Some("code-action-1".into()),
11632                    edit: Some(lsp::WorkspaceEdit::new(
11633                        [(
11634                            uri.clone(),
11635                            vec![lsp::TextEdit::new(
11636                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11637                                "applied-code-action-1-edit\n".to_string(),
11638                            )],
11639                        )]
11640                        .into_iter()
11641                        .collect(),
11642                    )),
11643                    command: Some(lsp::Command {
11644                        command: "the-command-for-code-action-1".into(),
11645                        ..Default::default()
11646                    }),
11647                    ..Default::default()
11648                }),
11649                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11650                    kind: Some("code-action-2".into()),
11651                    edit: Some(lsp::WorkspaceEdit::new(
11652                        [(
11653                            uri,
11654                            vec![lsp::TextEdit::new(
11655                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11656                                "applied-code-action-2-edit\n".to_string(),
11657                            )],
11658                        )]
11659                        .into_iter()
11660                        .collect(),
11661                    )),
11662                    ..Default::default()
11663                }),
11664            ]))
11665        },
11666    );
11667
11668    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11669        move |params, _| async move { Ok(params) }
11670    });
11671
11672    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11673    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11674        let fake = fake_server.clone();
11675        let lock = command_lock.clone();
11676        move |params, _| {
11677            assert_eq!(params.command, "the-command-for-code-action-1");
11678            let fake = fake.clone();
11679            let lock = lock.clone();
11680            async move {
11681                lock.lock().await;
11682                fake.server
11683                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11684                        label: None,
11685                        edit: lsp::WorkspaceEdit {
11686                            changes: Some(
11687                                [(
11688                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11689                                    vec![lsp::TextEdit {
11690                                        range: lsp::Range::new(
11691                                            lsp::Position::new(0, 0),
11692                                            lsp::Position::new(0, 0),
11693                                        ),
11694                                        new_text: "applied-code-action-1-command\n".into(),
11695                                    }],
11696                                )]
11697                                .into_iter()
11698                                .collect(),
11699                            ),
11700                            ..Default::default()
11701                        },
11702                    })
11703                    .await
11704                    .into_response()
11705                    .unwrap();
11706                Ok(Some(json!(null)))
11707            }
11708        }
11709    });
11710
11711    cx.executor().start_waiting();
11712    editor
11713        .update_in(cx, |editor, window, cx| {
11714            editor.perform_format(
11715                project.clone(),
11716                FormatTrigger::Manual,
11717                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11718                window,
11719                cx,
11720            )
11721        })
11722        .unwrap()
11723        .await;
11724    editor.update(cx, |editor, cx| {
11725        assert_eq!(
11726            editor.text(cx),
11727            r#"
11728                applied-code-action-2-edit
11729                applied-code-action-1-command
11730                applied-code-action-1-edit
11731                applied-formatting
11732                one
11733                two
11734                three
11735            "#
11736            .unindent()
11737        );
11738    });
11739
11740    editor.update_in(cx, |editor, window, cx| {
11741        editor.undo(&Default::default(), window, cx);
11742        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11743    });
11744
11745    // Perform a manual edit while waiting for an LSP command
11746    // that's being run as part of a formatting code action.
11747    let lock_guard = command_lock.lock().await;
11748    let format = editor
11749        .update_in(cx, |editor, window, cx| {
11750            editor.perform_format(
11751                project.clone(),
11752                FormatTrigger::Manual,
11753                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11754                window,
11755                cx,
11756            )
11757        })
11758        .unwrap();
11759    cx.run_until_parked();
11760    editor.update(cx, |editor, cx| {
11761        assert_eq!(
11762            editor.text(cx),
11763            r#"
11764                applied-code-action-1-edit
11765                applied-formatting
11766                one
11767                two
11768                three
11769            "#
11770            .unindent()
11771        );
11772
11773        editor.buffer.update(cx, |buffer, cx| {
11774            let ix = buffer.len(cx);
11775            buffer.edit([(ix..ix, "edited\n")], None, cx);
11776        });
11777    });
11778
11779    // Allow the LSP command to proceed. Because the buffer was edited,
11780    // the second code action will not be run.
11781    drop(lock_guard);
11782    format.await;
11783    editor.update_in(cx, |editor, window, cx| {
11784        assert_eq!(
11785            editor.text(cx),
11786            r#"
11787                applied-code-action-1-command
11788                applied-code-action-1-edit
11789                applied-formatting
11790                one
11791                two
11792                three
11793                edited
11794            "#
11795            .unindent()
11796        );
11797
11798        // The manual edit is undone first, because it is the last thing the user did
11799        // (even though the command completed afterwards).
11800        editor.undo(&Default::default(), window, cx);
11801        assert_eq!(
11802            editor.text(cx),
11803            r#"
11804                applied-code-action-1-command
11805                applied-code-action-1-edit
11806                applied-formatting
11807                one
11808                two
11809                three
11810            "#
11811            .unindent()
11812        );
11813
11814        // All the formatting (including the command, which completed after the manual edit)
11815        // is undone together.
11816        editor.undo(&Default::default(), window, cx);
11817        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11818    });
11819}
11820
11821#[gpui::test]
11822async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
11823    init_test(cx, |settings| {
11824        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11825            Formatter::LanguageServer { name: None },
11826        ])))
11827    });
11828
11829    let fs = FakeFs::new(cx.executor());
11830    fs.insert_file(path!("/file.ts"), Default::default()).await;
11831
11832    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11833
11834    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11835    language_registry.add(Arc::new(Language::new(
11836        LanguageConfig {
11837            name: "TypeScript".into(),
11838            matcher: LanguageMatcher {
11839                path_suffixes: vec!["ts".to_string()],
11840                ..Default::default()
11841            },
11842            ..LanguageConfig::default()
11843        },
11844        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11845    )));
11846    update_test_language_settings(cx, |settings| {
11847        settings.defaults.prettier = Some(PrettierSettings {
11848            allowed: true,
11849            ..PrettierSettings::default()
11850        });
11851    });
11852    let mut fake_servers = language_registry.register_fake_lsp(
11853        "TypeScript",
11854        FakeLspAdapter {
11855            capabilities: lsp::ServerCapabilities {
11856                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11857                ..Default::default()
11858            },
11859            ..Default::default()
11860        },
11861    );
11862
11863    let buffer = project
11864        .update(cx, |project, cx| {
11865            project.open_local_buffer(path!("/file.ts"), cx)
11866        })
11867        .await
11868        .unwrap();
11869
11870    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11871    let (editor, cx) = cx.add_window_view(|window, cx| {
11872        build_editor_with_project(project.clone(), buffer, window, cx)
11873    });
11874    editor.update_in(cx, |editor, window, cx| {
11875        editor.set_text(
11876            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11877            window,
11878            cx,
11879        )
11880    });
11881
11882    cx.executor().start_waiting();
11883    let fake_server = fake_servers.next().await.unwrap();
11884
11885    let format = editor
11886        .update_in(cx, |editor, window, cx| {
11887            editor.perform_code_action_kind(
11888                project.clone(),
11889                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11890                window,
11891                cx,
11892            )
11893        })
11894        .unwrap();
11895    fake_server
11896        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
11897            assert_eq!(
11898                params.text_document.uri,
11899                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
11900            );
11901            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11902                lsp::CodeAction {
11903                    title: "Organize Imports".to_string(),
11904                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
11905                    edit: Some(lsp::WorkspaceEdit {
11906                        changes: Some(
11907                            [(
11908                                params.text_document.uri.clone(),
11909                                vec![lsp::TextEdit::new(
11910                                    lsp::Range::new(
11911                                        lsp::Position::new(1, 0),
11912                                        lsp::Position::new(2, 0),
11913                                    ),
11914                                    "".to_string(),
11915                                )],
11916                            )]
11917                            .into_iter()
11918                            .collect(),
11919                        ),
11920                        ..Default::default()
11921                    }),
11922                    ..Default::default()
11923                },
11924            )]))
11925        })
11926        .next()
11927        .await;
11928    cx.executor().start_waiting();
11929    format.await;
11930    assert_eq!(
11931        editor.update(cx, |editor, cx| editor.text(cx)),
11932        "import { a } from 'module';\n\nconst x = a;\n"
11933    );
11934
11935    editor.update_in(cx, |editor, window, cx| {
11936        editor.set_text(
11937            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11938            window,
11939            cx,
11940        )
11941    });
11942    // Ensure we don't lock if code action hangs.
11943    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11944        move |params, _| async move {
11945            assert_eq!(
11946                params.text_document.uri,
11947                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
11948            );
11949            futures::future::pending::<()>().await;
11950            unreachable!()
11951        },
11952    );
11953    let format = editor
11954        .update_in(cx, |editor, window, cx| {
11955            editor.perform_code_action_kind(
11956                project,
11957                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11958                window,
11959                cx,
11960            )
11961        })
11962        .unwrap();
11963    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
11964    cx.executor().start_waiting();
11965    format.await;
11966    assert_eq!(
11967        editor.update(cx, |editor, cx| editor.text(cx)),
11968        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
11969    );
11970}
11971
11972#[gpui::test]
11973async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
11974    init_test(cx, |_| {});
11975
11976    let mut cx = EditorLspTestContext::new_rust(
11977        lsp::ServerCapabilities {
11978            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11979            ..Default::default()
11980        },
11981        cx,
11982    )
11983    .await;
11984
11985    cx.set_state(indoc! {"
11986        one.twoˇ
11987    "});
11988
11989    // The format request takes a long time. When it completes, it inserts
11990    // a newline and an indent before the `.`
11991    cx.lsp
11992        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
11993            let executor = cx.background_executor().clone();
11994            async move {
11995                executor.timer(Duration::from_millis(100)).await;
11996                Ok(Some(vec![lsp::TextEdit {
11997                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
11998                    new_text: "\n    ".into(),
11999                }]))
12000            }
12001        });
12002
12003    // Submit a format request.
12004    let format_1 = cx
12005        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12006        .unwrap();
12007    cx.executor().run_until_parked();
12008
12009    // Submit a second format request.
12010    let format_2 = cx
12011        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12012        .unwrap();
12013    cx.executor().run_until_parked();
12014
12015    // Wait for both format requests to complete
12016    cx.executor().advance_clock(Duration::from_millis(200));
12017    cx.executor().start_waiting();
12018    format_1.await.unwrap();
12019    cx.executor().start_waiting();
12020    format_2.await.unwrap();
12021
12022    // The formatting edits only happens once.
12023    cx.assert_editor_state(indoc! {"
12024        one
12025            .twoˇ
12026    "});
12027}
12028
12029#[gpui::test]
12030async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12031    init_test(cx, |settings| {
12032        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12033    });
12034
12035    let mut cx = EditorLspTestContext::new_rust(
12036        lsp::ServerCapabilities {
12037            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12038            ..Default::default()
12039        },
12040        cx,
12041    )
12042    .await;
12043
12044    // Set up a buffer white some trailing whitespace and no trailing newline.
12045    cx.set_state(
12046        &[
12047            "one ",   //
12048            "twoˇ",   //
12049            "three ", //
12050            "four",   //
12051        ]
12052        .join("\n"),
12053    );
12054
12055    // Submit a format request.
12056    let format = cx
12057        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12058        .unwrap();
12059
12060    // Record which buffer changes have been sent to the language server
12061    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12062    cx.lsp
12063        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12064            let buffer_changes = buffer_changes.clone();
12065            move |params, _| {
12066                buffer_changes.lock().extend(
12067                    params
12068                        .content_changes
12069                        .into_iter()
12070                        .map(|e| (e.range.unwrap(), e.text)),
12071                );
12072            }
12073        });
12074
12075    // Handle formatting requests to the language server.
12076    cx.lsp
12077        .set_request_handler::<lsp::request::Formatting, _, _>({
12078            let buffer_changes = buffer_changes.clone();
12079            move |_, _| {
12080                // When formatting is requested, trailing whitespace has already been stripped,
12081                // and the trailing newline has already been added.
12082                assert_eq!(
12083                    &buffer_changes.lock()[1..],
12084                    &[
12085                        (
12086                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12087                            "".into()
12088                        ),
12089                        (
12090                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12091                            "".into()
12092                        ),
12093                        (
12094                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12095                            "\n".into()
12096                        ),
12097                    ]
12098                );
12099
12100                // Insert blank lines between each line of the buffer.
12101                async move {
12102                    Ok(Some(vec![
12103                        lsp::TextEdit {
12104                            range: lsp::Range::new(
12105                                lsp::Position::new(1, 0),
12106                                lsp::Position::new(1, 0),
12107                            ),
12108                            new_text: "\n".into(),
12109                        },
12110                        lsp::TextEdit {
12111                            range: lsp::Range::new(
12112                                lsp::Position::new(2, 0),
12113                                lsp::Position::new(2, 0),
12114                            ),
12115                            new_text: "\n".into(),
12116                        },
12117                    ]))
12118                }
12119            }
12120        });
12121
12122    // After formatting the buffer, the trailing whitespace is stripped,
12123    // a newline is appended, and the edits provided by the language server
12124    // have been applied.
12125    format.await.unwrap();
12126    cx.assert_editor_state(
12127        &[
12128            "one",   //
12129            "",      //
12130            "twoˇ",  //
12131            "",      //
12132            "three", //
12133            "four",  //
12134            "",      //
12135        ]
12136        .join("\n"),
12137    );
12138
12139    // Undoing the formatting undoes the trailing whitespace removal, the
12140    // trailing newline, and the LSP edits.
12141    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12142    cx.assert_editor_state(
12143        &[
12144            "one ",   //
12145            "twoˇ",   //
12146            "three ", //
12147            "four",   //
12148        ]
12149        .join("\n"),
12150    );
12151}
12152
12153#[gpui::test]
12154async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12155    cx: &mut TestAppContext,
12156) {
12157    init_test(cx, |_| {});
12158
12159    cx.update(|cx| {
12160        cx.update_global::<SettingsStore, _>(|settings, cx| {
12161            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12162                settings.auto_signature_help = Some(true);
12163            });
12164        });
12165    });
12166
12167    let mut cx = EditorLspTestContext::new_rust(
12168        lsp::ServerCapabilities {
12169            signature_help_provider: Some(lsp::SignatureHelpOptions {
12170                ..Default::default()
12171            }),
12172            ..Default::default()
12173        },
12174        cx,
12175    )
12176    .await;
12177
12178    let language = Language::new(
12179        LanguageConfig {
12180            name: "Rust".into(),
12181            brackets: BracketPairConfig {
12182                pairs: vec![
12183                    BracketPair {
12184                        start: "{".to_string(),
12185                        end: "}".to_string(),
12186                        close: true,
12187                        surround: true,
12188                        newline: true,
12189                    },
12190                    BracketPair {
12191                        start: "(".to_string(),
12192                        end: ")".to_string(),
12193                        close: true,
12194                        surround: true,
12195                        newline: true,
12196                    },
12197                    BracketPair {
12198                        start: "/*".to_string(),
12199                        end: " */".to_string(),
12200                        close: true,
12201                        surround: true,
12202                        newline: true,
12203                    },
12204                    BracketPair {
12205                        start: "[".to_string(),
12206                        end: "]".to_string(),
12207                        close: false,
12208                        surround: false,
12209                        newline: true,
12210                    },
12211                    BracketPair {
12212                        start: "\"".to_string(),
12213                        end: "\"".to_string(),
12214                        close: true,
12215                        surround: true,
12216                        newline: false,
12217                    },
12218                    BracketPair {
12219                        start: "<".to_string(),
12220                        end: ">".to_string(),
12221                        close: false,
12222                        surround: true,
12223                        newline: true,
12224                    },
12225                ],
12226                ..Default::default()
12227            },
12228            autoclose_before: "})]".to_string(),
12229            ..Default::default()
12230        },
12231        Some(tree_sitter_rust::LANGUAGE.into()),
12232    );
12233    let language = Arc::new(language);
12234
12235    cx.language_registry().add(language.clone());
12236    cx.update_buffer(|buffer, cx| {
12237        buffer.set_language(Some(language), cx);
12238    });
12239
12240    cx.set_state(
12241        &r#"
12242            fn main() {
12243                sampleˇ
12244            }
12245        "#
12246        .unindent(),
12247    );
12248
12249    cx.update_editor(|editor, window, cx| {
12250        editor.handle_input("(", window, cx);
12251    });
12252    cx.assert_editor_state(
12253        &"
12254            fn main() {
12255                sample(ˇ)
12256            }
12257        "
12258        .unindent(),
12259    );
12260
12261    let mocked_response = lsp::SignatureHelp {
12262        signatures: vec![lsp::SignatureInformation {
12263            label: "fn sample(param1: u8, param2: u8)".to_string(),
12264            documentation: None,
12265            parameters: Some(vec![
12266                lsp::ParameterInformation {
12267                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12268                    documentation: None,
12269                },
12270                lsp::ParameterInformation {
12271                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12272                    documentation: None,
12273                },
12274            ]),
12275            active_parameter: None,
12276        }],
12277        active_signature: Some(0),
12278        active_parameter: Some(0),
12279    };
12280    handle_signature_help_request(&mut cx, mocked_response).await;
12281
12282    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12283        .await;
12284
12285    cx.editor(|editor, _, _| {
12286        let signature_help_state = editor.signature_help_state.popover().cloned();
12287        let signature = signature_help_state.unwrap();
12288        assert_eq!(
12289            signature.signatures[signature.current_signature].label,
12290            "fn sample(param1: u8, param2: u8)"
12291        );
12292    });
12293}
12294
12295#[gpui::test]
12296async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12297    init_test(cx, |_| {});
12298
12299    cx.update(|cx| {
12300        cx.update_global::<SettingsStore, _>(|settings, cx| {
12301            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12302                settings.auto_signature_help = Some(false);
12303                settings.show_signature_help_after_edits = Some(false);
12304            });
12305        });
12306    });
12307
12308    let mut cx = EditorLspTestContext::new_rust(
12309        lsp::ServerCapabilities {
12310            signature_help_provider: Some(lsp::SignatureHelpOptions {
12311                ..Default::default()
12312            }),
12313            ..Default::default()
12314        },
12315        cx,
12316    )
12317    .await;
12318
12319    let language = Language::new(
12320        LanguageConfig {
12321            name: "Rust".into(),
12322            brackets: BracketPairConfig {
12323                pairs: vec![
12324                    BracketPair {
12325                        start: "{".to_string(),
12326                        end: "}".to_string(),
12327                        close: true,
12328                        surround: true,
12329                        newline: true,
12330                    },
12331                    BracketPair {
12332                        start: "(".to_string(),
12333                        end: ")".to_string(),
12334                        close: true,
12335                        surround: true,
12336                        newline: true,
12337                    },
12338                    BracketPair {
12339                        start: "/*".to_string(),
12340                        end: " */".to_string(),
12341                        close: true,
12342                        surround: true,
12343                        newline: true,
12344                    },
12345                    BracketPair {
12346                        start: "[".to_string(),
12347                        end: "]".to_string(),
12348                        close: false,
12349                        surround: false,
12350                        newline: true,
12351                    },
12352                    BracketPair {
12353                        start: "\"".to_string(),
12354                        end: "\"".to_string(),
12355                        close: true,
12356                        surround: true,
12357                        newline: false,
12358                    },
12359                    BracketPair {
12360                        start: "<".to_string(),
12361                        end: ">".to_string(),
12362                        close: false,
12363                        surround: true,
12364                        newline: true,
12365                    },
12366                ],
12367                ..Default::default()
12368            },
12369            autoclose_before: "})]".to_string(),
12370            ..Default::default()
12371        },
12372        Some(tree_sitter_rust::LANGUAGE.into()),
12373    );
12374    let language = Arc::new(language);
12375
12376    cx.language_registry().add(language.clone());
12377    cx.update_buffer(|buffer, cx| {
12378        buffer.set_language(Some(language), cx);
12379    });
12380
12381    // Ensure that signature_help is not called when no signature help is enabled.
12382    cx.set_state(
12383        &r#"
12384            fn main() {
12385                sampleˇ
12386            }
12387        "#
12388        .unindent(),
12389    );
12390    cx.update_editor(|editor, window, cx| {
12391        editor.handle_input("(", window, cx);
12392    });
12393    cx.assert_editor_state(
12394        &"
12395            fn main() {
12396                sample(ˇ)
12397            }
12398        "
12399        .unindent(),
12400    );
12401    cx.editor(|editor, _, _| {
12402        assert!(editor.signature_help_state.task().is_none());
12403    });
12404
12405    let mocked_response = lsp::SignatureHelp {
12406        signatures: vec![lsp::SignatureInformation {
12407            label: "fn sample(param1: u8, param2: u8)".to_string(),
12408            documentation: None,
12409            parameters: Some(vec![
12410                lsp::ParameterInformation {
12411                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12412                    documentation: None,
12413                },
12414                lsp::ParameterInformation {
12415                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12416                    documentation: None,
12417                },
12418            ]),
12419            active_parameter: None,
12420        }],
12421        active_signature: Some(0),
12422        active_parameter: Some(0),
12423    };
12424
12425    // Ensure that signature_help is called when enabled afte edits
12426    cx.update(|_, cx| {
12427        cx.update_global::<SettingsStore, _>(|settings, cx| {
12428            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12429                settings.auto_signature_help = Some(false);
12430                settings.show_signature_help_after_edits = Some(true);
12431            });
12432        });
12433    });
12434    cx.set_state(
12435        &r#"
12436            fn main() {
12437                sampleˇ
12438            }
12439        "#
12440        .unindent(),
12441    );
12442    cx.update_editor(|editor, window, cx| {
12443        editor.handle_input("(", window, cx);
12444    });
12445    cx.assert_editor_state(
12446        &"
12447            fn main() {
12448                sample(ˇ)
12449            }
12450        "
12451        .unindent(),
12452    );
12453    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12454    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12455        .await;
12456    cx.update_editor(|editor, _, _| {
12457        let signature_help_state = editor.signature_help_state.popover().cloned();
12458        assert!(signature_help_state.is_some());
12459        let signature = signature_help_state.unwrap();
12460        assert_eq!(
12461            signature.signatures[signature.current_signature].label,
12462            "fn sample(param1: u8, param2: u8)"
12463        );
12464        editor.signature_help_state = SignatureHelpState::default();
12465    });
12466
12467    // Ensure that signature_help is called when auto signature help override is enabled
12468    cx.update(|_, cx| {
12469        cx.update_global::<SettingsStore, _>(|settings, cx| {
12470            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12471                settings.auto_signature_help = Some(true);
12472                settings.show_signature_help_after_edits = Some(false);
12473            });
12474        });
12475    });
12476    cx.set_state(
12477        &r#"
12478            fn main() {
12479                sampleˇ
12480            }
12481        "#
12482        .unindent(),
12483    );
12484    cx.update_editor(|editor, window, cx| {
12485        editor.handle_input("(", window, cx);
12486    });
12487    cx.assert_editor_state(
12488        &"
12489            fn main() {
12490                sample(ˇ)
12491            }
12492        "
12493        .unindent(),
12494    );
12495    handle_signature_help_request(&mut cx, mocked_response).await;
12496    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12497        .await;
12498    cx.editor(|editor, _, _| {
12499        let signature_help_state = editor.signature_help_state.popover().cloned();
12500        assert!(signature_help_state.is_some());
12501        let signature = signature_help_state.unwrap();
12502        assert_eq!(
12503            signature.signatures[signature.current_signature].label,
12504            "fn sample(param1: u8, param2: u8)"
12505        );
12506    });
12507}
12508
12509#[gpui::test]
12510async fn test_signature_help(cx: &mut TestAppContext) {
12511    init_test(cx, |_| {});
12512    cx.update(|cx| {
12513        cx.update_global::<SettingsStore, _>(|settings, cx| {
12514            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12515                settings.auto_signature_help = Some(true);
12516            });
12517        });
12518    });
12519
12520    let mut cx = EditorLspTestContext::new_rust(
12521        lsp::ServerCapabilities {
12522            signature_help_provider: Some(lsp::SignatureHelpOptions {
12523                ..Default::default()
12524            }),
12525            ..Default::default()
12526        },
12527        cx,
12528    )
12529    .await;
12530
12531    // A test that directly calls `show_signature_help`
12532    cx.update_editor(|editor, window, cx| {
12533        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12534    });
12535
12536    let mocked_response = lsp::SignatureHelp {
12537        signatures: vec![lsp::SignatureInformation {
12538            label: "fn sample(param1: u8, param2: u8)".to_string(),
12539            documentation: None,
12540            parameters: Some(vec![
12541                lsp::ParameterInformation {
12542                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12543                    documentation: None,
12544                },
12545                lsp::ParameterInformation {
12546                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12547                    documentation: None,
12548                },
12549            ]),
12550            active_parameter: None,
12551        }],
12552        active_signature: Some(0),
12553        active_parameter: Some(0),
12554    };
12555    handle_signature_help_request(&mut cx, mocked_response).await;
12556
12557    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12558        .await;
12559
12560    cx.editor(|editor, _, _| {
12561        let signature_help_state = editor.signature_help_state.popover().cloned();
12562        assert!(signature_help_state.is_some());
12563        let signature = signature_help_state.unwrap();
12564        assert_eq!(
12565            signature.signatures[signature.current_signature].label,
12566            "fn sample(param1: u8, param2: u8)"
12567        );
12568    });
12569
12570    // When exiting outside from inside the brackets, `signature_help` is closed.
12571    cx.set_state(indoc! {"
12572        fn main() {
12573            sample(ˇ);
12574        }
12575
12576        fn sample(param1: u8, param2: u8) {}
12577    "});
12578
12579    cx.update_editor(|editor, window, cx| {
12580        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12581            s.select_ranges([0..0])
12582        });
12583    });
12584
12585    let mocked_response = lsp::SignatureHelp {
12586        signatures: Vec::new(),
12587        active_signature: None,
12588        active_parameter: None,
12589    };
12590    handle_signature_help_request(&mut cx, mocked_response).await;
12591
12592    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12593        .await;
12594
12595    cx.editor(|editor, _, _| {
12596        assert!(!editor.signature_help_state.is_shown());
12597    });
12598
12599    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12600    cx.set_state(indoc! {"
12601        fn main() {
12602            sample(ˇ);
12603        }
12604
12605        fn sample(param1: u8, param2: u8) {}
12606    "});
12607
12608    let mocked_response = lsp::SignatureHelp {
12609        signatures: vec![lsp::SignatureInformation {
12610            label: "fn sample(param1: u8, param2: u8)".to_string(),
12611            documentation: None,
12612            parameters: Some(vec![
12613                lsp::ParameterInformation {
12614                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12615                    documentation: None,
12616                },
12617                lsp::ParameterInformation {
12618                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12619                    documentation: None,
12620                },
12621            ]),
12622            active_parameter: None,
12623        }],
12624        active_signature: Some(0),
12625        active_parameter: Some(0),
12626    };
12627    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12628    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12629        .await;
12630    cx.editor(|editor, _, _| {
12631        assert!(editor.signature_help_state.is_shown());
12632    });
12633
12634    // Restore the popover with more parameter input
12635    cx.set_state(indoc! {"
12636        fn main() {
12637            sample(param1, param2ˇ);
12638        }
12639
12640        fn sample(param1: u8, param2: u8) {}
12641    "});
12642
12643    let mocked_response = lsp::SignatureHelp {
12644        signatures: vec![lsp::SignatureInformation {
12645            label: "fn sample(param1: u8, param2: u8)".to_string(),
12646            documentation: None,
12647            parameters: Some(vec![
12648                lsp::ParameterInformation {
12649                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12650                    documentation: None,
12651                },
12652                lsp::ParameterInformation {
12653                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12654                    documentation: None,
12655                },
12656            ]),
12657            active_parameter: None,
12658        }],
12659        active_signature: Some(0),
12660        active_parameter: Some(1),
12661    };
12662    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12663    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12664        .await;
12665
12666    // When selecting a range, the popover is gone.
12667    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12668    cx.update_editor(|editor, window, cx| {
12669        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12670            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12671        })
12672    });
12673    cx.assert_editor_state(indoc! {"
12674        fn main() {
12675            sample(param1, «ˇparam2»);
12676        }
12677
12678        fn sample(param1: u8, param2: u8) {}
12679    "});
12680    cx.editor(|editor, _, _| {
12681        assert!(!editor.signature_help_state.is_shown());
12682    });
12683
12684    // When unselecting again, the popover is back if within the brackets.
12685    cx.update_editor(|editor, window, cx| {
12686        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12687            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12688        })
12689    });
12690    cx.assert_editor_state(indoc! {"
12691        fn main() {
12692            sample(param1, ˇparam2);
12693        }
12694
12695        fn sample(param1: u8, param2: u8) {}
12696    "});
12697    handle_signature_help_request(&mut cx, mocked_response).await;
12698    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12699        .await;
12700    cx.editor(|editor, _, _| {
12701        assert!(editor.signature_help_state.is_shown());
12702    });
12703
12704    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
12705    cx.update_editor(|editor, window, cx| {
12706        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12707            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
12708            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12709        })
12710    });
12711    cx.assert_editor_state(indoc! {"
12712        fn main() {
12713            sample(param1, ˇparam2);
12714        }
12715
12716        fn sample(param1: u8, param2: u8) {}
12717    "});
12718
12719    let mocked_response = lsp::SignatureHelp {
12720        signatures: vec![lsp::SignatureInformation {
12721            label: "fn sample(param1: u8, param2: u8)".to_string(),
12722            documentation: None,
12723            parameters: Some(vec![
12724                lsp::ParameterInformation {
12725                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12726                    documentation: None,
12727                },
12728                lsp::ParameterInformation {
12729                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12730                    documentation: None,
12731                },
12732            ]),
12733            active_parameter: None,
12734        }],
12735        active_signature: Some(0),
12736        active_parameter: Some(1),
12737    };
12738    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12739    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12740        .await;
12741    cx.update_editor(|editor, _, cx| {
12742        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
12743    });
12744    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12745        .await;
12746    cx.update_editor(|editor, window, cx| {
12747        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12748            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12749        })
12750    });
12751    cx.assert_editor_state(indoc! {"
12752        fn main() {
12753            sample(param1, «ˇparam2»);
12754        }
12755
12756        fn sample(param1: u8, param2: u8) {}
12757    "});
12758    cx.update_editor(|editor, window, cx| {
12759        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12760            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12761        })
12762    });
12763    cx.assert_editor_state(indoc! {"
12764        fn main() {
12765            sample(param1, ˇparam2);
12766        }
12767
12768        fn sample(param1: u8, param2: u8) {}
12769    "});
12770    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
12771        .await;
12772}
12773
12774#[gpui::test]
12775async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
12776    init_test(cx, |_| {});
12777
12778    let mut cx = EditorLspTestContext::new_rust(
12779        lsp::ServerCapabilities {
12780            signature_help_provider: Some(lsp::SignatureHelpOptions {
12781                ..Default::default()
12782            }),
12783            ..Default::default()
12784        },
12785        cx,
12786    )
12787    .await;
12788
12789    cx.set_state(indoc! {"
12790        fn main() {
12791            overloadedˇ
12792        }
12793    "});
12794
12795    cx.update_editor(|editor, window, cx| {
12796        editor.handle_input("(", window, cx);
12797        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12798    });
12799
12800    // Mock response with 3 signatures
12801    let mocked_response = lsp::SignatureHelp {
12802        signatures: vec![
12803            lsp::SignatureInformation {
12804                label: "fn overloaded(x: i32)".to_string(),
12805                documentation: None,
12806                parameters: Some(vec![lsp::ParameterInformation {
12807                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12808                    documentation: None,
12809                }]),
12810                active_parameter: None,
12811            },
12812            lsp::SignatureInformation {
12813                label: "fn overloaded(x: i32, y: i32)".to_string(),
12814                documentation: None,
12815                parameters: Some(vec![
12816                    lsp::ParameterInformation {
12817                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12818                        documentation: None,
12819                    },
12820                    lsp::ParameterInformation {
12821                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
12822                        documentation: None,
12823                    },
12824                ]),
12825                active_parameter: None,
12826            },
12827            lsp::SignatureInformation {
12828                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
12829                documentation: None,
12830                parameters: Some(vec![
12831                    lsp::ParameterInformation {
12832                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12833                        documentation: None,
12834                    },
12835                    lsp::ParameterInformation {
12836                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
12837                        documentation: None,
12838                    },
12839                    lsp::ParameterInformation {
12840                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
12841                        documentation: None,
12842                    },
12843                ]),
12844                active_parameter: None,
12845            },
12846        ],
12847        active_signature: Some(1),
12848        active_parameter: Some(0),
12849    };
12850    handle_signature_help_request(&mut cx, mocked_response).await;
12851
12852    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12853        .await;
12854
12855    // Verify we have multiple signatures and the right one is selected
12856    cx.editor(|editor, _, _| {
12857        let popover = editor.signature_help_state.popover().cloned().unwrap();
12858        assert_eq!(popover.signatures.len(), 3);
12859        // active_signature was 1, so that should be the current
12860        assert_eq!(popover.current_signature, 1);
12861        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
12862        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
12863        assert_eq!(
12864            popover.signatures[2].label,
12865            "fn overloaded(x: i32, y: i32, z: i32)"
12866        );
12867    });
12868
12869    // Test navigation functionality
12870    cx.update_editor(|editor, window, cx| {
12871        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12872    });
12873
12874    cx.editor(|editor, _, _| {
12875        let popover = editor.signature_help_state.popover().cloned().unwrap();
12876        assert_eq!(popover.current_signature, 2);
12877    });
12878
12879    // Test wrap around
12880    cx.update_editor(|editor, window, cx| {
12881        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12882    });
12883
12884    cx.editor(|editor, _, _| {
12885        let popover = editor.signature_help_state.popover().cloned().unwrap();
12886        assert_eq!(popover.current_signature, 0);
12887    });
12888
12889    // Test previous navigation
12890    cx.update_editor(|editor, window, cx| {
12891        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
12892    });
12893
12894    cx.editor(|editor, _, _| {
12895        let popover = editor.signature_help_state.popover().cloned().unwrap();
12896        assert_eq!(popover.current_signature, 2);
12897    });
12898}
12899
12900#[gpui::test]
12901async fn test_completion_mode(cx: &mut TestAppContext) {
12902    init_test(cx, |_| {});
12903    let mut cx = EditorLspTestContext::new_rust(
12904        lsp::ServerCapabilities {
12905            completion_provider: Some(lsp::CompletionOptions {
12906                resolve_provider: Some(true),
12907                ..Default::default()
12908            }),
12909            ..Default::default()
12910        },
12911        cx,
12912    )
12913    .await;
12914
12915    struct Run {
12916        run_description: &'static str,
12917        initial_state: String,
12918        buffer_marked_text: String,
12919        completion_label: &'static str,
12920        completion_text: &'static str,
12921        expected_with_insert_mode: String,
12922        expected_with_replace_mode: String,
12923        expected_with_replace_subsequence_mode: String,
12924        expected_with_replace_suffix_mode: String,
12925    }
12926
12927    let runs = [
12928        Run {
12929            run_description: "Start of word matches completion text",
12930            initial_state: "before ediˇ after".into(),
12931            buffer_marked_text: "before <edi|> after".into(),
12932            completion_label: "editor",
12933            completion_text: "editor",
12934            expected_with_insert_mode: "before editorˇ after".into(),
12935            expected_with_replace_mode: "before editorˇ after".into(),
12936            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12937            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12938        },
12939        Run {
12940            run_description: "Accept same text at the middle of the word",
12941            initial_state: "before ediˇtor after".into(),
12942            buffer_marked_text: "before <edi|tor> after".into(),
12943            completion_label: "editor",
12944            completion_text: "editor",
12945            expected_with_insert_mode: "before editorˇtor after".into(),
12946            expected_with_replace_mode: "before editorˇ after".into(),
12947            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12948            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12949        },
12950        Run {
12951            run_description: "End of word matches completion text -- cursor at end",
12952            initial_state: "before torˇ after".into(),
12953            buffer_marked_text: "before <tor|> after".into(),
12954            completion_label: "editor",
12955            completion_text: "editor",
12956            expected_with_insert_mode: "before editorˇ after".into(),
12957            expected_with_replace_mode: "before editorˇ after".into(),
12958            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12959            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12960        },
12961        Run {
12962            run_description: "End of word matches completion text -- cursor at start",
12963            initial_state: "before ˇtor after".into(),
12964            buffer_marked_text: "before <|tor> after".into(),
12965            completion_label: "editor",
12966            completion_text: "editor",
12967            expected_with_insert_mode: "before editorˇtor after".into(),
12968            expected_with_replace_mode: "before editorˇ after".into(),
12969            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12970            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12971        },
12972        Run {
12973            run_description: "Prepend text containing whitespace",
12974            initial_state: "pˇfield: bool".into(),
12975            buffer_marked_text: "<p|field>: bool".into(),
12976            completion_label: "pub ",
12977            completion_text: "pub ",
12978            expected_with_insert_mode: "pub ˇfield: bool".into(),
12979            expected_with_replace_mode: "pub ˇ: bool".into(),
12980            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
12981            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
12982        },
12983        Run {
12984            run_description: "Add element to start of list",
12985            initial_state: "[element_ˇelement_2]".into(),
12986            buffer_marked_text: "[<element_|element_2>]".into(),
12987            completion_label: "element_1",
12988            completion_text: "element_1",
12989            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
12990            expected_with_replace_mode: "[element_1ˇ]".into(),
12991            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
12992            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
12993        },
12994        Run {
12995            run_description: "Add element to start of list -- first and second elements are equal",
12996            initial_state: "[elˇelement]".into(),
12997            buffer_marked_text: "[<el|element>]".into(),
12998            completion_label: "element",
12999            completion_text: "element",
13000            expected_with_insert_mode: "[elementˇelement]".into(),
13001            expected_with_replace_mode: "[elementˇ]".into(),
13002            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13003            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13004        },
13005        Run {
13006            run_description: "Ends with matching suffix",
13007            initial_state: "SubˇError".into(),
13008            buffer_marked_text: "<Sub|Error>".into(),
13009            completion_label: "SubscriptionError",
13010            completion_text: "SubscriptionError",
13011            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13012            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13013            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13014            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13015        },
13016        Run {
13017            run_description: "Suffix is a subsequence -- contiguous",
13018            initial_state: "SubˇErr".into(),
13019            buffer_marked_text: "<Sub|Err>".into(),
13020            completion_label: "SubscriptionError",
13021            completion_text: "SubscriptionError",
13022            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13023            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13024            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13025            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13026        },
13027        Run {
13028            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13029            initial_state: "Suˇscrirr".into(),
13030            buffer_marked_text: "<Su|scrirr>".into(),
13031            completion_label: "SubscriptionError",
13032            completion_text: "SubscriptionError",
13033            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13034            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13035            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13036            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13037        },
13038        Run {
13039            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13040            initial_state: "foo(indˇix)".into(),
13041            buffer_marked_text: "foo(<ind|ix>)".into(),
13042            completion_label: "node_index",
13043            completion_text: "node_index",
13044            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13045            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13046            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13047            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13048        },
13049        Run {
13050            run_description: "Replace range ends before cursor - should extend to cursor",
13051            initial_state: "before editˇo after".into(),
13052            buffer_marked_text: "before <{ed}>it|o after".into(),
13053            completion_label: "editor",
13054            completion_text: "editor",
13055            expected_with_insert_mode: "before editorˇo after".into(),
13056            expected_with_replace_mode: "before editorˇo after".into(),
13057            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13058            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13059        },
13060        Run {
13061            run_description: "Uses label for suffix matching",
13062            initial_state: "before ediˇtor after".into(),
13063            buffer_marked_text: "before <edi|tor> after".into(),
13064            completion_label: "editor",
13065            completion_text: "editor()",
13066            expected_with_insert_mode: "before editor()ˇtor after".into(),
13067            expected_with_replace_mode: "before editor()ˇ after".into(),
13068            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13069            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13070        },
13071        Run {
13072            run_description: "Case insensitive subsequence and suffix matching",
13073            initial_state: "before EDiˇtoR after".into(),
13074            buffer_marked_text: "before <EDi|toR> after".into(),
13075            completion_label: "editor",
13076            completion_text: "editor",
13077            expected_with_insert_mode: "before editorˇtoR after".into(),
13078            expected_with_replace_mode: "before editorˇ after".into(),
13079            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13080            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13081        },
13082    ];
13083
13084    for run in runs {
13085        let run_variations = [
13086            (LspInsertMode::Insert, run.expected_with_insert_mode),
13087            (LspInsertMode::Replace, run.expected_with_replace_mode),
13088            (
13089                LspInsertMode::ReplaceSubsequence,
13090                run.expected_with_replace_subsequence_mode,
13091            ),
13092            (
13093                LspInsertMode::ReplaceSuffix,
13094                run.expected_with_replace_suffix_mode,
13095            ),
13096        ];
13097
13098        for (lsp_insert_mode, expected_text) in run_variations {
13099            eprintln!(
13100                "run = {:?}, mode = {lsp_insert_mode:.?}",
13101                run.run_description,
13102            );
13103
13104            update_test_language_settings(&mut cx, |settings| {
13105                settings.defaults.completions = Some(CompletionSettings {
13106                    lsp_insert_mode,
13107                    words: WordsCompletionMode::Disabled,
13108                    words_min_length: 0,
13109                    lsp: true,
13110                    lsp_fetch_timeout_ms: 0,
13111                });
13112            });
13113
13114            cx.set_state(&run.initial_state);
13115            cx.update_editor(|editor, window, cx| {
13116                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13117            });
13118
13119            let counter = Arc::new(AtomicUsize::new(0));
13120            handle_completion_request_with_insert_and_replace(
13121                &mut cx,
13122                &run.buffer_marked_text,
13123                vec![(run.completion_label, run.completion_text)],
13124                counter.clone(),
13125            )
13126            .await;
13127            cx.condition(|editor, _| editor.context_menu_visible())
13128                .await;
13129            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13130
13131            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13132                editor
13133                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13134                    .unwrap()
13135            });
13136            cx.assert_editor_state(&expected_text);
13137            handle_resolve_completion_request(&mut cx, None).await;
13138            apply_additional_edits.await.unwrap();
13139        }
13140    }
13141}
13142
13143#[gpui::test]
13144async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13145    init_test(cx, |_| {});
13146    let mut cx = EditorLspTestContext::new_rust(
13147        lsp::ServerCapabilities {
13148            completion_provider: Some(lsp::CompletionOptions {
13149                resolve_provider: Some(true),
13150                ..Default::default()
13151            }),
13152            ..Default::default()
13153        },
13154        cx,
13155    )
13156    .await;
13157
13158    let initial_state = "SubˇError";
13159    let buffer_marked_text = "<Sub|Error>";
13160    let completion_text = "SubscriptionError";
13161    let expected_with_insert_mode = "SubscriptionErrorˇError";
13162    let expected_with_replace_mode = "SubscriptionErrorˇ";
13163
13164    update_test_language_settings(&mut cx, |settings| {
13165        settings.defaults.completions = Some(CompletionSettings {
13166            words: WordsCompletionMode::Disabled,
13167            words_min_length: 0,
13168            // set the opposite here to ensure that the action is overriding the default behavior
13169            lsp_insert_mode: LspInsertMode::Insert,
13170            lsp: true,
13171            lsp_fetch_timeout_ms: 0,
13172        });
13173    });
13174
13175    cx.set_state(initial_state);
13176    cx.update_editor(|editor, window, cx| {
13177        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13178    });
13179
13180    let counter = Arc::new(AtomicUsize::new(0));
13181    handle_completion_request_with_insert_and_replace(
13182        &mut cx,
13183        buffer_marked_text,
13184        vec![(completion_text, completion_text)],
13185        counter.clone(),
13186    )
13187    .await;
13188    cx.condition(|editor, _| editor.context_menu_visible())
13189        .await;
13190    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13191
13192    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13193        editor
13194            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13195            .unwrap()
13196    });
13197    cx.assert_editor_state(expected_with_replace_mode);
13198    handle_resolve_completion_request(&mut cx, None).await;
13199    apply_additional_edits.await.unwrap();
13200
13201    update_test_language_settings(&mut cx, |settings| {
13202        settings.defaults.completions = Some(CompletionSettings {
13203            words: WordsCompletionMode::Disabled,
13204            words_min_length: 0,
13205            // set the opposite here to ensure that the action is overriding the default behavior
13206            lsp_insert_mode: LspInsertMode::Replace,
13207            lsp: true,
13208            lsp_fetch_timeout_ms: 0,
13209        });
13210    });
13211
13212    cx.set_state(initial_state);
13213    cx.update_editor(|editor, window, cx| {
13214        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13215    });
13216    handle_completion_request_with_insert_and_replace(
13217        &mut cx,
13218        buffer_marked_text,
13219        vec![(completion_text, completion_text)],
13220        counter.clone(),
13221    )
13222    .await;
13223    cx.condition(|editor, _| editor.context_menu_visible())
13224        .await;
13225    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13226
13227    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13228        editor
13229            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13230            .unwrap()
13231    });
13232    cx.assert_editor_state(expected_with_insert_mode);
13233    handle_resolve_completion_request(&mut cx, None).await;
13234    apply_additional_edits.await.unwrap();
13235}
13236
13237#[gpui::test]
13238async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13239    init_test(cx, |_| {});
13240    let mut cx = EditorLspTestContext::new_rust(
13241        lsp::ServerCapabilities {
13242            completion_provider: Some(lsp::CompletionOptions {
13243                resolve_provider: Some(true),
13244                ..Default::default()
13245            }),
13246            ..Default::default()
13247        },
13248        cx,
13249    )
13250    .await;
13251
13252    // scenario: surrounding text matches completion text
13253    let completion_text = "to_offset";
13254    let initial_state = indoc! {"
13255        1. buf.to_offˇsuffix
13256        2. buf.to_offˇsuf
13257        3. buf.to_offˇfix
13258        4. buf.to_offˇ
13259        5. into_offˇensive
13260        6. ˇsuffix
13261        7. let ˇ //
13262        8. aaˇzz
13263        9. buf.to_off«zzzzzˇ»suffix
13264        10. buf.«ˇzzzzz»suffix
13265        11. to_off«ˇzzzzz»
13266
13267        buf.to_offˇsuffix  // newest cursor
13268    "};
13269    let completion_marked_buffer = indoc! {"
13270        1. buf.to_offsuffix
13271        2. buf.to_offsuf
13272        3. buf.to_offfix
13273        4. buf.to_off
13274        5. into_offensive
13275        6. suffix
13276        7. let  //
13277        8. aazz
13278        9. buf.to_offzzzzzsuffix
13279        10. buf.zzzzzsuffix
13280        11. to_offzzzzz
13281
13282        buf.<to_off|suffix>  // newest cursor
13283    "};
13284    let expected = indoc! {"
13285        1. buf.to_offsetˇ
13286        2. buf.to_offsetˇsuf
13287        3. buf.to_offsetˇfix
13288        4. buf.to_offsetˇ
13289        5. into_offsetˇensive
13290        6. to_offsetˇsuffix
13291        7. let to_offsetˇ //
13292        8. aato_offsetˇzz
13293        9. buf.to_offsetˇ
13294        10. buf.to_offsetˇsuffix
13295        11. to_offsetˇ
13296
13297        buf.to_offsetˇ  // newest cursor
13298    "};
13299    cx.set_state(initial_state);
13300    cx.update_editor(|editor, window, cx| {
13301        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13302    });
13303    handle_completion_request_with_insert_and_replace(
13304        &mut cx,
13305        completion_marked_buffer,
13306        vec![(completion_text, completion_text)],
13307        Arc::new(AtomicUsize::new(0)),
13308    )
13309    .await;
13310    cx.condition(|editor, _| editor.context_menu_visible())
13311        .await;
13312    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13313        editor
13314            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13315            .unwrap()
13316    });
13317    cx.assert_editor_state(expected);
13318    handle_resolve_completion_request(&mut cx, None).await;
13319    apply_additional_edits.await.unwrap();
13320
13321    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13322    let completion_text = "foo_and_bar";
13323    let initial_state = indoc! {"
13324        1. ooanbˇ
13325        2. zooanbˇ
13326        3. ooanbˇz
13327        4. zooanbˇz
13328        5. ooanˇ
13329        6. oanbˇ
13330
13331        ooanbˇ
13332    "};
13333    let completion_marked_buffer = indoc! {"
13334        1. ooanb
13335        2. zooanb
13336        3. ooanbz
13337        4. zooanbz
13338        5. ooan
13339        6. oanb
13340
13341        <ooanb|>
13342    "};
13343    let expected = indoc! {"
13344        1. foo_and_barˇ
13345        2. zfoo_and_barˇ
13346        3. foo_and_barˇz
13347        4. zfoo_and_barˇz
13348        5. ooanfoo_and_barˇ
13349        6. oanbfoo_and_barˇ
13350
13351        foo_and_barˇ
13352    "};
13353    cx.set_state(initial_state);
13354    cx.update_editor(|editor, window, cx| {
13355        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13356    });
13357    handle_completion_request_with_insert_and_replace(
13358        &mut cx,
13359        completion_marked_buffer,
13360        vec![(completion_text, completion_text)],
13361        Arc::new(AtomicUsize::new(0)),
13362    )
13363    .await;
13364    cx.condition(|editor, _| editor.context_menu_visible())
13365        .await;
13366    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13367        editor
13368            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13369            .unwrap()
13370    });
13371    cx.assert_editor_state(expected);
13372    handle_resolve_completion_request(&mut cx, None).await;
13373    apply_additional_edits.await.unwrap();
13374
13375    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13376    // (expects the same as if it was inserted at the end)
13377    let completion_text = "foo_and_bar";
13378    let initial_state = indoc! {"
13379        1. ooˇanb
13380        2. zooˇanb
13381        3. ooˇanbz
13382        4. zooˇanbz
13383
13384        ooˇanb
13385    "};
13386    let completion_marked_buffer = indoc! {"
13387        1. ooanb
13388        2. zooanb
13389        3. ooanbz
13390        4. zooanbz
13391
13392        <oo|anb>
13393    "};
13394    let expected = indoc! {"
13395        1. foo_and_barˇ
13396        2. zfoo_and_barˇ
13397        3. foo_and_barˇz
13398        4. zfoo_and_barˇz
13399
13400        foo_and_barˇ
13401    "};
13402    cx.set_state(initial_state);
13403    cx.update_editor(|editor, window, cx| {
13404        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13405    });
13406    handle_completion_request_with_insert_and_replace(
13407        &mut cx,
13408        completion_marked_buffer,
13409        vec![(completion_text, completion_text)],
13410        Arc::new(AtomicUsize::new(0)),
13411    )
13412    .await;
13413    cx.condition(|editor, _| editor.context_menu_visible())
13414        .await;
13415    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13416        editor
13417            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13418            .unwrap()
13419    });
13420    cx.assert_editor_state(expected);
13421    handle_resolve_completion_request(&mut cx, None).await;
13422    apply_additional_edits.await.unwrap();
13423}
13424
13425// This used to crash
13426#[gpui::test]
13427async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13428    init_test(cx, |_| {});
13429
13430    let buffer_text = indoc! {"
13431        fn main() {
13432            10.satu;
13433
13434            //
13435            // separate cursors so they open in different excerpts (manually reproducible)
13436            //
13437
13438            10.satu20;
13439        }
13440    "};
13441    let multibuffer_text_with_selections = indoc! {"
13442        fn main() {
13443            10.satuˇ;
13444
13445            //
13446
13447            //
13448
13449            10.satuˇ20;
13450        }
13451    "};
13452    let expected_multibuffer = indoc! {"
13453        fn main() {
13454            10.saturating_sub()ˇ;
13455
13456            //
13457
13458            //
13459
13460            10.saturating_sub()ˇ;
13461        }
13462    "};
13463
13464    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13465    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13466
13467    let fs = FakeFs::new(cx.executor());
13468    fs.insert_tree(
13469        path!("/a"),
13470        json!({
13471            "main.rs": buffer_text,
13472        }),
13473    )
13474    .await;
13475
13476    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13477    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13478    language_registry.add(rust_lang());
13479    let mut fake_servers = language_registry.register_fake_lsp(
13480        "Rust",
13481        FakeLspAdapter {
13482            capabilities: lsp::ServerCapabilities {
13483                completion_provider: Some(lsp::CompletionOptions {
13484                    resolve_provider: None,
13485                    ..lsp::CompletionOptions::default()
13486                }),
13487                ..lsp::ServerCapabilities::default()
13488            },
13489            ..FakeLspAdapter::default()
13490        },
13491    );
13492    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13493    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13494    let buffer = project
13495        .update(cx, |project, cx| {
13496            project.open_local_buffer(path!("/a/main.rs"), cx)
13497        })
13498        .await
13499        .unwrap();
13500
13501    let multi_buffer = cx.new(|cx| {
13502        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13503        multi_buffer.push_excerpts(
13504            buffer.clone(),
13505            [ExcerptRange::new(0..first_excerpt_end)],
13506            cx,
13507        );
13508        multi_buffer.push_excerpts(
13509            buffer.clone(),
13510            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13511            cx,
13512        );
13513        multi_buffer
13514    });
13515
13516    let editor = workspace
13517        .update(cx, |_, window, cx| {
13518            cx.new(|cx| {
13519                Editor::new(
13520                    EditorMode::Full {
13521                        scale_ui_elements_with_buffer_font_size: false,
13522                        show_active_line_background: false,
13523                        sized_by_content: false,
13524                    },
13525                    multi_buffer.clone(),
13526                    Some(project.clone()),
13527                    window,
13528                    cx,
13529                )
13530            })
13531        })
13532        .unwrap();
13533
13534    let pane = workspace
13535        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13536        .unwrap();
13537    pane.update_in(cx, |pane, window, cx| {
13538        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13539    });
13540
13541    let fake_server = fake_servers.next().await.unwrap();
13542
13543    editor.update_in(cx, |editor, window, cx| {
13544        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13545            s.select_ranges([
13546                Point::new(1, 11)..Point::new(1, 11),
13547                Point::new(7, 11)..Point::new(7, 11),
13548            ])
13549        });
13550
13551        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13552    });
13553
13554    editor.update_in(cx, |editor, window, cx| {
13555        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13556    });
13557
13558    fake_server
13559        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13560            let completion_item = lsp::CompletionItem {
13561                label: "saturating_sub()".into(),
13562                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13563                    lsp::InsertReplaceEdit {
13564                        new_text: "saturating_sub()".to_owned(),
13565                        insert: lsp::Range::new(
13566                            lsp::Position::new(7, 7),
13567                            lsp::Position::new(7, 11),
13568                        ),
13569                        replace: lsp::Range::new(
13570                            lsp::Position::new(7, 7),
13571                            lsp::Position::new(7, 13),
13572                        ),
13573                    },
13574                )),
13575                ..lsp::CompletionItem::default()
13576            };
13577
13578            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13579        })
13580        .next()
13581        .await
13582        .unwrap();
13583
13584    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13585        .await;
13586
13587    editor
13588        .update_in(cx, |editor, window, cx| {
13589            editor
13590                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13591                .unwrap()
13592        })
13593        .await
13594        .unwrap();
13595
13596    editor.update(cx, |editor, cx| {
13597        assert_text_with_selections(editor, expected_multibuffer, cx);
13598    })
13599}
13600
13601#[gpui::test]
13602async fn test_completion(cx: &mut TestAppContext) {
13603    init_test(cx, |_| {});
13604
13605    let mut cx = EditorLspTestContext::new_rust(
13606        lsp::ServerCapabilities {
13607            completion_provider: Some(lsp::CompletionOptions {
13608                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13609                resolve_provider: Some(true),
13610                ..Default::default()
13611            }),
13612            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13613            ..Default::default()
13614        },
13615        cx,
13616    )
13617    .await;
13618    let counter = Arc::new(AtomicUsize::new(0));
13619
13620    cx.set_state(indoc! {"
13621        oneˇ
13622        two
13623        three
13624    "});
13625    cx.simulate_keystroke(".");
13626    handle_completion_request(
13627        indoc! {"
13628            one.|<>
13629            two
13630            three
13631        "},
13632        vec!["first_completion", "second_completion"],
13633        true,
13634        counter.clone(),
13635        &mut cx,
13636    )
13637    .await;
13638    cx.condition(|editor, _| editor.context_menu_visible())
13639        .await;
13640    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13641
13642    let _handler = handle_signature_help_request(
13643        &mut cx,
13644        lsp::SignatureHelp {
13645            signatures: vec![lsp::SignatureInformation {
13646                label: "test signature".to_string(),
13647                documentation: None,
13648                parameters: Some(vec![lsp::ParameterInformation {
13649                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13650                    documentation: None,
13651                }]),
13652                active_parameter: None,
13653            }],
13654            active_signature: None,
13655            active_parameter: None,
13656        },
13657    );
13658    cx.update_editor(|editor, window, cx| {
13659        assert!(
13660            !editor.signature_help_state.is_shown(),
13661            "No signature help was called for"
13662        );
13663        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13664    });
13665    cx.run_until_parked();
13666    cx.update_editor(|editor, _, _| {
13667        assert!(
13668            !editor.signature_help_state.is_shown(),
13669            "No signature help should be shown when completions menu is open"
13670        );
13671    });
13672
13673    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13674        editor.context_menu_next(&Default::default(), window, cx);
13675        editor
13676            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13677            .unwrap()
13678    });
13679    cx.assert_editor_state(indoc! {"
13680        one.second_completionˇ
13681        two
13682        three
13683    "});
13684
13685    handle_resolve_completion_request(
13686        &mut cx,
13687        Some(vec![
13688            (
13689                //This overlaps with the primary completion edit which is
13690                //misbehavior from the LSP spec, test that we filter it out
13691                indoc! {"
13692                    one.second_ˇcompletion
13693                    two
13694                    threeˇ
13695                "},
13696                "overlapping additional edit",
13697            ),
13698            (
13699                indoc! {"
13700                    one.second_completion
13701                    two
13702                    threeˇ
13703                "},
13704                "\nadditional edit",
13705            ),
13706        ]),
13707    )
13708    .await;
13709    apply_additional_edits.await.unwrap();
13710    cx.assert_editor_state(indoc! {"
13711        one.second_completionˇ
13712        two
13713        three
13714        additional edit
13715    "});
13716
13717    cx.set_state(indoc! {"
13718        one.second_completion
13719        twoˇ
13720        threeˇ
13721        additional edit
13722    "});
13723    cx.simulate_keystroke(" ");
13724    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13725    cx.simulate_keystroke("s");
13726    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13727
13728    cx.assert_editor_state(indoc! {"
13729        one.second_completion
13730        two sˇ
13731        three sˇ
13732        additional edit
13733    "});
13734    handle_completion_request(
13735        indoc! {"
13736            one.second_completion
13737            two s
13738            three <s|>
13739            additional edit
13740        "},
13741        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13742        true,
13743        counter.clone(),
13744        &mut cx,
13745    )
13746    .await;
13747    cx.condition(|editor, _| editor.context_menu_visible())
13748        .await;
13749    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13750
13751    cx.simulate_keystroke("i");
13752
13753    handle_completion_request(
13754        indoc! {"
13755            one.second_completion
13756            two si
13757            three <si|>
13758            additional edit
13759        "},
13760        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13761        true,
13762        counter.clone(),
13763        &mut cx,
13764    )
13765    .await;
13766    cx.condition(|editor, _| editor.context_menu_visible())
13767        .await;
13768    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13769
13770    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13771        editor
13772            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13773            .unwrap()
13774    });
13775    cx.assert_editor_state(indoc! {"
13776        one.second_completion
13777        two sixth_completionˇ
13778        three sixth_completionˇ
13779        additional edit
13780    "});
13781
13782    apply_additional_edits.await.unwrap();
13783
13784    update_test_language_settings(&mut cx, |settings| {
13785        settings.defaults.show_completions_on_input = Some(false);
13786    });
13787    cx.set_state("editorˇ");
13788    cx.simulate_keystroke(".");
13789    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13790    cx.simulate_keystrokes("c l o");
13791    cx.assert_editor_state("editor.cloˇ");
13792    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13793    cx.update_editor(|editor, window, cx| {
13794        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13795    });
13796    handle_completion_request(
13797        "editor.<clo|>",
13798        vec!["close", "clobber"],
13799        true,
13800        counter.clone(),
13801        &mut cx,
13802    )
13803    .await;
13804    cx.condition(|editor, _| editor.context_menu_visible())
13805        .await;
13806    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
13807
13808    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13809        editor
13810            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13811            .unwrap()
13812    });
13813    cx.assert_editor_state("editor.clobberˇ");
13814    handle_resolve_completion_request(&mut cx, None).await;
13815    apply_additional_edits.await.unwrap();
13816}
13817
13818#[gpui::test]
13819async fn test_completion_reuse(cx: &mut TestAppContext) {
13820    init_test(cx, |_| {});
13821
13822    let mut cx = EditorLspTestContext::new_rust(
13823        lsp::ServerCapabilities {
13824            completion_provider: Some(lsp::CompletionOptions {
13825                trigger_characters: Some(vec![".".to_string()]),
13826                ..Default::default()
13827            }),
13828            ..Default::default()
13829        },
13830        cx,
13831    )
13832    .await;
13833
13834    let counter = Arc::new(AtomicUsize::new(0));
13835    cx.set_state("objˇ");
13836    cx.simulate_keystroke(".");
13837
13838    // Initial completion request returns complete results
13839    let is_incomplete = false;
13840    handle_completion_request(
13841        "obj.|<>",
13842        vec!["a", "ab", "abc"],
13843        is_incomplete,
13844        counter.clone(),
13845        &mut cx,
13846    )
13847    .await;
13848    cx.run_until_parked();
13849    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13850    cx.assert_editor_state("obj.ˇ");
13851    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13852
13853    // Type "a" - filters existing completions
13854    cx.simulate_keystroke("a");
13855    cx.run_until_parked();
13856    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13857    cx.assert_editor_state("obj.aˇ");
13858    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13859
13860    // Type "b" - filters existing completions
13861    cx.simulate_keystroke("b");
13862    cx.run_until_parked();
13863    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13864    cx.assert_editor_state("obj.abˇ");
13865    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13866
13867    // Type "c" - filters existing completions
13868    cx.simulate_keystroke("c");
13869    cx.run_until_parked();
13870    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13871    cx.assert_editor_state("obj.abcˇ");
13872    check_displayed_completions(vec!["abc"], &mut cx);
13873
13874    // Backspace to delete "c" - filters existing completions
13875    cx.update_editor(|editor, window, cx| {
13876        editor.backspace(&Backspace, window, cx);
13877    });
13878    cx.run_until_parked();
13879    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13880    cx.assert_editor_state("obj.abˇ");
13881    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13882
13883    // Moving cursor to the left dismisses menu.
13884    cx.update_editor(|editor, window, cx| {
13885        editor.move_left(&MoveLeft, window, cx);
13886    });
13887    cx.run_until_parked();
13888    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13889    cx.assert_editor_state("obj.aˇb");
13890    cx.update_editor(|editor, _, _| {
13891        assert_eq!(editor.context_menu_visible(), false);
13892    });
13893
13894    // Type "b" - new request
13895    cx.simulate_keystroke("b");
13896    let is_incomplete = false;
13897    handle_completion_request(
13898        "obj.<ab|>a",
13899        vec!["ab", "abc"],
13900        is_incomplete,
13901        counter.clone(),
13902        &mut cx,
13903    )
13904    .await;
13905    cx.run_until_parked();
13906    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13907    cx.assert_editor_state("obj.abˇb");
13908    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13909
13910    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
13911    cx.update_editor(|editor, window, cx| {
13912        editor.backspace(&Backspace, window, cx);
13913    });
13914    let is_incomplete = false;
13915    handle_completion_request(
13916        "obj.<a|>b",
13917        vec!["a", "ab", "abc"],
13918        is_incomplete,
13919        counter.clone(),
13920        &mut cx,
13921    )
13922    .await;
13923    cx.run_until_parked();
13924    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13925    cx.assert_editor_state("obj.aˇb");
13926    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13927
13928    // Backspace to delete "a" - dismisses menu.
13929    cx.update_editor(|editor, window, cx| {
13930        editor.backspace(&Backspace, window, cx);
13931    });
13932    cx.run_until_parked();
13933    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13934    cx.assert_editor_state("obj.ˇb");
13935    cx.update_editor(|editor, _, _| {
13936        assert_eq!(editor.context_menu_visible(), false);
13937    });
13938}
13939
13940#[gpui::test]
13941async fn test_word_completion(cx: &mut TestAppContext) {
13942    let lsp_fetch_timeout_ms = 10;
13943    init_test(cx, |language_settings| {
13944        language_settings.defaults.completions = Some(CompletionSettings {
13945            words: WordsCompletionMode::Fallback,
13946            words_min_length: 0,
13947            lsp: true,
13948            lsp_fetch_timeout_ms: 10,
13949            lsp_insert_mode: LspInsertMode::Insert,
13950        });
13951    });
13952
13953    let mut cx = EditorLspTestContext::new_rust(
13954        lsp::ServerCapabilities {
13955            completion_provider: Some(lsp::CompletionOptions {
13956                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13957                ..lsp::CompletionOptions::default()
13958            }),
13959            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13960            ..lsp::ServerCapabilities::default()
13961        },
13962        cx,
13963    )
13964    .await;
13965
13966    let throttle_completions = Arc::new(AtomicBool::new(false));
13967
13968    let lsp_throttle_completions = throttle_completions.clone();
13969    let _completion_requests_handler =
13970        cx.lsp
13971            .server
13972            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
13973                let lsp_throttle_completions = lsp_throttle_completions.clone();
13974                let cx = cx.clone();
13975                async move {
13976                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
13977                        cx.background_executor()
13978                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
13979                            .await;
13980                    }
13981                    Ok(Some(lsp::CompletionResponse::Array(vec![
13982                        lsp::CompletionItem {
13983                            label: "first".into(),
13984                            ..lsp::CompletionItem::default()
13985                        },
13986                        lsp::CompletionItem {
13987                            label: "last".into(),
13988                            ..lsp::CompletionItem::default()
13989                        },
13990                    ])))
13991                }
13992            });
13993
13994    cx.set_state(indoc! {"
13995        oneˇ
13996        two
13997        three
13998    "});
13999    cx.simulate_keystroke(".");
14000    cx.executor().run_until_parked();
14001    cx.condition(|editor, _| editor.context_menu_visible())
14002        .await;
14003    cx.update_editor(|editor, window, cx| {
14004        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14005        {
14006            assert_eq!(
14007                completion_menu_entries(menu),
14008                &["first", "last"],
14009                "When LSP server is fast to reply, no fallback word completions are used"
14010            );
14011        } else {
14012            panic!("expected completion menu to be open");
14013        }
14014        editor.cancel(&Cancel, window, cx);
14015    });
14016    cx.executor().run_until_parked();
14017    cx.condition(|editor, _| !editor.context_menu_visible())
14018        .await;
14019
14020    throttle_completions.store(true, atomic::Ordering::Release);
14021    cx.simulate_keystroke(".");
14022    cx.executor()
14023        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14024    cx.executor().run_until_parked();
14025    cx.condition(|editor, _| editor.context_menu_visible())
14026        .await;
14027    cx.update_editor(|editor, _, _| {
14028        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14029        {
14030            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14031                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14032        } else {
14033            panic!("expected completion menu to be open");
14034        }
14035    });
14036}
14037
14038#[gpui::test]
14039async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14040    init_test(cx, |language_settings| {
14041        language_settings.defaults.completions = Some(CompletionSettings {
14042            words: WordsCompletionMode::Enabled,
14043            words_min_length: 0,
14044            lsp: true,
14045            lsp_fetch_timeout_ms: 0,
14046            lsp_insert_mode: LspInsertMode::Insert,
14047        });
14048    });
14049
14050    let mut cx = EditorLspTestContext::new_rust(
14051        lsp::ServerCapabilities {
14052            completion_provider: Some(lsp::CompletionOptions {
14053                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14054                ..lsp::CompletionOptions::default()
14055            }),
14056            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14057            ..lsp::ServerCapabilities::default()
14058        },
14059        cx,
14060    )
14061    .await;
14062
14063    let _completion_requests_handler =
14064        cx.lsp
14065            .server
14066            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14067                Ok(Some(lsp::CompletionResponse::Array(vec![
14068                    lsp::CompletionItem {
14069                        label: "first".into(),
14070                        ..lsp::CompletionItem::default()
14071                    },
14072                    lsp::CompletionItem {
14073                        label: "last".into(),
14074                        ..lsp::CompletionItem::default()
14075                    },
14076                ])))
14077            });
14078
14079    cx.set_state(indoc! {"ˇ
14080        first
14081        last
14082        second
14083    "});
14084    cx.simulate_keystroke(".");
14085    cx.executor().run_until_parked();
14086    cx.condition(|editor, _| editor.context_menu_visible())
14087        .await;
14088    cx.update_editor(|editor, _, _| {
14089        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14090        {
14091            assert_eq!(
14092                completion_menu_entries(menu),
14093                &["first", "last", "second"],
14094                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14095            );
14096        } else {
14097            panic!("expected completion menu to be open");
14098        }
14099    });
14100}
14101
14102#[gpui::test]
14103async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14104    init_test(cx, |language_settings| {
14105        language_settings.defaults.completions = Some(CompletionSettings {
14106            words: WordsCompletionMode::Disabled,
14107            words_min_length: 0,
14108            lsp: true,
14109            lsp_fetch_timeout_ms: 0,
14110            lsp_insert_mode: LspInsertMode::Insert,
14111        });
14112    });
14113
14114    let mut cx = EditorLspTestContext::new_rust(
14115        lsp::ServerCapabilities {
14116            completion_provider: Some(lsp::CompletionOptions {
14117                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14118                ..lsp::CompletionOptions::default()
14119            }),
14120            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14121            ..lsp::ServerCapabilities::default()
14122        },
14123        cx,
14124    )
14125    .await;
14126
14127    let _completion_requests_handler =
14128        cx.lsp
14129            .server
14130            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14131                panic!("LSP completions should not be queried when dealing with word completions")
14132            });
14133
14134    cx.set_state(indoc! {"ˇ
14135        first
14136        last
14137        second
14138    "});
14139    cx.update_editor(|editor, window, cx| {
14140        editor.show_word_completions(&ShowWordCompletions, window, cx);
14141    });
14142    cx.executor().run_until_parked();
14143    cx.condition(|editor, _| editor.context_menu_visible())
14144        .await;
14145    cx.update_editor(|editor, _, _| {
14146        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14147        {
14148            assert_eq!(
14149                completion_menu_entries(menu),
14150                &["first", "last", "second"],
14151                "`ShowWordCompletions` action should show word completions"
14152            );
14153        } else {
14154            panic!("expected completion menu to be open");
14155        }
14156    });
14157
14158    cx.simulate_keystroke("l");
14159    cx.executor().run_until_parked();
14160    cx.condition(|editor, _| editor.context_menu_visible())
14161        .await;
14162    cx.update_editor(|editor, _, _| {
14163        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14164        {
14165            assert_eq!(
14166                completion_menu_entries(menu),
14167                &["last"],
14168                "After showing word completions, further editing should filter them and not query the LSP"
14169            );
14170        } else {
14171            panic!("expected completion menu to be open");
14172        }
14173    });
14174}
14175
14176#[gpui::test]
14177async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14178    init_test(cx, |language_settings| {
14179        language_settings.defaults.completions = Some(CompletionSettings {
14180            words: WordsCompletionMode::Fallback,
14181            words_min_length: 0,
14182            lsp: false,
14183            lsp_fetch_timeout_ms: 0,
14184            lsp_insert_mode: LspInsertMode::Insert,
14185        });
14186    });
14187
14188    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14189
14190    cx.set_state(indoc! {"ˇ
14191        0_usize
14192        let
14193        33
14194        4.5f32
14195    "});
14196    cx.update_editor(|editor, window, cx| {
14197        editor.show_completions(&ShowCompletions::default(), window, cx);
14198    });
14199    cx.executor().run_until_parked();
14200    cx.condition(|editor, _| editor.context_menu_visible())
14201        .await;
14202    cx.update_editor(|editor, window, cx| {
14203        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14204        {
14205            assert_eq!(
14206                completion_menu_entries(menu),
14207                &["let"],
14208                "With no digits in the completion query, no digits should be in the word completions"
14209            );
14210        } else {
14211            panic!("expected completion menu to be open");
14212        }
14213        editor.cancel(&Cancel, window, cx);
14214    });
14215
14216    cx.set_state(indoc! {"14217        0_usize
14218        let
14219        3
14220        33.35f32
14221    "});
14222    cx.update_editor(|editor, window, cx| {
14223        editor.show_completions(&ShowCompletions::default(), window, cx);
14224    });
14225    cx.executor().run_until_parked();
14226    cx.condition(|editor, _| editor.context_menu_visible())
14227        .await;
14228    cx.update_editor(|editor, _, _| {
14229        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14230        {
14231            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14232                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14233        } else {
14234            panic!("expected completion menu to be open");
14235        }
14236    });
14237}
14238
14239#[gpui::test]
14240async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14241    init_test(cx, |language_settings| {
14242        language_settings.defaults.completions = Some(CompletionSettings {
14243            words: WordsCompletionMode::Enabled,
14244            words_min_length: 3,
14245            lsp: true,
14246            lsp_fetch_timeout_ms: 0,
14247            lsp_insert_mode: LspInsertMode::Insert,
14248        });
14249    });
14250
14251    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14252    cx.set_state(indoc! {"ˇ
14253        wow
14254        wowen
14255        wowser
14256    "});
14257    cx.simulate_keystroke("w");
14258    cx.executor().run_until_parked();
14259    cx.update_editor(|editor, _, _| {
14260        if editor.context_menu.borrow_mut().is_some() {
14261            panic!(
14262                "expected completion menu to be hidden, as words completion threshold is not met"
14263            );
14264        }
14265    });
14266
14267    cx.simulate_keystroke("o");
14268    cx.executor().run_until_parked();
14269    cx.update_editor(|editor, _, _| {
14270        if editor.context_menu.borrow_mut().is_some() {
14271            panic!(
14272                "expected completion menu to be hidden, as words completion threshold is not met still"
14273            );
14274        }
14275    });
14276
14277    cx.simulate_keystroke("w");
14278    cx.executor().run_until_parked();
14279    cx.update_editor(|editor, _, _| {
14280        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14281        {
14282            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14283        } else {
14284            panic!("expected completion menu to be open after the word completions threshold is met");
14285        }
14286    });
14287}
14288
14289fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14290    let position = || lsp::Position {
14291        line: params.text_document_position.position.line,
14292        character: params.text_document_position.position.character,
14293    };
14294    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14295        range: lsp::Range {
14296            start: position(),
14297            end: position(),
14298        },
14299        new_text: text.to_string(),
14300    }))
14301}
14302
14303#[gpui::test]
14304async fn test_multiline_completion(cx: &mut TestAppContext) {
14305    init_test(cx, |_| {});
14306
14307    let fs = FakeFs::new(cx.executor());
14308    fs.insert_tree(
14309        path!("/a"),
14310        json!({
14311            "main.ts": "a",
14312        }),
14313    )
14314    .await;
14315
14316    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14317    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14318    let typescript_language = Arc::new(Language::new(
14319        LanguageConfig {
14320            name: "TypeScript".into(),
14321            matcher: LanguageMatcher {
14322                path_suffixes: vec!["ts".to_string()],
14323                ..LanguageMatcher::default()
14324            },
14325            line_comments: vec!["// ".into()],
14326            ..LanguageConfig::default()
14327        },
14328        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14329    ));
14330    language_registry.add(typescript_language.clone());
14331    let mut fake_servers = language_registry.register_fake_lsp(
14332        "TypeScript",
14333        FakeLspAdapter {
14334            capabilities: lsp::ServerCapabilities {
14335                completion_provider: Some(lsp::CompletionOptions {
14336                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14337                    ..lsp::CompletionOptions::default()
14338                }),
14339                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14340                ..lsp::ServerCapabilities::default()
14341            },
14342            // Emulate vtsls label generation
14343            label_for_completion: Some(Box::new(|item, _| {
14344                let text = if let Some(description) = item
14345                    .label_details
14346                    .as_ref()
14347                    .and_then(|label_details| label_details.description.as_ref())
14348                {
14349                    format!("{} {}", item.label, description)
14350                } else if let Some(detail) = &item.detail {
14351                    format!("{} {}", item.label, detail)
14352                } else {
14353                    item.label.clone()
14354                };
14355                let len = text.len();
14356                Some(language::CodeLabel {
14357                    text,
14358                    runs: Vec::new(),
14359                    filter_range: 0..len,
14360                })
14361            })),
14362            ..FakeLspAdapter::default()
14363        },
14364    );
14365    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14366    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14367    let worktree_id = workspace
14368        .update(cx, |workspace, _window, cx| {
14369            workspace.project().update(cx, |project, cx| {
14370                project.worktrees(cx).next().unwrap().read(cx).id()
14371            })
14372        })
14373        .unwrap();
14374    let _buffer = project
14375        .update(cx, |project, cx| {
14376            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14377        })
14378        .await
14379        .unwrap();
14380    let editor = workspace
14381        .update(cx, |workspace, window, cx| {
14382            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14383        })
14384        .unwrap()
14385        .await
14386        .unwrap()
14387        .downcast::<Editor>()
14388        .unwrap();
14389    let fake_server = fake_servers.next().await.unwrap();
14390
14391    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14392    let multiline_label_2 = "a\nb\nc\n";
14393    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14394    let multiline_description = "d\ne\nf\n";
14395    let multiline_detail_2 = "g\nh\ni\n";
14396
14397    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14398        move |params, _| async move {
14399            Ok(Some(lsp::CompletionResponse::Array(vec![
14400                lsp::CompletionItem {
14401                    label: multiline_label.to_string(),
14402                    text_edit: gen_text_edit(&params, "new_text_1"),
14403                    ..lsp::CompletionItem::default()
14404                },
14405                lsp::CompletionItem {
14406                    label: "single line label 1".to_string(),
14407                    detail: Some(multiline_detail.to_string()),
14408                    text_edit: gen_text_edit(&params, "new_text_2"),
14409                    ..lsp::CompletionItem::default()
14410                },
14411                lsp::CompletionItem {
14412                    label: "single line label 2".to_string(),
14413                    label_details: Some(lsp::CompletionItemLabelDetails {
14414                        description: Some(multiline_description.to_string()),
14415                        detail: None,
14416                    }),
14417                    text_edit: gen_text_edit(&params, "new_text_2"),
14418                    ..lsp::CompletionItem::default()
14419                },
14420                lsp::CompletionItem {
14421                    label: multiline_label_2.to_string(),
14422                    detail: Some(multiline_detail_2.to_string()),
14423                    text_edit: gen_text_edit(&params, "new_text_3"),
14424                    ..lsp::CompletionItem::default()
14425                },
14426                lsp::CompletionItem {
14427                    label: "Label with many     spaces and \t but without newlines".to_string(),
14428                    detail: Some(
14429                        "Details with many     spaces and \t but without newlines".to_string(),
14430                    ),
14431                    text_edit: gen_text_edit(&params, "new_text_4"),
14432                    ..lsp::CompletionItem::default()
14433                },
14434            ])))
14435        },
14436    );
14437
14438    editor.update_in(cx, |editor, window, cx| {
14439        cx.focus_self(window);
14440        editor.move_to_end(&MoveToEnd, window, cx);
14441        editor.handle_input(".", window, cx);
14442    });
14443    cx.run_until_parked();
14444    completion_handle.next().await.unwrap();
14445
14446    editor.update(cx, |editor, _| {
14447        assert!(editor.context_menu_visible());
14448        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14449        {
14450            let completion_labels = menu
14451                .completions
14452                .borrow()
14453                .iter()
14454                .map(|c| c.label.text.clone())
14455                .collect::<Vec<_>>();
14456            assert_eq!(
14457                completion_labels,
14458                &[
14459                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14460                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14461                    "single line label 2 d e f ",
14462                    "a b c g h i ",
14463                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14464                ],
14465                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14466            );
14467
14468            for completion in menu
14469                .completions
14470                .borrow()
14471                .iter() {
14472                    assert_eq!(
14473                        completion.label.filter_range,
14474                        0..completion.label.text.len(),
14475                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14476                    );
14477                }
14478        } else {
14479            panic!("expected completion menu to be open");
14480        }
14481    });
14482}
14483
14484#[gpui::test]
14485async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14486    init_test(cx, |_| {});
14487    let mut cx = EditorLspTestContext::new_rust(
14488        lsp::ServerCapabilities {
14489            completion_provider: Some(lsp::CompletionOptions {
14490                trigger_characters: Some(vec![".".to_string()]),
14491                ..Default::default()
14492            }),
14493            ..Default::default()
14494        },
14495        cx,
14496    )
14497    .await;
14498    cx.lsp
14499        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14500            Ok(Some(lsp::CompletionResponse::Array(vec![
14501                lsp::CompletionItem {
14502                    label: "first".into(),
14503                    ..Default::default()
14504                },
14505                lsp::CompletionItem {
14506                    label: "last".into(),
14507                    ..Default::default()
14508                },
14509            ])))
14510        });
14511    cx.set_state("variableˇ");
14512    cx.simulate_keystroke(".");
14513    cx.executor().run_until_parked();
14514
14515    cx.update_editor(|editor, _, _| {
14516        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14517        {
14518            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14519        } else {
14520            panic!("expected completion menu to be open");
14521        }
14522    });
14523
14524    cx.update_editor(|editor, window, cx| {
14525        editor.move_page_down(&MovePageDown::default(), window, cx);
14526        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14527        {
14528            assert!(
14529                menu.selected_item == 1,
14530                "expected PageDown to select the last item from the context menu"
14531            );
14532        } else {
14533            panic!("expected completion menu to stay open after PageDown");
14534        }
14535    });
14536
14537    cx.update_editor(|editor, window, cx| {
14538        editor.move_page_up(&MovePageUp::default(), window, cx);
14539        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14540        {
14541            assert!(
14542                menu.selected_item == 0,
14543                "expected PageUp to select the first item from the context menu"
14544            );
14545        } else {
14546            panic!("expected completion menu to stay open after PageUp");
14547        }
14548    });
14549}
14550
14551#[gpui::test]
14552async fn test_as_is_completions(cx: &mut TestAppContext) {
14553    init_test(cx, |_| {});
14554    let mut cx = EditorLspTestContext::new_rust(
14555        lsp::ServerCapabilities {
14556            completion_provider: Some(lsp::CompletionOptions {
14557                ..Default::default()
14558            }),
14559            ..Default::default()
14560        },
14561        cx,
14562    )
14563    .await;
14564    cx.lsp
14565        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14566            Ok(Some(lsp::CompletionResponse::Array(vec![
14567                lsp::CompletionItem {
14568                    label: "unsafe".into(),
14569                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14570                        range: lsp::Range {
14571                            start: lsp::Position {
14572                                line: 1,
14573                                character: 2,
14574                            },
14575                            end: lsp::Position {
14576                                line: 1,
14577                                character: 3,
14578                            },
14579                        },
14580                        new_text: "unsafe".to_string(),
14581                    })),
14582                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14583                    ..Default::default()
14584                },
14585            ])))
14586        });
14587    cx.set_state("fn a() {}\n");
14588    cx.executor().run_until_parked();
14589    cx.update_editor(|editor, window, cx| {
14590        editor.show_completions(
14591            &ShowCompletions {
14592                trigger: Some("\n".into()),
14593            },
14594            window,
14595            cx,
14596        );
14597    });
14598    cx.executor().run_until_parked();
14599
14600    cx.update_editor(|editor, window, cx| {
14601        editor.confirm_completion(&Default::default(), window, cx)
14602    });
14603    cx.executor().run_until_parked();
14604    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14605}
14606
14607#[gpui::test]
14608async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14609    init_test(cx, |_| {});
14610    let language =
14611        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14612    let mut cx = EditorLspTestContext::new(
14613        language,
14614        lsp::ServerCapabilities {
14615            completion_provider: Some(lsp::CompletionOptions {
14616                ..lsp::CompletionOptions::default()
14617            }),
14618            ..lsp::ServerCapabilities::default()
14619        },
14620        cx,
14621    )
14622    .await;
14623
14624    cx.set_state(
14625        "#ifndef BAR_H
14626#define BAR_H
14627
14628#include <stdbool.h>
14629
14630int fn_branch(bool do_branch1, bool do_branch2);
14631
14632#endif // BAR_H
14633ˇ",
14634    );
14635    cx.executor().run_until_parked();
14636    cx.update_editor(|editor, window, cx| {
14637        editor.handle_input("#", window, cx);
14638    });
14639    cx.executor().run_until_parked();
14640    cx.update_editor(|editor, window, cx| {
14641        editor.handle_input("i", window, cx);
14642    });
14643    cx.executor().run_until_parked();
14644    cx.update_editor(|editor, window, cx| {
14645        editor.handle_input("n", window, cx);
14646    });
14647    cx.executor().run_until_parked();
14648    cx.assert_editor_state(
14649        "#ifndef BAR_H
14650#define BAR_H
14651
14652#include <stdbool.h>
14653
14654int fn_branch(bool do_branch1, bool do_branch2);
14655
14656#endif // BAR_H
14657#inˇ",
14658    );
14659
14660    cx.lsp
14661        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14662            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14663                is_incomplete: false,
14664                item_defaults: None,
14665                items: vec![lsp::CompletionItem {
14666                    kind: Some(lsp::CompletionItemKind::SNIPPET),
14667                    label_details: Some(lsp::CompletionItemLabelDetails {
14668                        detail: Some("header".to_string()),
14669                        description: None,
14670                    }),
14671                    label: " include".to_string(),
14672                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14673                        range: lsp::Range {
14674                            start: lsp::Position {
14675                                line: 8,
14676                                character: 1,
14677                            },
14678                            end: lsp::Position {
14679                                line: 8,
14680                                character: 1,
14681                            },
14682                        },
14683                        new_text: "include \"$0\"".to_string(),
14684                    })),
14685                    sort_text: Some("40b67681include".to_string()),
14686                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14687                    filter_text: Some("include".to_string()),
14688                    insert_text: Some("include \"$0\"".to_string()),
14689                    ..lsp::CompletionItem::default()
14690                }],
14691            })))
14692        });
14693    cx.update_editor(|editor, window, cx| {
14694        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14695    });
14696    cx.executor().run_until_parked();
14697    cx.update_editor(|editor, window, cx| {
14698        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14699    });
14700    cx.executor().run_until_parked();
14701    cx.assert_editor_state(
14702        "#ifndef BAR_H
14703#define BAR_H
14704
14705#include <stdbool.h>
14706
14707int fn_branch(bool do_branch1, bool do_branch2);
14708
14709#endif // BAR_H
14710#include \"ˇ\"",
14711    );
14712
14713    cx.lsp
14714        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14715            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14716                is_incomplete: true,
14717                item_defaults: None,
14718                items: vec![lsp::CompletionItem {
14719                    kind: Some(lsp::CompletionItemKind::FILE),
14720                    label: "AGL/".to_string(),
14721                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14722                        range: lsp::Range {
14723                            start: lsp::Position {
14724                                line: 8,
14725                                character: 10,
14726                            },
14727                            end: lsp::Position {
14728                                line: 8,
14729                                character: 11,
14730                            },
14731                        },
14732                        new_text: "AGL/".to_string(),
14733                    })),
14734                    sort_text: Some("40b67681AGL/".to_string()),
14735                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14736                    filter_text: Some("AGL/".to_string()),
14737                    insert_text: Some("AGL/".to_string()),
14738                    ..lsp::CompletionItem::default()
14739                }],
14740            })))
14741        });
14742    cx.update_editor(|editor, window, cx| {
14743        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14744    });
14745    cx.executor().run_until_parked();
14746    cx.update_editor(|editor, window, cx| {
14747        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14748    });
14749    cx.executor().run_until_parked();
14750    cx.assert_editor_state(
14751        r##"#ifndef BAR_H
14752#define BAR_H
14753
14754#include <stdbool.h>
14755
14756int fn_branch(bool do_branch1, bool do_branch2);
14757
14758#endif // BAR_H
14759#include "AGL/ˇ"##,
14760    );
14761
14762    cx.update_editor(|editor, window, cx| {
14763        editor.handle_input("\"", window, cx);
14764    });
14765    cx.executor().run_until_parked();
14766    cx.assert_editor_state(
14767        r##"#ifndef BAR_H
14768#define BAR_H
14769
14770#include <stdbool.h>
14771
14772int fn_branch(bool do_branch1, bool do_branch2);
14773
14774#endif // BAR_H
14775#include "AGL/"ˇ"##,
14776    );
14777}
14778
14779#[gpui::test]
14780async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
14781    init_test(cx, |_| {});
14782
14783    let mut cx = EditorLspTestContext::new_rust(
14784        lsp::ServerCapabilities {
14785            completion_provider: Some(lsp::CompletionOptions {
14786                trigger_characters: Some(vec![".".to_string()]),
14787                resolve_provider: Some(true),
14788                ..Default::default()
14789            }),
14790            ..Default::default()
14791        },
14792        cx,
14793    )
14794    .await;
14795
14796    cx.set_state("fn main() { let a = 2ˇ; }");
14797    cx.simulate_keystroke(".");
14798    let completion_item = lsp::CompletionItem {
14799        label: "Some".into(),
14800        kind: Some(lsp::CompletionItemKind::SNIPPET),
14801        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
14802        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
14803            kind: lsp::MarkupKind::Markdown,
14804            value: "```rust\nSome(2)\n```".to_string(),
14805        })),
14806        deprecated: Some(false),
14807        sort_text: Some("Some".to_string()),
14808        filter_text: Some("Some".to_string()),
14809        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14810        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14811            range: lsp::Range {
14812                start: lsp::Position {
14813                    line: 0,
14814                    character: 22,
14815                },
14816                end: lsp::Position {
14817                    line: 0,
14818                    character: 22,
14819                },
14820            },
14821            new_text: "Some(2)".to_string(),
14822        })),
14823        additional_text_edits: Some(vec![lsp::TextEdit {
14824            range: lsp::Range {
14825                start: lsp::Position {
14826                    line: 0,
14827                    character: 20,
14828                },
14829                end: lsp::Position {
14830                    line: 0,
14831                    character: 22,
14832                },
14833            },
14834            new_text: "".to_string(),
14835        }]),
14836        ..Default::default()
14837    };
14838
14839    let closure_completion_item = completion_item.clone();
14840    let counter = Arc::new(AtomicUsize::new(0));
14841    let counter_clone = counter.clone();
14842    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14843        let task_completion_item = closure_completion_item.clone();
14844        counter_clone.fetch_add(1, atomic::Ordering::Release);
14845        async move {
14846            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14847                is_incomplete: true,
14848                item_defaults: None,
14849                items: vec![task_completion_item],
14850            })))
14851        }
14852    });
14853
14854    cx.condition(|editor, _| editor.context_menu_visible())
14855        .await;
14856    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
14857    assert!(request.next().await.is_some());
14858    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14859
14860    cx.simulate_keystrokes("S o m");
14861    cx.condition(|editor, _| editor.context_menu_visible())
14862        .await;
14863    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
14864    assert!(request.next().await.is_some());
14865    assert!(request.next().await.is_some());
14866    assert!(request.next().await.is_some());
14867    request.close();
14868    assert!(request.next().await.is_none());
14869    assert_eq!(
14870        counter.load(atomic::Ordering::Acquire),
14871        4,
14872        "With the completions menu open, only one LSP request should happen per input"
14873    );
14874}
14875
14876#[gpui::test]
14877async fn test_toggle_comment(cx: &mut TestAppContext) {
14878    init_test(cx, |_| {});
14879    let mut cx = EditorTestContext::new(cx).await;
14880    let language = Arc::new(Language::new(
14881        LanguageConfig {
14882            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14883            ..Default::default()
14884        },
14885        Some(tree_sitter_rust::LANGUAGE.into()),
14886    ));
14887    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14888
14889    // If multiple selections intersect a line, the line is only toggled once.
14890    cx.set_state(indoc! {"
14891        fn a() {
14892            «//b();
14893            ˇ»// «c();
14894            //ˇ»  d();
14895        }
14896    "});
14897
14898    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14899
14900    cx.assert_editor_state(indoc! {"
14901        fn a() {
14902            «b();
14903            c();
14904            ˇ» d();
14905        }
14906    "});
14907
14908    // The comment prefix is inserted at the same column for every line in a
14909    // selection.
14910    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14911
14912    cx.assert_editor_state(indoc! {"
14913        fn a() {
14914            // «b();
14915            // c();
14916            ˇ»//  d();
14917        }
14918    "});
14919
14920    // If a selection ends at the beginning of a line, that line is not toggled.
14921    cx.set_selections_state(indoc! {"
14922        fn a() {
14923            // b();
14924            «// c();
14925        ˇ»    //  d();
14926        }
14927    "});
14928
14929    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14930
14931    cx.assert_editor_state(indoc! {"
14932        fn a() {
14933            // b();
14934            «c();
14935        ˇ»    //  d();
14936        }
14937    "});
14938
14939    // If a selection span a single line and is empty, the line is toggled.
14940    cx.set_state(indoc! {"
14941        fn a() {
14942            a();
14943            b();
14944        ˇ
14945        }
14946    "});
14947
14948    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14949
14950    cx.assert_editor_state(indoc! {"
14951        fn a() {
14952            a();
14953            b();
14954        //•ˇ
14955        }
14956    "});
14957
14958    // If a selection span multiple lines, empty lines are not toggled.
14959    cx.set_state(indoc! {"
14960        fn a() {
14961            «a();
14962
14963            c();ˇ»
14964        }
14965    "});
14966
14967    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14968
14969    cx.assert_editor_state(indoc! {"
14970        fn a() {
14971            // «a();
14972
14973            // c();ˇ»
14974        }
14975    "});
14976
14977    // If a selection includes multiple comment prefixes, all lines are uncommented.
14978    cx.set_state(indoc! {"
14979        fn a() {
14980            «// a();
14981            /// b();
14982            //! c();ˇ»
14983        }
14984    "});
14985
14986    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14987
14988    cx.assert_editor_state(indoc! {"
14989        fn a() {
14990            «a();
14991            b();
14992            c();ˇ»
14993        }
14994    "});
14995}
14996
14997#[gpui::test]
14998async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
14999    init_test(cx, |_| {});
15000    let mut cx = EditorTestContext::new(cx).await;
15001    let language = Arc::new(Language::new(
15002        LanguageConfig {
15003            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15004            ..Default::default()
15005        },
15006        Some(tree_sitter_rust::LANGUAGE.into()),
15007    ));
15008    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15009
15010    let toggle_comments = &ToggleComments {
15011        advance_downwards: false,
15012        ignore_indent: true,
15013    };
15014
15015    // If multiple selections intersect a line, the line is only toggled once.
15016    cx.set_state(indoc! {"
15017        fn a() {
15018        //    «b();
15019        //    c();
15020        //    ˇ» d();
15021        }
15022    "});
15023
15024    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15025
15026    cx.assert_editor_state(indoc! {"
15027        fn a() {
15028            «b();
15029            c();
15030            ˇ» d();
15031        }
15032    "});
15033
15034    // The comment prefix is inserted at the beginning of each line
15035    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15036
15037    cx.assert_editor_state(indoc! {"
15038        fn a() {
15039        //    «b();
15040        //    c();
15041        //    ˇ» d();
15042        }
15043    "});
15044
15045    // If a selection ends at the beginning of a line, that line is not toggled.
15046    cx.set_selections_state(indoc! {"
15047        fn a() {
15048        //    b();
15049        //    «c();
15050        ˇ»//     d();
15051        }
15052    "});
15053
15054    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15055
15056    cx.assert_editor_state(indoc! {"
15057        fn a() {
15058        //    b();
15059            «c();
15060        ˇ»//     d();
15061        }
15062    "});
15063
15064    // If a selection span a single line and is empty, the line is toggled.
15065    cx.set_state(indoc! {"
15066        fn a() {
15067            a();
15068            b();
15069        ˇ
15070        }
15071    "});
15072
15073    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15074
15075    cx.assert_editor_state(indoc! {"
15076        fn a() {
15077            a();
15078            b();
15079        //ˇ
15080        }
15081    "});
15082
15083    // If a selection span multiple lines, empty lines are not toggled.
15084    cx.set_state(indoc! {"
15085        fn a() {
15086            «a();
15087
15088            c();ˇ»
15089        }
15090    "});
15091
15092    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15093
15094    cx.assert_editor_state(indoc! {"
15095        fn a() {
15096        //    «a();
15097
15098        //    c();ˇ»
15099        }
15100    "});
15101
15102    // If a selection includes multiple comment prefixes, all lines are uncommented.
15103    cx.set_state(indoc! {"
15104        fn a() {
15105        //    «a();
15106        ///    b();
15107        //!    c();ˇ»
15108        }
15109    "});
15110
15111    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15112
15113    cx.assert_editor_state(indoc! {"
15114        fn a() {
15115            «a();
15116            b();
15117            c();ˇ»
15118        }
15119    "});
15120}
15121
15122#[gpui::test]
15123async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15124    init_test(cx, |_| {});
15125
15126    let language = Arc::new(Language::new(
15127        LanguageConfig {
15128            line_comments: vec!["// ".into()],
15129            ..Default::default()
15130        },
15131        Some(tree_sitter_rust::LANGUAGE.into()),
15132    ));
15133
15134    let mut cx = EditorTestContext::new(cx).await;
15135
15136    cx.language_registry().add(language.clone());
15137    cx.update_buffer(|buffer, cx| {
15138        buffer.set_language(Some(language), cx);
15139    });
15140
15141    let toggle_comments = &ToggleComments {
15142        advance_downwards: true,
15143        ignore_indent: false,
15144    };
15145
15146    // Single cursor on one line -> advance
15147    // Cursor moves horizontally 3 characters as well on non-blank line
15148    cx.set_state(indoc!(
15149        "fn a() {
15150             ˇdog();
15151             cat();
15152        }"
15153    ));
15154    cx.update_editor(|editor, window, cx| {
15155        editor.toggle_comments(toggle_comments, window, cx);
15156    });
15157    cx.assert_editor_state(indoc!(
15158        "fn a() {
15159             // dog();
15160             catˇ();
15161        }"
15162    ));
15163
15164    // Single selection on one line -> don't advance
15165    cx.set_state(indoc!(
15166        "fn a() {
15167             «dog()ˇ»;
15168             cat();
15169        }"
15170    ));
15171    cx.update_editor(|editor, window, cx| {
15172        editor.toggle_comments(toggle_comments, window, cx);
15173    });
15174    cx.assert_editor_state(indoc!(
15175        "fn a() {
15176             // «dog()ˇ»;
15177             cat();
15178        }"
15179    ));
15180
15181    // Multiple cursors on one line -> advance
15182    cx.set_state(indoc!(
15183        "fn a() {
15184             ˇdˇog();
15185             cat();
15186        }"
15187    ));
15188    cx.update_editor(|editor, window, cx| {
15189        editor.toggle_comments(toggle_comments, window, cx);
15190    });
15191    cx.assert_editor_state(indoc!(
15192        "fn a() {
15193             // dog();
15194             catˇ(ˇ);
15195        }"
15196    ));
15197
15198    // Multiple cursors on one line, with selection -> don't advance
15199    cx.set_state(indoc!(
15200        "fn a() {
15201             ˇdˇog«()ˇ»;
15202             cat();
15203        }"
15204    ));
15205    cx.update_editor(|editor, window, cx| {
15206        editor.toggle_comments(toggle_comments, window, cx);
15207    });
15208    cx.assert_editor_state(indoc!(
15209        "fn a() {
15210             // ˇdˇog«()ˇ»;
15211             cat();
15212        }"
15213    ));
15214
15215    // Single cursor on one line -> advance
15216    // Cursor moves to column 0 on blank line
15217    cx.set_state(indoc!(
15218        "fn a() {
15219             ˇdog();
15220
15221             cat();
15222        }"
15223    ));
15224    cx.update_editor(|editor, window, cx| {
15225        editor.toggle_comments(toggle_comments, window, cx);
15226    });
15227    cx.assert_editor_state(indoc!(
15228        "fn a() {
15229             // dog();
15230        ˇ
15231             cat();
15232        }"
15233    ));
15234
15235    // Single cursor on one line -> advance
15236    // Cursor starts and ends at column 0
15237    cx.set_state(indoc!(
15238        "fn a() {
15239         ˇ    dog();
15240             cat();
15241        }"
15242    ));
15243    cx.update_editor(|editor, window, cx| {
15244        editor.toggle_comments(toggle_comments, window, cx);
15245    });
15246    cx.assert_editor_state(indoc!(
15247        "fn a() {
15248             // dog();
15249         ˇ    cat();
15250        }"
15251    ));
15252}
15253
15254#[gpui::test]
15255async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15256    init_test(cx, |_| {});
15257
15258    let mut cx = EditorTestContext::new(cx).await;
15259
15260    let html_language = Arc::new(
15261        Language::new(
15262            LanguageConfig {
15263                name: "HTML".into(),
15264                block_comment: Some(BlockCommentConfig {
15265                    start: "<!-- ".into(),
15266                    prefix: "".into(),
15267                    end: " -->".into(),
15268                    tab_size: 0,
15269                }),
15270                ..Default::default()
15271            },
15272            Some(tree_sitter_html::LANGUAGE.into()),
15273        )
15274        .with_injection_query(
15275            r#"
15276            (script_element
15277                (raw_text) @injection.content
15278                (#set! injection.language "javascript"))
15279            "#,
15280        )
15281        .unwrap(),
15282    );
15283
15284    let javascript_language = Arc::new(Language::new(
15285        LanguageConfig {
15286            name: "JavaScript".into(),
15287            line_comments: vec!["// ".into()],
15288            ..Default::default()
15289        },
15290        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15291    ));
15292
15293    cx.language_registry().add(html_language.clone());
15294    cx.language_registry().add(javascript_language);
15295    cx.update_buffer(|buffer, cx| {
15296        buffer.set_language(Some(html_language), cx);
15297    });
15298
15299    // Toggle comments for empty selections
15300    cx.set_state(
15301        &r#"
15302            <p>A</p>ˇ
15303            <p>B</p>ˇ
15304            <p>C</p>ˇ
15305        "#
15306        .unindent(),
15307    );
15308    cx.update_editor(|editor, window, cx| {
15309        editor.toggle_comments(&ToggleComments::default(), window, cx)
15310    });
15311    cx.assert_editor_state(
15312        &r#"
15313            <!-- <p>A</p>ˇ -->
15314            <!-- <p>B</p>ˇ -->
15315            <!-- <p>C</p>ˇ -->
15316        "#
15317        .unindent(),
15318    );
15319    cx.update_editor(|editor, window, cx| {
15320        editor.toggle_comments(&ToggleComments::default(), window, cx)
15321    });
15322    cx.assert_editor_state(
15323        &r#"
15324            <p>A</p>ˇ
15325            <p>B</p>ˇ
15326            <p>C</p>ˇ
15327        "#
15328        .unindent(),
15329    );
15330
15331    // Toggle comments for mixture of empty and non-empty selections, where
15332    // multiple selections occupy a given line.
15333    cx.set_state(
15334        &r#"
15335            <p>A«</p>
15336            <p>ˇ»B</p>ˇ
15337            <p>C«</p>
15338            <p>ˇ»D</p>ˇ
15339        "#
15340        .unindent(),
15341    );
15342
15343    cx.update_editor(|editor, window, cx| {
15344        editor.toggle_comments(&ToggleComments::default(), window, cx)
15345    });
15346    cx.assert_editor_state(
15347        &r#"
15348            <!-- <p>A«</p>
15349            <p>ˇ»B</p>ˇ -->
15350            <!-- <p>C«</p>
15351            <p>ˇ»D</p>ˇ -->
15352        "#
15353        .unindent(),
15354    );
15355    cx.update_editor(|editor, window, cx| {
15356        editor.toggle_comments(&ToggleComments::default(), window, cx)
15357    });
15358    cx.assert_editor_state(
15359        &r#"
15360            <p>A«</p>
15361            <p>ˇ»B</p>ˇ
15362            <p>C«</p>
15363            <p>ˇ»D</p>ˇ
15364        "#
15365        .unindent(),
15366    );
15367
15368    // Toggle comments when different languages are active for different
15369    // selections.
15370    cx.set_state(
15371        &r#"
15372            ˇ<script>
15373                ˇvar x = new Y();
15374            ˇ</script>
15375        "#
15376        .unindent(),
15377    );
15378    cx.executor().run_until_parked();
15379    cx.update_editor(|editor, window, cx| {
15380        editor.toggle_comments(&ToggleComments::default(), window, cx)
15381    });
15382    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15383    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15384    cx.assert_editor_state(
15385        &r#"
15386            <!-- ˇ<script> -->
15387                // ˇvar x = new Y();
15388            <!-- ˇ</script> -->
15389        "#
15390        .unindent(),
15391    );
15392}
15393
15394#[gpui::test]
15395fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15396    init_test(cx, |_| {});
15397
15398    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15399    let multibuffer = cx.new(|cx| {
15400        let mut multibuffer = MultiBuffer::new(ReadWrite);
15401        multibuffer.push_excerpts(
15402            buffer.clone(),
15403            [
15404                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15405                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15406            ],
15407            cx,
15408        );
15409        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15410        multibuffer
15411    });
15412
15413    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15414    editor.update_in(cx, |editor, window, cx| {
15415        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15417            s.select_ranges([
15418                Point::new(0, 0)..Point::new(0, 0),
15419                Point::new(1, 0)..Point::new(1, 0),
15420            ])
15421        });
15422
15423        editor.handle_input("X", window, cx);
15424        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15425        assert_eq!(
15426            editor.selections.ranges(cx),
15427            [
15428                Point::new(0, 1)..Point::new(0, 1),
15429                Point::new(1, 1)..Point::new(1, 1),
15430            ]
15431        );
15432
15433        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15434        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15435            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15436        });
15437        editor.backspace(&Default::default(), window, cx);
15438        assert_eq!(editor.text(cx), "Xa\nbbb");
15439        assert_eq!(
15440            editor.selections.ranges(cx),
15441            [Point::new(1, 0)..Point::new(1, 0)]
15442        );
15443
15444        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15445            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15446        });
15447        editor.backspace(&Default::default(), window, cx);
15448        assert_eq!(editor.text(cx), "X\nbb");
15449        assert_eq!(
15450            editor.selections.ranges(cx),
15451            [Point::new(0, 1)..Point::new(0, 1)]
15452        );
15453    });
15454}
15455
15456#[gpui::test]
15457fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15458    init_test(cx, |_| {});
15459
15460    let markers = vec![('[', ']').into(), ('(', ')').into()];
15461    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15462        indoc! {"
15463            [aaaa
15464            (bbbb]
15465            cccc)",
15466        },
15467        markers.clone(),
15468    );
15469    let excerpt_ranges = markers.into_iter().map(|marker| {
15470        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15471        ExcerptRange::new(context)
15472    });
15473    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15474    let multibuffer = cx.new(|cx| {
15475        let mut multibuffer = MultiBuffer::new(ReadWrite);
15476        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15477        multibuffer
15478    });
15479
15480    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15481    editor.update_in(cx, |editor, window, cx| {
15482        let (expected_text, selection_ranges) = marked_text_ranges(
15483            indoc! {"
15484                aaaa
15485                bˇbbb
15486                bˇbbˇb
15487                cccc"
15488            },
15489            true,
15490        );
15491        assert_eq!(editor.text(cx), expected_text);
15492        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15493            s.select_ranges(selection_ranges)
15494        });
15495
15496        editor.handle_input("X", window, cx);
15497
15498        let (expected_text, expected_selections) = marked_text_ranges(
15499            indoc! {"
15500                aaaa
15501                bXˇbbXb
15502                bXˇbbXˇb
15503                cccc"
15504            },
15505            false,
15506        );
15507        assert_eq!(editor.text(cx), expected_text);
15508        assert_eq!(editor.selections.ranges(cx), expected_selections);
15509
15510        editor.newline(&Newline, window, cx);
15511        let (expected_text, expected_selections) = marked_text_ranges(
15512            indoc! {"
15513                aaaa
15514                bX
15515                ˇbbX
15516                b
15517                bX
15518                ˇbbX
15519                ˇb
15520                cccc"
15521            },
15522            false,
15523        );
15524        assert_eq!(editor.text(cx), expected_text);
15525        assert_eq!(editor.selections.ranges(cx), expected_selections);
15526    });
15527}
15528
15529#[gpui::test]
15530fn test_refresh_selections(cx: &mut TestAppContext) {
15531    init_test(cx, |_| {});
15532
15533    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15534    let mut excerpt1_id = None;
15535    let multibuffer = cx.new(|cx| {
15536        let mut multibuffer = MultiBuffer::new(ReadWrite);
15537        excerpt1_id = multibuffer
15538            .push_excerpts(
15539                buffer.clone(),
15540                [
15541                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15542                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15543                ],
15544                cx,
15545            )
15546            .into_iter()
15547            .next();
15548        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15549        multibuffer
15550    });
15551
15552    let editor = cx.add_window(|window, cx| {
15553        let mut editor = build_editor(multibuffer.clone(), window, cx);
15554        let snapshot = editor.snapshot(window, cx);
15555        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15556            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15557        });
15558        editor.begin_selection(
15559            Point::new(2, 1).to_display_point(&snapshot),
15560            true,
15561            1,
15562            window,
15563            cx,
15564        );
15565        assert_eq!(
15566            editor.selections.ranges(cx),
15567            [
15568                Point::new(1, 3)..Point::new(1, 3),
15569                Point::new(2, 1)..Point::new(2, 1),
15570            ]
15571        );
15572        editor
15573    });
15574
15575    // Refreshing selections is a no-op when excerpts haven't changed.
15576    _ = editor.update(cx, |editor, window, cx| {
15577        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15578        assert_eq!(
15579            editor.selections.ranges(cx),
15580            [
15581                Point::new(1, 3)..Point::new(1, 3),
15582                Point::new(2, 1)..Point::new(2, 1),
15583            ]
15584        );
15585    });
15586
15587    multibuffer.update(cx, |multibuffer, cx| {
15588        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15589    });
15590    _ = editor.update(cx, |editor, window, cx| {
15591        // Removing an excerpt causes the first selection to become degenerate.
15592        assert_eq!(
15593            editor.selections.ranges(cx),
15594            [
15595                Point::new(0, 0)..Point::new(0, 0),
15596                Point::new(0, 1)..Point::new(0, 1)
15597            ]
15598        );
15599
15600        // Refreshing selections will relocate the first selection to the original buffer
15601        // location.
15602        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15603        assert_eq!(
15604            editor.selections.ranges(cx),
15605            [
15606                Point::new(0, 1)..Point::new(0, 1),
15607                Point::new(0, 3)..Point::new(0, 3)
15608            ]
15609        );
15610        assert!(editor.selections.pending_anchor().is_some());
15611    });
15612}
15613
15614#[gpui::test]
15615fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15616    init_test(cx, |_| {});
15617
15618    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15619    let mut excerpt1_id = None;
15620    let multibuffer = cx.new(|cx| {
15621        let mut multibuffer = MultiBuffer::new(ReadWrite);
15622        excerpt1_id = multibuffer
15623            .push_excerpts(
15624                buffer.clone(),
15625                [
15626                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15627                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15628                ],
15629                cx,
15630            )
15631            .into_iter()
15632            .next();
15633        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15634        multibuffer
15635    });
15636
15637    let editor = cx.add_window(|window, cx| {
15638        let mut editor = build_editor(multibuffer.clone(), window, cx);
15639        let snapshot = editor.snapshot(window, cx);
15640        editor.begin_selection(
15641            Point::new(1, 3).to_display_point(&snapshot),
15642            false,
15643            1,
15644            window,
15645            cx,
15646        );
15647        assert_eq!(
15648            editor.selections.ranges(cx),
15649            [Point::new(1, 3)..Point::new(1, 3)]
15650        );
15651        editor
15652    });
15653
15654    multibuffer.update(cx, |multibuffer, cx| {
15655        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15656    });
15657    _ = editor.update(cx, |editor, window, cx| {
15658        assert_eq!(
15659            editor.selections.ranges(cx),
15660            [Point::new(0, 0)..Point::new(0, 0)]
15661        );
15662
15663        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
15664        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15665        assert_eq!(
15666            editor.selections.ranges(cx),
15667            [Point::new(0, 3)..Point::new(0, 3)]
15668        );
15669        assert!(editor.selections.pending_anchor().is_some());
15670    });
15671}
15672
15673#[gpui::test]
15674async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
15675    init_test(cx, |_| {});
15676
15677    let language = Arc::new(
15678        Language::new(
15679            LanguageConfig {
15680                brackets: BracketPairConfig {
15681                    pairs: vec![
15682                        BracketPair {
15683                            start: "{".to_string(),
15684                            end: "}".to_string(),
15685                            close: true,
15686                            surround: true,
15687                            newline: true,
15688                        },
15689                        BracketPair {
15690                            start: "/* ".to_string(),
15691                            end: " */".to_string(),
15692                            close: true,
15693                            surround: true,
15694                            newline: true,
15695                        },
15696                    ],
15697                    ..Default::default()
15698                },
15699                ..Default::default()
15700            },
15701            Some(tree_sitter_rust::LANGUAGE.into()),
15702        )
15703        .with_indents_query("")
15704        .unwrap(),
15705    );
15706
15707    let text = concat!(
15708        "{   }\n",     //
15709        "  x\n",       //
15710        "  /*   */\n", //
15711        "x\n",         //
15712        "{{} }\n",     //
15713    );
15714
15715    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15716    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15717    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15718    editor
15719        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
15720        .await;
15721
15722    editor.update_in(cx, |editor, window, cx| {
15723        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15724            s.select_display_ranges([
15725                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
15726                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
15727                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
15728            ])
15729        });
15730        editor.newline(&Newline, window, cx);
15731
15732        assert_eq!(
15733            editor.buffer().read(cx).read(cx).text(),
15734            concat!(
15735                "{ \n",    // Suppress rustfmt
15736                "\n",      //
15737                "}\n",     //
15738                "  x\n",   //
15739                "  /* \n", //
15740                "  \n",    //
15741                "  */\n",  //
15742                "x\n",     //
15743                "{{} \n",  //
15744                "}\n",     //
15745            )
15746        );
15747    });
15748}
15749
15750#[gpui::test]
15751fn test_highlighted_ranges(cx: &mut TestAppContext) {
15752    init_test(cx, |_| {});
15753
15754    let editor = cx.add_window(|window, cx| {
15755        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
15756        build_editor(buffer, window, cx)
15757    });
15758
15759    _ = editor.update(cx, |editor, window, cx| {
15760        struct Type1;
15761        struct Type2;
15762
15763        let buffer = editor.buffer.read(cx).snapshot(cx);
15764
15765        let anchor_range =
15766            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
15767
15768        editor.highlight_background::<Type1>(
15769            &[
15770                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
15771                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
15772                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
15773                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
15774            ],
15775            |_| Hsla::red(),
15776            cx,
15777        );
15778        editor.highlight_background::<Type2>(
15779            &[
15780                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
15781                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
15782                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
15783                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
15784            ],
15785            |_| Hsla::green(),
15786            cx,
15787        );
15788
15789        let snapshot = editor.snapshot(window, cx);
15790        let highlighted_ranges = editor.sorted_background_highlights_in_range(
15791            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
15792            &snapshot,
15793            cx.theme(),
15794        );
15795        assert_eq!(
15796            highlighted_ranges,
15797            &[
15798                (
15799                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
15800                    Hsla::green(),
15801                ),
15802                (
15803                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
15804                    Hsla::red(),
15805                ),
15806                (
15807                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
15808                    Hsla::green(),
15809                ),
15810                (
15811                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
15812                    Hsla::red(),
15813                ),
15814            ]
15815        );
15816        assert_eq!(
15817            editor.sorted_background_highlights_in_range(
15818                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
15819                &snapshot,
15820                cx.theme(),
15821            ),
15822            &[(
15823                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
15824                Hsla::red(),
15825            )]
15826        );
15827    });
15828}
15829
15830#[gpui::test]
15831async fn test_following(cx: &mut TestAppContext) {
15832    init_test(cx, |_| {});
15833
15834    let fs = FakeFs::new(cx.executor());
15835    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
15836
15837    let buffer = project.update(cx, |project, cx| {
15838        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
15839        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
15840    });
15841    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
15842    let follower = cx.update(|cx| {
15843        cx.open_window(
15844            WindowOptions {
15845                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
15846                    gpui::Point::new(px(0.), px(0.)),
15847                    gpui::Point::new(px(10.), px(80.)),
15848                ))),
15849                ..Default::default()
15850            },
15851            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
15852        )
15853        .unwrap()
15854    });
15855
15856    let is_still_following = Rc::new(RefCell::new(true));
15857    let follower_edit_event_count = Rc::new(RefCell::new(0));
15858    let pending_update = Rc::new(RefCell::new(None));
15859    let leader_entity = leader.root(cx).unwrap();
15860    let follower_entity = follower.root(cx).unwrap();
15861    _ = follower.update(cx, {
15862        let update = pending_update.clone();
15863        let is_still_following = is_still_following.clone();
15864        let follower_edit_event_count = follower_edit_event_count.clone();
15865        |_, window, cx| {
15866            cx.subscribe_in(
15867                &leader_entity,
15868                window,
15869                move |_, leader, event, window, cx| {
15870                    leader.read(cx).add_event_to_update_proto(
15871                        event,
15872                        &mut update.borrow_mut(),
15873                        window,
15874                        cx,
15875                    );
15876                },
15877            )
15878            .detach();
15879
15880            cx.subscribe_in(
15881                &follower_entity,
15882                window,
15883                move |_, _, event: &EditorEvent, _window, _cx| {
15884                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
15885                        *is_still_following.borrow_mut() = false;
15886                    }
15887
15888                    if let EditorEvent::BufferEdited = event {
15889                        *follower_edit_event_count.borrow_mut() += 1;
15890                    }
15891                },
15892            )
15893            .detach();
15894        }
15895    });
15896
15897    // Update the selections only
15898    _ = leader.update(cx, |leader, window, cx| {
15899        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15900            s.select_ranges([1..1])
15901        });
15902    });
15903    follower
15904        .update(cx, |follower, window, cx| {
15905            follower.apply_update_proto(
15906                &project,
15907                pending_update.borrow_mut().take().unwrap(),
15908                window,
15909                cx,
15910            )
15911        })
15912        .unwrap()
15913        .await
15914        .unwrap();
15915    _ = follower.update(cx, |follower, _, cx| {
15916        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
15917    });
15918    assert!(*is_still_following.borrow());
15919    assert_eq!(*follower_edit_event_count.borrow(), 0);
15920
15921    // Update the scroll position only
15922    _ = leader.update(cx, |leader, window, cx| {
15923        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15924    });
15925    follower
15926        .update(cx, |follower, window, cx| {
15927            follower.apply_update_proto(
15928                &project,
15929                pending_update.borrow_mut().take().unwrap(),
15930                window,
15931                cx,
15932            )
15933        })
15934        .unwrap()
15935        .await
15936        .unwrap();
15937    assert_eq!(
15938        follower
15939            .update(cx, |follower, _, cx| follower.scroll_position(cx))
15940            .unwrap(),
15941        gpui::Point::new(1.5, 3.5)
15942    );
15943    assert!(*is_still_following.borrow());
15944    assert_eq!(*follower_edit_event_count.borrow(), 0);
15945
15946    // Update the selections and scroll position. The follower's scroll position is updated
15947    // via autoscroll, not via the leader's exact scroll position.
15948    _ = leader.update(cx, |leader, window, cx| {
15949        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15950            s.select_ranges([0..0])
15951        });
15952        leader.request_autoscroll(Autoscroll::newest(), cx);
15953        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15954    });
15955    follower
15956        .update(cx, |follower, window, cx| {
15957            follower.apply_update_proto(
15958                &project,
15959                pending_update.borrow_mut().take().unwrap(),
15960                window,
15961                cx,
15962            )
15963        })
15964        .unwrap()
15965        .await
15966        .unwrap();
15967    _ = follower.update(cx, |follower, _, cx| {
15968        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
15969        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
15970    });
15971    assert!(*is_still_following.borrow());
15972
15973    // Creating a pending selection that precedes another selection
15974    _ = leader.update(cx, |leader, window, cx| {
15975        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15976            s.select_ranges([1..1])
15977        });
15978        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
15979    });
15980    follower
15981        .update(cx, |follower, window, cx| {
15982            follower.apply_update_proto(
15983                &project,
15984                pending_update.borrow_mut().take().unwrap(),
15985                window,
15986                cx,
15987            )
15988        })
15989        .unwrap()
15990        .await
15991        .unwrap();
15992    _ = follower.update(cx, |follower, _, cx| {
15993        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
15994    });
15995    assert!(*is_still_following.borrow());
15996
15997    // Extend the pending selection so that it surrounds another selection
15998    _ = leader.update(cx, |leader, window, cx| {
15999        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16000    });
16001    follower
16002        .update(cx, |follower, window, cx| {
16003            follower.apply_update_proto(
16004                &project,
16005                pending_update.borrow_mut().take().unwrap(),
16006                window,
16007                cx,
16008            )
16009        })
16010        .unwrap()
16011        .await
16012        .unwrap();
16013    _ = follower.update(cx, |follower, _, cx| {
16014        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16015    });
16016
16017    // Scrolling locally breaks the follow
16018    _ = follower.update(cx, |follower, window, cx| {
16019        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16020        follower.set_scroll_anchor(
16021            ScrollAnchor {
16022                anchor: top_anchor,
16023                offset: gpui::Point::new(0.0, 0.5),
16024            },
16025            window,
16026            cx,
16027        );
16028    });
16029    assert!(!(*is_still_following.borrow()));
16030}
16031
16032#[gpui::test]
16033async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16034    init_test(cx, |_| {});
16035
16036    let fs = FakeFs::new(cx.executor());
16037    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16038    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16039    let pane = workspace
16040        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16041        .unwrap();
16042
16043    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16044
16045    let leader = pane.update_in(cx, |_, window, cx| {
16046        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16047        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16048    });
16049
16050    // Start following the editor when it has no excerpts.
16051    let mut state_message =
16052        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16053    let workspace_entity = workspace.root(cx).unwrap();
16054    let follower_1 = cx
16055        .update_window(*workspace.deref(), |_, window, cx| {
16056            Editor::from_state_proto(
16057                workspace_entity,
16058                ViewId {
16059                    creator: CollaboratorId::PeerId(PeerId::default()),
16060                    id: 0,
16061                },
16062                &mut state_message,
16063                window,
16064                cx,
16065            )
16066        })
16067        .unwrap()
16068        .unwrap()
16069        .await
16070        .unwrap();
16071
16072    let update_message = Rc::new(RefCell::new(None));
16073    follower_1.update_in(cx, {
16074        let update = update_message.clone();
16075        |_, window, cx| {
16076            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16077                leader.read(cx).add_event_to_update_proto(
16078                    event,
16079                    &mut update.borrow_mut(),
16080                    window,
16081                    cx,
16082                );
16083            })
16084            .detach();
16085        }
16086    });
16087
16088    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16089        (
16090            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
16091            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
16092        )
16093    });
16094
16095    // Insert some excerpts.
16096    leader.update(cx, |leader, cx| {
16097        leader.buffer.update(cx, |multibuffer, cx| {
16098            multibuffer.set_excerpts_for_path(
16099                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
16100                buffer_1.clone(),
16101                vec![
16102                    Point::row_range(0..3),
16103                    Point::row_range(1..6),
16104                    Point::row_range(12..15),
16105                ],
16106                0,
16107                cx,
16108            );
16109            multibuffer.set_excerpts_for_path(
16110                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
16111                buffer_2.clone(),
16112                vec![Point::row_range(0..6), Point::row_range(8..12)],
16113                0,
16114                cx,
16115            );
16116        });
16117    });
16118
16119    // Apply the update of adding the excerpts.
16120    follower_1
16121        .update_in(cx, |follower, window, cx| {
16122            follower.apply_update_proto(
16123                &project,
16124                update_message.borrow().clone().unwrap(),
16125                window,
16126                cx,
16127            )
16128        })
16129        .await
16130        .unwrap();
16131    assert_eq!(
16132        follower_1.update(cx, |editor, cx| editor.text(cx)),
16133        leader.update(cx, |editor, cx| editor.text(cx))
16134    );
16135    update_message.borrow_mut().take();
16136
16137    // Start following separately after it already has excerpts.
16138    let mut state_message =
16139        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16140    let workspace_entity = workspace.root(cx).unwrap();
16141    let follower_2 = cx
16142        .update_window(*workspace.deref(), |_, window, cx| {
16143            Editor::from_state_proto(
16144                workspace_entity,
16145                ViewId {
16146                    creator: CollaboratorId::PeerId(PeerId::default()),
16147                    id: 0,
16148                },
16149                &mut state_message,
16150                window,
16151                cx,
16152            )
16153        })
16154        .unwrap()
16155        .unwrap()
16156        .await
16157        .unwrap();
16158    assert_eq!(
16159        follower_2.update(cx, |editor, cx| editor.text(cx)),
16160        leader.update(cx, |editor, cx| editor.text(cx))
16161    );
16162
16163    // Remove some excerpts.
16164    leader.update(cx, |leader, cx| {
16165        leader.buffer.update(cx, |multibuffer, cx| {
16166            let excerpt_ids = multibuffer.excerpt_ids();
16167            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16168            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16169        });
16170    });
16171
16172    // Apply the update of removing the excerpts.
16173    follower_1
16174        .update_in(cx, |follower, window, cx| {
16175            follower.apply_update_proto(
16176                &project,
16177                update_message.borrow().clone().unwrap(),
16178                window,
16179                cx,
16180            )
16181        })
16182        .await
16183        .unwrap();
16184    follower_2
16185        .update_in(cx, |follower, window, cx| {
16186            follower.apply_update_proto(
16187                &project,
16188                update_message.borrow().clone().unwrap(),
16189                window,
16190                cx,
16191            )
16192        })
16193        .await
16194        .unwrap();
16195    update_message.borrow_mut().take();
16196    assert_eq!(
16197        follower_1.update(cx, |editor, cx| editor.text(cx)),
16198        leader.update(cx, |editor, cx| editor.text(cx))
16199    );
16200}
16201
16202#[gpui::test]
16203async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16204    init_test(cx, |_| {});
16205
16206    let mut cx = EditorTestContext::new(cx).await;
16207    let lsp_store =
16208        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16209
16210    cx.set_state(indoc! {"
16211        ˇfn func(abc def: i32) -> u32 {
16212        }
16213    "});
16214
16215    cx.update(|_, cx| {
16216        lsp_store.update(cx, |lsp_store, cx| {
16217            lsp_store
16218                .update_diagnostics(
16219                    LanguageServerId(0),
16220                    lsp::PublishDiagnosticsParams {
16221                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16222                        version: None,
16223                        diagnostics: vec![
16224                            lsp::Diagnostic {
16225                                range: lsp::Range::new(
16226                                    lsp::Position::new(0, 11),
16227                                    lsp::Position::new(0, 12),
16228                                ),
16229                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16230                                ..Default::default()
16231                            },
16232                            lsp::Diagnostic {
16233                                range: lsp::Range::new(
16234                                    lsp::Position::new(0, 12),
16235                                    lsp::Position::new(0, 15),
16236                                ),
16237                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16238                                ..Default::default()
16239                            },
16240                            lsp::Diagnostic {
16241                                range: lsp::Range::new(
16242                                    lsp::Position::new(0, 25),
16243                                    lsp::Position::new(0, 28),
16244                                ),
16245                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16246                                ..Default::default()
16247                            },
16248                        ],
16249                    },
16250                    None,
16251                    DiagnosticSourceKind::Pushed,
16252                    &[],
16253                    cx,
16254                )
16255                .unwrap()
16256        });
16257    });
16258
16259    executor.run_until_parked();
16260
16261    cx.update_editor(|editor, window, cx| {
16262        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16263    });
16264
16265    cx.assert_editor_state(indoc! {"
16266        fn func(abc def: i32) -> ˇu32 {
16267        }
16268    "});
16269
16270    cx.update_editor(|editor, window, cx| {
16271        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16272    });
16273
16274    cx.assert_editor_state(indoc! {"
16275        fn func(abc ˇdef: i32) -> u32 {
16276        }
16277    "});
16278
16279    cx.update_editor(|editor, window, cx| {
16280        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16281    });
16282
16283    cx.assert_editor_state(indoc! {"
16284        fn func(abcˇ def: i32) -> u32 {
16285        }
16286    "});
16287
16288    cx.update_editor(|editor, window, cx| {
16289        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16290    });
16291
16292    cx.assert_editor_state(indoc! {"
16293        fn func(abc def: i32) -> ˇu32 {
16294        }
16295    "});
16296}
16297
16298#[gpui::test]
16299async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16300    init_test(cx, |_| {});
16301
16302    let mut cx = EditorTestContext::new(cx).await;
16303
16304    let diff_base = r#"
16305        use some::mod;
16306
16307        const A: u32 = 42;
16308
16309        fn main() {
16310            println!("hello");
16311
16312            println!("world");
16313        }
16314        "#
16315    .unindent();
16316
16317    // Edits are modified, removed, modified, added
16318    cx.set_state(
16319        &r#"
16320        use some::modified;
16321
16322        ˇ
16323        fn main() {
16324            println!("hello there");
16325
16326            println!("around the");
16327            println!("world");
16328        }
16329        "#
16330        .unindent(),
16331    );
16332
16333    cx.set_head_text(&diff_base);
16334    executor.run_until_parked();
16335
16336    cx.update_editor(|editor, window, cx| {
16337        //Wrap around the bottom of the buffer
16338        for _ in 0..3 {
16339            editor.go_to_next_hunk(&GoToHunk, window, cx);
16340        }
16341    });
16342
16343    cx.assert_editor_state(
16344        &r#"
16345        ˇuse some::modified;
16346
16347
16348        fn main() {
16349            println!("hello there");
16350
16351            println!("around the");
16352            println!("world");
16353        }
16354        "#
16355        .unindent(),
16356    );
16357
16358    cx.update_editor(|editor, window, cx| {
16359        //Wrap around the top of the buffer
16360        for _ in 0..2 {
16361            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16362        }
16363    });
16364
16365    cx.assert_editor_state(
16366        &r#"
16367        use some::modified;
16368
16369
16370        fn main() {
16371        ˇ    println!("hello there");
16372
16373            println!("around the");
16374            println!("world");
16375        }
16376        "#
16377        .unindent(),
16378    );
16379
16380    cx.update_editor(|editor, window, cx| {
16381        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16382    });
16383
16384    cx.assert_editor_state(
16385        &r#"
16386        use some::modified;
16387
16388        ˇ
16389        fn main() {
16390            println!("hello there");
16391
16392            println!("around the");
16393            println!("world");
16394        }
16395        "#
16396        .unindent(),
16397    );
16398
16399    cx.update_editor(|editor, window, cx| {
16400        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16401    });
16402
16403    cx.assert_editor_state(
16404        &r#"
16405        ˇuse some::modified;
16406
16407
16408        fn main() {
16409            println!("hello there");
16410
16411            println!("around the");
16412            println!("world");
16413        }
16414        "#
16415        .unindent(),
16416    );
16417
16418    cx.update_editor(|editor, window, cx| {
16419        for _ in 0..2 {
16420            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16421        }
16422    });
16423
16424    cx.assert_editor_state(
16425        &r#"
16426        use some::modified;
16427
16428
16429        fn main() {
16430        ˇ    println!("hello there");
16431
16432            println!("around the");
16433            println!("world");
16434        }
16435        "#
16436        .unindent(),
16437    );
16438
16439    cx.update_editor(|editor, window, cx| {
16440        editor.fold(&Fold, window, cx);
16441    });
16442
16443    cx.update_editor(|editor, window, cx| {
16444        editor.go_to_next_hunk(&GoToHunk, window, cx);
16445    });
16446
16447    cx.assert_editor_state(
16448        &r#"
16449        ˇuse some::modified;
16450
16451
16452        fn main() {
16453            println!("hello there");
16454
16455            println!("around the");
16456            println!("world");
16457        }
16458        "#
16459        .unindent(),
16460    );
16461}
16462
16463#[test]
16464fn test_split_words() {
16465    fn split(text: &str) -> Vec<&str> {
16466        split_words(text).collect()
16467    }
16468
16469    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16470    assert_eq!(split("hello_world"), &["hello_", "world"]);
16471    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16472    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16473    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16474    assert_eq!(split("helloworld"), &["helloworld"]);
16475
16476    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16477}
16478
16479#[gpui::test]
16480async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16481    init_test(cx, |_| {});
16482
16483    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16484    let mut assert = |before, after| {
16485        let _state_context = cx.set_state(before);
16486        cx.run_until_parked();
16487        cx.update_editor(|editor, window, cx| {
16488            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16489        });
16490        cx.run_until_parked();
16491        cx.assert_editor_state(after);
16492    };
16493
16494    // Outside bracket jumps to outside of matching bracket
16495    assert("console.logˇ(var);", "console.log(var)ˇ;");
16496    assert("console.log(var)ˇ;", "console.logˇ(var);");
16497
16498    // Inside bracket jumps to inside of matching bracket
16499    assert("console.log(ˇvar);", "console.log(varˇ);");
16500    assert("console.log(varˇ);", "console.log(ˇvar);");
16501
16502    // When outside a bracket and inside, favor jumping to the inside bracket
16503    assert(
16504        "console.log('foo', [1, 2, 3]ˇ);",
16505        "console.log(ˇ'foo', [1, 2, 3]);",
16506    );
16507    assert(
16508        "console.log(ˇ'foo', [1, 2, 3]);",
16509        "console.log('foo', [1, 2, 3]ˇ);",
16510    );
16511
16512    // Bias forward if two options are equally likely
16513    assert(
16514        "let result = curried_fun()ˇ();",
16515        "let result = curried_fun()()ˇ;",
16516    );
16517
16518    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16519    assert(
16520        indoc! {"
16521            function test() {
16522                console.log('test')ˇ
16523            }"},
16524        indoc! {"
16525            function test() {
16526                console.logˇ('test')
16527            }"},
16528    );
16529}
16530
16531#[gpui::test]
16532async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16533    init_test(cx, |_| {});
16534
16535    let fs = FakeFs::new(cx.executor());
16536    fs.insert_tree(
16537        path!("/a"),
16538        json!({
16539            "main.rs": "fn main() { let a = 5; }",
16540            "other.rs": "// Test file",
16541        }),
16542    )
16543    .await;
16544    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16545
16546    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16547    language_registry.add(Arc::new(Language::new(
16548        LanguageConfig {
16549            name: "Rust".into(),
16550            matcher: LanguageMatcher {
16551                path_suffixes: vec!["rs".to_string()],
16552                ..Default::default()
16553            },
16554            brackets: BracketPairConfig {
16555                pairs: vec![BracketPair {
16556                    start: "{".to_string(),
16557                    end: "}".to_string(),
16558                    close: true,
16559                    surround: true,
16560                    newline: true,
16561                }],
16562                disabled_scopes_by_bracket_ix: Vec::new(),
16563            },
16564            ..Default::default()
16565        },
16566        Some(tree_sitter_rust::LANGUAGE.into()),
16567    )));
16568    let mut fake_servers = language_registry.register_fake_lsp(
16569        "Rust",
16570        FakeLspAdapter {
16571            capabilities: lsp::ServerCapabilities {
16572                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16573                    first_trigger_character: "{".to_string(),
16574                    more_trigger_character: None,
16575                }),
16576                ..Default::default()
16577            },
16578            ..Default::default()
16579        },
16580    );
16581
16582    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16583
16584    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16585
16586    let worktree_id = workspace
16587        .update(cx, |workspace, _, cx| {
16588            workspace.project().update(cx, |project, cx| {
16589                project.worktrees(cx).next().unwrap().read(cx).id()
16590            })
16591        })
16592        .unwrap();
16593
16594    let buffer = project
16595        .update(cx, |project, cx| {
16596            project.open_local_buffer(path!("/a/main.rs"), cx)
16597        })
16598        .await
16599        .unwrap();
16600    let editor_handle = workspace
16601        .update(cx, |workspace, window, cx| {
16602            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16603        })
16604        .unwrap()
16605        .await
16606        .unwrap()
16607        .downcast::<Editor>()
16608        .unwrap();
16609
16610    cx.executor().start_waiting();
16611    let fake_server = fake_servers.next().await.unwrap();
16612
16613    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16614        |params, _| async move {
16615            assert_eq!(
16616                params.text_document_position.text_document.uri,
16617                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16618            );
16619            assert_eq!(
16620                params.text_document_position.position,
16621                lsp::Position::new(0, 21),
16622            );
16623
16624            Ok(Some(vec![lsp::TextEdit {
16625                new_text: "]".to_string(),
16626                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16627            }]))
16628        },
16629    );
16630
16631    editor_handle.update_in(cx, |editor, window, cx| {
16632        window.focus(&editor.focus_handle(cx));
16633        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16634            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16635        });
16636        editor.handle_input("{", window, cx);
16637    });
16638
16639    cx.executor().run_until_parked();
16640
16641    buffer.update(cx, |buffer, _| {
16642        assert_eq!(
16643            buffer.text(),
16644            "fn main() { let a = {5}; }",
16645            "No extra braces from on type formatting should appear in the buffer"
16646        )
16647    });
16648}
16649
16650#[gpui::test(iterations = 20, seeds(31))]
16651async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
16652    init_test(cx, |_| {});
16653
16654    let mut cx = EditorLspTestContext::new_rust(
16655        lsp::ServerCapabilities {
16656            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16657                first_trigger_character: ".".to_string(),
16658                more_trigger_character: None,
16659            }),
16660            ..Default::default()
16661        },
16662        cx,
16663    )
16664    .await;
16665
16666    cx.update_buffer(|buffer, _| {
16667        // This causes autoindent to be async.
16668        buffer.set_sync_parse_timeout(Duration::ZERO)
16669    });
16670
16671    cx.set_state("fn c() {\n    d()ˇ\n}\n");
16672    cx.simulate_keystroke("\n");
16673    cx.run_until_parked();
16674
16675    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
16676    let mut request =
16677        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
16678            let buffer_cloned = buffer_cloned.clone();
16679            async move {
16680                buffer_cloned.update(&mut cx, |buffer, _| {
16681                    assert_eq!(
16682                        buffer.text(),
16683                        "fn c() {\n    d()\n        .\n}\n",
16684                        "OnTypeFormatting should triggered after autoindent applied"
16685                    )
16686                })?;
16687
16688                Ok(Some(vec![]))
16689            }
16690        });
16691
16692    cx.simulate_keystroke(".");
16693    cx.run_until_parked();
16694
16695    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
16696    assert!(request.next().await.is_some());
16697    request.close();
16698    assert!(request.next().await.is_none());
16699}
16700
16701#[gpui::test]
16702async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
16703    init_test(cx, |_| {});
16704
16705    let fs = FakeFs::new(cx.executor());
16706    fs.insert_tree(
16707        path!("/a"),
16708        json!({
16709            "main.rs": "fn main() { let a = 5; }",
16710            "other.rs": "// Test file",
16711        }),
16712    )
16713    .await;
16714
16715    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16716
16717    let server_restarts = Arc::new(AtomicUsize::new(0));
16718    let closure_restarts = Arc::clone(&server_restarts);
16719    let language_server_name = "test language server";
16720    let language_name: LanguageName = "Rust".into();
16721
16722    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16723    language_registry.add(Arc::new(Language::new(
16724        LanguageConfig {
16725            name: language_name.clone(),
16726            matcher: LanguageMatcher {
16727                path_suffixes: vec!["rs".to_string()],
16728                ..Default::default()
16729            },
16730            ..Default::default()
16731        },
16732        Some(tree_sitter_rust::LANGUAGE.into()),
16733    )));
16734    let mut fake_servers = language_registry.register_fake_lsp(
16735        "Rust",
16736        FakeLspAdapter {
16737            name: language_server_name,
16738            initialization_options: Some(json!({
16739                "testOptionValue": true
16740            })),
16741            initializer: Some(Box::new(move |fake_server| {
16742                let task_restarts = Arc::clone(&closure_restarts);
16743                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
16744                    task_restarts.fetch_add(1, atomic::Ordering::Release);
16745                    futures::future::ready(Ok(()))
16746                });
16747            })),
16748            ..Default::default()
16749        },
16750    );
16751
16752    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16753    let _buffer = project
16754        .update(cx, |project, cx| {
16755            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
16756        })
16757        .await
16758        .unwrap();
16759    let _fake_server = fake_servers.next().await.unwrap();
16760    update_test_language_settings(cx, |language_settings| {
16761        language_settings.languages.0.insert(
16762            language_name.clone(),
16763            LanguageSettingsContent {
16764                tab_size: NonZeroU32::new(8),
16765                ..Default::default()
16766            },
16767        );
16768    });
16769    cx.executor().run_until_parked();
16770    assert_eq!(
16771        server_restarts.load(atomic::Ordering::Acquire),
16772        0,
16773        "Should not restart LSP server on an unrelated change"
16774    );
16775
16776    update_test_project_settings(cx, |project_settings| {
16777        project_settings.lsp.insert(
16778            "Some other server name".into(),
16779            LspSettings {
16780                binary: None,
16781                settings: None,
16782                initialization_options: Some(json!({
16783                    "some other init value": false
16784                })),
16785                enable_lsp_tasks: false,
16786            },
16787        );
16788    });
16789    cx.executor().run_until_parked();
16790    assert_eq!(
16791        server_restarts.load(atomic::Ordering::Acquire),
16792        0,
16793        "Should not restart LSP server on an unrelated LSP settings change"
16794    );
16795
16796    update_test_project_settings(cx, |project_settings| {
16797        project_settings.lsp.insert(
16798            language_server_name.into(),
16799            LspSettings {
16800                binary: None,
16801                settings: None,
16802                initialization_options: Some(json!({
16803                    "anotherInitValue": false
16804                })),
16805                enable_lsp_tasks: false,
16806            },
16807        );
16808    });
16809    cx.executor().run_until_parked();
16810    assert_eq!(
16811        server_restarts.load(atomic::Ordering::Acquire),
16812        1,
16813        "Should restart LSP server on a related LSP settings change"
16814    );
16815
16816    update_test_project_settings(cx, |project_settings| {
16817        project_settings.lsp.insert(
16818            language_server_name.into(),
16819            LspSettings {
16820                binary: None,
16821                settings: None,
16822                initialization_options: Some(json!({
16823                    "anotherInitValue": false
16824                })),
16825                enable_lsp_tasks: false,
16826            },
16827        );
16828    });
16829    cx.executor().run_until_parked();
16830    assert_eq!(
16831        server_restarts.load(atomic::Ordering::Acquire),
16832        1,
16833        "Should not restart LSP server on a related LSP settings change that is the same"
16834    );
16835
16836    update_test_project_settings(cx, |project_settings| {
16837        project_settings.lsp.insert(
16838            language_server_name.into(),
16839            LspSettings {
16840                binary: None,
16841                settings: None,
16842                initialization_options: None,
16843                enable_lsp_tasks: false,
16844            },
16845        );
16846    });
16847    cx.executor().run_until_parked();
16848    assert_eq!(
16849        server_restarts.load(atomic::Ordering::Acquire),
16850        2,
16851        "Should restart LSP server on another related LSP settings change"
16852    );
16853}
16854
16855#[gpui::test]
16856async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
16857    init_test(cx, |_| {});
16858
16859    let mut cx = EditorLspTestContext::new_rust(
16860        lsp::ServerCapabilities {
16861            completion_provider: Some(lsp::CompletionOptions {
16862                trigger_characters: Some(vec![".".to_string()]),
16863                resolve_provider: Some(true),
16864                ..Default::default()
16865            }),
16866            ..Default::default()
16867        },
16868        cx,
16869    )
16870    .await;
16871
16872    cx.set_state("fn main() { let a = 2ˇ; }");
16873    cx.simulate_keystroke(".");
16874    let completion_item = lsp::CompletionItem {
16875        label: "some".into(),
16876        kind: Some(lsp::CompletionItemKind::SNIPPET),
16877        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16878        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16879            kind: lsp::MarkupKind::Markdown,
16880            value: "```rust\nSome(2)\n```".to_string(),
16881        })),
16882        deprecated: Some(false),
16883        sort_text: Some("fffffff2".to_string()),
16884        filter_text: Some("some".to_string()),
16885        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16886        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16887            range: lsp::Range {
16888                start: lsp::Position {
16889                    line: 0,
16890                    character: 22,
16891                },
16892                end: lsp::Position {
16893                    line: 0,
16894                    character: 22,
16895                },
16896            },
16897            new_text: "Some(2)".to_string(),
16898        })),
16899        additional_text_edits: Some(vec![lsp::TextEdit {
16900            range: lsp::Range {
16901                start: lsp::Position {
16902                    line: 0,
16903                    character: 20,
16904                },
16905                end: lsp::Position {
16906                    line: 0,
16907                    character: 22,
16908                },
16909            },
16910            new_text: "".to_string(),
16911        }]),
16912        ..Default::default()
16913    };
16914
16915    let closure_completion_item = completion_item.clone();
16916    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16917        let task_completion_item = closure_completion_item.clone();
16918        async move {
16919            Ok(Some(lsp::CompletionResponse::Array(vec![
16920                task_completion_item,
16921            ])))
16922        }
16923    });
16924
16925    request.next().await;
16926
16927    cx.condition(|editor, _| editor.context_menu_visible())
16928        .await;
16929    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16930        editor
16931            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16932            .unwrap()
16933    });
16934    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
16935
16936    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16937        let task_completion_item = completion_item.clone();
16938        async move { Ok(task_completion_item) }
16939    })
16940    .next()
16941    .await
16942    .unwrap();
16943    apply_additional_edits.await.unwrap();
16944    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
16945}
16946
16947#[gpui::test]
16948async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
16949    init_test(cx, |_| {});
16950
16951    let mut cx = EditorLspTestContext::new_rust(
16952        lsp::ServerCapabilities {
16953            completion_provider: Some(lsp::CompletionOptions {
16954                trigger_characters: Some(vec![".".to_string()]),
16955                resolve_provider: Some(true),
16956                ..Default::default()
16957            }),
16958            ..Default::default()
16959        },
16960        cx,
16961    )
16962    .await;
16963
16964    cx.set_state("fn main() { let a = 2ˇ; }");
16965    cx.simulate_keystroke(".");
16966
16967    let item1 = lsp::CompletionItem {
16968        label: "method id()".to_string(),
16969        filter_text: Some("id".to_string()),
16970        detail: None,
16971        documentation: None,
16972        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16973            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16974            new_text: ".id".to_string(),
16975        })),
16976        ..lsp::CompletionItem::default()
16977    };
16978
16979    let item2 = lsp::CompletionItem {
16980        label: "other".to_string(),
16981        filter_text: Some("other".to_string()),
16982        detail: None,
16983        documentation: None,
16984        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16985            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16986            new_text: ".other".to_string(),
16987        })),
16988        ..lsp::CompletionItem::default()
16989    };
16990
16991    let item1 = item1.clone();
16992    cx.set_request_handler::<lsp::request::Completion, _, _>({
16993        let item1 = item1.clone();
16994        move |_, _, _| {
16995            let item1 = item1.clone();
16996            let item2 = item2.clone();
16997            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
16998        }
16999    })
17000    .next()
17001    .await;
17002
17003    cx.condition(|editor, _| editor.context_menu_visible())
17004        .await;
17005    cx.update_editor(|editor, _, _| {
17006        let context_menu = editor.context_menu.borrow_mut();
17007        let context_menu = context_menu
17008            .as_ref()
17009            .expect("Should have the context menu deployed");
17010        match context_menu {
17011            CodeContextMenu::Completions(completions_menu) => {
17012                let completions = completions_menu.completions.borrow_mut();
17013                assert_eq!(
17014                    completions
17015                        .iter()
17016                        .map(|completion| &completion.label.text)
17017                        .collect::<Vec<_>>(),
17018                    vec!["method id()", "other"]
17019                )
17020            }
17021            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17022        }
17023    });
17024
17025    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17026        let item1 = item1.clone();
17027        move |_, item_to_resolve, _| {
17028            let item1 = item1.clone();
17029            async move {
17030                if item1 == item_to_resolve {
17031                    Ok(lsp::CompletionItem {
17032                        label: "method id()".to_string(),
17033                        filter_text: Some("id".to_string()),
17034                        detail: Some("Now resolved!".to_string()),
17035                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17036                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17037                            range: lsp::Range::new(
17038                                lsp::Position::new(0, 22),
17039                                lsp::Position::new(0, 22),
17040                            ),
17041                            new_text: ".id".to_string(),
17042                        })),
17043                        ..lsp::CompletionItem::default()
17044                    })
17045                } else {
17046                    Ok(item_to_resolve)
17047                }
17048            }
17049        }
17050    })
17051    .next()
17052    .await
17053    .unwrap();
17054    cx.run_until_parked();
17055
17056    cx.update_editor(|editor, window, cx| {
17057        editor.context_menu_next(&Default::default(), window, cx);
17058    });
17059
17060    cx.update_editor(|editor, _, _| {
17061        let context_menu = editor.context_menu.borrow_mut();
17062        let context_menu = context_menu
17063            .as_ref()
17064            .expect("Should have the context menu deployed");
17065        match context_menu {
17066            CodeContextMenu::Completions(completions_menu) => {
17067                let completions = completions_menu.completions.borrow_mut();
17068                assert_eq!(
17069                    completions
17070                        .iter()
17071                        .map(|completion| &completion.label.text)
17072                        .collect::<Vec<_>>(),
17073                    vec!["method id() Now resolved!", "other"],
17074                    "Should update first completion label, but not second as the filter text did not match."
17075                );
17076            }
17077            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17078        }
17079    });
17080}
17081
17082#[gpui::test]
17083async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17084    init_test(cx, |_| {});
17085    let mut cx = EditorLspTestContext::new_rust(
17086        lsp::ServerCapabilities {
17087            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17088            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17089            completion_provider: Some(lsp::CompletionOptions {
17090                resolve_provider: Some(true),
17091                ..Default::default()
17092            }),
17093            ..Default::default()
17094        },
17095        cx,
17096    )
17097    .await;
17098    cx.set_state(indoc! {"
17099        struct TestStruct {
17100            field: i32
17101        }
17102
17103        fn mainˇ() {
17104            let unused_var = 42;
17105            let test_struct = TestStruct { field: 42 };
17106        }
17107    "});
17108    let symbol_range = cx.lsp_range(indoc! {"
17109        struct TestStruct {
17110            field: i32
17111        }
17112
17113        «fn main»() {
17114            let unused_var = 42;
17115            let test_struct = TestStruct { field: 42 };
17116        }
17117    "});
17118    let mut hover_requests =
17119        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17120            Ok(Some(lsp::Hover {
17121                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17122                    kind: lsp::MarkupKind::Markdown,
17123                    value: "Function documentation".to_string(),
17124                }),
17125                range: Some(symbol_range),
17126            }))
17127        });
17128
17129    // Case 1: Test that code action menu hide hover popover
17130    cx.dispatch_action(Hover);
17131    hover_requests.next().await;
17132    cx.condition(|editor, _| editor.hover_state.visible()).await;
17133    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17134        move |_, _, _| async move {
17135            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17136                lsp::CodeAction {
17137                    title: "Remove unused variable".to_string(),
17138                    kind: Some(CodeActionKind::QUICKFIX),
17139                    edit: Some(lsp::WorkspaceEdit {
17140                        changes: Some(
17141                            [(
17142                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17143                                vec![lsp::TextEdit {
17144                                    range: lsp::Range::new(
17145                                        lsp::Position::new(5, 4),
17146                                        lsp::Position::new(5, 27),
17147                                    ),
17148                                    new_text: "".to_string(),
17149                                }],
17150                            )]
17151                            .into_iter()
17152                            .collect(),
17153                        ),
17154                        ..Default::default()
17155                    }),
17156                    ..Default::default()
17157                },
17158            )]))
17159        },
17160    );
17161    cx.update_editor(|editor, window, cx| {
17162        editor.toggle_code_actions(
17163            &ToggleCodeActions {
17164                deployed_from: None,
17165                quick_launch: false,
17166            },
17167            window,
17168            cx,
17169        );
17170    });
17171    code_action_requests.next().await;
17172    cx.run_until_parked();
17173    cx.condition(|editor, _| editor.context_menu_visible())
17174        .await;
17175    cx.update_editor(|editor, _, _| {
17176        assert!(
17177            !editor.hover_state.visible(),
17178            "Hover popover should be hidden when code action menu is shown"
17179        );
17180        // Hide code actions
17181        editor.context_menu.take();
17182    });
17183
17184    // Case 2: Test that code completions hide hover popover
17185    cx.dispatch_action(Hover);
17186    hover_requests.next().await;
17187    cx.condition(|editor, _| editor.hover_state.visible()).await;
17188    let counter = Arc::new(AtomicUsize::new(0));
17189    let mut completion_requests =
17190        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17191            let counter = counter.clone();
17192            async move {
17193                counter.fetch_add(1, atomic::Ordering::Release);
17194                Ok(Some(lsp::CompletionResponse::Array(vec![
17195                    lsp::CompletionItem {
17196                        label: "main".into(),
17197                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17198                        detail: Some("() -> ()".to_string()),
17199                        ..Default::default()
17200                    },
17201                    lsp::CompletionItem {
17202                        label: "TestStruct".into(),
17203                        kind: Some(lsp::CompletionItemKind::STRUCT),
17204                        detail: Some("struct TestStruct".to_string()),
17205                        ..Default::default()
17206                    },
17207                ])))
17208            }
17209        });
17210    cx.update_editor(|editor, window, cx| {
17211        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17212    });
17213    completion_requests.next().await;
17214    cx.condition(|editor, _| editor.context_menu_visible())
17215        .await;
17216    cx.update_editor(|editor, _, _| {
17217        assert!(
17218            !editor.hover_state.visible(),
17219            "Hover popover should be hidden when completion menu is shown"
17220        );
17221    });
17222}
17223
17224#[gpui::test]
17225async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17226    init_test(cx, |_| {});
17227
17228    let mut cx = EditorLspTestContext::new_rust(
17229        lsp::ServerCapabilities {
17230            completion_provider: Some(lsp::CompletionOptions {
17231                trigger_characters: Some(vec![".".to_string()]),
17232                resolve_provider: Some(true),
17233                ..Default::default()
17234            }),
17235            ..Default::default()
17236        },
17237        cx,
17238    )
17239    .await;
17240
17241    cx.set_state("fn main() { let a = 2ˇ; }");
17242    cx.simulate_keystroke(".");
17243
17244    let unresolved_item_1 = lsp::CompletionItem {
17245        label: "id".to_string(),
17246        filter_text: Some("id".to_string()),
17247        detail: None,
17248        documentation: None,
17249        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17250            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17251            new_text: ".id".to_string(),
17252        })),
17253        ..lsp::CompletionItem::default()
17254    };
17255    let resolved_item_1 = lsp::CompletionItem {
17256        additional_text_edits: Some(vec![lsp::TextEdit {
17257            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17258            new_text: "!!".to_string(),
17259        }]),
17260        ..unresolved_item_1.clone()
17261    };
17262    let unresolved_item_2 = lsp::CompletionItem {
17263        label: "other".to_string(),
17264        filter_text: Some("other".to_string()),
17265        detail: None,
17266        documentation: None,
17267        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17268            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17269            new_text: ".other".to_string(),
17270        })),
17271        ..lsp::CompletionItem::default()
17272    };
17273    let resolved_item_2 = lsp::CompletionItem {
17274        additional_text_edits: Some(vec![lsp::TextEdit {
17275            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17276            new_text: "??".to_string(),
17277        }]),
17278        ..unresolved_item_2.clone()
17279    };
17280
17281    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17282    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17283    cx.lsp
17284        .server
17285        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17286            let unresolved_item_1 = unresolved_item_1.clone();
17287            let resolved_item_1 = resolved_item_1.clone();
17288            let unresolved_item_2 = unresolved_item_2.clone();
17289            let resolved_item_2 = resolved_item_2.clone();
17290            let resolve_requests_1 = resolve_requests_1.clone();
17291            let resolve_requests_2 = resolve_requests_2.clone();
17292            move |unresolved_request, _| {
17293                let unresolved_item_1 = unresolved_item_1.clone();
17294                let resolved_item_1 = resolved_item_1.clone();
17295                let unresolved_item_2 = unresolved_item_2.clone();
17296                let resolved_item_2 = resolved_item_2.clone();
17297                let resolve_requests_1 = resolve_requests_1.clone();
17298                let resolve_requests_2 = resolve_requests_2.clone();
17299                async move {
17300                    if unresolved_request == unresolved_item_1 {
17301                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17302                        Ok(resolved_item_1.clone())
17303                    } else if unresolved_request == unresolved_item_2 {
17304                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17305                        Ok(resolved_item_2.clone())
17306                    } else {
17307                        panic!("Unexpected completion item {unresolved_request:?}")
17308                    }
17309                }
17310            }
17311        })
17312        .detach();
17313
17314    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17315        let unresolved_item_1 = unresolved_item_1.clone();
17316        let unresolved_item_2 = unresolved_item_2.clone();
17317        async move {
17318            Ok(Some(lsp::CompletionResponse::Array(vec![
17319                unresolved_item_1,
17320                unresolved_item_2,
17321            ])))
17322        }
17323    })
17324    .next()
17325    .await;
17326
17327    cx.condition(|editor, _| editor.context_menu_visible())
17328        .await;
17329    cx.update_editor(|editor, _, _| {
17330        let context_menu = editor.context_menu.borrow_mut();
17331        let context_menu = context_menu
17332            .as_ref()
17333            .expect("Should have the context menu deployed");
17334        match context_menu {
17335            CodeContextMenu::Completions(completions_menu) => {
17336                let completions = completions_menu.completions.borrow_mut();
17337                assert_eq!(
17338                    completions
17339                        .iter()
17340                        .map(|completion| &completion.label.text)
17341                        .collect::<Vec<_>>(),
17342                    vec!["id", "other"]
17343                )
17344            }
17345            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17346        }
17347    });
17348    cx.run_until_parked();
17349
17350    cx.update_editor(|editor, window, cx| {
17351        editor.context_menu_next(&ContextMenuNext, window, cx);
17352    });
17353    cx.run_until_parked();
17354    cx.update_editor(|editor, window, cx| {
17355        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17356    });
17357    cx.run_until_parked();
17358    cx.update_editor(|editor, window, cx| {
17359        editor.context_menu_next(&ContextMenuNext, window, cx);
17360    });
17361    cx.run_until_parked();
17362    cx.update_editor(|editor, window, cx| {
17363        editor
17364            .compose_completion(&ComposeCompletion::default(), window, cx)
17365            .expect("No task returned")
17366    })
17367    .await
17368    .expect("Completion failed");
17369    cx.run_until_parked();
17370
17371    cx.update_editor(|editor, _, cx| {
17372        assert_eq!(
17373            resolve_requests_1.load(atomic::Ordering::Acquire),
17374            1,
17375            "Should always resolve once despite multiple selections"
17376        );
17377        assert_eq!(
17378            resolve_requests_2.load(atomic::Ordering::Acquire),
17379            1,
17380            "Should always resolve once after multiple selections and applying the completion"
17381        );
17382        assert_eq!(
17383            editor.text(cx),
17384            "fn main() { let a = ??.other; }",
17385            "Should use resolved data when applying the completion"
17386        );
17387    });
17388}
17389
17390#[gpui::test]
17391async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17392    init_test(cx, |_| {});
17393
17394    let item_0 = lsp::CompletionItem {
17395        label: "abs".into(),
17396        insert_text: Some("abs".into()),
17397        data: Some(json!({ "very": "special"})),
17398        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17399        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17400            lsp::InsertReplaceEdit {
17401                new_text: "abs".to_string(),
17402                insert: lsp::Range::default(),
17403                replace: lsp::Range::default(),
17404            },
17405        )),
17406        ..lsp::CompletionItem::default()
17407    };
17408    let items = iter::once(item_0.clone())
17409        .chain((11..51).map(|i| lsp::CompletionItem {
17410            label: format!("item_{}", i),
17411            insert_text: Some(format!("item_{}", i)),
17412            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17413            ..lsp::CompletionItem::default()
17414        }))
17415        .collect::<Vec<_>>();
17416
17417    let default_commit_characters = vec!["?".to_string()];
17418    let default_data = json!({ "default": "data"});
17419    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17420    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17421    let default_edit_range = lsp::Range {
17422        start: lsp::Position {
17423            line: 0,
17424            character: 5,
17425        },
17426        end: lsp::Position {
17427            line: 0,
17428            character: 5,
17429        },
17430    };
17431
17432    let mut cx = EditorLspTestContext::new_rust(
17433        lsp::ServerCapabilities {
17434            completion_provider: Some(lsp::CompletionOptions {
17435                trigger_characters: Some(vec![".".to_string()]),
17436                resolve_provider: Some(true),
17437                ..Default::default()
17438            }),
17439            ..Default::default()
17440        },
17441        cx,
17442    )
17443    .await;
17444
17445    cx.set_state("fn main() { let a = 2ˇ; }");
17446    cx.simulate_keystroke(".");
17447
17448    let completion_data = default_data.clone();
17449    let completion_characters = default_commit_characters.clone();
17450    let completion_items = items.clone();
17451    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17452        let default_data = completion_data.clone();
17453        let default_commit_characters = completion_characters.clone();
17454        let items = completion_items.clone();
17455        async move {
17456            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17457                items,
17458                item_defaults: Some(lsp::CompletionListItemDefaults {
17459                    data: Some(default_data.clone()),
17460                    commit_characters: Some(default_commit_characters.clone()),
17461                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17462                        default_edit_range,
17463                    )),
17464                    insert_text_format: Some(default_insert_text_format),
17465                    insert_text_mode: Some(default_insert_text_mode),
17466                }),
17467                ..lsp::CompletionList::default()
17468            })))
17469        }
17470    })
17471    .next()
17472    .await;
17473
17474    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17475    cx.lsp
17476        .server
17477        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17478            let closure_resolved_items = resolved_items.clone();
17479            move |item_to_resolve, _| {
17480                let closure_resolved_items = closure_resolved_items.clone();
17481                async move {
17482                    closure_resolved_items.lock().push(item_to_resolve.clone());
17483                    Ok(item_to_resolve)
17484                }
17485            }
17486        })
17487        .detach();
17488
17489    cx.condition(|editor, _| editor.context_menu_visible())
17490        .await;
17491    cx.run_until_parked();
17492    cx.update_editor(|editor, _, _| {
17493        let menu = editor.context_menu.borrow_mut();
17494        match menu.as_ref().expect("should have the completions menu") {
17495            CodeContextMenu::Completions(completions_menu) => {
17496                assert_eq!(
17497                    completions_menu
17498                        .entries
17499                        .borrow()
17500                        .iter()
17501                        .map(|mat| mat.string.clone())
17502                        .collect::<Vec<String>>(),
17503                    items
17504                        .iter()
17505                        .map(|completion| completion.label.clone())
17506                        .collect::<Vec<String>>()
17507                );
17508            }
17509            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17510        }
17511    });
17512    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17513    // with 4 from the end.
17514    assert_eq!(
17515        *resolved_items.lock(),
17516        [&items[0..16], &items[items.len() - 4..items.len()]]
17517            .concat()
17518            .iter()
17519            .cloned()
17520            .map(|mut item| {
17521                if item.data.is_none() {
17522                    item.data = Some(default_data.clone());
17523                }
17524                item
17525            })
17526            .collect::<Vec<lsp::CompletionItem>>(),
17527        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17528    );
17529    resolved_items.lock().clear();
17530
17531    cx.update_editor(|editor, window, cx| {
17532        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17533    });
17534    cx.run_until_parked();
17535    // Completions that have already been resolved are skipped.
17536    assert_eq!(
17537        *resolved_items.lock(),
17538        items[items.len() - 17..items.len() - 4]
17539            .iter()
17540            .cloned()
17541            .map(|mut item| {
17542                if item.data.is_none() {
17543                    item.data = Some(default_data.clone());
17544                }
17545                item
17546            })
17547            .collect::<Vec<lsp::CompletionItem>>()
17548    );
17549    resolved_items.lock().clear();
17550}
17551
17552#[gpui::test]
17553async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17554    init_test(cx, |_| {});
17555
17556    let mut cx = EditorLspTestContext::new(
17557        Language::new(
17558            LanguageConfig {
17559                matcher: LanguageMatcher {
17560                    path_suffixes: vec!["jsx".into()],
17561                    ..Default::default()
17562                },
17563                overrides: [(
17564                    "element".into(),
17565                    LanguageConfigOverride {
17566                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17567                        ..Default::default()
17568                    },
17569                )]
17570                .into_iter()
17571                .collect(),
17572                ..Default::default()
17573            },
17574            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17575        )
17576        .with_override_query("(jsx_self_closing_element) @element")
17577        .unwrap(),
17578        lsp::ServerCapabilities {
17579            completion_provider: Some(lsp::CompletionOptions {
17580                trigger_characters: Some(vec![":".to_string()]),
17581                ..Default::default()
17582            }),
17583            ..Default::default()
17584        },
17585        cx,
17586    )
17587    .await;
17588
17589    cx.lsp
17590        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17591            Ok(Some(lsp::CompletionResponse::Array(vec![
17592                lsp::CompletionItem {
17593                    label: "bg-blue".into(),
17594                    ..Default::default()
17595                },
17596                lsp::CompletionItem {
17597                    label: "bg-red".into(),
17598                    ..Default::default()
17599                },
17600                lsp::CompletionItem {
17601                    label: "bg-yellow".into(),
17602                    ..Default::default()
17603                },
17604            ])))
17605        });
17606
17607    cx.set_state(r#"<p class="bgˇ" />"#);
17608
17609    // Trigger completion when typing a dash, because the dash is an extra
17610    // word character in the 'element' scope, which contains the cursor.
17611    cx.simulate_keystroke("-");
17612    cx.executor().run_until_parked();
17613    cx.update_editor(|editor, _, _| {
17614        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17615        {
17616            assert_eq!(
17617                completion_menu_entries(menu),
17618                &["bg-blue", "bg-red", "bg-yellow"]
17619            );
17620        } else {
17621            panic!("expected completion menu to be open");
17622        }
17623    });
17624
17625    cx.simulate_keystroke("l");
17626    cx.executor().run_until_parked();
17627    cx.update_editor(|editor, _, _| {
17628        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17629        {
17630            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17631        } else {
17632            panic!("expected completion menu to be open");
17633        }
17634    });
17635
17636    // When filtering completions, consider the character after the '-' to
17637    // be the start of a subword.
17638    cx.set_state(r#"<p class="yelˇ" />"#);
17639    cx.simulate_keystroke("l");
17640    cx.executor().run_until_parked();
17641    cx.update_editor(|editor, _, _| {
17642        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17643        {
17644            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
17645        } else {
17646            panic!("expected completion menu to be open");
17647        }
17648    });
17649}
17650
17651fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
17652    let entries = menu.entries.borrow();
17653    entries.iter().map(|mat| mat.string.clone()).collect()
17654}
17655
17656#[gpui::test]
17657async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
17658    init_test(cx, |settings| {
17659        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
17660            Formatter::Prettier,
17661        )))
17662    });
17663
17664    let fs = FakeFs::new(cx.executor());
17665    fs.insert_file(path!("/file.ts"), Default::default()).await;
17666
17667    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
17668    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17669
17670    language_registry.add(Arc::new(Language::new(
17671        LanguageConfig {
17672            name: "TypeScript".into(),
17673            matcher: LanguageMatcher {
17674                path_suffixes: vec!["ts".to_string()],
17675                ..Default::default()
17676            },
17677            ..Default::default()
17678        },
17679        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17680    )));
17681    update_test_language_settings(cx, |settings| {
17682        settings.defaults.prettier = Some(PrettierSettings {
17683            allowed: true,
17684            ..PrettierSettings::default()
17685        });
17686    });
17687
17688    let test_plugin = "test_plugin";
17689    let _ = language_registry.register_fake_lsp(
17690        "TypeScript",
17691        FakeLspAdapter {
17692            prettier_plugins: vec![test_plugin],
17693            ..Default::default()
17694        },
17695    );
17696
17697    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
17698    let buffer = project
17699        .update(cx, |project, cx| {
17700            project.open_local_buffer(path!("/file.ts"), cx)
17701        })
17702        .await
17703        .unwrap();
17704
17705    let buffer_text = "one\ntwo\nthree\n";
17706    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17707    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17708    editor.update_in(cx, |editor, window, cx| {
17709        editor.set_text(buffer_text, window, cx)
17710    });
17711
17712    editor
17713        .update_in(cx, |editor, window, cx| {
17714            editor.perform_format(
17715                project.clone(),
17716                FormatTrigger::Manual,
17717                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
17718                window,
17719                cx,
17720            )
17721        })
17722        .unwrap()
17723        .await;
17724    assert_eq!(
17725        editor.update(cx, |editor, cx| editor.text(cx)),
17726        buffer_text.to_string() + prettier_format_suffix,
17727        "Test prettier formatting was not applied to the original buffer text",
17728    );
17729
17730    update_test_language_settings(cx, |settings| {
17731        settings.defaults.formatter = Some(SelectedFormatter::Auto)
17732    });
17733    let format = editor.update_in(cx, |editor, window, cx| {
17734        editor.perform_format(
17735            project.clone(),
17736            FormatTrigger::Manual,
17737            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
17738            window,
17739            cx,
17740        )
17741    });
17742    format.await.unwrap();
17743    assert_eq!(
17744        editor.update(cx, |editor, cx| editor.text(cx)),
17745        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
17746        "Autoformatting (via test prettier) was not applied to the original buffer text",
17747    );
17748}
17749
17750#[gpui::test]
17751async fn test_addition_reverts(cx: &mut TestAppContext) {
17752    init_test(cx, |_| {});
17753    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17754    let base_text = indoc! {r#"
17755        struct Row;
17756        struct Row1;
17757        struct Row2;
17758
17759        struct Row4;
17760        struct Row5;
17761        struct Row6;
17762
17763        struct Row8;
17764        struct Row9;
17765        struct Row10;"#};
17766
17767    // When addition hunks are not adjacent to carets, no hunk revert is performed
17768    assert_hunk_revert(
17769        indoc! {r#"struct Row;
17770                   struct Row1;
17771                   struct Row1.1;
17772                   struct Row1.2;
17773                   struct Row2;ˇ
17774
17775                   struct Row4;
17776                   struct Row5;
17777                   struct Row6;
17778
17779                   struct Row8;
17780                   ˇstruct Row9;
17781                   struct Row9.1;
17782                   struct Row9.2;
17783                   struct Row9.3;
17784                   struct Row10;"#},
17785        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
17786        indoc! {r#"struct Row;
17787                   struct Row1;
17788                   struct Row1.1;
17789                   struct Row1.2;
17790                   struct Row2;ˇ
17791
17792                   struct Row4;
17793                   struct Row5;
17794                   struct Row6;
17795
17796                   struct Row8;
17797                   ˇstruct Row9;
17798                   struct Row9.1;
17799                   struct Row9.2;
17800                   struct Row9.3;
17801                   struct Row10;"#},
17802        base_text,
17803        &mut cx,
17804    );
17805    // Same for selections
17806    assert_hunk_revert(
17807        indoc! {r#"struct Row;
17808                   struct Row1;
17809                   struct Row2;
17810                   struct Row2.1;
17811                   struct Row2.2;
17812                   «ˇ
17813                   struct Row4;
17814                   struct» Row5;
17815                   «struct Row6;
17816                   ˇ»
17817                   struct Row9.1;
17818                   struct Row9.2;
17819                   struct Row9.3;
17820                   struct Row8;
17821                   struct Row9;
17822                   struct Row10;"#},
17823        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
17824        indoc! {r#"struct Row;
17825                   struct Row1;
17826                   struct Row2;
17827                   struct Row2.1;
17828                   struct Row2.2;
17829                   «ˇ
17830                   struct Row4;
17831                   struct» Row5;
17832                   «struct Row6;
17833                   ˇ»
17834                   struct Row9.1;
17835                   struct Row9.2;
17836                   struct Row9.3;
17837                   struct Row8;
17838                   struct Row9;
17839                   struct Row10;"#},
17840        base_text,
17841        &mut cx,
17842    );
17843
17844    // When carets and selections intersect the addition hunks, those are reverted.
17845    // Adjacent carets got merged.
17846    assert_hunk_revert(
17847        indoc! {r#"struct Row;
17848                   ˇ// something on the top
17849                   struct Row1;
17850                   struct Row2;
17851                   struct Roˇw3.1;
17852                   struct Row2.2;
17853                   struct Row2.3;ˇ
17854
17855                   struct Row4;
17856                   struct ˇRow5.1;
17857                   struct Row5.2;
17858                   struct «Rowˇ»5.3;
17859                   struct Row5;
17860                   struct Row6;
17861                   ˇ
17862                   struct Row9.1;
17863                   struct «Rowˇ»9.2;
17864                   struct «ˇRow»9.3;
17865                   struct Row8;
17866                   struct Row9;
17867                   «ˇ// something on bottom»
17868                   struct Row10;"#},
17869        vec![
17870            DiffHunkStatusKind::Added,
17871            DiffHunkStatusKind::Added,
17872            DiffHunkStatusKind::Added,
17873            DiffHunkStatusKind::Added,
17874            DiffHunkStatusKind::Added,
17875        ],
17876        indoc! {r#"struct Row;
17877                   ˇstruct Row1;
17878                   struct Row2;
17879                   ˇ
17880                   struct Row4;
17881                   ˇstruct Row5;
17882                   struct Row6;
17883                   ˇ
17884                   ˇstruct Row8;
17885                   struct Row9;
17886                   ˇstruct Row10;"#},
17887        base_text,
17888        &mut cx,
17889    );
17890}
17891
17892#[gpui::test]
17893async fn test_modification_reverts(cx: &mut TestAppContext) {
17894    init_test(cx, |_| {});
17895    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17896    let base_text = indoc! {r#"
17897        struct Row;
17898        struct Row1;
17899        struct Row2;
17900
17901        struct Row4;
17902        struct Row5;
17903        struct Row6;
17904
17905        struct Row8;
17906        struct Row9;
17907        struct Row10;"#};
17908
17909    // Modification hunks behave the same as the addition ones.
17910    assert_hunk_revert(
17911        indoc! {r#"struct Row;
17912                   struct Row1;
17913                   struct Row33;
17914                   ˇ
17915                   struct Row4;
17916                   struct Row5;
17917                   struct Row6;
17918                   ˇ
17919                   struct Row99;
17920                   struct Row9;
17921                   struct Row10;"#},
17922        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17923        indoc! {r#"struct Row;
17924                   struct Row1;
17925                   struct Row33;
17926                   ˇ
17927                   struct Row4;
17928                   struct Row5;
17929                   struct Row6;
17930                   ˇ
17931                   struct Row99;
17932                   struct Row9;
17933                   struct Row10;"#},
17934        base_text,
17935        &mut cx,
17936    );
17937    assert_hunk_revert(
17938        indoc! {r#"struct Row;
17939                   struct Row1;
17940                   struct Row33;
17941                   «ˇ
17942                   struct Row4;
17943                   struct» Row5;
17944                   «struct Row6;
17945                   ˇ»
17946                   struct Row99;
17947                   struct Row9;
17948                   struct Row10;"#},
17949        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17950        indoc! {r#"struct Row;
17951                   struct Row1;
17952                   struct Row33;
17953                   «ˇ
17954                   struct Row4;
17955                   struct» Row5;
17956                   «struct Row6;
17957                   ˇ»
17958                   struct Row99;
17959                   struct Row9;
17960                   struct Row10;"#},
17961        base_text,
17962        &mut cx,
17963    );
17964
17965    assert_hunk_revert(
17966        indoc! {r#"ˇstruct Row1.1;
17967                   struct Row1;
17968                   «ˇstr»uct Row22;
17969
17970                   struct ˇRow44;
17971                   struct Row5;
17972                   struct «Rˇ»ow66;ˇ
17973
17974                   «struˇ»ct Row88;
17975                   struct Row9;
17976                   struct Row1011;ˇ"#},
17977        vec![
17978            DiffHunkStatusKind::Modified,
17979            DiffHunkStatusKind::Modified,
17980            DiffHunkStatusKind::Modified,
17981            DiffHunkStatusKind::Modified,
17982            DiffHunkStatusKind::Modified,
17983            DiffHunkStatusKind::Modified,
17984        ],
17985        indoc! {r#"struct Row;
17986                   ˇstruct Row1;
17987                   struct Row2;
17988                   ˇ
17989                   struct Row4;
17990                   ˇstruct Row5;
17991                   struct Row6;
17992                   ˇ
17993                   struct Row8;
17994                   ˇstruct Row9;
17995                   struct Row10;ˇ"#},
17996        base_text,
17997        &mut cx,
17998    );
17999}
18000
18001#[gpui::test]
18002async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18003    init_test(cx, |_| {});
18004    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18005    let base_text = indoc! {r#"
18006        one
18007
18008        two
18009        three
18010        "#};
18011
18012    cx.set_head_text(base_text);
18013    cx.set_state("\nˇ\n");
18014    cx.executor().run_until_parked();
18015    cx.update_editor(|editor, _window, cx| {
18016        editor.expand_selected_diff_hunks(cx);
18017    });
18018    cx.executor().run_until_parked();
18019    cx.update_editor(|editor, window, cx| {
18020        editor.backspace(&Default::default(), window, cx);
18021    });
18022    cx.run_until_parked();
18023    cx.assert_state_with_diff(
18024        indoc! {r#"
18025
18026        - two
18027        - threeˇ
18028        +
18029        "#}
18030        .to_string(),
18031    );
18032}
18033
18034#[gpui::test]
18035async fn test_deletion_reverts(cx: &mut TestAppContext) {
18036    init_test(cx, |_| {});
18037    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18038    let base_text = indoc! {r#"struct Row;
18039struct Row1;
18040struct Row2;
18041
18042struct Row4;
18043struct Row5;
18044struct Row6;
18045
18046struct Row8;
18047struct Row9;
18048struct Row10;"#};
18049
18050    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18051    assert_hunk_revert(
18052        indoc! {r#"struct Row;
18053                   struct Row2;
18054
18055                   ˇstruct Row4;
18056                   struct Row5;
18057                   struct Row6;
18058                   ˇ
18059                   struct Row8;
18060                   struct Row10;"#},
18061        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18062        indoc! {r#"struct Row;
18063                   struct Row2;
18064
18065                   ˇstruct Row4;
18066                   struct Row5;
18067                   struct Row6;
18068                   ˇ
18069                   struct Row8;
18070                   struct Row10;"#},
18071        base_text,
18072        &mut cx,
18073    );
18074    assert_hunk_revert(
18075        indoc! {r#"struct Row;
18076                   struct Row2;
18077
18078                   «ˇstruct Row4;
18079                   struct» Row5;
18080                   «struct Row6;
18081                   ˇ»
18082                   struct Row8;
18083                   struct Row10;"#},
18084        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18085        indoc! {r#"struct Row;
18086                   struct Row2;
18087
18088                   «ˇstruct Row4;
18089                   struct» Row5;
18090                   «struct Row6;
18091                   ˇ»
18092                   struct Row8;
18093                   struct Row10;"#},
18094        base_text,
18095        &mut cx,
18096    );
18097
18098    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18099    assert_hunk_revert(
18100        indoc! {r#"struct Row;
18101                   ˇstruct Row2;
18102
18103                   struct Row4;
18104                   struct Row5;
18105                   struct Row6;
18106
18107                   struct Row8;ˇ
18108                   struct Row10;"#},
18109        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18110        indoc! {r#"struct Row;
18111                   struct Row1;
18112                   ˇstruct Row2;
18113
18114                   struct Row4;
18115                   struct Row5;
18116                   struct Row6;
18117
18118                   struct Row8;ˇ
18119                   struct Row9;
18120                   struct Row10;"#},
18121        base_text,
18122        &mut cx,
18123    );
18124    assert_hunk_revert(
18125        indoc! {r#"struct Row;
18126                   struct Row2«ˇ;
18127                   struct Row4;
18128                   struct» Row5;
18129                   «struct Row6;
18130
18131                   struct Row8;ˇ»
18132                   struct Row10;"#},
18133        vec![
18134            DiffHunkStatusKind::Deleted,
18135            DiffHunkStatusKind::Deleted,
18136            DiffHunkStatusKind::Deleted,
18137        ],
18138        indoc! {r#"struct Row;
18139                   struct Row1;
18140                   struct Row2«ˇ;
18141
18142                   struct Row4;
18143                   struct» Row5;
18144                   «struct Row6;
18145
18146                   struct Row8;ˇ»
18147                   struct Row9;
18148                   struct Row10;"#},
18149        base_text,
18150        &mut cx,
18151    );
18152}
18153
18154#[gpui::test]
18155async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18156    init_test(cx, |_| {});
18157
18158    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18159    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18160    let base_text_3 =
18161        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18162
18163    let text_1 = edit_first_char_of_every_line(base_text_1);
18164    let text_2 = edit_first_char_of_every_line(base_text_2);
18165    let text_3 = edit_first_char_of_every_line(base_text_3);
18166
18167    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18168    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18169    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18170
18171    let multibuffer = cx.new(|cx| {
18172        let mut multibuffer = MultiBuffer::new(ReadWrite);
18173        multibuffer.push_excerpts(
18174            buffer_1.clone(),
18175            [
18176                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18177                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18178                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18179            ],
18180            cx,
18181        );
18182        multibuffer.push_excerpts(
18183            buffer_2.clone(),
18184            [
18185                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18186                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18187                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18188            ],
18189            cx,
18190        );
18191        multibuffer.push_excerpts(
18192            buffer_3.clone(),
18193            [
18194                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18195                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18196                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18197            ],
18198            cx,
18199        );
18200        multibuffer
18201    });
18202
18203    let fs = FakeFs::new(cx.executor());
18204    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18205    let (editor, cx) = cx
18206        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18207    editor.update_in(cx, |editor, _window, cx| {
18208        for (buffer, diff_base) in [
18209            (buffer_1.clone(), base_text_1),
18210            (buffer_2.clone(), base_text_2),
18211            (buffer_3.clone(), base_text_3),
18212        ] {
18213            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18214            editor
18215                .buffer
18216                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18217        }
18218    });
18219    cx.executor().run_until_parked();
18220
18221    editor.update_in(cx, |editor, window, cx| {
18222        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}");
18223        editor.select_all(&SelectAll, window, cx);
18224        editor.git_restore(&Default::default(), window, cx);
18225    });
18226    cx.executor().run_until_parked();
18227
18228    // When all ranges are selected, all buffer hunks are reverted.
18229    editor.update(cx, |editor, cx| {
18230        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");
18231    });
18232    buffer_1.update(cx, |buffer, _| {
18233        assert_eq!(buffer.text(), base_text_1);
18234    });
18235    buffer_2.update(cx, |buffer, _| {
18236        assert_eq!(buffer.text(), base_text_2);
18237    });
18238    buffer_3.update(cx, |buffer, _| {
18239        assert_eq!(buffer.text(), base_text_3);
18240    });
18241
18242    editor.update_in(cx, |editor, window, cx| {
18243        editor.undo(&Default::default(), window, cx);
18244    });
18245
18246    editor.update_in(cx, |editor, window, cx| {
18247        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18248            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18249        });
18250        editor.git_restore(&Default::default(), window, cx);
18251    });
18252
18253    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18254    // but not affect buffer_2 and its related excerpts.
18255    editor.update(cx, |editor, cx| {
18256        assert_eq!(
18257            editor.text(cx),
18258            "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}"
18259        );
18260    });
18261    buffer_1.update(cx, |buffer, _| {
18262        assert_eq!(buffer.text(), base_text_1);
18263    });
18264    buffer_2.update(cx, |buffer, _| {
18265        assert_eq!(
18266            buffer.text(),
18267            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18268        );
18269    });
18270    buffer_3.update(cx, |buffer, _| {
18271        assert_eq!(
18272            buffer.text(),
18273            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18274        );
18275    });
18276
18277    fn edit_first_char_of_every_line(text: &str) -> String {
18278        text.split('\n')
18279            .map(|line| format!("X{}", &line[1..]))
18280            .collect::<Vec<_>>()
18281            .join("\n")
18282    }
18283}
18284
18285#[gpui::test]
18286async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18287    init_test(cx, |_| {});
18288
18289    let cols = 4;
18290    let rows = 10;
18291    let sample_text_1 = sample_text(rows, cols, 'a');
18292    assert_eq!(
18293        sample_text_1,
18294        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18295    );
18296    let sample_text_2 = sample_text(rows, cols, 'l');
18297    assert_eq!(
18298        sample_text_2,
18299        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18300    );
18301    let sample_text_3 = sample_text(rows, cols, 'v');
18302    assert_eq!(
18303        sample_text_3,
18304        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18305    );
18306
18307    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18308    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18309    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18310
18311    let multi_buffer = cx.new(|cx| {
18312        let mut multibuffer = MultiBuffer::new(ReadWrite);
18313        multibuffer.push_excerpts(
18314            buffer_1.clone(),
18315            [
18316                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18317                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18318                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18319            ],
18320            cx,
18321        );
18322        multibuffer.push_excerpts(
18323            buffer_2.clone(),
18324            [
18325                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18326                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18327                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18328            ],
18329            cx,
18330        );
18331        multibuffer.push_excerpts(
18332            buffer_3.clone(),
18333            [
18334                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18335                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18336                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18337            ],
18338            cx,
18339        );
18340        multibuffer
18341    });
18342
18343    let fs = FakeFs::new(cx.executor());
18344    fs.insert_tree(
18345        "/a",
18346        json!({
18347            "main.rs": sample_text_1,
18348            "other.rs": sample_text_2,
18349            "lib.rs": sample_text_3,
18350        }),
18351    )
18352    .await;
18353    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18354    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18355    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18356    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18357        Editor::new(
18358            EditorMode::full(),
18359            multi_buffer,
18360            Some(project.clone()),
18361            window,
18362            cx,
18363        )
18364    });
18365    let multibuffer_item_id = workspace
18366        .update(cx, |workspace, window, cx| {
18367            assert!(
18368                workspace.active_item(cx).is_none(),
18369                "active item should be None before the first item is added"
18370            );
18371            workspace.add_item_to_active_pane(
18372                Box::new(multi_buffer_editor.clone()),
18373                None,
18374                true,
18375                window,
18376                cx,
18377            );
18378            let active_item = workspace
18379                .active_item(cx)
18380                .expect("should have an active item after adding the multi buffer");
18381            assert!(
18382                !active_item.is_singleton(cx),
18383                "A multi buffer was expected to active after adding"
18384            );
18385            active_item.item_id()
18386        })
18387        .unwrap();
18388    cx.executor().run_until_parked();
18389
18390    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18391        editor.change_selections(
18392            SelectionEffects::scroll(Autoscroll::Next),
18393            window,
18394            cx,
18395            |s| s.select_ranges(Some(1..2)),
18396        );
18397        editor.open_excerpts(&OpenExcerpts, window, cx);
18398    });
18399    cx.executor().run_until_parked();
18400    let first_item_id = workspace
18401        .update(cx, |workspace, window, cx| {
18402            let active_item = workspace
18403                .active_item(cx)
18404                .expect("should have an active item after navigating into the 1st buffer");
18405            let first_item_id = active_item.item_id();
18406            assert_ne!(
18407                first_item_id, multibuffer_item_id,
18408                "Should navigate into the 1st buffer and activate it"
18409            );
18410            assert!(
18411                active_item.is_singleton(cx),
18412                "New active item should be a singleton buffer"
18413            );
18414            assert_eq!(
18415                active_item
18416                    .act_as::<Editor>(cx)
18417                    .expect("should have navigated into an editor for the 1st buffer")
18418                    .read(cx)
18419                    .text(cx),
18420                sample_text_1
18421            );
18422
18423            workspace
18424                .go_back(workspace.active_pane().downgrade(), window, cx)
18425                .detach_and_log_err(cx);
18426
18427            first_item_id
18428        })
18429        .unwrap();
18430    cx.executor().run_until_parked();
18431    workspace
18432        .update(cx, |workspace, _, cx| {
18433            let active_item = workspace
18434                .active_item(cx)
18435                .expect("should have an active item after navigating back");
18436            assert_eq!(
18437                active_item.item_id(),
18438                multibuffer_item_id,
18439                "Should navigate back to the multi buffer"
18440            );
18441            assert!(!active_item.is_singleton(cx));
18442        })
18443        .unwrap();
18444
18445    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18446        editor.change_selections(
18447            SelectionEffects::scroll(Autoscroll::Next),
18448            window,
18449            cx,
18450            |s| s.select_ranges(Some(39..40)),
18451        );
18452        editor.open_excerpts(&OpenExcerpts, window, cx);
18453    });
18454    cx.executor().run_until_parked();
18455    let second_item_id = workspace
18456        .update(cx, |workspace, window, cx| {
18457            let active_item = workspace
18458                .active_item(cx)
18459                .expect("should have an active item after navigating into the 2nd buffer");
18460            let second_item_id = active_item.item_id();
18461            assert_ne!(
18462                second_item_id, multibuffer_item_id,
18463                "Should navigate away from the multibuffer"
18464            );
18465            assert_ne!(
18466                second_item_id, first_item_id,
18467                "Should navigate into the 2nd buffer and activate it"
18468            );
18469            assert!(
18470                active_item.is_singleton(cx),
18471                "New active item should be a singleton buffer"
18472            );
18473            assert_eq!(
18474                active_item
18475                    .act_as::<Editor>(cx)
18476                    .expect("should have navigated into an editor")
18477                    .read(cx)
18478                    .text(cx),
18479                sample_text_2
18480            );
18481
18482            workspace
18483                .go_back(workspace.active_pane().downgrade(), window, cx)
18484                .detach_and_log_err(cx);
18485
18486            second_item_id
18487        })
18488        .unwrap();
18489    cx.executor().run_until_parked();
18490    workspace
18491        .update(cx, |workspace, _, cx| {
18492            let active_item = workspace
18493                .active_item(cx)
18494                .expect("should have an active item after navigating back from the 2nd buffer");
18495            assert_eq!(
18496                active_item.item_id(),
18497                multibuffer_item_id,
18498                "Should navigate back from the 2nd buffer to the multi buffer"
18499            );
18500            assert!(!active_item.is_singleton(cx));
18501        })
18502        .unwrap();
18503
18504    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18505        editor.change_selections(
18506            SelectionEffects::scroll(Autoscroll::Next),
18507            window,
18508            cx,
18509            |s| s.select_ranges(Some(70..70)),
18510        );
18511        editor.open_excerpts(&OpenExcerpts, window, cx);
18512    });
18513    cx.executor().run_until_parked();
18514    workspace
18515        .update(cx, |workspace, window, cx| {
18516            let active_item = workspace
18517                .active_item(cx)
18518                .expect("should have an active item after navigating into the 3rd buffer");
18519            let third_item_id = active_item.item_id();
18520            assert_ne!(
18521                third_item_id, multibuffer_item_id,
18522                "Should navigate into the 3rd buffer and activate it"
18523            );
18524            assert_ne!(third_item_id, first_item_id);
18525            assert_ne!(third_item_id, second_item_id);
18526            assert!(
18527                active_item.is_singleton(cx),
18528                "New active item should be a singleton buffer"
18529            );
18530            assert_eq!(
18531                active_item
18532                    .act_as::<Editor>(cx)
18533                    .expect("should have navigated into an editor")
18534                    .read(cx)
18535                    .text(cx),
18536                sample_text_3
18537            );
18538
18539            workspace
18540                .go_back(workspace.active_pane().downgrade(), window, cx)
18541                .detach_and_log_err(cx);
18542        })
18543        .unwrap();
18544    cx.executor().run_until_parked();
18545    workspace
18546        .update(cx, |workspace, _, cx| {
18547            let active_item = workspace
18548                .active_item(cx)
18549                .expect("should have an active item after navigating back from the 3rd buffer");
18550            assert_eq!(
18551                active_item.item_id(),
18552                multibuffer_item_id,
18553                "Should navigate back from the 3rd buffer to the multi buffer"
18554            );
18555            assert!(!active_item.is_singleton(cx));
18556        })
18557        .unwrap();
18558}
18559
18560#[gpui::test]
18561async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18562    init_test(cx, |_| {});
18563
18564    let mut cx = EditorTestContext::new(cx).await;
18565
18566    let diff_base = r#"
18567        use some::mod;
18568
18569        const A: u32 = 42;
18570
18571        fn main() {
18572            println!("hello");
18573
18574            println!("world");
18575        }
18576        "#
18577    .unindent();
18578
18579    cx.set_state(
18580        &r#"
18581        use some::modified;
18582
18583        ˇ
18584        fn main() {
18585            println!("hello there");
18586
18587            println!("around the");
18588            println!("world");
18589        }
18590        "#
18591        .unindent(),
18592    );
18593
18594    cx.set_head_text(&diff_base);
18595    executor.run_until_parked();
18596
18597    cx.update_editor(|editor, window, cx| {
18598        editor.go_to_next_hunk(&GoToHunk, window, cx);
18599        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18600    });
18601    executor.run_until_parked();
18602    cx.assert_state_with_diff(
18603        r#"
18604          use some::modified;
18605
18606
18607          fn main() {
18608        -     println!("hello");
18609        + ˇ    println!("hello there");
18610
18611              println!("around the");
18612              println!("world");
18613          }
18614        "#
18615        .unindent(),
18616    );
18617
18618    cx.update_editor(|editor, window, cx| {
18619        for _ in 0..2 {
18620            editor.go_to_next_hunk(&GoToHunk, window, cx);
18621            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18622        }
18623    });
18624    executor.run_until_parked();
18625    cx.assert_state_with_diff(
18626        r#"
18627        - use some::mod;
18628        + ˇuse some::modified;
18629
18630
18631          fn main() {
18632        -     println!("hello");
18633        +     println!("hello there");
18634
18635        +     println!("around the");
18636              println!("world");
18637          }
18638        "#
18639        .unindent(),
18640    );
18641
18642    cx.update_editor(|editor, window, cx| {
18643        editor.go_to_next_hunk(&GoToHunk, window, cx);
18644        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18645    });
18646    executor.run_until_parked();
18647    cx.assert_state_with_diff(
18648        r#"
18649        - use some::mod;
18650        + use some::modified;
18651
18652        - const A: u32 = 42;
18653          ˇ
18654          fn main() {
18655        -     println!("hello");
18656        +     println!("hello there");
18657
18658        +     println!("around the");
18659              println!("world");
18660          }
18661        "#
18662        .unindent(),
18663    );
18664
18665    cx.update_editor(|editor, window, cx| {
18666        editor.cancel(&Cancel, window, cx);
18667    });
18668
18669    cx.assert_state_with_diff(
18670        r#"
18671          use some::modified;
18672
18673          ˇ
18674          fn main() {
18675              println!("hello there");
18676
18677              println!("around the");
18678              println!("world");
18679          }
18680        "#
18681        .unindent(),
18682    );
18683}
18684
18685#[gpui::test]
18686async fn test_diff_base_change_with_expanded_diff_hunks(
18687    executor: BackgroundExecutor,
18688    cx: &mut TestAppContext,
18689) {
18690    init_test(cx, |_| {});
18691
18692    let mut cx = EditorTestContext::new(cx).await;
18693
18694    let diff_base = r#"
18695        use some::mod1;
18696        use some::mod2;
18697
18698        const A: u32 = 42;
18699        const B: u32 = 42;
18700        const C: u32 = 42;
18701
18702        fn main() {
18703            println!("hello");
18704
18705            println!("world");
18706        }
18707        "#
18708    .unindent();
18709
18710    cx.set_state(
18711        &r#"
18712        use some::mod2;
18713
18714        const A: u32 = 42;
18715        const C: u32 = 42;
18716
18717        fn main(ˇ) {
18718            //println!("hello");
18719
18720            println!("world");
18721            //
18722            //
18723        }
18724        "#
18725        .unindent(),
18726    );
18727
18728    cx.set_head_text(&diff_base);
18729    executor.run_until_parked();
18730
18731    cx.update_editor(|editor, window, cx| {
18732        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18733    });
18734    executor.run_until_parked();
18735    cx.assert_state_with_diff(
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 = 42;
18743
18744          fn main(ˇ) {
18745        -     println!("hello");
18746        +     //println!("hello");
18747
18748              println!("world");
18749        +     //
18750        +     //
18751          }
18752        "#
18753        .unindent(),
18754    );
18755
18756    cx.set_head_text("new diff base!");
18757    executor.run_until_parked();
18758    cx.assert_state_with_diff(
18759        r#"
18760        - new diff base!
18761        + use some::mod2;
18762        +
18763        + const A: u32 = 42;
18764        + const C: u32 = 42;
18765        +
18766        + fn main(ˇ) {
18767        +     //println!("hello");
18768        +
18769        +     println!("world");
18770        +     //
18771        +     //
18772        + }
18773        "#
18774        .unindent(),
18775    );
18776}
18777
18778#[gpui::test]
18779async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
18780    init_test(cx, |_| {});
18781
18782    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
18783    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
18784    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
18785    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
18786    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
18787    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
18788
18789    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
18790    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
18791    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
18792
18793    let multi_buffer = cx.new(|cx| {
18794        let mut multibuffer = MultiBuffer::new(ReadWrite);
18795        multibuffer.push_excerpts(
18796            buffer_1.clone(),
18797            [
18798                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18799                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18800                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18801            ],
18802            cx,
18803        );
18804        multibuffer.push_excerpts(
18805            buffer_2.clone(),
18806            [
18807                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18808                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18809                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18810            ],
18811            cx,
18812        );
18813        multibuffer.push_excerpts(
18814            buffer_3.clone(),
18815            [
18816                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18817                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18818                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18819            ],
18820            cx,
18821        );
18822        multibuffer
18823    });
18824
18825    let editor =
18826        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18827    editor
18828        .update(cx, |editor, _window, cx| {
18829            for (buffer, diff_base) in [
18830                (buffer_1.clone(), file_1_old),
18831                (buffer_2.clone(), file_2_old),
18832                (buffer_3.clone(), file_3_old),
18833            ] {
18834                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18835                editor
18836                    .buffer
18837                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18838            }
18839        })
18840        .unwrap();
18841
18842    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18843    cx.run_until_parked();
18844
18845    cx.assert_editor_state(
18846        &"
18847            ˇaaa
18848            ccc
18849            ddd
18850
18851            ggg
18852            hhh
18853
18854
18855            lll
18856            mmm
18857            NNN
18858
18859            qqq
18860            rrr
18861
18862            uuu
18863            111
18864            222
18865            333
18866
18867            666
18868            777
18869
18870            000
18871            !!!"
18872        .unindent(),
18873    );
18874
18875    cx.update_editor(|editor, window, cx| {
18876        editor.select_all(&SelectAll, window, cx);
18877        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18878    });
18879    cx.executor().run_until_parked();
18880
18881    cx.assert_state_with_diff(
18882        "
18883            «aaa
18884          - bbb
18885            ccc
18886            ddd
18887
18888            ggg
18889            hhh
18890
18891
18892            lll
18893            mmm
18894          - nnn
18895          + NNN
18896
18897            qqq
18898            rrr
18899
18900            uuu
18901            111
18902            222
18903            333
18904
18905          + 666
18906            777
18907
18908            000
18909            !!!ˇ»"
18910            .unindent(),
18911    );
18912}
18913
18914#[gpui::test]
18915async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
18916    init_test(cx, |_| {});
18917
18918    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
18919    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
18920
18921    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
18922    let multi_buffer = cx.new(|cx| {
18923        let mut multibuffer = MultiBuffer::new(ReadWrite);
18924        multibuffer.push_excerpts(
18925            buffer.clone(),
18926            [
18927                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
18928                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
18929                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
18930            ],
18931            cx,
18932        );
18933        multibuffer
18934    });
18935
18936    let editor =
18937        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18938    editor
18939        .update(cx, |editor, _window, cx| {
18940            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
18941            editor
18942                .buffer
18943                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
18944        })
18945        .unwrap();
18946
18947    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18948    cx.run_until_parked();
18949
18950    cx.update_editor(|editor, window, cx| {
18951        editor.expand_all_diff_hunks(&Default::default(), window, cx)
18952    });
18953    cx.executor().run_until_parked();
18954
18955    // When the start of a hunk coincides with the start of its excerpt,
18956    // the hunk is expanded. When the start of a a hunk is earlier than
18957    // the start of its excerpt, the hunk is not expanded.
18958    cx.assert_state_with_diff(
18959        "
18960            ˇaaa
18961          - bbb
18962          + BBB
18963
18964          - ddd
18965          - eee
18966          + DDD
18967          + EEE
18968            fff
18969
18970            iii
18971        "
18972        .unindent(),
18973    );
18974}
18975
18976#[gpui::test]
18977async fn test_edits_around_expanded_insertion_hunks(
18978    executor: BackgroundExecutor,
18979    cx: &mut TestAppContext,
18980) {
18981    init_test(cx, |_| {});
18982
18983    let mut cx = EditorTestContext::new(cx).await;
18984
18985    let diff_base = r#"
18986        use some::mod1;
18987        use some::mod2;
18988
18989        const A: u32 = 42;
18990
18991        fn main() {
18992            println!("hello");
18993
18994            println!("world");
18995        }
18996        "#
18997    .unindent();
18998    executor.run_until_parked();
18999    cx.set_state(
19000        &r#"
19001        use some::mod1;
19002        use some::mod2;
19003
19004        const A: u32 = 42;
19005        const B: u32 = 42;
19006        const C: u32 = 42;
19007        ˇ
19008
19009        fn main() {
19010            println!("hello");
19011
19012            println!("world");
19013        }
19014        "#
19015        .unindent(),
19016    );
19017
19018    cx.set_head_text(&diff_base);
19019    executor.run_until_parked();
19020
19021    cx.update_editor(|editor, window, cx| {
19022        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19023    });
19024    executor.run_until_parked();
19025
19026    cx.assert_state_with_diff(
19027        r#"
19028        use some::mod1;
19029        use some::mod2;
19030
19031        const A: u32 = 42;
19032      + const B: u32 = 42;
19033      + const C: u32 = 42;
19034      + ˇ
19035
19036        fn main() {
19037            println!("hello");
19038
19039            println!("world");
19040        }
19041      "#
19042        .unindent(),
19043    );
19044
19045    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19046    executor.run_until_parked();
19047
19048    cx.assert_state_with_diff(
19049        r#"
19050        use some::mod1;
19051        use some::mod2;
19052
19053        const A: u32 = 42;
19054      + const B: u32 = 42;
19055      + const C: u32 = 42;
19056      + const D: u32 = 42;
19057      + ˇ
19058
19059        fn main() {
19060            println!("hello");
19061
19062            println!("world");
19063        }
19064      "#
19065        .unindent(),
19066    );
19067
19068    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19069    executor.run_until_parked();
19070
19071    cx.assert_state_with_diff(
19072        r#"
19073        use some::mod1;
19074        use some::mod2;
19075
19076        const A: u32 = 42;
19077      + const B: u32 = 42;
19078      + const C: u32 = 42;
19079      + const D: u32 = 42;
19080      + const E: u32 = 42;
19081      + ˇ
19082
19083        fn main() {
19084            println!("hello");
19085
19086            println!("world");
19087        }
19088      "#
19089        .unindent(),
19090    );
19091
19092    cx.update_editor(|editor, window, cx| {
19093        editor.delete_line(&DeleteLine, window, cx);
19094    });
19095    executor.run_until_parked();
19096
19097    cx.assert_state_with_diff(
19098        r#"
19099        use some::mod1;
19100        use some::mod2;
19101
19102        const A: u32 = 42;
19103      + const B: u32 = 42;
19104      + const C: u32 = 42;
19105      + const D: u32 = 42;
19106      + const E: u32 = 42;
19107        ˇ
19108        fn main() {
19109            println!("hello");
19110
19111            println!("world");
19112        }
19113      "#
19114        .unindent(),
19115    );
19116
19117    cx.update_editor(|editor, window, cx| {
19118        editor.move_up(&MoveUp, window, cx);
19119        editor.delete_line(&DeleteLine, window, cx);
19120        editor.move_up(&MoveUp, window, cx);
19121        editor.delete_line(&DeleteLine, window, cx);
19122        editor.move_up(&MoveUp, window, cx);
19123        editor.delete_line(&DeleteLine, window, cx);
19124    });
19125    executor.run_until_parked();
19126    cx.assert_state_with_diff(
19127        r#"
19128        use some::mod1;
19129        use some::mod2;
19130
19131        const A: u32 = 42;
19132      + const B: u32 = 42;
19133        ˇ
19134        fn main() {
19135            println!("hello");
19136
19137            println!("world");
19138        }
19139      "#
19140        .unindent(),
19141    );
19142
19143    cx.update_editor(|editor, window, cx| {
19144        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19145        editor.delete_line(&DeleteLine, window, cx);
19146    });
19147    executor.run_until_parked();
19148    cx.assert_state_with_diff(
19149        r#"
19150        ˇ
19151        fn main() {
19152            println!("hello");
19153
19154            println!("world");
19155        }
19156      "#
19157        .unindent(),
19158    );
19159}
19160
19161#[gpui::test]
19162async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19163    init_test(cx, |_| {});
19164
19165    let mut cx = EditorTestContext::new(cx).await;
19166    cx.set_head_text(indoc! { "
19167        one
19168        two
19169        three
19170        four
19171        five
19172        "
19173    });
19174    cx.set_state(indoc! { "
19175        one
19176        ˇthree
19177        five
19178    "});
19179    cx.run_until_parked();
19180    cx.update_editor(|editor, window, cx| {
19181        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19182    });
19183    cx.assert_state_with_diff(
19184        indoc! { "
19185        one
19186      - two
19187        ˇthree
19188      - four
19189        five
19190    "}
19191        .to_string(),
19192    );
19193    cx.update_editor(|editor, window, cx| {
19194        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19195    });
19196
19197    cx.assert_state_with_diff(
19198        indoc! { "
19199        one
19200        ˇthree
19201        five
19202    "}
19203        .to_string(),
19204    );
19205
19206    cx.set_state(indoc! { "
19207        one
19208        ˇTWO
19209        three
19210        four
19211        five
19212    "});
19213    cx.run_until_parked();
19214    cx.update_editor(|editor, window, cx| {
19215        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19216    });
19217
19218    cx.assert_state_with_diff(
19219        indoc! { "
19220            one
19221          - two
19222          + ˇTWO
19223            three
19224            four
19225            five
19226        "}
19227        .to_string(),
19228    );
19229    cx.update_editor(|editor, window, cx| {
19230        editor.move_up(&Default::default(), window, cx);
19231        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19232    });
19233    cx.assert_state_with_diff(
19234        indoc! { "
19235            one
19236            ˇTWO
19237            three
19238            four
19239            five
19240        "}
19241        .to_string(),
19242    );
19243}
19244
19245#[gpui::test]
19246async fn test_edits_around_expanded_deletion_hunks(
19247    executor: BackgroundExecutor,
19248    cx: &mut TestAppContext,
19249) {
19250    init_test(cx, |_| {});
19251
19252    let mut cx = EditorTestContext::new(cx).await;
19253
19254    let diff_base = r#"
19255        use some::mod1;
19256        use some::mod2;
19257
19258        const A: u32 = 42;
19259        const B: u32 = 42;
19260        const C: u32 = 42;
19261
19262
19263        fn main() {
19264            println!("hello");
19265
19266            println!("world");
19267        }
19268    "#
19269    .unindent();
19270    executor.run_until_parked();
19271    cx.set_state(
19272        &r#"
19273        use some::mod1;
19274        use some::mod2;
19275
19276        ˇconst B: u32 = 42;
19277        const C: u32 = 42;
19278
19279
19280        fn main() {
19281            println!("hello");
19282
19283            println!("world");
19284        }
19285        "#
19286        .unindent(),
19287    );
19288
19289    cx.set_head_text(&diff_base);
19290    executor.run_until_parked();
19291
19292    cx.update_editor(|editor, window, cx| {
19293        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19294    });
19295    executor.run_until_parked();
19296
19297    cx.assert_state_with_diff(
19298        r#"
19299        use some::mod1;
19300        use some::mod2;
19301
19302      - const A: u32 = 42;
19303        ˇconst B: u32 = 42;
19304        const C: u32 = 42;
19305
19306
19307        fn main() {
19308            println!("hello");
19309
19310            println!("world");
19311        }
19312      "#
19313        .unindent(),
19314    );
19315
19316    cx.update_editor(|editor, window, cx| {
19317        editor.delete_line(&DeleteLine, window, cx);
19318    });
19319    executor.run_until_parked();
19320    cx.assert_state_with_diff(
19321        r#"
19322        use some::mod1;
19323        use some::mod2;
19324
19325      - const A: u32 = 42;
19326      - const B: u32 = 42;
19327        ˇconst C: u32 = 42;
19328
19329
19330        fn main() {
19331            println!("hello");
19332
19333            println!("world");
19334        }
19335      "#
19336        .unindent(),
19337    );
19338
19339    cx.update_editor(|editor, window, cx| {
19340        editor.delete_line(&DeleteLine, window, cx);
19341    });
19342    executor.run_until_parked();
19343    cx.assert_state_with_diff(
19344        r#"
19345        use some::mod1;
19346        use some::mod2;
19347
19348      - const A: u32 = 42;
19349      - const B: u32 = 42;
19350      - const C: u32 = 42;
19351        ˇ
19352
19353        fn main() {
19354            println!("hello");
19355
19356            println!("world");
19357        }
19358      "#
19359        .unindent(),
19360    );
19361
19362    cx.update_editor(|editor, window, cx| {
19363        editor.handle_input("replacement", window, cx);
19364    });
19365    executor.run_until_parked();
19366    cx.assert_state_with_diff(
19367        r#"
19368        use some::mod1;
19369        use some::mod2;
19370
19371      - const A: u32 = 42;
19372      - const B: u32 = 42;
19373      - const C: u32 = 42;
19374      -
19375      + replacementˇ
19376
19377        fn main() {
19378            println!("hello");
19379
19380            println!("world");
19381        }
19382      "#
19383        .unindent(),
19384    );
19385}
19386
19387#[gpui::test]
19388async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19389    init_test(cx, |_| {});
19390
19391    let mut cx = EditorTestContext::new(cx).await;
19392
19393    let base_text = r#"
19394        one
19395        two
19396        three
19397        four
19398        five
19399    "#
19400    .unindent();
19401    executor.run_until_parked();
19402    cx.set_state(
19403        &r#"
19404        one
19405        two
19406        fˇour
19407        five
19408        "#
19409        .unindent(),
19410    );
19411
19412    cx.set_head_text(&base_text);
19413    executor.run_until_parked();
19414
19415    cx.update_editor(|editor, window, cx| {
19416        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19417    });
19418    executor.run_until_parked();
19419
19420    cx.assert_state_with_diff(
19421        r#"
19422          one
19423          two
19424        - three
19425          fˇour
19426          five
19427        "#
19428        .unindent(),
19429    );
19430
19431    cx.update_editor(|editor, window, cx| {
19432        editor.backspace(&Backspace, window, cx);
19433        editor.backspace(&Backspace, window, cx);
19434    });
19435    executor.run_until_parked();
19436    cx.assert_state_with_diff(
19437        r#"
19438          one
19439          two
19440        - threeˇ
19441        - four
19442        + our
19443          five
19444        "#
19445        .unindent(),
19446    );
19447}
19448
19449#[gpui::test]
19450async fn test_edit_after_expanded_modification_hunk(
19451    executor: BackgroundExecutor,
19452    cx: &mut TestAppContext,
19453) {
19454    init_test(cx, |_| {});
19455
19456    let mut cx = EditorTestContext::new(cx).await;
19457
19458    let diff_base = r#"
19459        use some::mod1;
19460        use some::mod2;
19461
19462        const A: u32 = 42;
19463        const B: u32 = 42;
19464        const C: u32 = 42;
19465        const D: u32 = 42;
19466
19467
19468        fn main() {
19469            println!("hello");
19470
19471            println!("world");
19472        }"#
19473    .unindent();
19474
19475    cx.set_state(
19476        &r#"
19477        use some::mod1;
19478        use some::mod2;
19479
19480        const A: u32 = 42;
19481        const B: u32 = 42;
19482        const C: u32 = 43ˇ
19483        const D: u32 = 42;
19484
19485
19486        fn main() {
19487            println!("hello");
19488
19489            println!("world");
19490        }"#
19491        .unindent(),
19492    );
19493
19494    cx.set_head_text(&diff_base);
19495    executor.run_until_parked();
19496    cx.update_editor(|editor, window, cx| {
19497        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19498    });
19499    executor.run_until_parked();
19500
19501    cx.assert_state_with_diff(
19502        r#"
19503        use some::mod1;
19504        use some::mod2;
19505
19506        const A: u32 = 42;
19507        const B: u32 = 42;
19508      - const C: u32 = 42;
19509      + const C: u32 = 43ˇ
19510        const D: u32 = 42;
19511
19512
19513        fn main() {
19514            println!("hello");
19515
19516            println!("world");
19517        }"#
19518        .unindent(),
19519    );
19520
19521    cx.update_editor(|editor, window, cx| {
19522        editor.handle_input("\nnew_line\n", window, cx);
19523    });
19524    executor.run_until_parked();
19525
19526    cx.assert_state_with_diff(
19527        r#"
19528        use some::mod1;
19529        use some::mod2;
19530
19531        const A: u32 = 42;
19532        const B: u32 = 42;
19533      - const C: u32 = 42;
19534      + const C: u32 = 43
19535      + new_line
19536      + ˇ
19537        const D: u32 = 42;
19538
19539
19540        fn main() {
19541            println!("hello");
19542
19543            println!("world");
19544        }"#
19545        .unindent(),
19546    );
19547}
19548
19549#[gpui::test]
19550async fn test_stage_and_unstage_added_file_hunk(
19551    executor: BackgroundExecutor,
19552    cx: &mut TestAppContext,
19553) {
19554    init_test(cx, |_| {});
19555
19556    let mut cx = EditorTestContext::new(cx).await;
19557    cx.update_editor(|editor, _, cx| {
19558        editor.set_expand_all_diff_hunks(cx);
19559    });
19560
19561    let working_copy = r#"
19562            ˇfn main() {
19563                println!("hello, world!");
19564            }
19565        "#
19566    .unindent();
19567
19568    cx.set_state(&working_copy);
19569    executor.run_until_parked();
19570
19571    cx.assert_state_with_diff(
19572        r#"
19573            + ˇfn main() {
19574            +     println!("hello, world!");
19575            + }
19576        "#
19577        .unindent(),
19578    );
19579    cx.assert_index_text(None);
19580
19581    cx.update_editor(|editor, window, cx| {
19582        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19583    });
19584    executor.run_until_parked();
19585    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19586    cx.assert_state_with_diff(
19587        r#"
19588            + ˇfn main() {
19589            +     println!("hello, world!");
19590            + }
19591        "#
19592        .unindent(),
19593    );
19594
19595    cx.update_editor(|editor, window, cx| {
19596        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19597    });
19598    executor.run_until_parked();
19599    cx.assert_index_text(None);
19600}
19601
19602async fn setup_indent_guides_editor(
19603    text: &str,
19604    cx: &mut TestAppContext,
19605) -> (BufferId, EditorTestContext) {
19606    init_test(cx, |_| {});
19607
19608    let mut cx = EditorTestContext::new(cx).await;
19609
19610    let buffer_id = cx.update_editor(|editor, window, cx| {
19611        editor.set_text(text, window, cx);
19612        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19613
19614        buffer_ids[0]
19615    });
19616
19617    (buffer_id, cx)
19618}
19619
19620fn assert_indent_guides(
19621    range: Range<u32>,
19622    expected: Vec<IndentGuide>,
19623    active_indices: Option<Vec<usize>>,
19624    cx: &mut EditorTestContext,
19625) {
19626    let indent_guides = cx.update_editor(|editor, window, cx| {
19627        let snapshot = editor.snapshot(window, cx).display_snapshot;
19628        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19629            editor,
19630            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19631            true,
19632            &snapshot,
19633            cx,
19634        );
19635
19636        indent_guides.sort_by(|a, b| {
19637            a.depth.cmp(&b.depth).then(
19638                a.start_row
19639                    .cmp(&b.start_row)
19640                    .then(a.end_row.cmp(&b.end_row)),
19641            )
19642        });
19643        indent_guides
19644    });
19645
19646    if let Some(expected) = active_indices {
19647        let active_indices = cx.update_editor(|editor, window, cx| {
19648            let snapshot = editor.snapshot(window, cx).display_snapshot;
19649            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
19650        });
19651
19652        assert_eq!(
19653            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
19654            expected,
19655            "Active indent guide indices do not match"
19656        );
19657    }
19658
19659    assert_eq!(indent_guides, expected, "Indent guides do not match");
19660}
19661
19662fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
19663    IndentGuide {
19664        buffer_id,
19665        start_row: MultiBufferRow(start_row),
19666        end_row: MultiBufferRow(end_row),
19667        depth,
19668        tab_size: 4,
19669        settings: IndentGuideSettings {
19670            enabled: true,
19671            line_width: 1,
19672            active_line_width: 1,
19673            ..Default::default()
19674        },
19675    }
19676}
19677
19678#[gpui::test]
19679async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
19680    let (buffer_id, mut cx) = setup_indent_guides_editor(
19681        &"
19682        fn main() {
19683            let a = 1;
19684        }"
19685        .unindent(),
19686        cx,
19687    )
19688    .await;
19689
19690    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19691}
19692
19693#[gpui::test]
19694async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
19695    let (buffer_id, mut cx) = setup_indent_guides_editor(
19696        &"
19697        fn main() {
19698            let a = 1;
19699            let b = 2;
19700        }"
19701        .unindent(),
19702        cx,
19703    )
19704    .await;
19705
19706    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
19707}
19708
19709#[gpui::test]
19710async fn test_indent_guide_nested(cx: &mut TestAppContext) {
19711    let (buffer_id, mut cx) = setup_indent_guides_editor(
19712        &"
19713        fn main() {
19714            let a = 1;
19715            if a == 3 {
19716                let b = 2;
19717            } else {
19718                let c = 3;
19719            }
19720        }"
19721        .unindent(),
19722        cx,
19723    )
19724    .await;
19725
19726    assert_indent_guides(
19727        0..8,
19728        vec![
19729            indent_guide(buffer_id, 1, 6, 0),
19730            indent_guide(buffer_id, 3, 3, 1),
19731            indent_guide(buffer_id, 5, 5, 1),
19732        ],
19733        None,
19734        &mut cx,
19735    );
19736}
19737
19738#[gpui::test]
19739async fn test_indent_guide_tab(cx: &mut TestAppContext) {
19740    let (buffer_id, mut cx) = setup_indent_guides_editor(
19741        &"
19742        fn main() {
19743            let a = 1;
19744                let b = 2;
19745            let c = 3;
19746        }"
19747        .unindent(),
19748        cx,
19749    )
19750    .await;
19751
19752    assert_indent_guides(
19753        0..5,
19754        vec![
19755            indent_guide(buffer_id, 1, 3, 0),
19756            indent_guide(buffer_id, 2, 2, 1),
19757        ],
19758        None,
19759        &mut cx,
19760    );
19761}
19762
19763#[gpui::test]
19764async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
19765    let (buffer_id, mut cx) = setup_indent_guides_editor(
19766        &"
19767        fn main() {
19768            let a = 1;
19769
19770            let c = 3;
19771        }"
19772        .unindent(),
19773        cx,
19774    )
19775    .await;
19776
19777    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
19778}
19779
19780#[gpui::test]
19781async fn test_indent_guide_complex(cx: &mut TestAppContext) {
19782    let (buffer_id, mut cx) = setup_indent_guides_editor(
19783        &"
19784        fn main() {
19785            let a = 1;
19786
19787            let c = 3;
19788
19789            if a == 3 {
19790                let b = 2;
19791            } else {
19792                let c = 3;
19793            }
19794        }"
19795        .unindent(),
19796        cx,
19797    )
19798    .await;
19799
19800    assert_indent_guides(
19801        0..11,
19802        vec![
19803            indent_guide(buffer_id, 1, 9, 0),
19804            indent_guide(buffer_id, 6, 6, 1),
19805            indent_guide(buffer_id, 8, 8, 1),
19806        ],
19807        None,
19808        &mut cx,
19809    );
19810}
19811
19812#[gpui::test]
19813async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
19814    let (buffer_id, mut cx) = setup_indent_guides_editor(
19815        &"
19816        fn main() {
19817            let a = 1;
19818
19819            let c = 3;
19820
19821            if a == 3 {
19822                let b = 2;
19823            } else {
19824                let c = 3;
19825            }
19826        }"
19827        .unindent(),
19828        cx,
19829    )
19830    .await;
19831
19832    assert_indent_guides(
19833        1..11,
19834        vec![
19835            indent_guide(buffer_id, 1, 9, 0),
19836            indent_guide(buffer_id, 6, 6, 1),
19837            indent_guide(buffer_id, 8, 8, 1),
19838        ],
19839        None,
19840        &mut cx,
19841    );
19842}
19843
19844#[gpui::test]
19845async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
19846    let (buffer_id, mut cx) = setup_indent_guides_editor(
19847        &"
19848        fn main() {
19849            let a = 1;
19850
19851            let c = 3;
19852
19853            if a == 3 {
19854                let b = 2;
19855            } else {
19856                let c = 3;
19857            }
19858        }"
19859        .unindent(),
19860        cx,
19861    )
19862    .await;
19863
19864    assert_indent_guides(
19865        1..10,
19866        vec![
19867            indent_guide(buffer_id, 1, 9, 0),
19868            indent_guide(buffer_id, 6, 6, 1),
19869            indent_guide(buffer_id, 8, 8, 1),
19870        ],
19871        None,
19872        &mut cx,
19873    );
19874}
19875
19876#[gpui::test]
19877async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
19878    let (buffer_id, mut cx) = setup_indent_guides_editor(
19879        &"
19880        fn main() {
19881            if a {
19882                b(
19883                    c,
19884                    d,
19885                )
19886            } else {
19887                e(
19888                    f
19889                )
19890            }
19891        }"
19892        .unindent(),
19893        cx,
19894    )
19895    .await;
19896
19897    assert_indent_guides(
19898        0..11,
19899        vec![
19900            indent_guide(buffer_id, 1, 10, 0),
19901            indent_guide(buffer_id, 2, 5, 1),
19902            indent_guide(buffer_id, 7, 9, 1),
19903            indent_guide(buffer_id, 3, 4, 2),
19904            indent_guide(buffer_id, 8, 8, 2),
19905        ],
19906        None,
19907        &mut cx,
19908    );
19909
19910    cx.update_editor(|editor, window, cx| {
19911        editor.fold_at(MultiBufferRow(2), window, cx);
19912        assert_eq!(
19913            editor.display_text(cx),
19914            "
19915            fn main() {
19916                if a {
19917                    b(⋯
19918                    )
19919                } else {
19920                    e(
19921                        f
19922                    )
19923                }
19924            }"
19925            .unindent()
19926        );
19927    });
19928
19929    assert_indent_guides(
19930        0..11,
19931        vec![
19932            indent_guide(buffer_id, 1, 10, 0),
19933            indent_guide(buffer_id, 2, 5, 1),
19934            indent_guide(buffer_id, 7, 9, 1),
19935            indent_guide(buffer_id, 8, 8, 2),
19936        ],
19937        None,
19938        &mut cx,
19939    );
19940}
19941
19942#[gpui::test]
19943async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
19944    let (buffer_id, mut cx) = setup_indent_guides_editor(
19945        &"
19946        block1
19947            block2
19948                block3
19949                    block4
19950            block2
19951        block1
19952        block1"
19953            .unindent(),
19954        cx,
19955    )
19956    .await;
19957
19958    assert_indent_guides(
19959        1..10,
19960        vec![
19961            indent_guide(buffer_id, 1, 4, 0),
19962            indent_guide(buffer_id, 2, 3, 1),
19963            indent_guide(buffer_id, 3, 3, 2),
19964        ],
19965        None,
19966        &mut cx,
19967    );
19968}
19969
19970#[gpui::test]
19971async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
19972    let (buffer_id, mut cx) = setup_indent_guides_editor(
19973        &"
19974        block1
19975            block2
19976                block3
19977
19978        block1
19979        block1"
19980            .unindent(),
19981        cx,
19982    )
19983    .await;
19984
19985    assert_indent_guides(
19986        0..6,
19987        vec![
19988            indent_guide(buffer_id, 1, 2, 0),
19989            indent_guide(buffer_id, 2, 2, 1),
19990        ],
19991        None,
19992        &mut cx,
19993    );
19994}
19995
19996#[gpui::test]
19997async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
19998    let (buffer_id, mut cx) = setup_indent_guides_editor(
19999        &"
20000        function component() {
20001        \treturn (
20002        \t\t\t
20003        \t\t<div>
20004        \t\t\t<abc></abc>
20005        \t\t</div>
20006        \t)
20007        }"
20008        .unindent(),
20009        cx,
20010    )
20011    .await;
20012
20013    assert_indent_guides(
20014        0..8,
20015        vec![
20016            indent_guide(buffer_id, 1, 6, 0),
20017            indent_guide(buffer_id, 2, 5, 1),
20018            indent_guide(buffer_id, 4, 4, 2),
20019        ],
20020        None,
20021        &mut cx,
20022    );
20023}
20024
20025#[gpui::test]
20026async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20027    let (buffer_id, mut cx) = setup_indent_guides_editor(
20028        &"
20029        function component() {
20030        \treturn (
20031        \t
20032        \t\t<div>
20033        \t\t\t<abc></abc>
20034        \t\t</div>
20035        \t)
20036        }"
20037        .unindent(),
20038        cx,
20039    )
20040    .await;
20041
20042    assert_indent_guides(
20043        0..8,
20044        vec![
20045            indent_guide(buffer_id, 1, 6, 0),
20046            indent_guide(buffer_id, 2, 5, 1),
20047            indent_guide(buffer_id, 4, 4, 2),
20048        ],
20049        None,
20050        &mut cx,
20051    );
20052}
20053
20054#[gpui::test]
20055async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20056    let (buffer_id, mut cx) = setup_indent_guides_editor(
20057        &"
20058        block1
20059
20060
20061
20062            block2
20063        "
20064        .unindent(),
20065        cx,
20066    )
20067    .await;
20068
20069    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20070}
20071
20072#[gpui::test]
20073async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20074    let (buffer_id, mut cx) = setup_indent_guides_editor(
20075        &"
20076        def a:
20077        \tb = 3
20078        \tif True:
20079        \t\tc = 4
20080        \t\td = 5
20081        \tprint(b)
20082        "
20083        .unindent(),
20084        cx,
20085    )
20086    .await;
20087
20088    assert_indent_guides(
20089        0..6,
20090        vec![
20091            indent_guide(buffer_id, 1, 5, 0),
20092            indent_guide(buffer_id, 3, 4, 1),
20093        ],
20094        None,
20095        &mut cx,
20096    );
20097}
20098
20099#[gpui::test]
20100async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20101    let (buffer_id, mut cx) = setup_indent_guides_editor(
20102        &"
20103    fn main() {
20104        let a = 1;
20105    }"
20106        .unindent(),
20107        cx,
20108    )
20109    .await;
20110
20111    cx.update_editor(|editor, window, cx| {
20112        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20113            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20114        });
20115    });
20116
20117    assert_indent_guides(
20118        0..3,
20119        vec![indent_guide(buffer_id, 1, 1, 0)],
20120        Some(vec![0]),
20121        &mut cx,
20122    );
20123}
20124
20125#[gpui::test]
20126async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20127    let (buffer_id, mut cx) = setup_indent_guides_editor(
20128        &"
20129    fn main() {
20130        if 1 == 2 {
20131            let a = 1;
20132        }
20133    }"
20134        .unindent(),
20135        cx,
20136    )
20137    .await;
20138
20139    cx.update_editor(|editor, window, cx| {
20140        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20141            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20142        });
20143    });
20144
20145    assert_indent_guides(
20146        0..4,
20147        vec![
20148            indent_guide(buffer_id, 1, 3, 0),
20149            indent_guide(buffer_id, 2, 2, 1),
20150        ],
20151        Some(vec![1]),
20152        &mut cx,
20153    );
20154
20155    cx.update_editor(|editor, window, cx| {
20156        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20157            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20158        });
20159    });
20160
20161    assert_indent_guides(
20162        0..4,
20163        vec![
20164            indent_guide(buffer_id, 1, 3, 0),
20165            indent_guide(buffer_id, 2, 2, 1),
20166        ],
20167        Some(vec![1]),
20168        &mut cx,
20169    );
20170
20171    cx.update_editor(|editor, window, cx| {
20172        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20173            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20174        });
20175    });
20176
20177    assert_indent_guides(
20178        0..4,
20179        vec![
20180            indent_guide(buffer_id, 1, 3, 0),
20181            indent_guide(buffer_id, 2, 2, 1),
20182        ],
20183        Some(vec![0]),
20184        &mut cx,
20185    );
20186}
20187
20188#[gpui::test]
20189async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20190    let (buffer_id, mut cx) = setup_indent_guides_editor(
20191        &"
20192    fn main() {
20193        let a = 1;
20194
20195        let b = 2;
20196    }"
20197        .unindent(),
20198        cx,
20199    )
20200    .await;
20201
20202    cx.update_editor(|editor, window, cx| {
20203        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20204            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20205        });
20206    });
20207
20208    assert_indent_guides(
20209        0..5,
20210        vec![indent_guide(buffer_id, 1, 3, 0)],
20211        Some(vec![0]),
20212        &mut cx,
20213    );
20214}
20215
20216#[gpui::test]
20217async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20218    let (buffer_id, mut cx) = setup_indent_guides_editor(
20219        &"
20220    def m:
20221        a = 1
20222        pass"
20223            .unindent(),
20224        cx,
20225    )
20226    .await;
20227
20228    cx.update_editor(|editor, window, cx| {
20229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20230            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20231        });
20232    });
20233
20234    assert_indent_guides(
20235        0..3,
20236        vec![indent_guide(buffer_id, 1, 2, 0)],
20237        Some(vec![0]),
20238        &mut cx,
20239    );
20240}
20241
20242#[gpui::test]
20243async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20244    init_test(cx, |_| {});
20245    let mut cx = EditorTestContext::new(cx).await;
20246    let text = indoc! {
20247        "
20248        impl A {
20249            fn b() {
20250                0;
20251                3;
20252                5;
20253                6;
20254                7;
20255            }
20256        }
20257        "
20258    };
20259    let base_text = indoc! {
20260        "
20261        impl A {
20262            fn b() {
20263                0;
20264                1;
20265                2;
20266                3;
20267                4;
20268            }
20269            fn c() {
20270                5;
20271                6;
20272                7;
20273            }
20274        }
20275        "
20276    };
20277
20278    cx.update_editor(|editor, window, cx| {
20279        editor.set_text(text, window, cx);
20280
20281        editor.buffer().update(cx, |multibuffer, cx| {
20282            let buffer = multibuffer.as_singleton().unwrap();
20283            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20284
20285            multibuffer.set_all_diff_hunks_expanded(cx);
20286            multibuffer.add_diff(diff, cx);
20287
20288            buffer.read(cx).remote_id()
20289        })
20290    });
20291    cx.run_until_parked();
20292
20293    cx.assert_state_with_diff(
20294        indoc! { "
20295          impl A {
20296              fn b() {
20297                  0;
20298        -         1;
20299        -         2;
20300                  3;
20301        -         4;
20302        -     }
20303        -     fn c() {
20304                  5;
20305                  6;
20306                  7;
20307              }
20308          }
20309          ˇ"
20310        }
20311        .to_string(),
20312    );
20313
20314    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20315        editor
20316            .snapshot(window, cx)
20317            .buffer_snapshot
20318            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20319            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20320            .collect::<Vec<_>>()
20321    });
20322    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20323    assert_eq!(
20324        actual_guides,
20325        vec![
20326            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20327            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20328            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20329        ]
20330    );
20331}
20332
20333#[gpui::test]
20334async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20335    init_test(cx, |_| {});
20336    let mut cx = EditorTestContext::new(cx).await;
20337
20338    let diff_base = r#"
20339        a
20340        b
20341        c
20342        "#
20343    .unindent();
20344
20345    cx.set_state(
20346        &r#"
20347        ˇA
20348        b
20349        C
20350        "#
20351        .unindent(),
20352    );
20353    cx.set_head_text(&diff_base);
20354    cx.update_editor(|editor, window, cx| {
20355        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20356    });
20357    executor.run_until_parked();
20358
20359    let both_hunks_expanded = r#"
20360        - a
20361        + ˇA
20362          b
20363        - c
20364        + C
20365        "#
20366    .unindent();
20367
20368    cx.assert_state_with_diff(both_hunks_expanded.clone());
20369
20370    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20371        let snapshot = editor.snapshot(window, cx);
20372        let hunks = editor
20373            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20374            .collect::<Vec<_>>();
20375        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20376        let buffer_id = hunks[0].buffer_id;
20377        hunks
20378            .into_iter()
20379            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20380            .collect::<Vec<_>>()
20381    });
20382    assert_eq!(hunk_ranges.len(), 2);
20383
20384    cx.update_editor(|editor, _, cx| {
20385        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20386    });
20387    executor.run_until_parked();
20388
20389    let second_hunk_expanded = r#"
20390          ˇA
20391          b
20392        - c
20393        + C
20394        "#
20395    .unindent();
20396
20397    cx.assert_state_with_diff(second_hunk_expanded);
20398
20399    cx.update_editor(|editor, _, cx| {
20400        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20401    });
20402    executor.run_until_parked();
20403
20404    cx.assert_state_with_diff(both_hunks_expanded.clone());
20405
20406    cx.update_editor(|editor, _, cx| {
20407        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20408    });
20409    executor.run_until_parked();
20410
20411    let first_hunk_expanded = r#"
20412        - a
20413        + ˇA
20414          b
20415          C
20416        "#
20417    .unindent();
20418
20419    cx.assert_state_with_diff(first_hunk_expanded);
20420
20421    cx.update_editor(|editor, _, cx| {
20422        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20423    });
20424    executor.run_until_parked();
20425
20426    cx.assert_state_with_diff(both_hunks_expanded);
20427
20428    cx.set_state(
20429        &r#"
20430        ˇA
20431        b
20432        "#
20433        .unindent(),
20434    );
20435    cx.run_until_parked();
20436
20437    // TODO this cursor position seems bad
20438    cx.assert_state_with_diff(
20439        r#"
20440        - ˇa
20441        + A
20442          b
20443        "#
20444        .unindent(),
20445    );
20446
20447    cx.update_editor(|editor, window, cx| {
20448        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20449    });
20450
20451    cx.assert_state_with_diff(
20452        r#"
20453            - ˇa
20454            + A
20455              b
20456            - c
20457            "#
20458        .unindent(),
20459    );
20460
20461    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20462        let snapshot = editor.snapshot(window, cx);
20463        let hunks = editor
20464            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20465            .collect::<Vec<_>>();
20466        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20467        let buffer_id = hunks[0].buffer_id;
20468        hunks
20469            .into_iter()
20470            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20471            .collect::<Vec<_>>()
20472    });
20473    assert_eq!(hunk_ranges.len(), 2);
20474
20475    cx.update_editor(|editor, _, cx| {
20476        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20477    });
20478    executor.run_until_parked();
20479
20480    cx.assert_state_with_diff(
20481        r#"
20482        - ˇa
20483        + A
20484          b
20485        "#
20486        .unindent(),
20487    );
20488}
20489
20490#[gpui::test]
20491async fn test_toggle_deletion_hunk_at_start_of_file(
20492    executor: BackgroundExecutor,
20493    cx: &mut TestAppContext,
20494) {
20495    init_test(cx, |_| {});
20496    let mut cx = EditorTestContext::new(cx).await;
20497
20498    let diff_base = r#"
20499        a
20500        b
20501        c
20502        "#
20503    .unindent();
20504
20505    cx.set_state(
20506        &r#"
20507        ˇb
20508        c
20509        "#
20510        .unindent(),
20511    );
20512    cx.set_head_text(&diff_base);
20513    cx.update_editor(|editor, window, cx| {
20514        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20515    });
20516    executor.run_until_parked();
20517
20518    let hunk_expanded = r#"
20519        - a
20520          ˇb
20521          c
20522        "#
20523    .unindent();
20524
20525    cx.assert_state_with_diff(hunk_expanded.clone());
20526
20527    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20528        let snapshot = editor.snapshot(window, cx);
20529        let hunks = editor
20530            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20531            .collect::<Vec<_>>();
20532        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20533        let buffer_id = hunks[0].buffer_id;
20534        hunks
20535            .into_iter()
20536            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20537            .collect::<Vec<_>>()
20538    });
20539    assert_eq!(hunk_ranges.len(), 1);
20540
20541    cx.update_editor(|editor, _, cx| {
20542        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20543    });
20544    executor.run_until_parked();
20545
20546    let hunk_collapsed = r#"
20547          ˇb
20548          c
20549        "#
20550    .unindent();
20551
20552    cx.assert_state_with_diff(hunk_collapsed);
20553
20554    cx.update_editor(|editor, _, cx| {
20555        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20556    });
20557    executor.run_until_parked();
20558
20559    cx.assert_state_with_diff(hunk_expanded);
20560}
20561
20562#[gpui::test]
20563async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20564    init_test(cx, |_| {});
20565
20566    let fs = FakeFs::new(cx.executor());
20567    fs.insert_tree(
20568        path!("/test"),
20569        json!({
20570            ".git": {},
20571            "file-1": "ONE\n",
20572            "file-2": "TWO\n",
20573            "file-3": "THREE\n",
20574        }),
20575    )
20576    .await;
20577
20578    fs.set_head_for_repo(
20579        path!("/test/.git").as_ref(),
20580        &[
20581            ("file-1".into(), "one\n".into()),
20582            ("file-2".into(), "two\n".into()),
20583            ("file-3".into(), "three\n".into()),
20584        ],
20585        "deadbeef",
20586    );
20587
20588    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20589    let mut buffers = vec![];
20590    for i in 1..=3 {
20591        let buffer = project
20592            .update(cx, |project, cx| {
20593                let path = format!(path!("/test/file-{}"), i);
20594                project.open_local_buffer(path, cx)
20595            })
20596            .await
20597            .unwrap();
20598        buffers.push(buffer);
20599    }
20600
20601    let multibuffer = cx.new(|cx| {
20602        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20603        multibuffer.set_all_diff_hunks_expanded(cx);
20604        for buffer in &buffers {
20605            let snapshot = buffer.read(cx).snapshot();
20606            multibuffer.set_excerpts_for_path(
20607                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20608                buffer.clone(),
20609                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20610                2,
20611                cx,
20612            );
20613        }
20614        multibuffer
20615    });
20616
20617    let editor = cx.add_window(|window, cx| {
20618        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20619    });
20620    cx.run_until_parked();
20621
20622    let snapshot = editor
20623        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20624        .unwrap();
20625    let hunks = snapshot
20626        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20627        .map(|hunk| match hunk {
20628            DisplayDiffHunk::Unfolded {
20629                display_row_range, ..
20630            } => display_row_range,
20631            DisplayDiffHunk::Folded { .. } => unreachable!(),
20632        })
20633        .collect::<Vec<_>>();
20634    assert_eq!(
20635        hunks,
20636        [
20637            DisplayRow(2)..DisplayRow(4),
20638            DisplayRow(7)..DisplayRow(9),
20639            DisplayRow(12)..DisplayRow(14),
20640        ]
20641    );
20642}
20643
20644#[gpui::test]
20645async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
20646    init_test(cx, |_| {});
20647
20648    let mut cx = EditorTestContext::new(cx).await;
20649    cx.set_head_text(indoc! { "
20650        one
20651        two
20652        three
20653        four
20654        five
20655        "
20656    });
20657    cx.set_index_text(indoc! { "
20658        one
20659        two
20660        three
20661        four
20662        five
20663        "
20664    });
20665    cx.set_state(indoc! {"
20666        one
20667        TWO
20668        ˇTHREE
20669        FOUR
20670        five
20671    "});
20672    cx.run_until_parked();
20673    cx.update_editor(|editor, window, cx| {
20674        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20675    });
20676    cx.run_until_parked();
20677    cx.assert_index_text(Some(indoc! {"
20678        one
20679        TWO
20680        THREE
20681        FOUR
20682        five
20683    "}));
20684    cx.set_state(indoc! { "
20685        one
20686        TWO
20687        ˇTHREE-HUNDRED
20688        FOUR
20689        five
20690    "});
20691    cx.run_until_parked();
20692    cx.update_editor(|editor, window, cx| {
20693        let snapshot = editor.snapshot(window, cx);
20694        let hunks = editor
20695            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20696            .collect::<Vec<_>>();
20697        assert_eq!(hunks.len(), 1);
20698        assert_eq!(
20699            hunks[0].status(),
20700            DiffHunkStatus {
20701                kind: DiffHunkStatusKind::Modified,
20702                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
20703            }
20704        );
20705
20706        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20707    });
20708    cx.run_until_parked();
20709    cx.assert_index_text(Some(indoc! {"
20710        one
20711        TWO
20712        THREE-HUNDRED
20713        FOUR
20714        five
20715    "}));
20716}
20717
20718#[gpui::test]
20719fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
20720    init_test(cx, |_| {});
20721
20722    let editor = cx.add_window(|window, cx| {
20723        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
20724        build_editor(buffer, window, cx)
20725    });
20726
20727    let render_args = Arc::new(Mutex::new(None));
20728    let snapshot = editor
20729        .update(cx, |editor, window, cx| {
20730            let snapshot = editor.buffer().read(cx).snapshot(cx);
20731            let range =
20732                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
20733
20734            struct RenderArgs {
20735                row: MultiBufferRow,
20736                folded: bool,
20737                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
20738            }
20739
20740            let crease = Crease::inline(
20741                range,
20742                FoldPlaceholder::test(),
20743                {
20744                    let toggle_callback = render_args.clone();
20745                    move |row, folded, callback, _window, _cx| {
20746                        *toggle_callback.lock() = Some(RenderArgs {
20747                            row,
20748                            folded,
20749                            callback,
20750                        });
20751                        div()
20752                    }
20753                },
20754                |_row, _folded, _window, _cx| div(),
20755            );
20756
20757            editor.insert_creases(Some(crease), cx);
20758            let snapshot = editor.snapshot(window, cx);
20759            let _div =
20760                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
20761            snapshot
20762        })
20763        .unwrap();
20764
20765    let render_args = render_args.lock().take().unwrap();
20766    assert_eq!(render_args.row, MultiBufferRow(1));
20767    assert!(!render_args.folded);
20768    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
20769
20770    cx.update_window(*editor, |_, window, cx| {
20771        (render_args.callback)(true, window, cx)
20772    })
20773    .unwrap();
20774    let snapshot = editor
20775        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20776        .unwrap();
20777    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
20778
20779    cx.update_window(*editor, |_, window, cx| {
20780        (render_args.callback)(false, window, cx)
20781    })
20782    .unwrap();
20783    let snapshot = editor
20784        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20785        .unwrap();
20786    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
20787}
20788
20789#[gpui::test]
20790async fn test_input_text(cx: &mut TestAppContext) {
20791    init_test(cx, |_| {});
20792    let mut cx = EditorTestContext::new(cx).await;
20793
20794    cx.set_state(
20795        &r#"ˇone
20796        two
20797
20798        three
20799        fourˇ
20800        five
20801
20802        siˇx"#
20803            .unindent(),
20804    );
20805
20806    cx.dispatch_action(HandleInput(String::new()));
20807    cx.assert_editor_state(
20808        &r#"ˇone
20809        two
20810
20811        three
20812        fourˇ
20813        five
20814
20815        siˇx"#
20816            .unindent(),
20817    );
20818
20819    cx.dispatch_action(HandleInput("AAAA".to_string()));
20820    cx.assert_editor_state(
20821        &r#"AAAAˇone
20822        two
20823
20824        three
20825        fourAAAAˇ
20826        five
20827
20828        siAAAAˇx"#
20829            .unindent(),
20830    );
20831}
20832
20833#[gpui::test]
20834async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
20835    init_test(cx, |_| {});
20836
20837    let mut cx = EditorTestContext::new(cx).await;
20838    cx.set_state(
20839        r#"let foo = 1;
20840let foo = 2;
20841let foo = 3;
20842let fooˇ = 4;
20843let foo = 5;
20844let foo = 6;
20845let foo = 7;
20846let foo = 8;
20847let foo = 9;
20848let foo = 10;
20849let foo = 11;
20850let foo = 12;
20851let foo = 13;
20852let foo = 14;
20853let foo = 15;"#,
20854    );
20855
20856    cx.update_editor(|e, window, cx| {
20857        assert_eq!(
20858            e.next_scroll_position,
20859            NextScrollCursorCenterTopBottom::Center,
20860            "Default next scroll direction is center",
20861        );
20862
20863        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20864        assert_eq!(
20865            e.next_scroll_position,
20866            NextScrollCursorCenterTopBottom::Top,
20867            "After center, next scroll direction should be top",
20868        );
20869
20870        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20871        assert_eq!(
20872            e.next_scroll_position,
20873            NextScrollCursorCenterTopBottom::Bottom,
20874            "After top, next scroll direction should be bottom",
20875        );
20876
20877        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20878        assert_eq!(
20879            e.next_scroll_position,
20880            NextScrollCursorCenterTopBottom::Center,
20881            "After bottom, scrolling should start over",
20882        );
20883
20884        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20885        assert_eq!(
20886            e.next_scroll_position,
20887            NextScrollCursorCenterTopBottom::Top,
20888            "Scrolling continues if retriggered fast enough"
20889        );
20890    });
20891
20892    cx.executor()
20893        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
20894    cx.executor().run_until_parked();
20895    cx.update_editor(|e, _, _| {
20896        assert_eq!(
20897            e.next_scroll_position,
20898            NextScrollCursorCenterTopBottom::Center,
20899            "If scrolling is not triggered fast enough, it should reset"
20900        );
20901    });
20902}
20903
20904#[gpui::test]
20905async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
20906    init_test(cx, |_| {});
20907    let mut cx = EditorLspTestContext::new_rust(
20908        lsp::ServerCapabilities {
20909            definition_provider: Some(lsp::OneOf::Left(true)),
20910            references_provider: Some(lsp::OneOf::Left(true)),
20911            ..lsp::ServerCapabilities::default()
20912        },
20913        cx,
20914    )
20915    .await;
20916
20917    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
20918        let go_to_definition = cx
20919            .lsp
20920            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20921                move |params, _| async move {
20922                    if empty_go_to_definition {
20923                        Ok(None)
20924                    } else {
20925                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
20926                            uri: params.text_document_position_params.text_document.uri,
20927                            range: lsp::Range::new(
20928                                lsp::Position::new(4, 3),
20929                                lsp::Position::new(4, 6),
20930                            ),
20931                        })))
20932                    }
20933                },
20934            );
20935        let references = cx
20936            .lsp
20937            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
20938                Ok(Some(vec![lsp::Location {
20939                    uri: params.text_document_position.text_document.uri,
20940                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
20941                }]))
20942            });
20943        (go_to_definition, references)
20944    };
20945
20946    cx.set_state(
20947        &r#"fn one() {
20948            let mut a = ˇtwo();
20949        }
20950
20951        fn two() {}"#
20952            .unindent(),
20953    );
20954    set_up_lsp_handlers(false, &mut cx);
20955    let navigated = cx
20956        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20957        .await
20958        .expect("Failed to navigate to definition");
20959    assert_eq!(
20960        navigated,
20961        Navigated::Yes,
20962        "Should have navigated to definition from the GetDefinition response"
20963    );
20964    cx.assert_editor_state(
20965        &r#"fn one() {
20966            let mut a = two();
20967        }
20968
20969        fn «twoˇ»() {}"#
20970            .unindent(),
20971    );
20972
20973    let editors = cx.update_workspace(|workspace, _, cx| {
20974        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20975    });
20976    cx.update_editor(|_, _, test_editor_cx| {
20977        assert_eq!(
20978            editors.len(),
20979            1,
20980            "Initially, only one, test, editor should be open in the workspace"
20981        );
20982        assert_eq!(
20983            test_editor_cx.entity(),
20984            editors.last().expect("Asserted len is 1").clone()
20985        );
20986    });
20987
20988    set_up_lsp_handlers(true, &mut cx);
20989    let navigated = cx
20990        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20991        .await
20992        .expect("Failed to navigate to lookup references");
20993    assert_eq!(
20994        navigated,
20995        Navigated::Yes,
20996        "Should have navigated to references as a fallback after empty GoToDefinition response"
20997    );
20998    // We should not change the selections in the existing file,
20999    // if opening another milti buffer with the references
21000    cx.assert_editor_state(
21001        &r#"fn one() {
21002            let mut a = two();
21003        }
21004
21005        fn «twoˇ»() {}"#
21006            .unindent(),
21007    );
21008    let editors = cx.update_workspace(|workspace, _, cx| {
21009        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21010    });
21011    cx.update_editor(|_, _, test_editor_cx| {
21012        assert_eq!(
21013            editors.len(),
21014            2,
21015            "After falling back to references search, we open a new editor with the results"
21016        );
21017        let references_fallback_text = editors
21018            .into_iter()
21019            .find(|new_editor| *new_editor != test_editor_cx.entity())
21020            .expect("Should have one non-test editor now")
21021            .read(test_editor_cx)
21022            .text(test_editor_cx);
21023        assert_eq!(
21024            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21025            "Should use the range from the references response and not the GoToDefinition one"
21026        );
21027    });
21028}
21029
21030#[gpui::test]
21031async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21032    init_test(cx, |_| {});
21033    cx.update(|cx| {
21034        let mut editor_settings = EditorSettings::get_global(cx).clone();
21035        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21036        EditorSettings::override_global(editor_settings, cx);
21037    });
21038    let mut cx = EditorLspTestContext::new_rust(
21039        lsp::ServerCapabilities {
21040            definition_provider: Some(lsp::OneOf::Left(true)),
21041            references_provider: Some(lsp::OneOf::Left(true)),
21042            ..lsp::ServerCapabilities::default()
21043        },
21044        cx,
21045    )
21046    .await;
21047    let original_state = r#"fn one() {
21048        let mut a = ˇtwo();
21049    }
21050
21051    fn two() {}"#
21052        .unindent();
21053    cx.set_state(&original_state);
21054
21055    let mut go_to_definition = cx
21056        .lsp
21057        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21058            move |_, _| async move { Ok(None) },
21059        );
21060    let _references = cx
21061        .lsp
21062        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21063            panic!("Should not call for references with no go to definition fallback")
21064        });
21065
21066    let navigated = cx
21067        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21068        .await
21069        .expect("Failed to navigate to lookup references");
21070    go_to_definition
21071        .next()
21072        .await
21073        .expect("Should have called the go_to_definition handler");
21074
21075    assert_eq!(
21076        navigated,
21077        Navigated::No,
21078        "Should have navigated to references as a fallback after empty GoToDefinition response"
21079    );
21080    cx.assert_editor_state(&original_state);
21081    let editors = cx.update_workspace(|workspace, _, cx| {
21082        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21083    });
21084    cx.update_editor(|_, _, _| {
21085        assert_eq!(
21086            editors.len(),
21087            1,
21088            "After unsuccessful fallback, no other editor should have been opened"
21089        );
21090    });
21091}
21092
21093#[gpui::test]
21094async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21095    init_test(cx, |_| {});
21096
21097    let language = Arc::new(Language::new(
21098        LanguageConfig::default(),
21099        Some(tree_sitter_rust::LANGUAGE.into()),
21100    ));
21101
21102    let text = r#"
21103        #[cfg(test)]
21104        mod tests() {
21105            #[test]
21106            fn runnable_1() {
21107                let a = 1;
21108            }
21109
21110            #[test]
21111            fn runnable_2() {
21112                let a = 1;
21113                let b = 2;
21114            }
21115        }
21116    "#
21117    .unindent();
21118
21119    let fs = FakeFs::new(cx.executor());
21120    fs.insert_file("/file.rs", Default::default()).await;
21121
21122    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21123    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21124    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21125    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21126    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21127
21128    let editor = cx.new_window_entity(|window, cx| {
21129        Editor::new(
21130            EditorMode::full(),
21131            multi_buffer,
21132            Some(project.clone()),
21133            window,
21134            cx,
21135        )
21136    });
21137
21138    editor.update_in(cx, |editor, window, cx| {
21139        let snapshot = editor.buffer().read(cx).snapshot(cx);
21140        editor.tasks.insert(
21141            (buffer.read(cx).remote_id(), 3),
21142            RunnableTasks {
21143                templates: vec![],
21144                offset: snapshot.anchor_before(43),
21145                column: 0,
21146                extra_variables: HashMap::default(),
21147                context_range: BufferOffset(43)..BufferOffset(85),
21148            },
21149        );
21150        editor.tasks.insert(
21151            (buffer.read(cx).remote_id(), 8),
21152            RunnableTasks {
21153                templates: vec![],
21154                offset: snapshot.anchor_before(86),
21155                column: 0,
21156                extra_variables: HashMap::default(),
21157                context_range: BufferOffset(86)..BufferOffset(191),
21158            },
21159        );
21160
21161        // Test finding task when cursor is inside function body
21162        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21163            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21164        });
21165        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21166        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21167
21168        // Test finding task when cursor is on function name
21169        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21170            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21171        });
21172        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21173        assert_eq!(row, 8, "Should find task when cursor is on function name");
21174    });
21175}
21176
21177#[gpui::test]
21178async fn test_folding_buffers(cx: &mut TestAppContext) {
21179    init_test(cx, |_| {});
21180
21181    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21182    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21183    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21184
21185    let fs = FakeFs::new(cx.executor());
21186    fs.insert_tree(
21187        path!("/a"),
21188        json!({
21189            "first.rs": sample_text_1,
21190            "second.rs": sample_text_2,
21191            "third.rs": sample_text_3,
21192        }),
21193    )
21194    .await;
21195    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21196    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21197    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21198    let worktree = project.update(cx, |project, cx| {
21199        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21200        assert_eq!(worktrees.len(), 1);
21201        worktrees.pop().unwrap()
21202    });
21203    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21204
21205    let buffer_1 = project
21206        .update(cx, |project, cx| {
21207            project.open_buffer((worktree_id, "first.rs"), cx)
21208        })
21209        .await
21210        .unwrap();
21211    let buffer_2 = project
21212        .update(cx, |project, cx| {
21213            project.open_buffer((worktree_id, "second.rs"), cx)
21214        })
21215        .await
21216        .unwrap();
21217    let buffer_3 = project
21218        .update(cx, |project, cx| {
21219            project.open_buffer((worktree_id, "third.rs"), cx)
21220        })
21221        .await
21222        .unwrap();
21223
21224    let multi_buffer = cx.new(|cx| {
21225        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21226        multi_buffer.push_excerpts(
21227            buffer_1.clone(),
21228            [
21229                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21230                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21231                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21232            ],
21233            cx,
21234        );
21235        multi_buffer.push_excerpts(
21236            buffer_2.clone(),
21237            [
21238                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21239                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21240                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21241            ],
21242            cx,
21243        );
21244        multi_buffer.push_excerpts(
21245            buffer_3.clone(),
21246            [
21247                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21248                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21249                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21250            ],
21251            cx,
21252        );
21253        multi_buffer
21254    });
21255    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21256        Editor::new(
21257            EditorMode::full(),
21258            multi_buffer.clone(),
21259            Some(project.clone()),
21260            window,
21261            cx,
21262        )
21263    });
21264
21265    assert_eq!(
21266        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21267        "\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",
21268    );
21269
21270    multi_buffer_editor.update(cx, |editor, cx| {
21271        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21272    });
21273    assert_eq!(
21274        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21275        "\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",
21276        "After folding the first buffer, its text should not be displayed"
21277    );
21278
21279    multi_buffer_editor.update(cx, |editor, cx| {
21280        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21281    });
21282    assert_eq!(
21283        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21284        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21285        "After folding the second buffer, its text should not be displayed"
21286    );
21287
21288    multi_buffer_editor.update(cx, |editor, cx| {
21289        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21290    });
21291    assert_eq!(
21292        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21293        "\n\n\n\n\n",
21294        "After folding the third buffer, its text should not be displayed"
21295    );
21296
21297    // Emulate selection inside the fold logic, that should work
21298    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21299        editor
21300            .snapshot(window, cx)
21301            .next_line_boundary(Point::new(0, 4));
21302    });
21303
21304    multi_buffer_editor.update(cx, |editor, cx| {
21305        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21306    });
21307    assert_eq!(
21308        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21309        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21310        "After unfolding the second buffer, its text should be displayed"
21311    );
21312
21313    // Typing inside of buffer 1 causes that buffer to be unfolded.
21314    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21315        assert_eq!(
21316            multi_buffer
21317                .read(cx)
21318                .snapshot(cx)
21319                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21320                .collect::<String>(),
21321            "bbbb"
21322        );
21323        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21324            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21325        });
21326        editor.handle_input("B", window, cx);
21327    });
21328
21329    assert_eq!(
21330        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21331        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21332        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21333    );
21334
21335    multi_buffer_editor.update(cx, |editor, cx| {
21336        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21337    });
21338    assert_eq!(
21339        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21340        "\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",
21341        "After unfolding the all buffers, all original text should be displayed"
21342    );
21343}
21344
21345#[gpui::test]
21346async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21347    init_test(cx, |_| {});
21348
21349    let sample_text_1 = "1111\n2222\n3333".to_string();
21350    let sample_text_2 = "4444\n5555\n6666".to_string();
21351    let sample_text_3 = "7777\n8888\n9999".to_string();
21352
21353    let fs = FakeFs::new(cx.executor());
21354    fs.insert_tree(
21355        path!("/a"),
21356        json!({
21357            "first.rs": sample_text_1,
21358            "second.rs": sample_text_2,
21359            "third.rs": sample_text_3,
21360        }),
21361    )
21362    .await;
21363    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21364    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21365    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21366    let worktree = project.update(cx, |project, cx| {
21367        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21368        assert_eq!(worktrees.len(), 1);
21369        worktrees.pop().unwrap()
21370    });
21371    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21372
21373    let buffer_1 = project
21374        .update(cx, |project, cx| {
21375            project.open_buffer((worktree_id, "first.rs"), cx)
21376        })
21377        .await
21378        .unwrap();
21379    let buffer_2 = project
21380        .update(cx, |project, cx| {
21381            project.open_buffer((worktree_id, "second.rs"), cx)
21382        })
21383        .await
21384        .unwrap();
21385    let buffer_3 = project
21386        .update(cx, |project, cx| {
21387            project.open_buffer((worktree_id, "third.rs"), cx)
21388        })
21389        .await
21390        .unwrap();
21391
21392    let multi_buffer = cx.new(|cx| {
21393        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21394        multi_buffer.push_excerpts(
21395            buffer_1.clone(),
21396            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21397            cx,
21398        );
21399        multi_buffer.push_excerpts(
21400            buffer_2.clone(),
21401            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21402            cx,
21403        );
21404        multi_buffer.push_excerpts(
21405            buffer_3.clone(),
21406            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21407            cx,
21408        );
21409        multi_buffer
21410    });
21411
21412    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21413        Editor::new(
21414            EditorMode::full(),
21415            multi_buffer,
21416            Some(project.clone()),
21417            window,
21418            cx,
21419        )
21420    });
21421
21422    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21423    assert_eq!(
21424        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21425        full_text,
21426    );
21427
21428    multi_buffer_editor.update(cx, |editor, cx| {
21429        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21430    });
21431    assert_eq!(
21432        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21433        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21434        "After folding the first buffer, its text should not be displayed"
21435    );
21436
21437    multi_buffer_editor.update(cx, |editor, cx| {
21438        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21439    });
21440
21441    assert_eq!(
21442        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21443        "\n\n\n\n\n\n7777\n8888\n9999",
21444        "After folding the second buffer, its text should not be displayed"
21445    );
21446
21447    multi_buffer_editor.update(cx, |editor, cx| {
21448        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21449    });
21450    assert_eq!(
21451        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21452        "\n\n\n\n\n",
21453        "After folding the third buffer, its text should not be displayed"
21454    );
21455
21456    multi_buffer_editor.update(cx, |editor, cx| {
21457        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21458    });
21459    assert_eq!(
21460        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21461        "\n\n\n\n4444\n5555\n6666\n\n",
21462        "After unfolding the second buffer, its text should be displayed"
21463    );
21464
21465    multi_buffer_editor.update(cx, |editor, cx| {
21466        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21467    });
21468    assert_eq!(
21469        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21470        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21471        "After unfolding the first buffer, its text should be displayed"
21472    );
21473
21474    multi_buffer_editor.update(cx, |editor, cx| {
21475        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21476    });
21477    assert_eq!(
21478        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21479        full_text,
21480        "After unfolding all buffers, all original text should be displayed"
21481    );
21482}
21483
21484#[gpui::test]
21485async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21486    init_test(cx, |_| {});
21487
21488    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21489
21490    let fs = FakeFs::new(cx.executor());
21491    fs.insert_tree(
21492        path!("/a"),
21493        json!({
21494            "main.rs": sample_text,
21495        }),
21496    )
21497    .await;
21498    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21499    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21500    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21501    let worktree = project.update(cx, |project, cx| {
21502        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21503        assert_eq!(worktrees.len(), 1);
21504        worktrees.pop().unwrap()
21505    });
21506    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21507
21508    let buffer_1 = project
21509        .update(cx, |project, cx| {
21510            project.open_buffer((worktree_id, "main.rs"), cx)
21511        })
21512        .await
21513        .unwrap();
21514
21515    let multi_buffer = cx.new(|cx| {
21516        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21517        multi_buffer.push_excerpts(
21518            buffer_1.clone(),
21519            [ExcerptRange::new(
21520                Point::new(0, 0)
21521                    ..Point::new(
21522                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
21523                        0,
21524                    ),
21525            )],
21526            cx,
21527        );
21528        multi_buffer
21529    });
21530    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21531        Editor::new(
21532            EditorMode::full(),
21533            multi_buffer,
21534            Some(project.clone()),
21535            window,
21536            cx,
21537        )
21538    });
21539
21540    let selection_range = Point::new(1, 0)..Point::new(2, 0);
21541    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21542        enum TestHighlight {}
21543        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
21544        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
21545        editor.highlight_text::<TestHighlight>(
21546            vec![highlight_range.clone()],
21547            HighlightStyle::color(Hsla::green()),
21548            cx,
21549        );
21550        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21551            s.select_ranges(Some(highlight_range))
21552        });
21553    });
21554
21555    let full_text = format!("\n\n{sample_text}");
21556    assert_eq!(
21557        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21558        full_text,
21559    );
21560}
21561
21562#[gpui::test]
21563async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
21564    init_test(cx, |_| {});
21565    cx.update(|cx| {
21566        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
21567            "keymaps/default-linux.json",
21568            cx,
21569        )
21570        .unwrap();
21571        cx.bind_keys(default_key_bindings);
21572    });
21573
21574    let (editor, cx) = cx.add_window_view(|window, cx| {
21575        let multi_buffer = MultiBuffer::build_multi(
21576            [
21577                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
21578                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
21579                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
21580                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
21581            ],
21582            cx,
21583        );
21584        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
21585
21586        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
21587        // fold all but the second buffer, so that we test navigating between two
21588        // adjacent folded buffers, as well as folded buffers at the start and
21589        // end the multibuffer
21590        editor.fold_buffer(buffer_ids[0], cx);
21591        editor.fold_buffer(buffer_ids[2], cx);
21592        editor.fold_buffer(buffer_ids[3], cx);
21593
21594        editor
21595    });
21596    cx.simulate_resize(size(px(1000.), px(1000.)));
21597
21598    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
21599    cx.assert_excerpts_with_selections(indoc! {"
21600        [EXCERPT]
21601        ˇ[FOLDED]
21602        [EXCERPT]
21603        a1
21604        b1
21605        [EXCERPT]
21606        [FOLDED]
21607        [EXCERPT]
21608        [FOLDED]
21609        "
21610    });
21611    cx.simulate_keystroke("down");
21612    cx.assert_excerpts_with_selections(indoc! {"
21613        [EXCERPT]
21614        [FOLDED]
21615        [EXCERPT]
21616        ˇa1
21617        b1
21618        [EXCERPT]
21619        [FOLDED]
21620        [EXCERPT]
21621        [FOLDED]
21622        "
21623    });
21624    cx.simulate_keystroke("down");
21625    cx.assert_excerpts_with_selections(indoc! {"
21626        [EXCERPT]
21627        [FOLDED]
21628        [EXCERPT]
21629        a1
21630        ˇb1
21631        [EXCERPT]
21632        [FOLDED]
21633        [EXCERPT]
21634        [FOLDED]
21635        "
21636    });
21637    cx.simulate_keystroke("down");
21638    cx.assert_excerpts_with_selections(indoc! {"
21639        [EXCERPT]
21640        [FOLDED]
21641        [EXCERPT]
21642        a1
21643        b1
21644        ˇ[EXCERPT]
21645        [FOLDED]
21646        [EXCERPT]
21647        [FOLDED]
21648        "
21649    });
21650    cx.simulate_keystroke("down");
21651    cx.assert_excerpts_with_selections(indoc! {"
21652        [EXCERPT]
21653        [FOLDED]
21654        [EXCERPT]
21655        a1
21656        b1
21657        [EXCERPT]
21658        ˇ[FOLDED]
21659        [EXCERPT]
21660        [FOLDED]
21661        "
21662    });
21663    for _ in 0..5 {
21664        cx.simulate_keystroke("down");
21665        cx.assert_excerpts_with_selections(indoc! {"
21666            [EXCERPT]
21667            [FOLDED]
21668            [EXCERPT]
21669            a1
21670            b1
21671            [EXCERPT]
21672            [FOLDED]
21673            [EXCERPT]
21674            ˇ[FOLDED]
21675            "
21676        });
21677    }
21678
21679    cx.simulate_keystroke("up");
21680    cx.assert_excerpts_with_selections(indoc! {"
21681        [EXCERPT]
21682        [FOLDED]
21683        [EXCERPT]
21684        a1
21685        b1
21686        [EXCERPT]
21687        ˇ[FOLDED]
21688        [EXCERPT]
21689        [FOLDED]
21690        "
21691    });
21692    cx.simulate_keystroke("up");
21693    cx.assert_excerpts_with_selections(indoc! {"
21694        [EXCERPT]
21695        [FOLDED]
21696        [EXCERPT]
21697        a1
21698        b1
21699        ˇ[EXCERPT]
21700        [FOLDED]
21701        [EXCERPT]
21702        [FOLDED]
21703        "
21704    });
21705    cx.simulate_keystroke("up");
21706    cx.assert_excerpts_with_selections(indoc! {"
21707        [EXCERPT]
21708        [FOLDED]
21709        [EXCERPT]
21710        a1
21711        ˇb1
21712        [EXCERPT]
21713        [FOLDED]
21714        [EXCERPT]
21715        [FOLDED]
21716        "
21717    });
21718    cx.simulate_keystroke("up");
21719    cx.assert_excerpts_with_selections(indoc! {"
21720        [EXCERPT]
21721        [FOLDED]
21722        [EXCERPT]
21723        ˇa1
21724        b1
21725        [EXCERPT]
21726        [FOLDED]
21727        [EXCERPT]
21728        [FOLDED]
21729        "
21730    });
21731    for _ in 0..5 {
21732        cx.simulate_keystroke("up");
21733        cx.assert_excerpts_with_selections(indoc! {"
21734            [EXCERPT]
21735            ˇ[FOLDED]
21736            [EXCERPT]
21737            a1
21738            b1
21739            [EXCERPT]
21740            [FOLDED]
21741            [EXCERPT]
21742            [FOLDED]
21743            "
21744        });
21745    }
21746}
21747
21748#[gpui::test]
21749async fn test_edit_prediction_text(cx: &mut TestAppContext) {
21750    init_test(cx, |_| {});
21751
21752    // Simple insertion
21753    assert_highlighted_edits(
21754        "Hello, world!",
21755        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
21756        true,
21757        cx,
21758        |highlighted_edits, cx| {
21759            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
21760            assert_eq!(highlighted_edits.highlights.len(), 1);
21761            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
21762            assert_eq!(
21763                highlighted_edits.highlights[0].1.background_color,
21764                Some(cx.theme().status().created_background)
21765            );
21766        },
21767    )
21768    .await;
21769
21770    // Replacement
21771    assert_highlighted_edits(
21772        "This is a test.",
21773        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
21774        false,
21775        cx,
21776        |highlighted_edits, cx| {
21777            assert_eq!(highlighted_edits.text, "That is a test.");
21778            assert_eq!(highlighted_edits.highlights.len(), 1);
21779            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
21780            assert_eq!(
21781                highlighted_edits.highlights[0].1.background_color,
21782                Some(cx.theme().status().created_background)
21783            );
21784        },
21785    )
21786    .await;
21787
21788    // Multiple edits
21789    assert_highlighted_edits(
21790        "Hello, world!",
21791        vec![
21792            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
21793            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
21794        ],
21795        false,
21796        cx,
21797        |highlighted_edits, cx| {
21798            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
21799            assert_eq!(highlighted_edits.highlights.len(), 2);
21800            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
21801            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
21802            assert_eq!(
21803                highlighted_edits.highlights[0].1.background_color,
21804                Some(cx.theme().status().created_background)
21805            );
21806            assert_eq!(
21807                highlighted_edits.highlights[1].1.background_color,
21808                Some(cx.theme().status().created_background)
21809            );
21810        },
21811    )
21812    .await;
21813
21814    // Multiple lines with edits
21815    assert_highlighted_edits(
21816        "First line\nSecond line\nThird line\nFourth line",
21817        vec![
21818            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
21819            (
21820                Point::new(2, 0)..Point::new(2, 10),
21821                "New third line".to_string(),
21822            ),
21823            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
21824        ],
21825        false,
21826        cx,
21827        |highlighted_edits, cx| {
21828            assert_eq!(
21829                highlighted_edits.text,
21830                "Second modified\nNew third line\nFourth updated line"
21831            );
21832            assert_eq!(highlighted_edits.highlights.len(), 3);
21833            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
21834            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
21835            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
21836            for highlight in &highlighted_edits.highlights {
21837                assert_eq!(
21838                    highlight.1.background_color,
21839                    Some(cx.theme().status().created_background)
21840                );
21841            }
21842        },
21843    )
21844    .await;
21845}
21846
21847#[gpui::test]
21848async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
21849    init_test(cx, |_| {});
21850
21851    // Deletion
21852    assert_highlighted_edits(
21853        "Hello, world!",
21854        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
21855        true,
21856        cx,
21857        |highlighted_edits, cx| {
21858            assert_eq!(highlighted_edits.text, "Hello, world!");
21859            assert_eq!(highlighted_edits.highlights.len(), 1);
21860            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
21861            assert_eq!(
21862                highlighted_edits.highlights[0].1.background_color,
21863                Some(cx.theme().status().deleted_background)
21864            );
21865        },
21866    )
21867    .await;
21868
21869    // Insertion
21870    assert_highlighted_edits(
21871        "Hello, world!",
21872        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
21873        true,
21874        cx,
21875        |highlighted_edits, cx| {
21876            assert_eq!(highlighted_edits.highlights.len(), 1);
21877            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
21878            assert_eq!(
21879                highlighted_edits.highlights[0].1.background_color,
21880                Some(cx.theme().status().created_background)
21881            );
21882        },
21883    )
21884    .await;
21885}
21886
21887async fn assert_highlighted_edits(
21888    text: &str,
21889    edits: Vec<(Range<Point>, String)>,
21890    include_deletions: bool,
21891    cx: &mut TestAppContext,
21892    assertion_fn: impl Fn(HighlightedText, &App),
21893) {
21894    let window = cx.add_window(|window, cx| {
21895        let buffer = MultiBuffer::build_simple(text, cx);
21896        Editor::new(EditorMode::full(), buffer, None, window, cx)
21897    });
21898    let cx = &mut VisualTestContext::from_window(*window, cx);
21899
21900    let (buffer, snapshot) = window
21901        .update(cx, |editor, _window, cx| {
21902            (
21903                editor.buffer().clone(),
21904                editor.buffer().read(cx).snapshot(cx),
21905            )
21906        })
21907        .unwrap();
21908
21909    let edits = edits
21910        .into_iter()
21911        .map(|(range, edit)| {
21912            (
21913                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
21914                edit,
21915            )
21916        })
21917        .collect::<Vec<_>>();
21918
21919    let text_anchor_edits = edits
21920        .clone()
21921        .into_iter()
21922        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
21923        .collect::<Vec<_>>();
21924
21925    let edit_preview = window
21926        .update(cx, |_, _window, cx| {
21927            buffer
21928                .read(cx)
21929                .as_singleton()
21930                .unwrap()
21931                .read(cx)
21932                .preview_edits(text_anchor_edits.into(), cx)
21933        })
21934        .unwrap()
21935        .await;
21936
21937    cx.update(|_window, cx| {
21938        let highlighted_edits = edit_prediction_edit_text(
21939            snapshot.as_singleton().unwrap().2,
21940            &edits,
21941            &edit_preview,
21942            include_deletions,
21943            cx,
21944        );
21945        assertion_fn(highlighted_edits, cx)
21946    });
21947}
21948
21949#[track_caller]
21950fn assert_breakpoint(
21951    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
21952    path: &Arc<Path>,
21953    expected: Vec<(u32, Breakpoint)>,
21954) {
21955    if expected.is_empty() {
21956        assert!(!breakpoints.contains_key(path), "{}", path.display());
21957    } else {
21958        let mut breakpoint = breakpoints
21959            .get(path)
21960            .unwrap()
21961            .iter()
21962            .map(|breakpoint| {
21963                (
21964                    breakpoint.row,
21965                    Breakpoint {
21966                        message: breakpoint.message.clone(),
21967                        state: breakpoint.state,
21968                        condition: breakpoint.condition.clone(),
21969                        hit_condition: breakpoint.hit_condition.clone(),
21970                    },
21971                )
21972            })
21973            .collect::<Vec<_>>();
21974
21975        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
21976
21977        assert_eq!(expected, breakpoint);
21978    }
21979}
21980
21981fn add_log_breakpoint_at_cursor(
21982    editor: &mut Editor,
21983    log_message: &str,
21984    window: &mut Window,
21985    cx: &mut Context<Editor>,
21986) {
21987    let (anchor, bp) = editor
21988        .breakpoints_at_cursors(window, cx)
21989        .first()
21990        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
21991        .unwrap_or_else(|| {
21992            let cursor_position: Point = editor.selections.newest(cx).head();
21993
21994            let breakpoint_position = editor
21995                .snapshot(window, cx)
21996                .display_snapshot
21997                .buffer_snapshot
21998                .anchor_before(Point::new(cursor_position.row, 0));
21999
22000            (breakpoint_position, Breakpoint::new_log(log_message))
22001        });
22002
22003    editor.edit_breakpoint_at_anchor(
22004        anchor,
22005        bp,
22006        BreakpointEditAction::EditLogMessage(log_message.into()),
22007        cx,
22008    );
22009}
22010
22011#[gpui::test]
22012async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22013    init_test(cx, |_| {});
22014
22015    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22016    let fs = FakeFs::new(cx.executor());
22017    fs.insert_tree(
22018        path!("/a"),
22019        json!({
22020            "main.rs": sample_text,
22021        }),
22022    )
22023    .await;
22024    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22025    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22026    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22027
22028    let fs = FakeFs::new(cx.executor());
22029    fs.insert_tree(
22030        path!("/a"),
22031        json!({
22032            "main.rs": sample_text,
22033        }),
22034    )
22035    .await;
22036    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22037    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22038    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22039    let worktree_id = workspace
22040        .update(cx, |workspace, _window, cx| {
22041            workspace.project().update(cx, |project, cx| {
22042                project.worktrees(cx).next().unwrap().read(cx).id()
22043            })
22044        })
22045        .unwrap();
22046
22047    let buffer = project
22048        .update(cx, |project, cx| {
22049            project.open_buffer((worktree_id, "main.rs"), cx)
22050        })
22051        .await
22052        .unwrap();
22053
22054    let (editor, cx) = cx.add_window_view(|window, cx| {
22055        Editor::new(
22056            EditorMode::full(),
22057            MultiBuffer::build_from_buffer(buffer, cx),
22058            Some(project.clone()),
22059            window,
22060            cx,
22061        )
22062    });
22063
22064    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22065    let abs_path = project.read_with(cx, |project, cx| {
22066        project
22067            .absolute_path(&project_path, cx)
22068            .map(Arc::from)
22069            .unwrap()
22070    });
22071
22072    // assert we can add breakpoint on the first line
22073    editor.update_in(cx, |editor, window, cx| {
22074        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22075        editor.move_to_end(&MoveToEnd, window, cx);
22076        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22077    });
22078
22079    let breakpoints = editor.update(cx, |editor, cx| {
22080        editor
22081            .breakpoint_store()
22082            .as_ref()
22083            .unwrap()
22084            .read(cx)
22085            .all_source_breakpoints(cx)
22086    });
22087
22088    assert_eq!(1, breakpoints.len());
22089    assert_breakpoint(
22090        &breakpoints,
22091        &abs_path,
22092        vec![
22093            (0, Breakpoint::new_standard()),
22094            (3, Breakpoint::new_standard()),
22095        ],
22096    );
22097
22098    editor.update_in(cx, |editor, window, cx| {
22099        editor.move_to_beginning(&MoveToBeginning, window, cx);
22100        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22101    });
22102
22103    let breakpoints = editor.update(cx, |editor, cx| {
22104        editor
22105            .breakpoint_store()
22106            .as_ref()
22107            .unwrap()
22108            .read(cx)
22109            .all_source_breakpoints(cx)
22110    });
22111
22112    assert_eq!(1, breakpoints.len());
22113    assert_breakpoint(
22114        &breakpoints,
22115        &abs_path,
22116        vec![(3, Breakpoint::new_standard())],
22117    );
22118
22119    editor.update_in(cx, |editor, window, cx| {
22120        editor.move_to_end(&MoveToEnd, window, cx);
22121        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22122    });
22123
22124    let breakpoints = editor.update(cx, |editor, cx| {
22125        editor
22126            .breakpoint_store()
22127            .as_ref()
22128            .unwrap()
22129            .read(cx)
22130            .all_source_breakpoints(cx)
22131    });
22132
22133    assert_eq!(0, breakpoints.len());
22134    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22135}
22136
22137#[gpui::test]
22138async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22139    init_test(cx, |_| {});
22140
22141    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22142
22143    let fs = FakeFs::new(cx.executor());
22144    fs.insert_tree(
22145        path!("/a"),
22146        json!({
22147            "main.rs": sample_text,
22148        }),
22149    )
22150    .await;
22151    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22152    let (workspace, cx) =
22153        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22154
22155    let worktree_id = workspace.update(cx, |workspace, cx| {
22156        workspace.project().update(cx, |project, cx| {
22157            project.worktrees(cx).next().unwrap().read(cx).id()
22158        })
22159    });
22160
22161    let buffer = project
22162        .update(cx, |project, cx| {
22163            project.open_buffer((worktree_id, "main.rs"), cx)
22164        })
22165        .await
22166        .unwrap();
22167
22168    let (editor, cx) = cx.add_window_view(|window, cx| {
22169        Editor::new(
22170            EditorMode::full(),
22171            MultiBuffer::build_from_buffer(buffer, cx),
22172            Some(project.clone()),
22173            window,
22174            cx,
22175        )
22176    });
22177
22178    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22179    let abs_path = project.read_with(cx, |project, cx| {
22180        project
22181            .absolute_path(&project_path, cx)
22182            .map(Arc::from)
22183            .unwrap()
22184    });
22185
22186    editor.update_in(cx, |editor, window, cx| {
22187        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22188    });
22189
22190    let breakpoints = editor.update(cx, |editor, cx| {
22191        editor
22192            .breakpoint_store()
22193            .as_ref()
22194            .unwrap()
22195            .read(cx)
22196            .all_source_breakpoints(cx)
22197    });
22198
22199    assert_breakpoint(
22200        &breakpoints,
22201        &abs_path,
22202        vec![(0, Breakpoint::new_log("hello world"))],
22203    );
22204
22205    // Removing a log message from a log breakpoint should remove it
22206    editor.update_in(cx, |editor, window, cx| {
22207        add_log_breakpoint_at_cursor(editor, "", window, cx);
22208    });
22209
22210    let breakpoints = editor.update(cx, |editor, cx| {
22211        editor
22212            .breakpoint_store()
22213            .as_ref()
22214            .unwrap()
22215            .read(cx)
22216            .all_source_breakpoints(cx)
22217    });
22218
22219    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22220
22221    editor.update_in(cx, |editor, window, cx| {
22222        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22223        editor.move_to_end(&MoveToEnd, window, cx);
22224        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22225        // Not adding a log message to a standard breakpoint shouldn't remove it
22226        add_log_breakpoint_at_cursor(editor, "", window, cx);
22227    });
22228
22229    let breakpoints = editor.update(cx, |editor, cx| {
22230        editor
22231            .breakpoint_store()
22232            .as_ref()
22233            .unwrap()
22234            .read(cx)
22235            .all_source_breakpoints(cx)
22236    });
22237
22238    assert_breakpoint(
22239        &breakpoints,
22240        &abs_path,
22241        vec![
22242            (0, Breakpoint::new_standard()),
22243            (3, Breakpoint::new_standard()),
22244        ],
22245    );
22246
22247    editor.update_in(cx, |editor, window, cx| {
22248        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22249    });
22250
22251    let breakpoints = editor.update(cx, |editor, cx| {
22252        editor
22253            .breakpoint_store()
22254            .as_ref()
22255            .unwrap()
22256            .read(cx)
22257            .all_source_breakpoints(cx)
22258    });
22259
22260    assert_breakpoint(
22261        &breakpoints,
22262        &abs_path,
22263        vec![
22264            (0, Breakpoint::new_standard()),
22265            (3, Breakpoint::new_log("hello world")),
22266        ],
22267    );
22268
22269    editor.update_in(cx, |editor, window, cx| {
22270        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22271    });
22272
22273    let breakpoints = editor.update(cx, |editor, cx| {
22274        editor
22275            .breakpoint_store()
22276            .as_ref()
22277            .unwrap()
22278            .read(cx)
22279            .all_source_breakpoints(cx)
22280    });
22281
22282    assert_breakpoint(
22283        &breakpoints,
22284        &abs_path,
22285        vec![
22286            (0, Breakpoint::new_standard()),
22287            (3, Breakpoint::new_log("hello Earth!!")),
22288        ],
22289    );
22290}
22291
22292/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22293/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22294/// or when breakpoints were placed out of order. This tests for a regression too
22295#[gpui::test]
22296async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22297    init_test(cx, |_| {});
22298
22299    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22300    let fs = FakeFs::new(cx.executor());
22301    fs.insert_tree(
22302        path!("/a"),
22303        json!({
22304            "main.rs": sample_text,
22305        }),
22306    )
22307    .await;
22308    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22309    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22310    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22311
22312    let fs = FakeFs::new(cx.executor());
22313    fs.insert_tree(
22314        path!("/a"),
22315        json!({
22316            "main.rs": sample_text,
22317        }),
22318    )
22319    .await;
22320    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22321    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22322    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22323    let worktree_id = workspace
22324        .update(cx, |workspace, _window, cx| {
22325            workspace.project().update(cx, |project, cx| {
22326                project.worktrees(cx).next().unwrap().read(cx).id()
22327            })
22328        })
22329        .unwrap();
22330
22331    let buffer = project
22332        .update(cx, |project, cx| {
22333            project.open_buffer((worktree_id, "main.rs"), cx)
22334        })
22335        .await
22336        .unwrap();
22337
22338    let (editor, cx) = cx.add_window_view(|window, cx| {
22339        Editor::new(
22340            EditorMode::full(),
22341            MultiBuffer::build_from_buffer(buffer, cx),
22342            Some(project.clone()),
22343            window,
22344            cx,
22345        )
22346    });
22347
22348    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22349    let abs_path = project.read_with(cx, |project, cx| {
22350        project
22351            .absolute_path(&project_path, cx)
22352            .map(Arc::from)
22353            .unwrap()
22354    });
22355
22356    // assert we can add breakpoint on the first line
22357    editor.update_in(cx, |editor, window, cx| {
22358        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22359        editor.move_to_end(&MoveToEnd, window, cx);
22360        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22361        editor.move_up(&MoveUp, window, cx);
22362        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22363    });
22364
22365    let breakpoints = editor.update(cx, |editor, cx| {
22366        editor
22367            .breakpoint_store()
22368            .as_ref()
22369            .unwrap()
22370            .read(cx)
22371            .all_source_breakpoints(cx)
22372    });
22373
22374    assert_eq!(1, breakpoints.len());
22375    assert_breakpoint(
22376        &breakpoints,
22377        &abs_path,
22378        vec![
22379            (0, Breakpoint::new_standard()),
22380            (2, Breakpoint::new_standard()),
22381            (3, Breakpoint::new_standard()),
22382        ],
22383    );
22384
22385    editor.update_in(cx, |editor, window, cx| {
22386        editor.move_to_beginning(&MoveToBeginning, window, cx);
22387        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22388        editor.move_to_end(&MoveToEnd, window, cx);
22389        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22390        // Disabling a breakpoint that doesn't exist should do nothing
22391        editor.move_up(&MoveUp, window, cx);
22392        editor.move_up(&MoveUp, window, cx);
22393        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22394    });
22395
22396    let breakpoints = editor.update(cx, |editor, cx| {
22397        editor
22398            .breakpoint_store()
22399            .as_ref()
22400            .unwrap()
22401            .read(cx)
22402            .all_source_breakpoints(cx)
22403    });
22404
22405    let disable_breakpoint = {
22406        let mut bp = Breakpoint::new_standard();
22407        bp.state = BreakpointState::Disabled;
22408        bp
22409    };
22410
22411    assert_eq!(1, breakpoints.len());
22412    assert_breakpoint(
22413        &breakpoints,
22414        &abs_path,
22415        vec![
22416            (0, disable_breakpoint.clone()),
22417            (2, Breakpoint::new_standard()),
22418            (3, disable_breakpoint.clone()),
22419        ],
22420    );
22421
22422    editor.update_in(cx, |editor, window, cx| {
22423        editor.move_to_beginning(&MoveToBeginning, window, cx);
22424        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22425        editor.move_to_end(&MoveToEnd, window, cx);
22426        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22427        editor.move_up(&MoveUp, window, cx);
22428        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22429    });
22430
22431    let breakpoints = editor.update(cx, |editor, cx| {
22432        editor
22433            .breakpoint_store()
22434            .as_ref()
22435            .unwrap()
22436            .read(cx)
22437            .all_source_breakpoints(cx)
22438    });
22439
22440    assert_eq!(1, breakpoints.len());
22441    assert_breakpoint(
22442        &breakpoints,
22443        &abs_path,
22444        vec![
22445            (0, Breakpoint::new_standard()),
22446            (2, disable_breakpoint),
22447            (3, Breakpoint::new_standard()),
22448        ],
22449    );
22450}
22451
22452#[gpui::test]
22453async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22454    init_test(cx, |_| {});
22455    let capabilities = lsp::ServerCapabilities {
22456        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22457            prepare_provider: Some(true),
22458            work_done_progress_options: Default::default(),
22459        })),
22460        ..Default::default()
22461    };
22462    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22463
22464    cx.set_state(indoc! {"
22465        struct Fˇoo {}
22466    "});
22467
22468    cx.update_editor(|editor, _, cx| {
22469        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22470        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22471        editor.highlight_background::<DocumentHighlightRead>(
22472            &[highlight_range],
22473            |theme| theme.colors().editor_document_highlight_read_background,
22474            cx,
22475        );
22476    });
22477
22478    let mut prepare_rename_handler = cx
22479        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22480            move |_, _, _| async move {
22481                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22482                    start: lsp::Position {
22483                        line: 0,
22484                        character: 7,
22485                    },
22486                    end: lsp::Position {
22487                        line: 0,
22488                        character: 10,
22489                    },
22490                })))
22491            },
22492        );
22493    let prepare_rename_task = cx
22494        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22495        .expect("Prepare rename was not started");
22496    prepare_rename_handler.next().await.unwrap();
22497    prepare_rename_task.await.expect("Prepare rename failed");
22498
22499    let mut rename_handler =
22500        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22501            let edit = lsp::TextEdit {
22502                range: lsp::Range {
22503                    start: lsp::Position {
22504                        line: 0,
22505                        character: 7,
22506                    },
22507                    end: lsp::Position {
22508                        line: 0,
22509                        character: 10,
22510                    },
22511                },
22512                new_text: "FooRenamed".to_string(),
22513            };
22514            Ok(Some(lsp::WorkspaceEdit::new(
22515                // Specify the same edit twice
22516                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
22517            )))
22518        });
22519    let rename_task = cx
22520        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22521        .expect("Confirm rename was not started");
22522    rename_handler.next().await.unwrap();
22523    rename_task.await.expect("Confirm rename failed");
22524    cx.run_until_parked();
22525
22526    // Despite two edits, only one is actually applied as those are identical
22527    cx.assert_editor_state(indoc! {"
22528        struct FooRenamedˇ {}
22529    "});
22530}
22531
22532#[gpui::test]
22533async fn test_rename_without_prepare(cx: &mut TestAppContext) {
22534    init_test(cx, |_| {});
22535    // These capabilities indicate that the server does not support prepare rename.
22536    let capabilities = lsp::ServerCapabilities {
22537        rename_provider: Some(lsp::OneOf::Left(true)),
22538        ..Default::default()
22539    };
22540    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22541
22542    cx.set_state(indoc! {"
22543        struct Fˇoo {}
22544    "});
22545
22546    cx.update_editor(|editor, _window, cx| {
22547        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22548        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22549        editor.highlight_background::<DocumentHighlightRead>(
22550            &[highlight_range],
22551            |theme| theme.colors().editor_document_highlight_read_background,
22552            cx,
22553        );
22554    });
22555
22556    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22557        .expect("Prepare rename was not started")
22558        .await
22559        .expect("Prepare rename failed");
22560
22561    let mut rename_handler =
22562        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22563            let edit = lsp::TextEdit {
22564                range: lsp::Range {
22565                    start: lsp::Position {
22566                        line: 0,
22567                        character: 7,
22568                    },
22569                    end: lsp::Position {
22570                        line: 0,
22571                        character: 10,
22572                    },
22573                },
22574                new_text: "FooRenamed".to_string(),
22575            };
22576            Ok(Some(lsp::WorkspaceEdit::new(
22577                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
22578            )))
22579        });
22580    let rename_task = cx
22581        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22582        .expect("Confirm rename was not started");
22583    rename_handler.next().await.unwrap();
22584    rename_task.await.expect("Confirm rename failed");
22585    cx.run_until_parked();
22586
22587    // Correct range is renamed, as `surrounding_word` is used to find it.
22588    cx.assert_editor_state(indoc! {"
22589        struct FooRenamedˇ {}
22590    "});
22591}
22592
22593#[gpui::test]
22594async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
22595    init_test(cx, |_| {});
22596    let mut cx = EditorTestContext::new(cx).await;
22597
22598    let language = Arc::new(
22599        Language::new(
22600            LanguageConfig::default(),
22601            Some(tree_sitter_html::LANGUAGE.into()),
22602        )
22603        .with_brackets_query(
22604            r#"
22605            ("<" @open "/>" @close)
22606            ("</" @open ">" @close)
22607            ("<" @open ">" @close)
22608            ("\"" @open "\"" @close)
22609            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
22610        "#,
22611        )
22612        .unwrap(),
22613    );
22614    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22615
22616    cx.set_state(indoc! {"
22617        <span>ˇ</span>
22618    "});
22619    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22620    cx.assert_editor_state(indoc! {"
22621        <span>
22622        ˇ
22623        </span>
22624    "});
22625
22626    cx.set_state(indoc! {"
22627        <span><span></span>ˇ</span>
22628    "});
22629    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22630    cx.assert_editor_state(indoc! {"
22631        <span><span></span>
22632        ˇ</span>
22633    "});
22634
22635    cx.set_state(indoc! {"
22636        <span>ˇ
22637        </span>
22638    "});
22639    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22640    cx.assert_editor_state(indoc! {"
22641        <span>
22642        ˇ
22643        </span>
22644    "});
22645}
22646
22647#[gpui::test(iterations = 10)]
22648async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
22649    init_test(cx, |_| {});
22650
22651    let fs = FakeFs::new(cx.executor());
22652    fs.insert_tree(
22653        path!("/dir"),
22654        json!({
22655            "a.ts": "a",
22656        }),
22657    )
22658    .await;
22659
22660    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
22661    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22662    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22663
22664    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22665    language_registry.add(Arc::new(Language::new(
22666        LanguageConfig {
22667            name: "TypeScript".into(),
22668            matcher: LanguageMatcher {
22669                path_suffixes: vec!["ts".to_string()],
22670                ..Default::default()
22671            },
22672            ..Default::default()
22673        },
22674        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
22675    )));
22676    let mut fake_language_servers = language_registry.register_fake_lsp(
22677        "TypeScript",
22678        FakeLspAdapter {
22679            capabilities: lsp::ServerCapabilities {
22680                code_lens_provider: Some(lsp::CodeLensOptions {
22681                    resolve_provider: Some(true),
22682                }),
22683                execute_command_provider: Some(lsp::ExecuteCommandOptions {
22684                    commands: vec!["_the/command".to_string()],
22685                    ..lsp::ExecuteCommandOptions::default()
22686                }),
22687                ..lsp::ServerCapabilities::default()
22688            },
22689            ..FakeLspAdapter::default()
22690        },
22691    );
22692
22693    let editor = workspace
22694        .update(cx, |workspace, window, cx| {
22695            workspace.open_abs_path(
22696                PathBuf::from(path!("/dir/a.ts")),
22697                OpenOptions::default(),
22698                window,
22699                cx,
22700            )
22701        })
22702        .unwrap()
22703        .await
22704        .unwrap()
22705        .downcast::<Editor>()
22706        .unwrap();
22707    cx.executor().run_until_parked();
22708
22709    let fake_server = fake_language_servers.next().await.unwrap();
22710
22711    let buffer = editor.update(cx, |editor, cx| {
22712        editor
22713            .buffer()
22714            .read(cx)
22715            .as_singleton()
22716            .expect("have opened a single file by path")
22717    });
22718
22719    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
22720    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
22721    drop(buffer_snapshot);
22722    let actions = cx
22723        .update_window(*workspace, |_, window, cx| {
22724            project.code_actions(&buffer, anchor..anchor, window, cx)
22725        })
22726        .unwrap();
22727
22728    fake_server
22729        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
22730            Ok(Some(vec![
22731                lsp::CodeLens {
22732                    range: lsp::Range::default(),
22733                    command: Some(lsp::Command {
22734                        title: "Code lens command".to_owned(),
22735                        command: "_the/command".to_owned(),
22736                        arguments: None,
22737                    }),
22738                    data: None,
22739                },
22740                lsp::CodeLens {
22741                    range: lsp::Range::default(),
22742                    command: Some(lsp::Command {
22743                        title: "Command not in capabilities".to_owned(),
22744                        command: "not in capabilities".to_owned(),
22745                        arguments: None,
22746                    }),
22747                    data: None,
22748                },
22749                lsp::CodeLens {
22750                    range: lsp::Range {
22751                        start: lsp::Position {
22752                            line: 1,
22753                            character: 1,
22754                        },
22755                        end: lsp::Position {
22756                            line: 1,
22757                            character: 1,
22758                        },
22759                    },
22760                    command: Some(lsp::Command {
22761                        title: "Command not in range".to_owned(),
22762                        command: "_the/command".to_owned(),
22763                        arguments: None,
22764                    }),
22765                    data: None,
22766                },
22767            ]))
22768        })
22769        .next()
22770        .await;
22771
22772    let actions = actions.await.unwrap();
22773    assert_eq!(
22774        actions.len(),
22775        1,
22776        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
22777    );
22778    let action = actions[0].clone();
22779    let apply = project.update(cx, |project, cx| {
22780        project.apply_code_action(buffer.clone(), action, true, cx)
22781    });
22782
22783    // Resolving the code action does not populate its edits. In absence of
22784    // edits, we must execute the given command.
22785    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
22786        |mut lens, _| async move {
22787            let lens_command = lens.command.as_mut().expect("should have a command");
22788            assert_eq!(lens_command.title, "Code lens command");
22789            lens_command.arguments = Some(vec![json!("the-argument")]);
22790            Ok(lens)
22791        },
22792    );
22793
22794    // While executing the command, the language server sends the editor
22795    // a `workspaceEdit` request.
22796    fake_server
22797        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
22798            let fake = fake_server.clone();
22799            move |params, _| {
22800                assert_eq!(params.command, "_the/command");
22801                let fake = fake.clone();
22802                async move {
22803                    fake.server
22804                        .request::<lsp::request::ApplyWorkspaceEdit>(
22805                            lsp::ApplyWorkspaceEditParams {
22806                                label: None,
22807                                edit: lsp::WorkspaceEdit {
22808                                    changes: Some(
22809                                        [(
22810                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
22811                                            vec![lsp::TextEdit {
22812                                                range: lsp::Range::new(
22813                                                    lsp::Position::new(0, 0),
22814                                                    lsp::Position::new(0, 0),
22815                                                ),
22816                                                new_text: "X".into(),
22817                                            }],
22818                                        )]
22819                                        .into_iter()
22820                                        .collect(),
22821                                    ),
22822                                    ..lsp::WorkspaceEdit::default()
22823                                },
22824                            },
22825                        )
22826                        .await
22827                        .into_response()
22828                        .unwrap();
22829                    Ok(Some(json!(null)))
22830                }
22831            }
22832        })
22833        .next()
22834        .await;
22835
22836    // Applying the code lens command returns a project transaction containing the edits
22837    // sent by the language server in its `workspaceEdit` request.
22838    let transaction = apply.await.unwrap();
22839    assert!(transaction.0.contains_key(&buffer));
22840    buffer.update(cx, |buffer, cx| {
22841        assert_eq!(buffer.text(), "Xa");
22842        buffer.undo(cx);
22843        assert_eq!(buffer.text(), "a");
22844    });
22845
22846    let actions_after_edits = cx
22847        .update_window(*workspace, |_, window, cx| {
22848            project.code_actions(&buffer, anchor..anchor, window, cx)
22849        })
22850        .unwrap()
22851        .await
22852        .unwrap();
22853    assert_eq!(
22854        actions, actions_after_edits,
22855        "For the same selection, same code lens actions should be returned"
22856    );
22857
22858    let _responses =
22859        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
22860            panic!("No more code lens requests are expected");
22861        });
22862    editor.update_in(cx, |editor, window, cx| {
22863        editor.select_all(&SelectAll, window, cx);
22864    });
22865    cx.executor().run_until_parked();
22866    let new_actions = cx
22867        .update_window(*workspace, |_, window, cx| {
22868            project.code_actions(&buffer, anchor..anchor, window, cx)
22869        })
22870        .unwrap()
22871        .await
22872        .unwrap();
22873    assert_eq!(
22874        actions, new_actions,
22875        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
22876    );
22877}
22878
22879#[gpui::test]
22880async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
22881    init_test(cx, |_| {});
22882
22883    let fs = FakeFs::new(cx.executor());
22884    let main_text = r#"fn main() {
22885println!("1");
22886println!("2");
22887println!("3");
22888println!("4");
22889println!("5");
22890}"#;
22891    let lib_text = "mod foo {}";
22892    fs.insert_tree(
22893        path!("/a"),
22894        json!({
22895            "lib.rs": lib_text,
22896            "main.rs": main_text,
22897        }),
22898    )
22899    .await;
22900
22901    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22902    let (workspace, cx) =
22903        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22904    let worktree_id = workspace.update(cx, |workspace, cx| {
22905        workspace.project().update(cx, |project, cx| {
22906            project.worktrees(cx).next().unwrap().read(cx).id()
22907        })
22908    });
22909
22910    let expected_ranges = vec![
22911        Point::new(0, 0)..Point::new(0, 0),
22912        Point::new(1, 0)..Point::new(1, 1),
22913        Point::new(2, 0)..Point::new(2, 2),
22914        Point::new(3, 0)..Point::new(3, 3),
22915    ];
22916
22917    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22918    let editor_1 = workspace
22919        .update_in(cx, |workspace, window, cx| {
22920            workspace.open_path(
22921                (worktree_id, "main.rs"),
22922                Some(pane_1.downgrade()),
22923                true,
22924                window,
22925                cx,
22926            )
22927        })
22928        .unwrap()
22929        .await
22930        .downcast::<Editor>()
22931        .unwrap();
22932    pane_1.update(cx, |pane, cx| {
22933        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22934        open_editor.update(cx, |editor, cx| {
22935            assert_eq!(
22936                editor.display_text(cx),
22937                main_text,
22938                "Original main.rs text on initial open",
22939            );
22940            assert_eq!(
22941                editor
22942                    .selections
22943                    .all::<Point>(cx)
22944                    .into_iter()
22945                    .map(|s| s.range())
22946                    .collect::<Vec<_>>(),
22947                vec![Point::zero()..Point::zero()],
22948                "Default selections on initial open",
22949            );
22950        })
22951    });
22952    editor_1.update_in(cx, |editor, window, cx| {
22953        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22954            s.select_ranges(expected_ranges.clone());
22955        });
22956    });
22957
22958    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
22959        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
22960    });
22961    let editor_2 = workspace
22962        .update_in(cx, |workspace, window, cx| {
22963            workspace.open_path(
22964                (worktree_id, "main.rs"),
22965                Some(pane_2.downgrade()),
22966                true,
22967                window,
22968                cx,
22969            )
22970        })
22971        .unwrap()
22972        .await
22973        .downcast::<Editor>()
22974        .unwrap();
22975    pane_2.update(cx, |pane, cx| {
22976        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22977        open_editor.update(cx, |editor, cx| {
22978            assert_eq!(
22979                editor.display_text(cx),
22980                main_text,
22981                "Original main.rs text on initial open in another panel",
22982            );
22983            assert_eq!(
22984                editor
22985                    .selections
22986                    .all::<Point>(cx)
22987                    .into_iter()
22988                    .map(|s| s.range())
22989                    .collect::<Vec<_>>(),
22990                vec![Point::zero()..Point::zero()],
22991                "Default selections on initial open in another panel",
22992            );
22993        })
22994    });
22995
22996    editor_2.update_in(cx, |editor, window, cx| {
22997        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
22998    });
22999
23000    let _other_editor_1 = workspace
23001        .update_in(cx, |workspace, window, cx| {
23002            workspace.open_path(
23003                (worktree_id, "lib.rs"),
23004                Some(pane_1.downgrade()),
23005                true,
23006                window,
23007                cx,
23008            )
23009        })
23010        .unwrap()
23011        .await
23012        .downcast::<Editor>()
23013        .unwrap();
23014    pane_1
23015        .update_in(cx, |pane, window, cx| {
23016            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23017        })
23018        .await
23019        .unwrap();
23020    drop(editor_1);
23021    pane_1.update(cx, |pane, cx| {
23022        pane.active_item()
23023            .unwrap()
23024            .downcast::<Editor>()
23025            .unwrap()
23026            .update(cx, |editor, cx| {
23027                assert_eq!(
23028                    editor.display_text(cx),
23029                    lib_text,
23030                    "Other file should be open and active",
23031                );
23032            });
23033        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23034    });
23035
23036    let _other_editor_2 = workspace
23037        .update_in(cx, |workspace, window, cx| {
23038            workspace.open_path(
23039                (worktree_id, "lib.rs"),
23040                Some(pane_2.downgrade()),
23041                true,
23042                window,
23043                cx,
23044            )
23045        })
23046        .unwrap()
23047        .await
23048        .downcast::<Editor>()
23049        .unwrap();
23050    pane_2
23051        .update_in(cx, |pane, window, cx| {
23052            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23053        })
23054        .await
23055        .unwrap();
23056    drop(editor_2);
23057    pane_2.update(cx, |pane, cx| {
23058        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23059        open_editor.update(cx, |editor, cx| {
23060            assert_eq!(
23061                editor.display_text(cx),
23062                lib_text,
23063                "Other file should be open and active in another panel too",
23064            );
23065        });
23066        assert_eq!(
23067            pane.items().count(),
23068            1,
23069            "No other editors should be open in another pane",
23070        );
23071    });
23072
23073    let _editor_1_reopened = workspace
23074        .update_in(cx, |workspace, window, cx| {
23075            workspace.open_path(
23076                (worktree_id, "main.rs"),
23077                Some(pane_1.downgrade()),
23078                true,
23079                window,
23080                cx,
23081            )
23082        })
23083        .unwrap()
23084        .await
23085        .downcast::<Editor>()
23086        .unwrap();
23087    let _editor_2_reopened = workspace
23088        .update_in(cx, |workspace, window, cx| {
23089            workspace.open_path(
23090                (worktree_id, "main.rs"),
23091                Some(pane_2.downgrade()),
23092                true,
23093                window,
23094                cx,
23095            )
23096        })
23097        .unwrap()
23098        .await
23099        .downcast::<Editor>()
23100        .unwrap();
23101    pane_1.update(cx, |pane, cx| {
23102        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23103        open_editor.update(cx, |editor, cx| {
23104            assert_eq!(
23105                editor.display_text(cx),
23106                main_text,
23107                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23108            );
23109            assert_eq!(
23110                editor
23111                    .selections
23112                    .all::<Point>(cx)
23113                    .into_iter()
23114                    .map(|s| s.range())
23115                    .collect::<Vec<_>>(),
23116                expected_ranges,
23117                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23118            );
23119        })
23120    });
23121    pane_2.update(cx, |pane, cx| {
23122        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23123        open_editor.update(cx, |editor, cx| {
23124            assert_eq!(
23125                editor.display_text(cx),
23126                r#"fn main() {
23127⋯rintln!("1");
23128⋯intln!("2");
23129⋯ntln!("3");
23130println!("4");
23131println!("5");
23132}"#,
23133                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23134            );
23135            assert_eq!(
23136                editor
23137                    .selections
23138                    .all::<Point>(cx)
23139                    .into_iter()
23140                    .map(|s| s.range())
23141                    .collect::<Vec<_>>(),
23142                vec![Point::zero()..Point::zero()],
23143                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23144            );
23145        })
23146    });
23147}
23148
23149#[gpui::test]
23150async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23151    init_test(cx, |_| {});
23152
23153    let fs = FakeFs::new(cx.executor());
23154    let main_text = r#"fn main() {
23155println!("1");
23156println!("2");
23157println!("3");
23158println!("4");
23159println!("5");
23160}"#;
23161    let lib_text = "mod foo {}";
23162    fs.insert_tree(
23163        path!("/a"),
23164        json!({
23165            "lib.rs": lib_text,
23166            "main.rs": main_text,
23167        }),
23168    )
23169    .await;
23170
23171    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23172    let (workspace, cx) =
23173        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23174    let worktree_id = workspace.update(cx, |workspace, cx| {
23175        workspace.project().update(cx, |project, cx| {
23176            project.worktrees(cx).next().unwrap().read(cx).id()
23177        })
23178    });
23179
23180    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23181    let editor = workspace
23182        .update_in(cx, |workspace, window, cx| {
23183            workspace.open_path(
23184                (worktree_id, "main.rs"),
23185                Some(pane.downgrade()),
23186                true,
23187                window,
23188                cx,
23189            )
23190        })
23191        .unwrap()
23192        .await
23193        .downcast::<Editor>()
23194        .unwrap();
23195    pane.update(cx, |pane, cx| {
23196        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23197        open_editor.update(cx, |editor, cx| {
23198            assert_eq!(
23199                editor.display_text(cx),
23200                main_text,
23201                "Original main.rs text on initial open",
23202            );
23203        })
23204    });
23205    editor.update_in(cx, |editor, window, cx| {
23206        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23207    });
23208
23209    cx.update_global(|store: &mut SettingsStore, cx| {
23210        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23211            s.restore_on_file_reopen = Some(false);
23212        });
23213    });
23214    editor.update_in(cx, |editor, window, cx| {
23215        editor.fold_ranges(
23216            vec![
23217                Point::new(1, 0)..Point::new(1, 1),
23218                Point::new(2, 0)..Point::new(2, 2),
23219                Point::new(3, 0)..Point::new(3, 3),
23220            ],
23221            false,
23222            window,
23223            cx,
23224        );
23225    });
23226    pane.update_in(cx, |pane, window, cx| {
23227        pane.close_all_items(&CloseAllItems::default(), window, cx)
23228    })
23229    .await
23230    .unwrap();
23231    pane.update(cx, |pane, _| {
23232        assert!(pane.active_item().is_none());
23233    });
23234    cx.update_global(|store: &mut SettingsStore, cx| {
23235        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
23236            s.restore_on_file_reopen = Some(true);
23237        });
23238    });
23239
23240    let _editor_reopened = workspace
23241        .update_in(cx, |workspace, window, cx| {
23242            workspace.open_path(
23243                (worktree_id, "main.rs"),
23244                Some(pane.downgrade()),
23245                true,
23246                window,
23247                cx,
23248            )
23249        })
23250        .unwrap()
23251        .await
23252        .downcast::<Editor>()
23253        .unwrap();
23254    pane.update(cx, |pane, cx| {
23255        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23256        open_editor.update(cx, |editor, cx| {
23257            assert_eq!(
23258                editor.display_text(cx),
23259                main_text,
23260                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23261            );
23262        })
23263    });
23264}
23265
23266#[gpui::test]
23267async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23268    struct EmptyModalView {
23269        focus_handle: gpui::FocusHandle,
23270    }
23271    impl EventEmitter<DismissEvent> for EmptyModalView {}
23272    impl Render for EmptyModalView {
23273        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23274            div()
23275        }
23276    }
23277    impl Focusable for EmptyModalView {
23278        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23279            self.focus_handle.clone()
23280        }
23281    }
23282    impl workspace::ModalView for EmptyModalView {}
23283    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23284        EmptyModalView {
23285            focus_handle: cx.focus_handle(),
23286        }
23287    }
23288
23289    init_test(cx, |_| {});
23290
23291    let fs = FakeFs::new(cx.executor());
23292    let project = Project::test(fs, [], cx).await;
23293    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23294    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23295    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23296    let editor = cx.new_window_entity(|window, cx| {
23297        Editor::new(
23298            EditorMode::full(),
23299            buffer,
23300            Some(project.clone()),
23301            window,
23302            cx,
23303        )
23304    });
23305    workspace
23306        .update(cx, |workspace, window, cx| {
23307            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23308        })
23309        .unwrap();
23310    editor.update_in(cx, |editor, window, cx| {
23311        editor.open_context_menu(&OpenContextMenu, window, cx);
23312        assert!(editor.mouse_context_menu.is_some());
23313    });
23314    workspace
23315        .update(cx, |workspace, window, cx| {
23316            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23317        })
23318        .unwrap();
23319    cx.read(|cx| {
23320        assert!(editor.read(cx).mouse_context_menu.is_none());
23321    });
23322}
23323
23324#[gpui::test]
23325async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23326    init_test(cx, |_| {});
23327
23328    let fs = FakeFs::new(cx.executor());
23329    fs.insert_file(path!("/file.html"), Default::default())
23330        .await;
23331
23332    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23333
23334    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23335    let html_language = Arc::new(Language::new(
23336        LanguageConfig {
23337            name: "HTML".into(),
23338            matcher: LanguageMatcher {
23339                path_suffixes: vec!["html".to_string()],
23340                ..LanguageMatcher::default()
23341            },
23342            brackets: BracketPairConfig {
23343                pairs: vec![BracketPair {
23344                    start: "<".into(),
23345                    end: ">".into(),
23346                    close: true,
23347                    ..Default::default()
23348                }],
23349                ..Default::default()
23350            },
23351            ..Default::default()
23352        },
23353        Some(tree_sitter_html::LANGUAGE.into()),
23354    ));
23355    language_registry.add(html_language);
23356    let mut fake_servers = language_registry.register_fake_lsp(
23357        "HTML",
23358        FakeLspAdapter {
23359            capabilities: lsp::ServerCapabilities {
23360                completion_provider: Some(lsp::CompletionOptions {
23361                    resolve_provider: Some(true),
23362                    ..Default::default()
23363                }),
23364                ..Default::default()
23365            },
23366            ..Default::default()
23367        },
23368    );
23369
23370    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23371    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23372
23373    let worktree_id = workspace
23374        .update(cx, |workspace, _window, cx| {
23375            workspace.project().update(cx, |project, cx| {
23376                project.worktrees(cx).next().unwrap().read(cx).id()
23377            })
23378        })
23379        .unwrap();
23380    project
23381        .update(cx, |project, cx| {
23382            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23383        })
23384        .await
23385        .unwrap();
23386    let editor = workspace
23387        .update(cx, |workspace, window, cx| {
23388            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23389        })
23390        .unwrap()
23391        .await
23392        .unwrap()
23393        .downcast::<Editor>()
23394        .unwrap();
23395
23396    let fake_server = fake_servers.next().await.unwrap();
23397    editor.update_in(cx, |editor, window, cx| {
23398        editor.set_text("<ad></ad>", window, cx);
23399        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23400            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23401        });
23402        let Some((buffer, _)) = editor
23403            .buffer
23404            .read(cx)
23405            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23406        else {
23407            panic!("Failed to get buffer for selection position");
23408        };
23409        let buffer = buffer.read(cx);
23410        let buffer_id = buffer.remote_id();
23411        let opening_range =
23412            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
23413        let closing_range =
23414            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
23415        let mut linked_ranges = HashMap::default();
23416        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23417        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23418    });
23419    let mut completion_handle =
23420        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23421            Ok(Some(lsp::CompletionResponse::Array(vec![
23422                lsp::CompletionItem {
23423                    label: "head".to_string(),
23424                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23425                        lsp::InsertReplaceEdit {
23426                            new_text: "head".to_string(),
23427                            insert: lsp::Range::new(
23428                                lsp::Position::new(0, 1),
23429                                lsp::Position::new(0, 3),
23430                            ),
23431                            replace: lsp::Range::new(
23432                                lsp::Position::new(0, 1),
23433                                lsp::Position::new(0, 3),
23434                            ),
23435                        },
23436                    )),
23437                    ..Default::default()
23438                },
23439            ])))
23440        });
23441    editor.update_in(cx, |editor, window, cx| {
23442        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23443    });
23444    cx.run_until_parked();
23445    completion_handle.next().await.unwrap();
23446    editor.update(cx, |editor, _| {
23447        assert!(
23448            editor.context_menu_visible(),
23449            "Completion menu should be visible"
23450        );
23451    });
23452    editor.update_in(cx, |editor, window, cx| {
23453        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23454    });
23455    cx.executor().run_until_parked();
23456    editor.update(cx, |editor, cx| {
23457        assert_eq!(editor.text(cx), "<head></head>");
23458    });
23459}
23460
23461#[gpui::test]
23462async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
23463    init_test(cx, |_| {});
23464
23465    let fs = FakeFs::new(cx.executor());
23466    fs.insert_tree(
23467        path!("/root"),
23468        json!({
23469            "a": {
23470                "main.rs": "fn main() {}",
23471            },
23472            "foo": {
23473                "bar": {
23474                    "external_file.rs": "pub mod external {}",
23475                }
23476            }
23477        }),
23478    )
23479    .await;
23480
23481    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
23482    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23483    language_registry.add(rust_lang());
23484    let _fake_servers = language_registry.register_fake_lsp(
23485        "Rust",
23486        FakeLspAdapter {
23487            ..FakeLspAdapter::default()
23488        },
23489    );
23490    let (workspace, cx) =
23491        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23492    let worktree_id = workspace.update(cx, |workspace, cx| {
23493        workspace.project().update(cx, |project, cx| {
23494            project.worktrees(cx).next().unwrap().read(cx).id()
23495        })
23496    });
23497
23498    let assert_language_servers_count =
23499        |expected: usize, context: &str, cx: &mut VisualTestContext| {
23500            project.update(cx, |project, cx| {
23501                let current = project
23502                    .lsp_store()
23503                    .read(cx)
23504                    .as_local()
23505                    .unwrap()
23506                    .language_servers
23507                    .len();
23508                assert_eq!(expected, current, "{context}");
23509            });
23510        };
23511
23512    assert_language_servers_count(
23513        0,
23514        "No servers should be running before any file is open",
23515        cx,
23516    );
23517    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23518    let main_editor = workspace
23519        .update_in(cx, |workspace, window, cx| {
23520            workspace.open_path(
23521                (worktree_id, "main.rs"),
23522                Some(pane.downgrade()),
23523                true,
23524                window,
23525                cx,
23526            )
23527        })
23528        .unwrap()
23529        .await
23530        .downcast::<Editor>()
23531        .unwrap();
23532    pane.update(cx, |pane, cx| {
23533        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23534        open_editor.update(cx, |editor, cx| {
23535            assert_eq!(
23536                editor.display_text(cx),
23537                "fn main() {}",
23538                "Original main.rs text on initial open",
23539            );
23540        });
23541        assert_eq!(open_editor, main_editor);
23542    });
23543    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
23544
23545    let external_editor = workspace
23546        .update_in(cx, |workspace, window, cx| {
23547            workspace.open_abs_path(
23548                PathBuf::from("/root/foo/bar/external_file.rs"),
23549                OpenOptions::default(),
23550                window,
23551                cx,
23552            )
23553        })
23554        .await
23555        .expect("opening external file")
23556        .downcast::<Editor>()
23557        .expect("downcasted external file's open element to editor");
23558    pane.update(cx, |pane, cx| {
23559        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23560        open_editor.update(cx, |editor, cx| {
23561            assert_eq!(
23562                editor.display_text(cx),
23563                "pub mod external {}",
23564                "External file is open now",
23565            );
23566        });
23567        assert_eq!(open_editor, external_editor);
23568    });
23569    assert_language_servers_count(
23570        1,
23571        "Second, external, *.rs file should join the existing server",
23572        cx,
23573    );
23574
23575    pane.update_in(cx, |pane, window, cx| {
23576        pane.close_active_item(&CloseActiveItem::default(), window, cx)
23577    })
23578    .await
23579    .unwrap();
23580    pane.update_in(cx, |pane, window, cx| {
23581        pane.navigate_backward(&Default::default(), window, cx);
23582    });
23583    cx.run_until_parked();
23584    pane.update(cx, |pane, cx| {
23585        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23586        open_editor.update(cx, |editor, cx| {
23587            assert_eq!(
23588                editor.display_text(cx),
23589                "pub mod external {}",
23590                "External file is open now",
23591            );
23592        });
23593    });
23594    assert_language_servers_count(
23595        1,
23596        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
23597        cx,
23598    );
23599
23600    cx.update(|_, cx| {
23601        workspace::reload(cx);
23602    });
23603    assert_language_servers_count(
23604        1,
23605        "After reloading the worktree with local and external files opened, only one project should be started",
23606        cx,
23607    );
23608}
23609
23610#[gpui::test]
23611async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
23612    init_test(cx, |_| {});
23613
23614    let mut cx = EditorTestContext::new(cx).await;
23615    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23616    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23617
23618    // test cursor move to start of each line on tab
23619    // for `if`, `elif`, `else`, `while`, `with` and `for`
23620    cx.set_state(indoc! {"
23621        def main():
23622        ˇ    for item in items:
23623        ˇ        while item.active:
23624        ˇ            if item.value > 10:
23625        ˇ                continue
23626        ˇ            elif item.value < 0:
23627        ˇ                break
23628        ˇ            else:
23629        ˇ                with item.context() as ctx:
23630        ˇ                    yield count
23631        ˇ        else:
23632        ˇ            log('while else')
23633        ˇ    else:
23634        ˇ        log('for else')
23635    "});
23636    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23637    cx.assert_editor_state(indoc! {"
23638        def main():
23639            ˇfor item in items:
23640                ˇwhile item.active:
23641                    ˇif item.value > 10:
23642                        ˇcontinue
23643                    ˇelif item.value < 0:
23644                        ˇbreak
23645                    ˇelse:
23646                        ˇwith item.context() as ctx:
23647                            ˇyield count
23648                ˇelse:
23649                    ˇlog('while else')
23650            ˇelse:
23651                ˇlog('for else')
23652    "});
23653    // test relative indent is preserved when tab
23654    // for `if`, `elif`, `else`, `while`, `with` and `for`
23655    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23656    cx.assert_editor_state(indoc! {"
23657        def main():
23658                ˇfor item in items:
23659                    ˇwhile item.active:
23660                        ˇif item.value > 10:
23661                            ˇcontinue
23662                        ˇelif item.value < 0:
23663                            ˇbreak
23664                        ˇelse:
23665                            ˇwith item.context() as ctx:
23666                                ˇyield count
23667                    ˇelse:
23668                        ˇlog('while else')
23669                ˇelse:
23670                    ˇlog('for else')
23671    "});
23672
23673    // test cursor move to start of each line on tab
23674    // for `try`, `except`, `else`, `finally`, `match` and `def`
23675    cx.set_state(indoc! {"
23676        def main():
23677        ˇ    try:
23678        ˇ        fetch()
23679        ˇ    except ValueError:
23680        ˇ        handle_error()
23681        ˇ    else:
23682        ˇ        match value:
23683        ˇ            case _:
23684        ˇ    finally:
23685        ˇ        def status():
23686        ˇ            return 0
23687    "});
23688    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23689    cx.assert_editor_state(indoc! {"
23690        def main():
23691            ˇtry:
23692                ˇfetch()
23693            ˇexcept ValueError:
23694                ˇhandle_error()
23695            ˇelse:
23696                ˇmatch value:
23697                    ˇcase _:
23698            ˇfinally:
23699                ˇdef status():
23700                    ˇreturn 0
23701    "});
23702    // test relative indent is preserved when tab
23703    // for `try`, `except`, `else`, `finally`, `match` and `def`
23704    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23705    cx.assert_editor_state(indoc! {"
23706        def main():
23707                ˇtry:
23708                    ˇfetch()
23709                ˇexcept ValueError:
23710                    ˇhandle_error()
23711                ˇelse:
23712                    ˇmatch value:
23713                        ˇcase _:
23714                ˇfinally:
23715                    ˇdef status():
23716                        ˇreturn 0
23717    "});
23718}
23719
23720#[gpui::test]
23721async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
23722    init_test(cx, |_| {});
23723
23724    let mut cx = EditorTestContext::new(cx).await;
23725    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23726    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23727
23728    // test `else` auto outdents when typed inside `if` block
23729    cx.set_state(indoc! {"
23730        def main():
23731            if i == 2:
23732                return
23733                ˇ
23734    "});
23735    cx.update_editor(|editor, window, cx| {
23736        editor.handle_input("else:", window, cx);
23737    });
23738    cx.assert_editor_state(indoc! {"
23739        def main():
23740            if i == 2:
23741                return
23742            else:ˇ
23743    "});
23744
23745    // test `except` auto outdents when typed inside `try` block
23746    cx.set_state(indoc! {"
23747        def main():
23748            try:
23749                i = 2
23750                ˇ
23751    "});
23752    cx.update_editor(|editor, window, cx| {
23753        editor.handle_input("except:", window, cx);
23754    });
23755    cx.assert_editor_state(indoc! {"
23756        def main():
23757            try:
23758                i = 2
23759            except:ˇ
23760    "});
23761
23762    // test `else` auto outdents when typed inside `except` block
23763    cx.set_state(indoc! {"
23764        def main():
23765            try:
23766                i = 2
23767            except:
23768                j = 2
23769                ˇ
23770    "});
23771    cx.update_editor(|editor, window, cx| {
23772        editor.handle_input("else:", window, cx);
23773    });
23774    cx.assert_editor_state(indoc! {"
23775        def main():
23776            try:
23777                i = 2
23778            except:
23779                j = 2
23780            else:ˇ
23781    "});
23782
23783    // test `finally` auto outdents when typed inside `else` block
23784    cx.set_state(indoc! {"
23785        def main():
23786            try:
23787                i = 2
23788            except:
23789                j = 2
23790            else:
23791                k = 2
23792                ˇ
23793    "});
23794    cx.update_editor(|editor, window, cx| {
23795        editor.handle_input("finally:", window, cx);
23796    });
23797    cx.assert_editor_state(indoc! {"
23798        def main():
23799            try:
23800                i = 2
23801            except:
23802                j = 2
23803            else:
23804                k = 2
23805            finally:ˇ
23806    "});
23807
23808    // test `else` does not outdents when typed inside `except` block right after for block
23809    cx.set_state(indoc! {"
23810        def main():
23811            try:
23812                i = 2
23813            except:
23814                for i in range(n):
23815                    pass
23816                ˇ
23817    "});
23818    cx.update_editor(|editor, window, cx| {
23819        editor.handle_input("else:", window, cx);
23820    });
23821    cx.assert_editor_state(indoc! {"
23822        def main():
23823            try:
23824                i = 2
23825            except:
23826                for i in range(n):
23827                    pass
23828                else:ˇ
23829    "});
23830
23831    // test `finally` auto outdents when typed inside `else` block right after for block
23832    cx.set_state(indoc! {"
23833        def main():
23834            try:
23835                i = 2
23836            except:
23837                j = 2
23838            else:
23839                for i in range(n):
23840                    pass
23841                ˇ
23842    "});
23843    cx.update_editor(|editor, window, cx| {
23844        editor.handle_input("finally:", window, cx);
23845    });
23846    cx.assert_editor_state(indoc! {"
23847        def main():
23848            try:
23849                i = 2
23850            except:
23851                j = 2
23852            else:
23853                for i in range(n):
23854                    pass
23855            finally:ˇ
23856    "});
23857
23858    // test `except` outdents to inner "try" block
23859    cx.set_state(indoc! {"
23860        def main():
23861            try:
23862                i = 2
23863                if i == 2:
23864                    try:
23865                        i = 3
23866                        ˇ
23867    "});
23868    cx.update_editor(|editor, window, cx| {
23869        editor.handle_input("except:", window, cx);
23870    });
23871    cx.assert_editor_state(indoc! {"
23872        def main():
23873            try:
23874                i = 2
23875                if i == 2:
23876                    try:
23877                        i = 3
23878                    except:ˇ
23879    "});
23880
23881    // test `except` outdents to outer "try" block
23882    cx.set_state(indoc! {"
23883        def main():
23884            try:
23885                i = 2
23886                if i == 2:
23887                    try:
23888                        i = 3
23889                ˇ
23890    "});
23891    cx.update_editor(|editor, window, cx| {
23892        editor.handle_input("except:", window, cx);
23893    });
23894    cx.assert_editor_state(indoc! {"
23895        def main():
23896            try:
23897                i = 2
23898                if i == 2:
23899                    try:
23900                        i = 3
23901            except:ˇ
23902    "});
23903
23904    // test `else` stays at correct indent when typed after `for` block
23905    cx.set_state(indoc! {"
23906        def main():
23907            for i in range(10):
23908                if i == 3:
23909                    break
23910            ˇ
23911    "});
23912    cx.update_editor(|editor, window, cx| {
23913        editor.handle_input("else:", window, cx);
23914    });
23915    cx.assert_editor_state(indoc! {"
23916        def main():
23917            for i in range(10):
23918                if i == 3:
23919                    break
23920            else:ˇ
23921    "});
23922
23923    // test does not outdent on typing after line with square brackets
23924    cx.set_state(indoc! {"
23925        def f() -> list[str]:
23926            ˇ
23927    "});
23928    cx.update_editor(|editor, window, cx| {
23929        editor.handle_input("a", window, cx);
23930    });
23931    cx.assert_editor_state(indoc! {"
23932        def f() -> list[str]:
2393323934    "});
23935
23936    // test does not outdent on typing : after case keyword
23937    cx.set_state(indoc! {"
23938        match 1:
23939            caseˇ
23940    "});
23941    cx.update_editor(|editor, window, cx| {
23942        editor.handle_input(":", window, cx);
23943    });
23944    cx.assert_editor_state(indoc! {"
23945        match 1:
23946            case:ˇ
23947    "});
23948}
23949
23950#[gpui::test]
23951async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
23952    init_test(cx, |_| {});
23953    update_test_language_settings(cx, |settings| {
23954        settings.defaults.extend_comment_on_newline = Some(false);
23955    });
23956    let mut cx = EditorTestContext::new(cx).await;
23957    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23958    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23959
23960    // test correct indent after newline on comment
23961    cx.set_state(indoc! {"
23962        # COMMENT:ˇ
23963    "});
23964    cx.update_editor(|editor, window, cx| {
23965        editor.newline(&Newline, window, cx);
23966    });
23967    cx.assert_editor_state(indoc! {"
23968        # COMMENT:
23969        ˇ
23970    "});
23971
23972    // test correct indent after newline in brackets
23973    cx.set_state(indoc! {"
23974        {ˇ}
23975    "});
23976    cx.update_editor(|editor, window, cx| {
23977        editor.newline(&Newline, window, cx);
23978    });
23979    cx.run_until_parked();
23980    cx.assert_editor_state(indoc! {"
23981        {
23982            ˇ
23983        }
23984    "});
23985
23986    cx.set_state(indoc! {"
23987        (ˇ)
23988    "});
23989    cx.update_editor(|editor, window, cx| {
23990        editor.newline(&Newline, window, cx);
23991    });
23992    cx.run_until_parked();
23993    cx.assert_editor_state(indoc! {"
23994        (
23995            ˇ
23996        )
23997    "});
23998
23999    // do not indent after empty lists or dictionaries
24000    cx.set_state(indoc! {"
24001        a = []ˇ
24002    "});
24003    cx.update_editor(|editor, window, cx| {
24004        editor.newline(&Newline, window, cx);
24005    });
24006    cx.run_until_parked();
24007    cx.assert_editor_state(indoc! {"
24008        a = []
24009        ˇ
24010    "});
24011}
24012
24013#[gpui::test]
24014async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24015    init_test(cx, |_| {});
24016
24017    let mut cx = EditorTestContext::new(cx).await;
24018    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24019    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24020
24021    // test cursor move to start of each line on tab
24022    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24023    cx.set_state(indoc! {"
24024        function main() {
24025        ˇ    for item in $items; do
24026        ˇ        while [ -n \"$item\" ]; do
24027        ˇ            if [ \"$value\" -gt 10 ]; then
24028        ˇ                continue
24029        ˇ            elif [ \"$value\" -lt 0 ]; then
24030        ˇ                break
24031        ˇ            else
24032        ˇ                echo \"$item\"
24033        ˇ            fi
24034        ˇ        done
24035        ˇ    done
24036        ˇ}
24037    "});
24038    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24039    cx.assert_editor_state(indoc! {"
24040        function main() {
24041            ˇfor item in $items; do
24042                ˇwhile [ -n \"$item\" ]; do
24043                    ˇif [ \"$value\" -gt 10 ]; then
24044                        ˇcontinue
24045                    ˇelif [ \"$value\" -lt 0 ]; then
24046                        ˇbreak
24047                    ˇelse
24048                        ˇecho \"$item\"
24049                    ˇfi
24050                ˇdone
24051            ˇdone
24052        ˇ}
24053    "});
24054    // test relative indent is preserved when tab
24055    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24056    cx.assert_editor_state(indoc! {"
24057        function main() {
24058                ˇfor item in $items; do
24059                    ˇwhile [ -n \"$item\" ]; do
24060                        ˇif [ \"$value\" -gt 10 ]; then
24061                            ˇcontinue
24062                        ˇelif [ \"$value\" -lt 0 ]; then
24063                            ˇbreak
24064                        ˇelse
24065                            ˇecho \"$item\"
24066                        ˇfi
24067                    ˇdone
24068                ˇdone
24069            ˇ}
24070    "});
24071
24072    // test cursor move to start of each line on tab
24073    // for `case` statement with patterns
24074    cx.set_state(indoc! {"
24075        function handle() {
24076        ˇ    case \"$1\" in
24077        ˇ        start)
24078        ˇ            echo \"a\"
24079        ˇ            ;;
24080        ˇ        stop)
24081        ˇ            echo \"b\"
24082        ˇ            ;;
24083        ˇ        *)
24084        ˇ            echo \"c\"
24085        ˇ            ;;
24086        ˇ    esac
24087        ˇ}
24088    "});
24089    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24090    cx.assert_editor_state(indoc! {"
24091        function handle() {
24092            ˇcase \"$1\" in
24093                ˇstart)
24094                    ˇecho \"a\"
24095                    ˇ;;
24096                ˇstop)
24097                    ˇecho \"b\"
24098                    ˇ;;
24099                ˇ*)
24100                    ˇecho \"c\"
24101                    ˇ;;
24102            ˇesac
24103        ˇ}
24104    "});
24105}
24106
24107#[gpui::test]
24108async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24109    init_test(cx, |_| {});
24110
24111    let mut cx = EditorTestContext::new(cx).await;
24112    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24113    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24114
24115    // test indents on comment insert
24116    cx.set_state(indoc! {"
24117        function main() {
24118        ˇ    for item in $items; do
24119        ˇ        while [ -n \"$item\" ]; do
24120        ˇ            if [ \"$value\" -gt 10 ]; then
24121        ˇ                continue
24122        ˇ            elif [ \"$value\" -lt 0 ]; then
24123        ˇ                break
24124        ˇ            else
24125        ˇ                echo \"$item\"
24126        ˇ            fi
24127        ˇ        done
24128        ˇ    done
24129        ˇ}
24130    "});
24131    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24132    cx.assert_editor_state(indoc! {"
24133        function main() {
24134        #ˇ    for item in $items; do
24135        #ˇ        while [ -n \"$item\" ]; do
24136        #ˇ            if [ \"$value\" -gt 10 ]; then
24137        #ˇ                continue
24138        #ˇ            elif [ \"$value\" -lt 0 ]; then
24139        #ˇ                break
24140        #ˇ            else
24141        #ˇ                echo \"$item\"
24142        #ˇ            fi
24143        #ˇ        done
24144        #ˇ    done
24145        #ˇ}
24146    "});
24147}
24148
24149#[gpui::test]
24150async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24151    init_test(cx, |_| {});
24152
24153    let mut cx = EditorTestContext::new(cx).await;
24154    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24155    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24156
24157    // test `else` auto outdents when typed inside `if` block
24158    cx.set_state(indoc! {"
24159        if [ \"$1\" = \"test\" ]; then
24160            echo \"foo bar\"
24161            ˇ
24162    "});
24163    cx.update_editor(|editor, window, cx| {
24164        editor.handle_input("else", window, cx);
24165    });
24166    cx.assert_editor_state(indoc! {"
24167        if [ \"$1\" = \"test\" ]; then
24168            echo \"foo bar\"
24169        elseˇ
24170    "});
24171
24172    // test `elif` auto outdents when typed inside `if` block
24173    cx.set_state(indoc! {"
24174        if [ \"$1\" = \"test\" ]; then
24175            echo \"foo bar\"
24176            ˇ
24177    "});
24178    cx.update_editor(|editor, window, cx| {
24179        editor.handle_input("elif", window, cx);
24180    });
24181    cx.assert_editor_state(indoc! {"
24182        if [ \"$1\" = \"test\" ]; then
24183            echo \"foo bar\"
24184        elifˇ
24185    "});
24186
24187    // test `fi` auto outdents when typed inside `else` block
24188    cx.set_state(indoc! {"
24189        if [ \"$1\" = \"test\" ]; then
24190            echo \"foo bar\"
24191        else
24192            echo \"bar baz\"
24193            ˇ
24194    "});
24195    cx.update_editor(|editor, window, cx| {
24196        editor.handle_input("fi", window, cx);
24197    });
24198    cx.assert_editor_state(indoc! {"
24199        if [ \"$1\" = \"test\" ]; then
24200            echo \"foo bar\"
24201        else
24202            echo \"bar baz\"
24203        fiˇ
24204    "});
24205
24206    // test `done` auto outdents when typed inside `while` block
24207    cx.set_state(indoc! {"
24208        while read line; do
24209            echo \"$line\"
24210            ˇ
24211    "});
24212    cx.update_editor(|editor, window, cx| {
24213        editor.handle_input("done", window, cx);
24214    });
24215    cx.assert_editor_state(indoc! {"
24216        while read line; do
24217            echo \"$line\"
24218        doneˇ
24219    "});
24220
24221    // test `done` auto outdents when typed inside `for` block
24222    cx.set_state(indoc! {"
24223        for file in *.txt; do
24224            cat \"$file\"
24225            ˇ
24226    "});
24227    cx.update_editor(|editor, window, cx| {
24228        editor.handle_input("done", window, cx);
24229    });
24230    cx.assert_editor_state(indoc! {"
24231        for file in *.txt; do
24232            cat \"$file\"
24233        doneˇ
24234    "});
24235
24236    // test `esac` auto outdents when typed inside `case` block
24237    cx.set_state(indoc! {"
24238        case \"$1\" in
24239            start)
24240                echo \"foo bar\"
24241                ;;
24242            stop)
24243                echo \"bar baz\"
24244                ;;
24245            ˇ
24246    "});
24247    cx.update_editor(|editor, window, cx| {
24248        editor.handle_input("esac", window, cx);
24249    });
24250    cx.assert_editor_state(indoc! {"
24251        case \"$1\" in
24252            start)
24253                echo \"foo bar\"
24254                ;;
24255            stop)
24256                echo \"bar baz\"
24257                ;;
24258        esacˇ
24259    "});
24260
24261    // test `*)` auto outdents when typed inside `case` block
24262    cx.set_state(indoc! {"
24263        case \"$1\" in
24264            start)
24265                echo \"foo bar\"
24266                ;;
24267                ˇ
24268    "});
24269    cx.update_editor(|editor, window, cx| {
24270        editor.handle_input("*)", window, cx);
24271    });
24272    cx.assert_editor_state(indoc! {"
24273        case \"$1\" in
24274            start)
24275                echo \"foo bar\"
24276                ;;
24277            *)ˇ
24278    "});
24279
24280    // test `fi` outdents to correct level with nested if blocks
24281    cx.set_state(indoc! {"
24282        if [ \"$1\" = \"test\" ]; then
24283            echo \"outer if\"
24284            if [ \"$2\" = \"debug\" ]; then
24285                echo \"inner if\"
24286                ˇ
24287    "});
24288    cx.update_editor(|editor, window, cx| {
24289        editor.handle_input("fi", window, cx);
24290    });
24291    cx.assert_editor_state(indoc! {"
24292        if [ \"$1\" = \"test\" ]; then
24293            echo \"outer if\"
24294            if [ \"$2\" = \"debug\" ]; then
24295                echo \"inner if\"
24296            fiˇ
24297    "});
24298}
24299
24300#[gpui::test]
24301async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24302    init_test(cx, |_| {});
24303    update_test_language_settings(cx, |settings| {
24304        settings.defaults.extend_comment_on_newline = Some(false);
24305    });
24306    let mut cx = EditorTestContext::new(cx).await;
24307    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24308    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24309
24310    // test correct indent after newline on comment
24311    cx.set_state(indoc! {"
24312        # COMMENT:ˇ
24313    "});
24314    cx.update_editor(|editor, window, cx| {
24315        editor.newline(&Newline, window, cx);
24316    });
24317    cx.assert_editor_state(indoc! {"
24318        # COMMENT:
24319        ˇ
24320    "});
24321
24322    // test correct indent after newline after `then`
24323    cx.set_state(indoc! {"
24324
24325        if [ \"$1\" = \"test\" ]; thenˇ
24326    "});
24327    cx.update_editor(|editor, window, cx| {
24328        editor.newline(&Newline, window, cx);
24329    });
24330    cx.run_until_parked();
24331    cx.assert_editor_state(indoc! {"
24332
24333        if [ \"$1\" = \"test\" ]; then
24334            ˇ
24335    "});
24336
24337    // test correct indent after newline after `else`
24338    cx.set_state(indoc! {"
24339        if [ \"$1\" = \"test\" ]; then
24340        elseˇ
24341    "});
24342    cx.update_editor(|editor, window, cx| {
24343        editor.newline(&Newline, window, cx);
24344    });
24345    cx.run_until_parked();
24346    cx.assert_editor_state(indoc! {"
24347        if [ \"$1\" = \"test\" ]; then
24348        else
24349            ˇ
24350    "});
24351
24352    // test correct indent after newline after `elif`
24353    cx.set_state(indoc! {"
24354        if [ \"$1\" = \"test\" ]; then
24355        elifˇ
24356    "});
24357    cx.update_editor(|editor, window, cx| {
24358        editor.newline(&Newline, window, cx);
24359    });
24360    cx.run_until_parked();
24361    cx.assert_editor_state(indoc! {"
24362        if [ \"$1\" = \"test\" ]; then
24363        elif
24364            ˇ
24365    "});
24366
24367    // test correct indent after newline after `do`
24368    cx.set_state(indoc! {"
24369        for file in *.txt; doˇ
24370    "});
24371    cx.update_editor(|editor, window, cx| {
24372        editor.newline(&Newline, window, cx);
24373    });
24374    cx.run_until_parked();
24375    cx.assert_editor_state(indoc! {"
24376        for file in *.txt; do
24377            ˇ
24378    "});
24379
24380    // test correct indent after newline after case pattern
24381    cx.set_state(indoc! {"
24382        case \"$1\" in
24383            start)ˇ
24384    "});
24385    cx.update_editor(|editor, window, cx| {
24386        editor.newline(&Newline, window, cx);
24387    });
24388    cx.run_until_parked();
24389    cx.assert_editor_state(indoc! {"
24390        case \"$1\" in
24391            start)
24392                ˇ
24393    "});
24394
24395    // test correct indent after newline after case pattern
24396    cx.set_state(indoc! {"
24397        case \"$1\" in
24398            start)
24399                ;;
24400            *)ˇ
24401    "});
24402    cx.update_editor(|editor, window, cx| {
24403        editor.newline(&Newline, window, cx);
24404    });
24405    cx.run_until_parked();
24406    cx.assert_editor_state(indoc! {"
24407        case \"$1\" in
24408            start)
24409                ;;
24410            *)
24411                ˇ
24412    "});
24413
24414    // test correct indent after newline after function opening brace
24415    cx.set_state(indoc! {"
24416        function test() {ˇ}
24417    "});
24418    cx.update_editor(|editor, window, cx| {
24419        editor.newline(&Newline, window, cx);
24420    });
24421    cx.run_until_parked();
24422    cx.assert_editor_state(indoc! {"
24423        function test() {
24424            ˇ
24425        }
24426    "});
24427
24428    // test no extra indent after semicolon on same line
24429    cx.set_state(indoc! {"
24430        echo \"test\"24431    "});
24432    cx.update_editor(|editor, window, cx| {
24433        editor.newline(&Newline, window, cx);
24434    });
24435    cx.run_until_parked();
24436    cx.assert_editor_state(indoc! {"
24437        echo \"test\";
24438        ˇ
24439    "});
24440}
24441
24442fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24443    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24444    point..point
24445}
24446
24447#[track_caller]
24448fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24449    let (text, ranges) = marked_text_ranges(marked_text, true);
24450    assert_eq!(editor.text(cx), text);
24451    assert_eq!(
24452        editor.selections.ranges(cx),
24453        ranges,
24454        "Assert selections are {}",
24455        marked_text
24456    );
24457}
24458
24459pub fn handle_signature_help_request(
24460    cx: &mut EditorLspTestContext,
24461    mocked_response: lsp::SignatureHelp,
24462) -> impl Future<Output = ()> + use<> {
24463    let mut request =
24464        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
24465            let mocked_response = mocked_response.clone();
24466            async move { Ok(Some(mocked_response)) }
24467        });
24468
24469    async move {
24470        request.next().await;
24471    }
24472}
24473
24474#[track_caller]
24475pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
24476    cx.update_editor(|editor, _, _| {
24477        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
24478            let entries = menu.entries.borrow();
24479            let entries = entries
24480                .iter()
24481                .map(|entry| entry.string.as_str())
24482                .collect::<Vec<_>>();
24483            assert_eq!(entries, expected);
24484        } else {
24485            panic!("Expected completions menu");
24486        }
24487    });
24488}
24489
24490/// Handle completion request passing a marked string specifying where the completion
24491/// should be triggered from using '|' character, what range should be replaced, and what completions
24492/// should be returned using '<' and '>' to delimit the range.
24493///
24494/// Also see `handle_completion_request_with_insert_and_replace`.
24495#[track_caller]
24496pub fn handle_completion_request(
24497    marked_string: &str,
24498    completions: Vec<&'static str>,
24499    is_incomplete: bool,
24500    counter: Arc<AtomicUsize>,
24501    cx: &mut EditorLspTestContext,
24502) -> impl Future<Output = ()> {
24503    let complete_from_marker: TextRangeMarker = '|'.into();
24504    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24505    let (_, mut marked_ranges) = marked_text_ranges_by(
24506        marked_string,
24507        vec![complete_from_marker.clone(), replace_range_marker.clone()],
24508    );
24509
24510    let complete_from_position =
24511        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24512    let replace_range =
24513        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24514
24515    let mut request =
24516        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24517            let completions = completions.clone();
24518            counter.fetch_add(1, atomic::Ordering::Release);
24519            async move {
24520                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24521                assert_eq!(
24522                    params.text_document_position.position,
24523                    complete_from_position
24524                );
24525                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
24526                    is_incomplete,
24527                    item_defaults: None,
24528                    items: completions
24529                        .iter()
24530                        .map(|completion_text| lsp::CompletionItem {
24531                            label: completion_text.to_string(),
24532                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
24533                                range: replace_range,
24534                                new_text: completion_text.to_string(),
24535                            })),
24536                            ..Default::default()
24537                        })
24538                        .collect(),
24539                })))
24540            }
24541        });
24542
24543    async move {
24544        request.next().await;
24545    }
24546}
24547
24548/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
24549/// given instead, which also contains an `insert` range.
24550///
24551/// This function uses markers to define ranges:
24552/// - `|` marks the cursor position
24553/// - `<>` marks the replace range
24554/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
24555pub fn handle_completion_request_with_insert_and_replace(
24556    cx: &mut EditorLspTestContext,
24557    marked_string: &str,
24558    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
24559    counter: Arc<AtomicUsize>,
24560) -> impl Future<Output = ()> {
24561    let complete_from_marker: TextRangeMarker = '|'.into();
24562    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24563    let insert_range_marker: TextRangeMarker = ('{', '}').into();
24564
24565    let (_, mut marked_ranges) = marked_text_ranges_by(
24566        marked_string,
24567        vec![
24568            complete_from_marker.clone(),
24569            replace_range_marker.clone(),
24570            insert_range_marker.clone(),
24571        ],
24572    );
24573
24574    let complete_from_position =
24575        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24576    let replace_range =
24577        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24578
24579    let insert_range = match marked_ranges.remove(&insert_range_marker) {
24580        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
24581        _ => lsp::Range {
24582            start: replace_range.start,
24583            end: complete_from_position,
24584        },
24585    };
24586
24587    let mut request =
24588        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24589            let completions = completions.clone();
24590            counter.fetch_add(1, atomic::Ordering::Release);
24591            async move {
24592                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24593                assert_eq!(
24594                    params.text_document_position.position, complete_from_position,
24595                    "marker `|` position doesn't match",
24596                );
24597                Ok(Some(lsp::CompletionResponse::Array(
24598                    completions
24599                        .iter()
24600                        .map(|(label, new_text)| lsp::CompletionItem {
24601                            label: label.to_string(),
24602                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24603                                lsp::InsertReplaceEdit {
24604                                    insert: insert_range,
24605                                    replace: replace_range,
24606                                    new_text: new_text.to_string(),
24607                                },
24608                            )),
24609                            ..Default::default()
24610                        })
24611                        .collect(),
24612                )))
24613            }
24614        });
24615
24616    async move {
24617        request.next().await;
24618    }
24619}
24620
24621fn handle_resolve_completion_request(
24622    cx: &mut EditorLspTestContext,
24623    edits: Option<Vec<(&'static str, &'static str)>>,
24624) -> impl Future<Output = ()> {
24625    let edits = edits.map(|edits| {
24626        edits
24627            .iter()
24628            .map(|(marked_string, new_text)| {
24629                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
24630                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
24631                lsp::TextEdit::new(replace_range, new_text.to_string())
24632            })
24633            .collect::<Vec<_>>()
24634    });
24635
24636    let mut request =
24637        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
24638            let edits = edits.clone();
24639            async move {
24640                Ok(lsp::CompletionItem {
24641                    additional_text_edits: edits,
24642                    ..Default::default()
24643                })
24644            }
24645        });
24646
24647    async move {
24648        request.next().await;
24649    }
24650}
24651
24652pub(crate) fn update_test_language_settings(
24653    cx: &mut TestAppContext,
24654    f: impl Fn(&mut AllLanguageSettingsContent),
24655) {
24656    cx.update(|cx| {
24657        SettingsStore::update_global(cx, |store, cx| {
24658            store.update_user_settings::<AllLanguageSettings>(cx, f);
24659        });
24660    });
24661}
24662
24663pub(crate) fn update_test_project_settings(
24664    cx: &mut TestAppContext,
24665    f: impl Fn(&mut ProjectSettings),
24666) {
24667    cx.update(|cx| {
24668        SettingsStore::update_global(cx, |store, cx| {
24669            store.update_user_settings::<ProjectSettings>(cx, f);
24670        });
24671    });
24672}
24673
24674pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
24675    cx.update(|cx| {
24676        assets::Assets.load_test_fonts(cx);
24677        let store = SettingsStore::test(cx);
24678        cx.set_global(store);
24679        theme::init(theme::LoadThemes::JustBase, cx);
24680        release_channel::init(SemanticVersion::default(), cx);
24681        client::init_settings(cx);
24682        language::init(cx);
24683        Project::init_settings(cx);
24684        workspace::init_settings(cx);
24685        crate::init(cx);
24686    });
24687    zlog::init_test();
24688    update_test_language_settings(cx, f);
24689}
24690
24691#[track_caller]
24692fn assert_hunk_revert(
24693    not_reverted_text_with_selections: &str,
24694    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
24695    expected_reverted_text_with_selections: &str,
24696    base_text: &str,
24697    cx: &mut EditorLspTestContext,
24698) {
24699    cx.set_state(not_reverted_text_with_selections);
24700    cx.set_head_text(base_text);
24701    cx.executor().run_until_parked();
24702
24703    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
24704        let snapshot = editor.snapshot(window, cx);
24705        let reverted_hunk_statuses = snapshot
24706            .buffer_snapshot
24707            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
24708            .map(|hunk| hunk.status().kind)
24709            .collect::<Vec<_>>();
24710
24711        editor.git_restore(&Default::default(), window, cx);
24712        reverted_hunk_statuses
24713    });
24714    cx.executor().run_until_parked();
24715    cx.assert_editor_state(expected_reverted_text_with_selections);
24716    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
24717}
24718
24719#[gpui::test(iterations = 10)]
24720async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
24721    init_test(cx, |_| {});
24722
24723    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
24724    let counter = diagnostic_requests.clone();
24725
24726    let fs = FakeFs::new(cx.executor());
24727    fs.insert_tree(
24728        path!("/a"),
24729        json!({
24730            "first.rs": "fn main() { let a = 5; }",
24731            "second.rs": "// Test file",
24732        }),
24733    )
24734    .await;
24735
24736    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24737    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24738    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24739
24740    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24741    language_registry.add(rust_lang());
24742    let mut fake_servers = language_registry.register_fake_lsp(
24743        "Rust",
24744        FakeLspAdapter {
24745            capabilities: lsp::ServerCapabilities {
24746                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
24747                    lsp::DiagnosticOptions {
24748                        identifier: None,
24749                        inter_file_dependencies: true,
24750                        workspace_diagnostics: true,
24751                        work_done_progress_options: Default::default(),
24752                    },
24753                )),
24754                ..Default::default()
24755            },
24756            ..Default::default()
24757        },
24758    );
24759
24760    let editor = workspace
24761        .update(cx, |workspace, window, cx| {
24762            workspace.open_abs_path(
24763                PathBuf::from(path!("/a/first.rs")),
24764                OpenOptions::default(),
24765                window,
24766                cx,
24767            )
24768        })
24769        .unwrap()
24770        .await
24771        .unwrap()
24772        .downcast::<Editor>()
24773        .unwrap();
24774    let fake_server = fake_servers.next().await.unwrap();
24775    let server_id = fake_server.server.server_id();
24776    let mut first_request = fake_server
24777        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
24778            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
24779            let result_id = Some(new_result_id.to_string());
24780            assert_eq!(
24781                params.text_document.uri,
24782                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
24783            );
24784            async move {
24785                Ok(lsp::DocumentDiagnosticReportResult::Report(
24786                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
24787                        related_documents: None,
24788                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
24789                            items: Vec::new(),
24790                            result_id,
24791                        },
24792                    }),
24793                ))
24794            }
24795        });
24796
24797    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
24798        project.update(cx, |project, cx| {
24799            let buffer_id = editor
24800                .read(cx)
24801                .buffer()
24802                .read(cx)
24803                .as_singleton()
24804                .expect("created a singleton buffer")
24805                .read(cx)
24806                .remote_id();
24807            let buffer_result_id = project
24808                .lsp_store()
24809                .read(cx)
24810                .result_id(server_id, buffer_id, cx);
24811            assert_eq!(expected, buffer_result_id);
24812        });
24813    };
24814
24815    ensure_result_id(None, cx);
24816    cx.executor().advance_clock(Duration::from_millis(60));
24817    cx.executor().run_until_parked();
24818    assert_eq!(
24819        diagnostic_requests.load(atomic::Ordering::Acquire),
24820        1,
24821        "Opening file should trigger diagnostic request"
24822    );
24823    first_request
24824        .next()
24825        .await
24826        .expect("should have sent the first diagnostics pull request");
24827    ensure_result_id(Some("1".to_string()), cx);
24828
24829    // Editing should trigger diagnostics
24830    editor.update_in(cx, |editor, window, cx| {
24831        editor.handle_input("2", window, cx)
24832    });
24833    cx.executor().advance_clock(Duration::from_millis(60));
24834    cx.executor().run_until_parked();
24835    assert_eq!(
24836        diagnostic_requests.load(atomic::Ordering::Acquire),
24837        2,
24838        "Editing should trigger diagnostic request"
24839    );
24840    ensure_result_id(Some("2".to_string()), cx);
24841
24842    // Moving cursor should not trigger diagnostic request
24843    editor.update_in(cx, |editor, window, cx| {
24844        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24845            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
24846        });
24847    });
24848    cx.executor().advance_clock(Duration::from_millis(60));
24849    cx.executor().run_until_parked();
24850    assert_eq!(
24851        diagnostic_requests.load(atomic::Ordering::Acquire),
24852        2,
24853        "Cursor movement should not trigger diagnostic request"
24854    );
24855    ensure_result_id(Some("2".to_string()), cx);
24856    // Multiple rapid edits should be debounced
24857    for _ in 0..5 {
24858        editor.update_in(cx, |editor, window, cx| {
24859            editor.handle_input("x", window, cx)
24860        });
24861    }
24862    cx.executor().advance_clock(Duration::from_millis(60));
24863    cx.executor().run_until_parked();
24864
24865    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
24866    assert!(
24867        final_requests <= 4,
24868        "Multiple rapid edits should be debounced (got {final_requests} requests)",
24869    );
24870    ensure_result_id(Some(final_requests.to_string()), cx);
24871}
24872
24873#[gpui::test]
24874async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
24875    // Regression test for issue #11671
24876    // Previously, adding a cursor after moving multiple cursors would reset
24877    // the cursor count instead of adding to the existing cursors.
24878    init_test(cx, |_| {});
24879    let mut cx = EditorTestContext::new(cx).await;
24880
24881    // Create a simple buffer with cursor at start
24882    cx.set_state(indoc! {"
24883        ˇaaaa
24884        bbbb
24885        cccc
24886        dddd
24887        eeee
24888        ffff
24889        gggg
24890        hhhh"});
24891
24892    // Add 2 cursors below (so we have 3 total)
24893    cx.update_editor(|editor, window, cx| {
24894        editor.add_selection_below(&Default::default(), window, cx);
24895        editor.add_selection_below(&Default::default(), window, cx);
24896    });
24897
24898    // Verify we have 3 cursors
24899    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
24900    assert_eq!(
24901        initial_count, 3,
24902        "Should have 3 cursors after adding 2 below"
24903    );
24904
24905    // Move down one line
24906    cx.update_editor(|editor, window, cx| {
24907        editor.move_down(&MoveDown, window, cx);
24908    });
24909
24910    // Add another cursor below
24911    cx.update_editor(|editor, window, cx| {
24912        editor.add_selection_below(&Default::default(), window, cx);
24913    });
24914
24915    // Should now have 4 cursors (3 original + 1 new)
24916    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
24917    assert_eq!(
24918        final_count, 4,
24919        "Should have 4 cursors after moving and adding another"
24920    );
24921}
24922
24923#[gpui::test(iterations = 10)]
24924async fn test_document_colors(cx: &mut TestAppContext) {
24925    let expected_color = Rgba {
24926        r: 0.33,
24927        g: 0.33,
24928        b: 0.33,
24929        a: 0.33,
24930    };
24931
24932    init_test(cx, |_| {});
24933
24934    let fs = FakeFs::new(cx.executor());
24935    fs.insert_tree(
24936        path!("/a"),
24937        json!({
24938            "first.rs": "fn main() { let a = 5; }",
24939        }),
24940    )
24941    .await;
24942
24943    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24944    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24945    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24946
24947    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24948    language_registry.add(rust_lang());
24949    let mut fake_servers = language_registry.register_fake_lsp(
24950        "Rust",
24951        FakeLspAdapter {
24952            capabilities: lsp::ServerCapabilities {
24953                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
24954                ..lsp::ServerCapabilities::default()
24955            },
24956            name: "rust-analyzer",
24957            ..FakeLspAdapter::default()
24958        },
24959    );
24960    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
24961        "Rust",
24962        FakeLspAdapter {
24963            capabilities: lsp::ServerCapabilities {
24964                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
24965                ..lsp::ServerCapabilities::default()
24966            },
24967            name: "not-rust-analyzer",
24968            ..FakeLspAdapter::default()
24969        },
24970    );
24971
24972    let editor = workspace
24973        .update(cx, |workspace, window, cx| {
24974            workspace.open_abs_path(
24975                PathBuf::from(path!("/a/first.rs")),
24976                OpenOptions::default(),
24977                window,
24978                cx,
24979            )
24980        })
24981        .unwrap()
24982        .await
24983        .unwrap()
24984        .downcast::<Editor>()
24985        .unwrap();
24986    let fake_language_server = fake_servers.next().await.unwrap();
24987    let fake_language_server_without_capabilities =
24988        fake_servers_without_capabilities.next().await.unwrap();
24989    let requests_made = Arc::new(AtomicUsize::new(0));
24990    let closure_requests_made = Arc::clone(&requests_made);
24991    let mut color_request_handle = fake_language_server
24992        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
24993            let requests_made = Arc::clone(&closure_requests_made);
24994            async move {
24995                assert_eq!(
24996                    params.text_document.uri,
24997                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
24998                );
24999                requests_made.fetch_add(1, atomic::Ordering::Release);
25000                Ok(vec![
25001                    lsp::ColorInformation {
25002                        range: lsp::Range {
25003                            start: lsp::Position {
25004                                line: 0,
25005                                character: 0,
25006                            },
25007                            end: lsp::Position {
25008                                line: 0,
25009                                character: 1,
25010                            },
25011                        },
25012                        color: lsp::Color {
25013                            red: 0.33,
25014                            green: 0.33,
25015                            blue: 0.33,
25016                            alpha: 0.33,
25017                        },
25018                    },
25019                    lsp::ColorInformation {
25020                        range: lsp::Range {
25021                            start: lsp::Position {
25022                                line: 0,
25023                                character: 0,
25024                            },
25025                            end: lsp::Position {
25026                                line: 0,
25027                                character: 1,
25028                            },
25029                        },
25030                        color: lsp::Color {
25031                            red: 0.33,
25032                            green: 0.33,
25033                            blue: 0.33,
25034                            alpha: 0.33,
25035                        },
25036                    },
25037                ])
25038            }
25039        });
25040
25041    let _handle = fake_language_server_without_capabilities
25042        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25043            panic!("Should not be called");
25044        });
25045    cx.executor().advance_clock(Duration::from_millis(100));
25046    color_request_handle.next().await.unwrap();
25047    cx.run_until_parked();
25048    assert_eq!(
25049        1,
25050        requests_made.load(atomic::Ordering::Acquire),
25051        "Should query for colors once per editor open"
25052    );
25053    editor.update_in(cx, |editor, _, cx| {
25054        assert_eq!(
25055            vec![expected_color],
25056            extract_color_inlays(editor, cx),
25057            "Should have an initial inlay"
25058        );
25059    });
25060
25061    // opening another file in a split should not influence the LSP query counter
25062    workspace
25063        .update(cx, |workspace, window, cx| {
25064            assert_eq!(
25065                workspace.panes().len(),
25066                1,
25067                "Should have one pane with one editor"
25068            );
25069            workspace.move_item_to_pane_in_direction(
25070                &MoveItemToPaneInDirection {
25071                    direction: SplitDirection::Right,
25072                    focus: false,
25073                    clone: true,
25074                },
25075                window,
25076                cx,
25077            );
25078        })
25079        .unwrap();
25080    cx.run_until_parked();
25081    workspace
25082        .update(cx, |workspace, _, cx| {
25083            let panes = workspace.panes();
25084            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25085            for pane in panes {
25086                let editor = pane
25087                    .read(cx)
25088                    .active_item()
25089                    .and_then(|item| item.downcast::<Editor>())
25090                    .expect("Should have opened an editor in each split");
25091                let editor_file = editor
25092                    .read(cx)
25093                    .buffer()
25094                    .read(cx)
25095                    .as_singleton()
25096                    .expect("test deals with singleton buffers")
25097                    .read(cx)
25098                    .file()
25099                    .expect("test buffese should have a file")
25100                    .path();
25101                assert_eq!(
25102                    editor_file.as_ref(),
25103                    Path::new("first.rs"),
25104                    "Both editors should be opened for the same file"
25105                )
25106            }
25107        })
25108        .unwrap();
25109
25110    cx.executor().advance_clock(Duration::from_millis(500));
25111    let save = editor.update_in(cx, |editor, window, cx| {
25112        editor.move_to_end(&MoveToEnd, window, cx);
25113        editor.handle_input("dirty", window, cx);
25114        editor.save(
25115            SaveOptions {
25116                format: true,
25117                autosave: true,
25118            },
25119            project.clone(),
25120            window,
25121            cx,
25122        )
25123    });
25124    save.await.unwrap();
25125
25126    color_request_handle.next().await.unwrap();
25127    cx.run_until_parked();
25128    assert_eq!(
25129        3,
25130        requests_made.load(atomic::Ordering::Acquire),
25131        "Should query for colors once per save and once per formatting after save"
25132    );
25133
25134    drop(editor);
25135    let close = workspace
25136        .update(cx, |workspace, window, cx| {
25137            workspace.active_pane().update(cx, |pane, cx| {
25138                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25139            })
25140        })
25141        .unwrap();
25142    close.await.unwrap();
25143    let close = workspace
25144        .update(cx, |workspace, window, cx| {
25145            workspace.active_pane().update(cx, |pane, cx| {
25146                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25147            })
25148        })
25149        .unwrap();
25150    close.await.unwrap();
25151    assert_eq!(
25152        3,
25153        requests_made.load(atomic::Ordering::Acquire),
25154        "After saving and closing all editors, no extra requests should be made"
25155    );
25156    workspace
25157        .update(cx, |workspace, _, cx| {
25158            assert!(
25159                workspace.active_item(cx).is_none(),
25160                "Should close all editors"
25161            )
25162        })
25163        .unwrap();
25164
25165    workspace
25166        .update(cx, |workspace, window, cx| {
25167            workspace.active_pane().update(cx, |pane, cx| {
25168                pane.navigate_backward(&Default::default(), window, cx);
25169            })
25170        })
25171        .unwrap();
25172    cx.executor().advance_clock(Duration::from_millis(100));
25173    cx.run_until_parked();
25174    let editor = workspace
25175        .update(cx, |workspace, _, cx| {
25176            workspace
25177                .active_item(cx)
25178                .expect("Should have reopened the editor again after navigating back")
25179                .downcast::<Editor>()
25180                .expect("Should be an editor")
25181        })
25182        .unwrap();
25183    color_request_handle.next().await.unwrap();
25184    assert_eq!(
25185        3,
25186        requests_made.load(atomic::Ordering::Acquire),
25187        "Cache should be reused on buffer close and reopen"
25188    );
25189    editor.update(cx, |editor, cx| {
25190        assert_eq!(
25191            vec![expected_color],
25192            extract_color_inlays(editor, cx),
25193            "Should have an initial inlay"
25194        );
25195    });
25196}
25197
25198#[gpui::test]
25199async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25200    init_test(cx, |_| {});
25201    let (editor, cx) = cx.add_window_view(Editor::single_line);
25202    editor.update_in(cx, |editor, window, cx| {
25203        editor.set_text("oops\n\nwow\n", window, cx)
25204    });
25205    cx.run_until_parked();
25206    editor.update(cx, |editor, cx| {
25207        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25208    });
25209    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25210    cx.run_until_parked();
25211    editor.update(cx, |editor, cx| {
25212        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25213    });
25214}
25215
25216#[gpui::test]
25217async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25218    init_test(cx, |_| {});
25219
25220    cx.update(|cx| {
25221        register_project_item::<Editor>(cx);
25222    });
25223
25224    let fs = FakeFs::new(cx.executor());
25225    fs.insert_tree("/root1", json!({})).await;
25226    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25227        .await;
25228
25229    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25230    let (workspace, cx) =
25231        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25232
25233    let worktree_id = project.update(cx, |project, cx| {
25234        project.worktrees(cx).next().unwrap().read(cx).id()
25235    });
25236
25237    let handle = workspace
25238        .update_in(cx, |workspace, window, cx| {
25239            let project_path = (worktree_id, "one.pdf");
25240            workspace.open_path(project_path, None, true, window, cx)
25241        })
25242        .await
25243        .unwrap();
25244
25245    assert_eq!(
25246        handle.to_any().entity_type(),
25247        TypeId::of::<InvalidBufferView>()
25248    );
25249}
25250
25251#[track_caller]
25252fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
25253    editor
25254        .all_inlays(cx)
25255        .into_iter()
25256        .filter_map(|inlay| inlay.get_color())
25257        .map(Rgba::from)
25258        .collect()
25259}