editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings, FormatterList,
   29        LanguageSettingsContent, LspInsertMode, PrettierSettings, SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::{LspSettings, ProjectSettings},
   42};
   43use serde_json::{self, json};
   44use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   45use std::{
   46    iter,
   47    sync::atomic::{self, AtomicUsize},
   48};
   49use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   50use text::ToPoint as _;
   51use unindent::Unindent;
   52use util::{
   53    assert_set_eq, path,
   54    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   55    uri,
   56};
   57use workspace::{
   58    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   59    OpenOptions, ViewId,
   60    invalid_buffer_view::InvalidBufferView,
   61    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   62    register_project_item,
   63};
   64
   65#[gpui::test]
   66fn test_edit_events(cx: &mut TestAppContext) {
   67    init_test(cx, |_| {});
   68
   69    let buffer = cx.new(|cx| {
   70        let mut buffer = language::Buffer::local("123456", cx);
   71        buffer.set_group_interval(Duration::from_secs(1));
   72        buffer
   73    });
   74
   75    let events = Rc::new(RefCell::new(Vec::new()));
   76    let editor1 = cx.add_window({
   77        let events = events.clone();
   78        |window, cx| {
   79            let entity = cx.entity();
   80            cx.subscribe_in(
   81                &entity,
   82                window,
   83                move |_, _, event: &EditorEvent, _, _| match event {
   84                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   85                    EditorEvent::BufferEdited => {
   86                        events.borrow_mut().push(("editor1", "buffer edited"))
   87                    }
   88                    _ => {}
   89                },
   90            )
   91            .detach();
   92            Editor::for_buffer(buffer.clone(), None, window, cx)
   93        }
   94    });
   95
   96    let editor2 = cx.add_window({
   97        let events = events.clone();
   98        |window, cx| {
   99            cx.subscribe_in(
  100                &cx.entity(),
  101                window,
  102                move |_, _, event: &EditorEvent, _, _| match event {
  103                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  104                    EditorEvent::BufferEdited => {
  105                        events.borrow_mut().push(("editor2", "buffer edited"))
  106                    }
  107                    _ => {}
  108                },
  109            )
  110            .detach();
  111            Editor::for_buffer(buffer.clone(), None, window, cx)
  112        }
  113    });
  114
  115    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  116
  117    // Mutating editor 1 will emit an `Edited` event only for that editor.
  118    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  119    assert_eq!(
  120        mem::take(&mut *events.borrow_mut()),
  121        [
  122            ("editor1", "edited"),
  123            ("editor1", "buffer edited"),
  124            ("editor2", "buffer edited"),
  125        ]
  126    );
  127
  128    // Mutating editor 2 will emit an `Edited` event only for that editor.
  129    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  130    assert_eq!(
  131        mem::take(&mut *events.borrow_mut()),
  132        [
  133            ("editor2", "edited"),
  134            ("editor1", "buffer edited"),
  135            ("editor2", "buffer edited"),
  136        ]
  137    );
  138
  139    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  140    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  141    assert_eq!(
  142        mem::take(&mut *events.borrow_mut()),
  143        [
  144            ("editor1", "edited"),
  145            ("editor1", "buffer edited"),
  146            ("editor2", "buffer edited"),
  147        ]
  148    );
  149
  150    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  151    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  152    assert_eq!(
  153        mem::take(&mut *events.borrow_mut()),
  154        [
  155            ("editor1", "edited"),
  156            ("editor1", "buffer edited"),
  157            ("editor2", "buffer edited"),
  158        ]
  159    );
  160
  161    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  162    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  163    assert_eq!(
  164        mem::take(&mut *events.borrow_mut()),
  165        [
  166            ("editor2", "edited"),
  167            ("editor1", "buffer edited"),
  168            ("editor2", "buffer edited"),
  169        ]
  170    );
  171
  172    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  173    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  174    assert_eq!(
  175        mem::take(&mut *events.borrow_mut()),
  176        [
  177            ("editor2", "edited"),
  178            ("editor1", "buffer edited"),
  179            ("editor2", "buffer edited"),
  180        ]
  181    );
  182
  183    // No event is emitted when the mutation is a no-op.
  184    _ = editor2.update(cx, |editor, window, cx| {
  185        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  186            s.select_ranges([0..0])
  187        });
  188
  189        editor.backspace(&Backspace, window, cx);
  190    });
  191    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  192}
  193
  194#[gpui::test]
  195fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  196    init_test(cx, |_| {});
  197
  198    let mut now = Instant::now();
  199    let group_interval = Duration::from_millis(1);
  200    let buffer = cx.new(|cx| {
  201        let mut buf = language::Buffer::local("123456", cx);
  202        buf.set_group_interval(group_interval);
  203        buf
  204    });
  205    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  206    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  207
  208    _ = editor.update(cx, |editor, window, cx| {
  209        editor.start_transaction_at(now, window, cx);
  210        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  211            s.select_ranges([2..4])
  212        });
  213
  214        editor.insert("cd", window, cx);
  215        editor.end_transaction_at(now, cx);
  216        assert_eq!(editor.text(cx), "12cd56");
  217        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  218
  219        editor.start_transaction_at(now, window, cx);
  220        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  221            s.select_ranges([4..5])
  222        });
  223        editor.insert("e", window, cx);
  224        editor.end_transaction_at(now, cx);
  225        assert_eq!(editor.text(cx), "12cde6");
  226        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  227
  228        now += group_interval + Duration::from_millis(1);
  229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  230            s.select_ranges([2..2])
  231        });
  232
  233        // Simulate an edit in another editor
  234        buffer.update(cx, |buffer, cx| {
  235            buffer.start_transaction_at(now, cx);
  236            buffer.edit([(0..1, "a")], None, cx);
  237            buffer.edit([(1..1, "b")], None, cx);
  238            buffer.end_transaction_at(now, cx);
  239        });
  240
  241        assert_eq!(editor.text(cx), "ab2cde6");
  242        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  243
  244        // Last transaction happened past the group interval in a different editor.
  245        // Undo it individually and don't restore selections.
  246        editor.undo(&Undo, window, cx);
  247        assert_eq!(editor.text(cx), "12cde6");
  248        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  249
  250        // First two transactions happened within the group interval in this editor.
  251        // Undo them together and restore selections.
  252        editor.undo(&Undo, window, cx);
  253        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  254        assert_eq!(editor.text(cx), "123456");
  255        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  256
  257        // Redo the first two transactions together.
  258        editor.redo(&Redo, window, cx);
  259        assert_eq!(editor.text(cx), "12cde6");
  260        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  261
  262        // Redo the last transaction on its own.
  263        editor.redo(&Redo, window, cx);
  264        assert_eq!(editor.text(cx), "ab2cde6");
  265        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  266
  267        // Test empty transactions.
  268        editor.start_transaction_at(now, window, cx);
  269        editor.end_transaction_at(now, cx);
  270        editor.undo(&Undo, window, cx);
  271        assert_eq!(editor.text(cx), "12cde6");
  272    });
  273}
  274
  275#[gpui::test]
  276fn test_ime_composition(cx: &mut TestAppContext) {
  277    init_test(cx, |_| {});
  278
  279    let buffer = cx.new(|cx| {
  280        let mut buffer = language::Buffer::local("abcde", cx);
  281        // Ensure automatic grouping doesn't occur.
  282        buffer.set_group_interval(Duration::ZERO);
  283        buffer
  284    });
  285
  286    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  287    cx.add_window(|window, cx| {
  288        let mut editor = build_editor(buffer.clone(), window, cx);
  289
  290        // Start a new IME composition.
  291        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  292        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  293        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  294        assert_eq!(editor.text(cx), "äbcde");
  295        assert_eq!(
  296            editor.marked_text_ranges(cx),
  297            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  298        );
  299
  300        // Finalize IME composition.
  301        editor.replace_text_in_range(None, "ā", window, cx);
  302        assert_eq!(editor.text(cx), "ābcde");
  303        assert_eq!(editor.marked_text_ranges(cx), None);
  304
  305        // IME composition edits are grouped and are undone/redone at once.
  306        editor.undo(&Default::default(), window, cx);
  307        assert_eq!(editor.text(cx), "abcde");
  308        assert_eq!(editor.marked_text_ranges(cx), None);
  309        editor.redo(&Default::default(), window, cx);
  310        assert_eq!(editor.text(cx), "ābcde");
  311        assert_eq!(editor.marked_text_ranges(cx), None);
  312
  313        // Start a new IME composition.
  314        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  315        assert_eq!(
  316            editor.marked_text_ranges(cx),
  317            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  318        );
  319
  320        // Undoing during an IME composition cancels it.
  321        editor.undo(&Default::default(), window, cx);
  322        assert_eq!(editor.text(cx), "ābcde");
  323        assert_eq!(editor.marked_text_ranges(cx), None);
  324
  325        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  326        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  327        assert_eq!(editor.text(cx), "ābcdè");
  328        assert_eq!(
  329            editor.marked_text_ranges(cx),
  330            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  331        );
  332
  333        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  334        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  335        assert_eq!(editor.text(cx), "ābcdę");
  336        assert_eq!(editor.marked_text_ranges(cx), None);
  337
  338        // Start a new IME composition with multiple cursors.
  339        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  340            s.select_ranges([
  341                OffsetUtf16(1)..OffsetUtf16(1),
  342                OffsetUtf16(3)..OffsetUtf16(3),
  343                OffsetUtf16(5)..OffsetUtf16(5),
  344            ])
  345        });
  346        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  347        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  348        assert_eq!(
  349            editor.marked_text_ranges(cx),
  350            Some(vec![
  351                OffsetUtf16(0)..OffsetUtf16(3),
  352                OffsetUtf16(4)..OffsetUtf16(7),
  353                OffsetUtf16(8)..OffsetUtf16(11)
  354            ])
  355        );
  356
  357        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  358        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  359        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  360        assert_eq!(
  361            editor.marked_text_ranges(cx),
  362            Some(vec![
  363                OffsetUtf16(1)..OffsetUtf16(2),
  364                OffsetUtf16(5)..OffsetUtf16(6),
  365                OffsetUtf16(9)..OffsetUtf16(10)
  366            ])
  367        );
  368
  369        // Finalize IME composition with multiple cursors.
  370        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  371        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  372        assert_eq!(editor.marked_text_ranges(cx), None);
  373
  374        editor
  375    });
  376}
  377
  378#[gpui::test]
  379fn test_selection_with_mouse(cx: &mut TestAppContext) {
  380    init_test(cx, |_| {});
  381
  382    let editor = cx.add_window(|window, cx| {
  383        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  384        build_editor(buffer, window, cx)
  385    });
  386
  387    _ = editor.update(cx, |editor, window, cx| {
  388        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  389    });
  390    assert_eq!(
  391        editor
  392            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  393            .unwrap(),
  394        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  395    );
  396
  397    _ = editor.update(cx, |editor, window, cx| {
  398        editor.update_selection(
  399            DisplayPoint::new(DisplayRow(3), 3),
  400            0,
  401            gpui::Point::<f32>::default(),
  402            window,
  403            cx,
  404        );
  405    });
  406
  407    assert_eq!(
  408        editor
  409            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  410            .unwrap(),
  411        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  412    );
  413
  414    _ = editor.update(cx, |editor, window, cx| {
  415        editor.update_selection(
  416            DisplayPoint::new(DisplayRow(1), 1),
  417            0,
  418            gpui::Point::<f32>::default(),
  419            window,
  420            cx,
  421        );
  422    });
  423
  424    assert_eq!(
  425        editor
  426            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  427            .unwrap(),
  428        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  429    );
  430
  431    _ = editor.update(cx, |editor, window, cx| {
  432        editor.end_selection(window, cx);
  433        editor.update_selection(
  434            DisplayPoint::new(DisplayRow(3), 3),
  435            0,
  436            gpui::Point::<f32>::default(),
  437            window,
  438            cx,
  439        );
  440    });
  441
  442    assert_eq!(
  443        editor
  444            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  445            .unwrap(),
  446        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  447    );
  448
  449    _ = editor.update(cx, |editor, window, cx| {
  450        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  451        editor.update_selection(
  452            DisplayPoint::new(DisplayRow(0), 0),
  453            0,
  454            gpui::Point::<f32>::default(),
  455            window,
  456            cx,
  457        );
  458    });
  459
  460    assert_eq!(
  461        editor
  462            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  463            .unwrap(),
  464        [
  465            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  466            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  467        ]
  468    );
  469
  470    _ = editor.update(cx, |editor, window, cx| {
  471        editor.end_selection(window, cx);
  472    });
  473
  474    assert_eq!(
  475        editor
  476            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  477            .unwrap(),
  478        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  479    );
  480}
  481
  482#[gpui::test]
  483fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  484    init_test(cx, |_| {});
  485
  486    let editor = cx.add_window(|window, cx| {
  487        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  488        build_editor(buffer, window, cx)
  489    });
  490
  491    _ = editor.update(cx, |editor, window, cx| {
  492        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  493    });
  494
  495    _ = editor.update(cx, |editor, window, cx| {
  496        editor.end_selection(window, cx);
  497    });
  498
  499    _ = editor.update(cx, |editor, window, cx| {
  500        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  501    });
  502
  503    _ = editor.update(cx, |editor, window, cx| {
  504        editor.end_selection(window, cx);
  505    });
  506
  507    assert_eq!(
  508        editor
  509            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  510            .unwrap(),
  511        [
  512            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  513            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  514        ]
  515    );
  516
  517    _ = editor.update(cx, |editor, window, cx| {
  518        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  519    });
  520
  521    _ = editor.update(cx, |editor, window, cx| {
  522        editor.end_selection(window, cx);
  523    });
  524
  525    assert_eq!(
  526        editor
  527            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  528            .unwrap(),
  529        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  530    );
  531}
  532
  533#[gpui::test]
  534fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  535    init_test(cx, |_| {});
  536
  537    let editor = cx.add_window(|window, cx| {
  538        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  539        build_editor(buffer, window, cx)
  540    });
  541
  542    _ = editor.update(cx, |editor, window, cx| {
  543        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  544        assert_eq!(
  545            editor.selections.display_ranges(cx),
  546            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  547        );
  548    });
  549
  550    _ = editor.update(cx, |editor, window, cx| {
  551        editor.update_selection(
  552            DisplayPoint::new(DisplayRow(3), 3),
  553            0,
  554            gpui::Point::<f32>::default(),
  555            window,
  556            cx,
  557        );
  558        assert_eq!(
  559            editor.selections.display_ranges(cx),
  560            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  561        );
  562    });
  563
  564    _ = editor.update(cx, |editor, window, cx| {
  565        editor.cancel(&Cancel, window, cx);
  566        editor.update_selection(
  567            DisplayPoint::new(DisplayRow(1), 1),
  568            0,
  569            gpui::Point::<f32>::default(),
  570            window,
  571            cx,
  572        );
  573        assert_eq!(
  574            editor.selections.display_ranges(cx),
  575            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  576        );
  577    });
  578}
  579
  580#[gpui::test]
  581fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  582    init_test(cx, |_| {});
  583
  584    let editor = cx.add_window(|window, cx| {
  585        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  586        build_editor(buffer, window, cx)
  587    });
  588
  589    _ = editor.update(cx, |editor, window, cx| {
  590        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  591        assert_eq!(
  592            editor.selections.display_ranges(cx),
  593            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  594        );
  595
  596        editor.move_down(&Default::default(), window, cx);
  597        assert_eq!(
  598            editor.selections.display_ranges(cx),
  599            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  600        );
  601
  602        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  603        assert_eq!(
  604            editor.selections.display_ranges(cx),
  605            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  606        );
  607
  608        editor.move_up(&Default::default(), window, cx);
  609        assert_eq!(
  610            editor.selections.display_ranges(cx),
  611            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  612        );
  613    });
  614}
  615
  616#[gpui::test]
  617fn test_clone(cx: &mut TestAppContext) {
  618    init_test(cx, |_| {});
  619
  620    let (text, selection_ranges) = marked_text_ranges(
  621        indoc! {"
  622            one
  623            two
  624            threeˇ
  625            four
  626            fiveˇ
  627        "},
  628        true,
  629    );
  630
  631    let editor = cx.add_window(|window, cx| {
  632        let buffer = MultiBuffer::build_simple(&text, cx);
  633        build_editor(buffer, window, cx)
  634    });
  635
  636    _ = editor.update(cx, |editor, window, cx| {
  637        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  638            s.select_ranges(selection_ranges.clone())
  639        });
  640        editor.fold_creases(
  641            vec![
  642                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  643                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  644            ],
  645            true,
  646            window,
  647            cx,
  648        );
  649    });
  650
  651    let cloned_editor = editor
  652        .update(cx, |editor, _, cx| {
  653            cx.open_window(Default::default(), |window, cx| {
  654                cx.new(|cx| editor.clone(window, cx))
  655            })
  656        })
  657        .unwrap()
  658        .unwrap();
  659
  660    let snapshot = editor
  661        .update(cx, |e, window, cx| e.snapshot(window, cx))
  662        .unwrap();
  663    let cloned_snapshot = cloned_editor
  664        .update(cx, |e, window, cx| e.snapshot(window, cx))
  665        .unwrap();
  666
  667    assert_eq!(
  668        cloned_editor
  669            .update(cx, |e, _, cx| e.display_text(cx))
  670            .unwrap(),
  671        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  672    );
  673    assert_eq!(
  674        cloned_snapshot
  675            .folds_in_range(0..text.len())
  676            .collect::<Vec<_>>(),
  677        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  678    );
  679    assert_set_eq!(
  680        cloned_editor
  681            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  682            .unwrap(),
  683        editor
  684            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  685            .unwrap()
  686    );
  687    assert_set_eq!(
  688        cloned_editor
  689            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  690            .unwrap(),
  691        editor
  692            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  693            .unwrap()
  694    );
  695}
  696
  697#[gpui::test]
  698async fn test_navigation_history(cx: &mut TestAppContext) {
  699    init_test(cx, |_| {});
  700
  701    use workspace::item::Item;
  702
  703    let fs = FakeFs::new(cx.executor());
  704    let project = Project::test(fs, [], cx).await;
  705    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  706    let pane = workspace
  707        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  708        .unwrap();
  709
  710    _ = workspace.update(cx, |_v, window, cx| {
  711        cx.new(|cx| {
  712            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  713            let mut editor = build_editor(buffer, window, cx);
  714            let handle = cx.entity();
  715            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  716
  717            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  718                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  719            }
  720
  721            // Move the cursor a small distance.
  722            // Nothing is added to the navigation history.
  723            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  724                s.select_display_ranges([
  725                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  726                ])
  727            });
  728            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  729                s.select_display_ranges([
  730                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  731                ])
  732            });
  733            assert!(pop_history(&mut editor, cx).is_none());
  734
  735            // Move the cursor a large distance.
  736            // The history can jump back to the previous position.
  737            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  738                s.select_display_ranges([
  739                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  740                ])
  741            });
  742            let nav_entry = pop_history(&mut editor, cx).unwrap();
  743            editor.navigate(nav_entry.data.unwrap(), window, cx);
  744            assert_eq!(nav_entry.item.id(), cx.entity_id());
  745            assert_eq!(
  746                editor.selections.display_ranges(cx),
  747                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  748            );
  749            assert!(pop_history(&mut editor, cx).is_none());
  750
  751            // Move the cursor a small distance via the mouse.
  752            // Nothing is added to the navigation history.
  753            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  754            editor.end_selection(window, cx);
  755            assert_eq!(
  756                editor.selections.display_ranges(cx),
  757                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  758            );
  759            assert!(pop_history(&mut editor, cx).is_none());
  760
  761            // Move the cursor a large distance via the mouse.
  762            // The history can jump back to the previous position.
  763            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  764            editor.end_selection(window, cx);
  765            assert_eq!(
  766                editor.selections.display_ranges(cx),
  767                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  768            );
  769            let nav_entry = pop_history(&mut editor, cx).unwrap();
  770            editor.navigate(nav_entry.data.unwrap(), window, cx);
  771            assert_eq!(nav_entry.item.id(), cx.entity_id());
  772            assert_eq!(
  773                editor.selections.display_ranges(cx),
  774                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  775            );
  776            assert!(pop_history(&mut editor, cx).is_none());
  777
  778            // Set scroll position to check later
  779            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  780            let original_scroll_position = editor.scroll_manager.anchor();
  781
  782            // Jump to the end of the document and adjust scroll
  783            editor.move_to_end(&MoveToEnd, window, cx);
  784            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  785            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  786
  787            let nav_entry = pop_history(&mut editor, cx).unwrap();
  788            editor.navigate(nav_entry.data.unwrap(), window, cx);
  789            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  790
  791            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  792            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  793            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  794            let invalid_point = Point::new(9999, 0);
  795            editor.navigate(
  796                Box::new(NavigationData {
  797                    cursor_anchor: invalid_anchor,
  798                    cursor_position: invalid_point,
  799                    scroll_anchor: ScrollAnchor {
  800                        anchor: invalid_anchor,
  801                        offset: Default::default(),
  802                    },
  803                    scroll_top_row: invalid_point.row,
  804                }),
  805                window,
  806                cx,
  807            );
  808            assert_eq!(
  809                editor.selections.display_ranges(cx),
  810                &[editor.max_point(cx)..editor.max_point(cx)]
  811            );
  812            assert_eq!(
  813                editor.scroll_position(cx),
  814                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  815            );
  816
  817            editor
  818        })
  819    });
  820}
  821
  822#[gpui::test]
  823fn test_cancel(cx: &mut TestAppContext) {
  824    init_test(cx, |_| {});
  825
  826    let editor = cx.add_window(|window, cx| {
  827        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  828        build_editor(buffer, window, cx)
  829    });
  830
  831    _ = editor.update(cx, |editor, window, cx| {
  832        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  833        editor.update_selection(
  834            DisplayPoint::new(DisplayRow(1), 1),
  835            0,
  836            gpui::Point::<f32>::default(),
  837            window,
  838            cx,
  839        );
  840        editor.end_selection(window, cx);
  841
  842        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  843        editor.update_selection(
  844            DisplayPoint::new(DisplayRow(0), 3),
  845            0,
  846            gpui::Point::<f32>::default(),
  847            window,
  848            cx,
  849        );
  850        editor.end_selection(window, cx);
  851        assert_eq!(
  852            editor.selections.display_ranges(cx),
  853            [
  854                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  855                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  856            ]
  857        );
  858    });
  859
  860    _ = editor.update(cx, |editor, window, cx| {
  861        editor.cancel(&Cancel, window, cx);
  862        assert_eq!(
  863            editor.selections.display_ranges(cx),
  864            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  865        );
  866    });
  867
  868    _ = editor.update(cx, |editor, window, cx| {
  869        editor.cancel(&Cancel, window, cx);
  870        assert_eq!(
  871            editor.selections.display_ranges(cx),
  872            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  873        );
  874    });
  875}
  876
  877#[gpui::test]
  878fn test_fold_action(cx: &mut TestAppContext) {
  879    init_test(cx, |_| {});
  880
  881    let editor = cx.add_window(|window, cx| {
  882        let buffer = MultiBuffer::build_simple(
  883            &"
  884                impl Foo {
  885                    // Hello!
  886
  887                    fn a() {
  888                        1
  889                    }
  890
  891                    fn b() {
  892                        2
  893                    }
  894
  895                    fn c() {
  896                        3
  897                    }
  898                }
  899            "
  900            .unindent(),
  901            cx,
  902        );
  903        build_editor(buffer, window, cx)
  904    });
  905
  906    _ = editor.update(cx, |editor, window, cx| {
  907        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  908            s.select_display_ranges([
  909                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  910            ]);
  911        });
  912        editor.fold(&Fold, window, cx);
  913        assert_eq!(
  914            editor.display_text(cx),
  915            "
  916                impl Foo {
  917                    // Hello!
  918
  919                    fn a() {
  920                        1
  921                    }
  922
  923                    fn b() {⋯
  924                    }
  925
  926                    fn c() {⋯
  927                    }
  928                }
  929            "
  930            .unindent(),
  931        );
  932
  933        editor.fold(&Fold, window, cx);
  934        assert_eq!(
  935            editor.display_text(cx),
  936            "
  937                impl Foo {⋯
  938                }
  939            "
  940            .unindent(),
  941        );
  942
  943        editor.unfold_lines(&UnfoldLines, window, cx);
  944        assert_eq!(
  945            editor.display_text(cx),
  946            "
  947                impl Foo {
  948                    // Hello!
  949
  950                    fn a() {
  951                        1
  952                    }
  953
  954                    fn b() {⋯
  955                    }
  956
  957                    fn c() {⋯
  958                    }
  959                }
  960            "
  961            .unindent(),
  962        );
  963
  964        editor.unfold_lines(&UnfoldLines, window, cx);
  965        assert_eq!(
  966            editor.display_text(cx),
  967            editor.buffer.read(cx).read(cx).text()
  968        );
  969    });
  970}
  971
  972#[gpui::test]
  973fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  974    init_test(cx, |_| {});
  975
  976    let editor = cx.add_window(|window, cx| {
  977        let buffer = MultiBuffer::build_simple(
  978            &"
  979                class Foo:
  980                    # Hello!
  981
  982                    def a():
  983                        print(1)
  984
  985                    def b():
  986                        print(2)
  987
  988                    def c():
  989                        print(3)
  990            "
  991            .unindent(),
  992            cx,
  993        );
  994        build_editor(buffer, window, cx)
  995    });
  996
  997    _ = editor.update(cx, |editor, window, cx| {
  998        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  999            s.select_display_ranges([
 1000                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1001            ]);
 1002        });
 1003        editor.fold(&Fold, window, cx);
 1004        assert_eq!(
 1005            editor.display_text(cx),
 1006            "
 1007                class Foo:
 1008                    # Hello!
 1009
 1010                    def a():
 1011                        print(1)
 1012
 1013                    def b():⋯
 1014
 1015                    def c():⋯
 1016            "
 1017            .unindent(),
 1018        );
 1019
 1020        editor.fold(&Fold, window, cx);
 1021        assert_eq!(
 1022            editor.display_text(cx),
 1023            "
 1024                class Foo:⋯
 1025            "
 1026            .unindent(),
 1027        );
 1028
 1029        editor.unfold_lines(&UnfoldLines, window, cx);
 1030        assert_eq!(
 1031            editor.display_text(cx),
 1032            "
 1033                class Foo:
 1034                    # Hello!
 1035
 1036                    def a():
 1037                        print(1)
 1038
 1039                    def b():⋯
 1040
 1041                    def c():⋯
 1042            "
 1043            .unindent(),
 1044        );
 1045
 1046        editor.unfold_lines(&UnfoldLines, window, cx);
 1047        assert_eq!(
 1048            editor.display_text(cx),
 1049            editor.buffer.read(cx).read(cx).text()
 1050        );
 1051    });
 1052}
 1053
 1054#[gpui::test]
 1055fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1056    init_test(cx, |_| {});
 1057
 1058    let editor = cx.add_window(|window, cx| {
 1059        let buffer = MultiBuffer::build_simple(
 1060            &"
 1061                class Foo:
 1062                    # Hello!
 1063
 1064                    def a():
 1065                        print(1)
 1066
 1067                    def b():
 1068                        print(2)
 1069
 1070
 1071                    def c():
 1072                        print(3)
 1073
 1074
 1075            "
 1076            .unindent(),
 1077            cx,
 1078        );
 1079        build_editor(buffer, window, cx)
 1080    });
 1081
 1082    _ = editor.update(cx, |editor, window, cx| {
 1083        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1084            s.select_display_ranges([
 1085                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1086            ]);
 1087        });
 1088        editor.fold(&Fold, window, cx);
 1089        assert_eq!(
 1090            editor.display_text(cx),
 1091            "
 1092                class Foo:
 1093                    # Hello!
 1094
 1095                    def a():
 1096                        print(1)
 1097
 1098                    def b():⋯
 1099
 1100
 1101                    def c():⋯
 1102
 1103
 1104            "
 1105            .unindent(),
 1106        );
 1107
 1108        editor.fold(&Fold, window, cx);
 1109        assert_eq!(
 1110            editor.display_text(cx),
 1111            "
 1112                class Foo:⋯
 1113
 1114
 1115            "
 1116            .unindent(),
 1117        );
 1118
 1119        editor.unfold_lines(&UnfoldLines, window, cx);
 1120        assert_eq!(
 1121            editor.display_text(cx),
 1122            "
 1123                class Foo:
 1124                    # Hello!
 1125
 1126                    def a():
 1127                        print(1)
 1128
 1129                    def b():⋯
 1130
 1131
 1132                    def c():⋯
 1133
 1134
 1135            "
 1136            .unindent(),
 1137        );
 1138
 1139        editor.unfold_lines(&UnfoldLines, window, cx);
 1140        assert_eq!(
 1141            editor.display_text(cx),
 1142            editor.buffer.read(cx).read(cx).text()
 1143        );
 1144    });
 1145}
 1146
 1147#[gpui::test]
 1148fn test_fold_at_level(cx: &mut TestAppContext) {
 1149    init_test(cx, |_| {});
 1150
 1151    let editor = cx.add_window(|window, cx| {
 1152        let buffer = MultiBuffer::build_simple(
 1153            &"
 1154                class Foo:
 1155                    # Hello!
 1156
 1157                    def a():
 1158                        print(1)
 1159
 1160                    def b():
 1161                        print(2)
 1162
 1163
 1164                class Bar:
 1165                    # World!
 1166
 1167                    def a():
 1168                        print(1)
 1169
 1170                    def b():
 1171                        print(2)
 1172
 1173
 1174            "
 1175            .unindent(),
 1176            cx,
 1177        );
 1178        build_editor(buffer, window, cx)
 1179    });
 1180
 1181    _ = editor.update(cx, |editor, window, cx| {
 1182        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1183        assert_eq!(
 1184            editor.display_text(cx),
 1185            "
 1186                class Foo:
 1187                    # Hello!
 1188
 1189                    def a():⋯
 1190
 1191                    def b():⋯
 1192
 1193
 1194                class Bar:
 1195                    # World!
 1196
 1197                    def a():⋯
 1198
 1199                    def b():⋯
 1200
 1201
 1202            "
 1203            .unindent(),
 1204        );
 1205
 1206        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1207        assert_eq!(
 1208            editor.display_text(cx),
 1209            "
 1210                class Foo:⋯
 1211
 1212
 1213                class Bar:⋯
 1214
 1215
 1216            "
 1217            .unindent(),
 1218        );
 1219
 1220        editor.unfold_all(&UnfoldAll, window, cx);
 1221        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1222        assert_eq!(
 1223            editor.display_text(cx),
 1224            "
 1225                class Foo:
 1226                    # Hello!
 1227
 1228                    def a():
 1229                        print(1)
 1230
 1231                    def b():
 1232                        print(2)
 1233
 1234
 1235                class Bar:
 1236                    # World!
 1237
 1238                    def a():
 1239                        print(1)
 1240
 1241                    def b():
 1242                        print(2)
 1243
 1244
 1245            "
 1246            .unindent(),
 1247        );
 1248
 1249        assert_eq!(
 1250            editor.display_text(cx),
 1251            editor.buffer.read(cx).read(cx).text()
 1252        );
 1253    });
 1254}
 1255
 1256#[gpui::test]
 1257fn test_move_cursor(cx: &mut TestAppContext) {
 1258    init_test(cx, |_| {});
 1259
 1260    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1261    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1262
 1263    buffer.update(cx, |buffer, cx| {
 1264        buffer.edit(
 1265            vec![
 1266                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1267                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1268            ],
 1269            None,
 1270            cx,
 1271        );
 1272    });
 1273    _ = editor.update(cx, |editor, window, cx| {
 1274        assert_eq!(
 1275            editor.selections.display_ranges(cx),
 1276            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1277        );
 1278
 1279        editor.move_down(&MoveDown, window, cx);
 1280        assert_eq!(
 1281            editor.selections.display_ranges(cx),
 1282            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1283        );
 1284
 1285        editor.move_right(&MoveRight, window, cx);
 1286        assert_eq!(
 1287            editor.selections.display_ranges(cx),
 1288            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1289        );
 1290
 1291        editor.move_left(&MoveLeft, window, cx);
 1292        assert_eq!(
 1293            editor.selections.display_ranges(cx),
 1294            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1295        );
 1296
 1297        editor.move_up(&MoveUp, window, cx);
 1298        assert_eq!(
 1299            editor.selections.display_ranges(cx),
 1300            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1301        );
 1302
 1303        editor.move_to_end(&MoveToEnd, window, cx);
 1304        assert_eq!(
 1305            editor.selections.display_ranges(cx),
 1306            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1307        );
 1308
 1309        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1310        assert_eq!(
 1311            editor.selections.display_ranges(cx),
 1312            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1313        );
 1314
 1315        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1316            s.select_display_ranges([
 1317                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1318            ]);
 1319        });
 1320        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1321        assert_eq!(
 1322            editor.selections.display_ranges(cx),
 1323            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1324        );
 1325
 1326        editor.select_to_end(&SelectToEnd, window, cx);
 1327        assert_eq!(
 1328            editor.selections.display_ranges(cx),
 1329            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1330        );
 1331    });
 1332}
 1333
 1334#[gpui::test]
 1335fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1336    init_test(cx, |_| {});
 1337
 1338    let editor = cx.add_window(|window, cx| {
 1339        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1340        build_editor(buffer, window, cx)
 1341    });
 1342
 1343    assert_eq!('🟥'.len_utf8(), 4);
 1344    assert_eq!('α'.len_utf8(), 2);
 1345
 1346    _ = editor.update(cx, |editor, window, cx| {
 1347        editor.fold_creases(
 1348            vec![
 1349                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1350                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1351                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1352            ],
 1353            true,
 1354            window,
 1355            cx,
 1356        );
 1357        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1358
 1359        editor.move_right(&MoveRight, window, cx);
 1360        assert_eq!(
 1361            editor.selections.display_ranges(cx),
 1362            &[empty_range(0, "🟥".len())]
 1363        );
 1364        editor.move_right(&MoveRight, window, cx);
 1365        assert_eq!(
 1366            editor.selections.display_ranges(cx),
 1367            &[empty_range(0, "🟥🟧".len())]
 1368        );
 1369        editor.move_right(&MoveRight, window, cx);
 1370        assert_eq!(
 1371            editor.selections.display_ranges(cx),
 1372            &[empty_range(0, "🟥🟧⋯".len())]
 1373        );
 1374
 1375        editor.move_down(&MoveDown, window, cx);
 1376        assert_eq!(
 1377            editor.selections.display_ranges(cx),
 1378            &[empty_range(1, "ab⋯e".len())]
 1379        );
 1380        editor.move_left(&MoveLeft, window, cx);
 1381        assert_eq!(
 1382            editor.selections.display_ranges(cx),
 1383            &[empty_range(1, "ab⋯".len())]
 1384        );
 1385        editor.move_left(&MoveLeft, window, cx);
 1386        assert_eq!(
 1387            editor.selections.display_ranges(cx),
 1388            &[empty_range(1, "ab".len())]
 1389        );
 1390        editor.move_left(&MoveLeft, window, cx);
 1391        assert_eq!(
 1392            editor.selections.display_ranges(cx),
 1393            &[empty_range(1, "a".len())]
 1394        );
 1395
 1396        editor.move_down(&MoveDown, window, cx);
 1397        assert_eq!(
 1398            editor.selections.display_ranges(cx),
 1399            &[empty_range(2, "α".len())]
 1400        );
 1401        editor.move_right(&MoveRight, window, cx);
 1402        assert_eq!(
 1403            editor.selections.display_ranges(cx),
 1404            &[empty_range(2, "αβ".len())]
 1405        );
 1406        editor.move_right(&MoveRight, window, cx);
 1407        assert_eq!(
 1408            editor.selections.display_ranges(cx),
 1409            &[empty_range(2, "αβ⋯".len())]
 1410        );
 1411        editor.move_right(&MoveRight, window, cx);
 1412        assert_eq!(
 1413            editor.selections.display_ranges(cx),
 1414            &[empty_range(2, "αβ⋯ε".len())]
 1415        );
 1416
 1417        editor.move_up(&MoveUp, window, cx);
 1418        assert_eq!(
 1419            editor.selections.display_ranges(cx),
 1420            &[empty_range(1, "ab⋯e".len())]
 1421        );
 1422        editor.move_down(&MoveDown, window, cx);
 1423        assert_eq!(
 1424            editor.selections.display_ranges(cx),
 1425            &[empty_range(2, "αβ⋯ε".len())]
 1426        );
 1427        editor.move_up(&MoveUp, window, cx);
 1428        assert_eq!(
 1429            editor.selections.display_ranges(cx),
 1430            &[empty_range(1, "ab⋯e".len())]
 1431        );
 1432
 1433        editor.move_up(&MoveUp, window, cx);
 1434        assert_eq!(
 1435            editor.selections.display_ranges(cx),
 1436            &[empty_range(0, "🟥🟧".len())]
 1437        );
 1438        editor.move_left(&MoveLeft, window, cx);
 1439        assert_eq!(
 1440            editor.selections.display_ranges(cx),
 1441            &[empty_range(0, "🟥".len())]
 1442        );
 1443        editor.move_left(&MoveLeft, window, cx);
 1444        assert_eq!(
 1445            editor.selections.display_ranges(cx),
 1446            &[empty_range(0, "".len())]
 1447        );
 1448    });
 1449}
 1450
 1451#[gpui::test]
 1452fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1453    init_test(cx, |_| {});
 1454
 1455    let editor = cx.add_window(|window, cx| {
 1456        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1457        build_editor(buffer, window, cx)
 1458    });
 1459    _ = editor.update(cx, |editor, window, cx| {
 1460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1461            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1462        });
 1463
 1464        // moving above start of document should move selection to start of document,
 1465        // but the next move down should still be at the original goal_x
 1466        editor.move_up(&MoveUp, window, cx);
 1467        assert_eq!(
 1468            editor.selections.display_ranges(cx),
 1469            &[empty_range(0, "".len())]
 1470        );
 1471
 1472        editor.move_down(&MoveDown, window, cx);
 1473        assert_eq!(
 1474            editor.selections.display_ranges(cx),
 1475            &[empty_range(1, "abcd".len())]
 1476        );
 1477
 1478        editor.move_down(&MoveDown, window, cx);
 1479        assert_eq!(
 1480            editor.selections.display_ranges(cx),
 1481            &[empty_range(2, "αβγ".len())]
 1482        );
 1483
 1484        editor.move_down(&MoveDown, window, cx);
 1485        assert_eq!(
 1486            editor.selections.display_ranges(cx),
 1487            &[empty_range(3, "abcd".len())]
 1488        );
 1489
 1490        editor.move_down(&MoveDown, window, cx);
 1491        assert_eq!(
 1492            editor.selections.display_ranges(cx),
 1493            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1494        );
 1495
 1496        // moving past end of document should not change goal_x
 1497        editor.move_down(&MoveDown, window, cx);
 1498        assert_eq!(
 1499            editor.selections.display_ranges(cx),
 1500            &[empty_range(5, "".len())]
 1501        );
 1502
 1503        editor.move_down(&MoveDown, window, cx);
 1504        assert_eq!(
 1505            editor.selections.display_ranges(cx),
 1506            &[empty_range(5, "".len())]
 1507        );
 1508
 1509        editor.move_up(&MoveUp, window, cx);
 1510        assert_eq!(
 1511            editor.selections.display_ranges(cx),
 1512            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1513        );
 1514
 1515        editor.move_up(&MoveUp, window, cx);
 1516        assert_eq!(
 1517            editor.selections.display_ranges(cx),
 1518            &[empty_range(3, "abcd".len())]
 1519        );
 1520
 1521        editor.move_up(&MoveUp, window, cx);
 1522        assert_eq!(
 1523            editor.selections.display_ranges(cx),
 1524            &[empty_range(2, "αβγ".len())]
 1525        );
 1526    });
 1527}
 1528
 1529#[gpui::test]
 1530fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1531    init_test(cx, |_| {});
 1532    let move_to_beg = MoveToBeginningOfLine {
 1533        stop_at_soft_wraps: true,
 1534        stop_at_indent: true,
 1535    };
 1536
 1537    let delete_to_beg = DeleteToBeginningOfLine {
 1538        stop_at_indent: false,
 1539    };
 1540
 1541    let move_to_end = MoveToEndOfLine {
 1542        stop_at_soft_wraps: true,
 1543    };
 1544
 1545    let editor = cx.add_window(|window, cx| {
 1546        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1547        build_editor(buffer, window, cx)
 1548    });
 1549    _ = editor.update(cx, |editor, window, cx| {
 1550        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1551            s.select_display_ranges([
 1552                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1553                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1554            ]);
 1555        });
 1556    });
 1557
 1558    _ = editor.update(cx, |editor, window, cx| {
 1559        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1560        assert_eq!(
 1561            editor.selections.display_ranges(cx),
 1562            &[
 1563                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1564                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1565            ]
 1566        );
 1567    });
 1568
 1569    _ = editor.update(cx, |editor, window, cx| {
 1570        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1571        assert_eq!(
 1572            editor.selections.display_ranges(cx),
 1573            &[
 1574                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1575                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1576            ]
 1577        );
 1578    });
 1579
 1580    _ = editor.update(cx, |editor, window, cx| {
 1581        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1582        assert_eq!(
 1583            editor.selections.display_ranges(cx),
 1584            &[
 1585                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1586                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1587            ]
 1588        );
 1589    });
 1590
 1591    _ = editor.update(cx, |editor, window, cx| {
 1592        editor.move_to_end_of_line(&move_to_end, window, cx);
 1593        assert_eq!(
 1594            editor.selections.display_ranges(cx),
 1595            &[
 1596                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1597                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1598            ]
 1599        );
 1600    });
 1601
 1602    // Moving to the end of line again is a no-op.
 1603    _ = editor.update(cx, |editor, window, cx| {
 1604        editor.move_to_end_of_line(&move_to_end, window, cx);
 1605        assert_eq!(
 1606            editor.selections.display_ranges(cx),
 1607            &[
 1608                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1609                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1610            ]
 1611        );
 1612    });
 1613
 1614    _ = editor.update(cx, |editor, window, cx| {
 1615        editor.move_left(&MoveLeft, window, cx);
 1616        editor.select_to_beginning_of_line(
 1617            &SelectToBeginningOfLine {
 1618                stop_at_soft_wraps: true,
 1619                stop_at_indent: true,
 1620            },
 1621            window,
 1622            cx,
 1623        );
 1624        assert_eq!(
 1625            editor.selections.display_ranges(cx),
 1626            &[
 1627                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1628                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1629            ]
 1630        );
 1631    });
 1632
 1633    _ = editor.update(cx, |editor, window, cx| {
 1634        editor.select_to_beginning_of_line(
 1635            &SelectToBeginningOfLine {
 1636                stop_at_soft_wraps: true,
 1637                stop_at_indent: true,
 1638            },
 1639            window,
 1640            cx,
 1641        );
 1642        assert_eq!(
 1643            editor.selections.display_ranges(cx),
 1644            &[
 1645                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1646                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1647            ]
 1648        );
 1649    });
 1650
 1651    _ = editor.update(cx, |editor, window, cx| {
 1652        editor.select_to_beginning_of_line(
 1653            &SelectToBeginningOfLine {
 1654                stop_at_soft_wraps: true,
 1655                stop_at_indent: true,
 1656            },
 1657            window,
 1658            cx,
 1659        );
 1660        assert_eq!(
 1661            editor.selections.display_ranges(cx),
 1662            &[
 1663                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1664                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1665            ]
 1666        );
 1667    });
 1668
 1669    _ = editor.update(cx, |editor, window, cx| {
 1670        editor.select_to_end_of_line(
 1671            &SelectToEndOfLine {
 1672                stop_at_soft_wraps: true,
 1673            },
 1674            window,
 1675            cx,
 1676        );
 1677        assert_eq!(
 1678            editor.selections.display_ranges(cx),
 1679            &[
 1680                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1681                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1682            ]
 1683        );
 1684    });
 1685
 1686    _ = editor.update(cx, |editor, window, cx| {
 1687        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1688        assert_eq!(editor.display_text(cx), "ab\n  de");
 1689        assert_eq!(
 1690            editor.selections.display_ranges(cx),
 1691            &[
 1692                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1693                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1694            ]
 1695        );
 1696    });
 1697
 1698    _ = editor.update(cx, |editor, window, cx| {
 1699        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1700        assert_eq!(editor.display_text(cx), "\n");
 1701        assert_eq!(
 1702            editor.selections.display_ranges(cx),
 1703            &[
 1704                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1705                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1706            ]
 1707        );
 1708    });
 1709}
 1710
 1711#[gpui::test]
 1712fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1713    init_test(cx, |_| {});
 1714    let move_to_beg = MoveToBeginningOfLine {
 1715        stop_at_soft_wraps: false,
 1716        stop_at_indent: false,
 1717    };
 1718
 1719    let move_to_end = MoveToEndOfLine {
 1720        stop_at_soft_wraps: false,
 1721    };
 1722
 1723    let editor = cx.add_window(|window, cx| {
 1724        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1725        build_editor(buffer, window, cx)
 1726    });
 1727
 1728    _ = editor.update(cx, |editor, window, cx| {
 1729        editor.set_wrap_width(Some(140.0.into()), cx);
 1730
 1731        // We expect the following lines after wrapping
 1732        // ```
 1733        // thequickbrownfox
 1734        // jumpedoverthelazydo
 1735        // gs
 1736        // ```
 1737        // The final `gs` was soft-wrapped onto a new line.
 1738        assert_eq!(
 1739            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1740            editor.display_text(cx),
 1741        );
 1742
 1743        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1744        // Start the cursor at the `k` on the first line
 1745        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1746            s.select_display_ranges([
 1747                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1748            ]);
 1749        });
 1750
 1751        // Moving to the beginning of the line should put us at the beginning of the line.
 1752        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1753        assert_eq!(
 1754            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1755            editor.selections.display_ranges(cx)
 1756        );
 1757
 1758        // Moving to the end of the line should put us at the end of the line.
 1759        editor.move_to_end_of_line(&move_to_end, window, cx);
 1760        assert_eq!(
 1761            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1762            editor.selections.display_ranges(cx)
 1763        );
 1764
 1765        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1766        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1767        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1768            s.select_display_ranges([
 1769                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1770            ]);
 1771        });
 1772
 1773        // Moving to the beginning of the line should put us at the start of the second line of
 1774        // display text, i.e., the `j`.
 1775        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1776        assert_eq!(
 1777            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1778            editor.selections.display_ranges(cx)
 1779        );
 1780
 1781        // Moving to the beginning of the line again should be a no-op.
 1782        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1783        assert_eq!(
 1784            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1785            editor.selections.display_ranges(cx)
 1786        );
 1787
 1788        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1789        // next display line.
 1790        editor.move_to_end_of_line(&move_to_end, window, cx);
 1791        assert_eq!(
 1792            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1793            editor.selections.display_ranges(cx)
 1794        );
 1795
 1796        // Moving to the end of the line again should be a no-op.
 1797        editor.move_to_end_of_line(&move_to_end, window, cx);
 1798        assert_eq!(
 1799            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1800            editor.selections.display_ranges(cx)
 1801        );
 1802    });
 1803}
 1804
 1805#[gpui::test]
 1806fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1807    init_test(cx, |_| {});
 1808
 1809    let move_to_beg = MoveToBeginningOfLine {
 1810        stop_at_soft_wraps: true,
 1811        stop_at_indent: true,
 1812    };
 1813
 1814    let select_to_beg = SelectToBeginningOfLine {
 1815        stop_at_soft_wraps: true,
 1816        stop_at_indent: true,
 1817    };
 1818
 1819    let delete_to_beg = DeleteToBeginningOfLine {
 1820        stop_at_indent: true,
 1821    };
 1822
 1823    let move_to_end = MoveToEndOfLine {
 1824        stop_at_soft_wraps: false,
 1825    };
 1826
 1827    let editor = cx.add_window(|window, cx| {
 1828        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1829        build_editor(buffer, window, cx)
 1830    });
 1831
 1832    _ = editor.update(cx, |editor, window, cx| {
 1833        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1834            s.select_display_ranges([
 1835                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1836                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1837            ]);
 1838        });
 1839
 1840        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1841        // and the second cursor at the first non-whitespace character in the line.
 1842        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1843        assert_eq!(
 1844            editor.selections.display_ranges(cx),
 1845            &[
 1846                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1847                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1848            ]
 1849        );
 1850
 1851        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1852        // and should move the second cursor to the beginning of the line.
 1853        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1854        assert_eq!(
 1855            editor.selections.display_ranges(cx),
 1856            &[
 1857                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1858                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1859            ]
 1860        );
 1861
 1862        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1863        // and should move the second cursor back to the first non-whitespace character in the line.
 1864        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1865        assert_eq!(
 1866            editor.selections.display_ranges(cx),
 1867            &[
 1868                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1869                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1870            ]
 1871        );
 1872
 1873        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1874        // and to the first non-whitespace character in the line for the second cursor.
 1875        editor.move_to_end_of_line(&move_to_end, window, cx);
 1876        editor.move_left(&MoveLeft, window, cx);
 1877        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1878        assert_eq!(
 1879            editor.selections.display_ranges(cx),
 1880            &[
 1881                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1882                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1883            ]
 1884        );
 1885
 1886        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1887        // and should select to the beginning of the line for the second cursor.
 1888        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1889        assert_eq!(
 1890            editor.selections.display_ranges(cx),
 1891            &[
 1892                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1893                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1894            ]
 1895        );
 1896
 1897        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1898        // and should delete to the first non-whitespace character in the line for the second cursor.
 1899        editor.move_to_end_of_line(&move_to_end, window, cx);
 1900        editor.move_left(&MoveLeft, window, cx);
 1901        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1902        assert_eq!(editor.text(cx), "c\n  f");
 1903    });
 1904}
 1905
 1906#[gpui::test]
 1907fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1908    init_test(cx, |_| {});
 1909
 1910    let move_to_beg = MoveToBeginningOfLine {
 1911        stop_at_soft_wraps: true,
 1912        stop_at_indent: true,
 1913    };
 1914
 1915    let editor = cx.add_window(|window, cx| {
 1916        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1917        build_editor(buffer, window, cx)
 1918    });
 1919
 1920    _ = editor.update(cx, |editor, window, cx| {
 1921        // test cursor between line_start and indent_start
 1922        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1923            s.select_display_ranges([
 1924                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1925            ]);
 1926        });
 1927
 1928        // cursor should move to line_start
 1929        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1930        assert_eq!(
 1931            editor.selections.display_ranges(cx),
 1932            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1933        );
 1934
 1935        // cursor should move to indent_start
 1936        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1937        assert_eq!(
 1938            editor.selections.display_ranges(cx),
 1939            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 1940        );
 1941
 1942        // cursor should move to back to line_start
 1943        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1944        assert_eq!(
 1945            editor.selections.display_ranges(cx),
 1946            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1947        );
 1948    });
 1949}
 1950
 1951#[gpui::test]
 1952fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1953    init_test(cx, |_| {});
 1954
 1955    let editor = cx.add_window(|window, cx| {
 1956        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1957        build_editor(buffer, window, cx)
 1958    });
 1959    _ = editor.update(cx, |editor, window, cx| {
 1960        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1961            s.select_display_ranges([
 1962                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1963                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1964            ])
 1965        });
 1966        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1967        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1968
 1969        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1970        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1971
 1972        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1973        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1974
 1975        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1976        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1977
 1978        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1979        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1980
 1981        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1982        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1983
 1984        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1985        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1986
 1987        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1988        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1989
 1990        editor.move_right(&MoveRight, window, cx);
 1991        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1992        assert_selection_ranges(
 1993            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1994            editor,
 1995            cx,
 1996        );
 1997
 1998        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1999        assert_selection_ranges(
 2000            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2001            editor,
 2002            cx,
 2003        );
 2004
 2005        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2006        assert_selection_ranges(
 2007            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2008            editor,
 2009            cx,
 2010        );
 2011    });
 2012}
 2013
 2014#[gpui::test]
 2015fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2016    init_test(cx, |_| {});
 2017
 2018    let editor = cx.add_window(|window, cx| {
 2019        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2020        build_editor(buffer, window, cx)
 2021    });
 2022
 2023    _ = editor.update(cx, |editor, window, cx| {
 2024        editor.set_wrap_width(Some(140.0.into()), cx);
 2025        assert_eq!(
 2026            editor.display_text(cx),
 2027            "use one::{\n    two::three::\n    four::five\n};"
 2028        );
 2029
 2030        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2031            s.select_display_ranges([
 2032                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2033            ]);
 2034        });
 2035
 2036        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2037        assert_eq!(
 2038            editor.selections.display_ranges(cx),
 2039            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2040        );
 2041
 2042        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2043        assert_eq!(
 2044            editor.selections.display_ranges(cx),
 2045            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2046        );
 2047
 2048        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2049        assert_eq!(
 2050            editor.selections.display_ranges(cx),
 2051            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2052        );
 2053
 2054        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2055        assert_eq!(
 2056            editor.selections.display_ranges(cx),
 2057            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2058        );
 2059
 2060        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2061        assert_eq!(
 2062            editor.selections.display_ranges(cx),
 2063            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2064        );
 2065
 2066        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2067        assert_eq!(
 2068            editor.selections.display_ranges(cx),
 2069            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2070        );
 2071    });
 2072}
 2073
 2074#[gpui::test]
 2075async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2076    init_test(cx, |_| {});
 2077    let mut cx = EditorTestContext::new(cx).await;
 2078
 2079    let line_height = cx.editor(|editor, window, _| {
 2080        editor
 2081            .style()
 2082            .unwrap()
 2083            .text
 2084            .line_height_in_pixels(window.rem_size())
 2085    });
 2086    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2087
 2088    cx.set_state(
 2089        &r#"ˇone
 2090        two
 2091
 2092        three
 2093        fourˇ
 2094        five
 2095
 2096        six"#
 2097            .unindent(),
 2098    );
 2099
 2100    cx.update_editor(|editor, window, cx| {
 2101        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2102    });
 2103    cx.assert_editor_state(
 2104        &r#"one
 2105        two
 2106        ˇ
 2107        three
 2108        four
 2109        five
 2110        ˇ
 2111        six"#
 2112            .unindent(),
 2113    );
 2114
 2115    cx.update_editor(|editor, window, cx| {
 2116        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2117    });
 2118    cx.assert_editor_state(
 2119        &r#"one
 2120        two
 2121
 2122        three
 2123        four
 2124        five
 2125        ˇ
 2126        sixˇ"#
 2127            .unindent(),
 2128    );
 2129
 2130    cx.update_editor(|editor, window, cx| {
 2131        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2132    });
 2133    cx.assert_editor_state(
 2134        &r#"one
 2135        two
 2136
 2137        three
 2138        four
 2139        five
 2140
 2141        sixˇ"#
 2142            .unindent(),
 2143    );
 2144
 2145    cx.update_editor(|editor, window, cx| {
 2146        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2147    });
 2148    cx.assert_editor_state(
 2149        &r#"one
 2150        two
 2151
 2152        three
 2153        four
 2154        five
 2155        ˇ
 2156        six"#
 2157            .unindent(),
 2158    );
 2159
 2160    cx.update_editor(|editor, window, cx| {
 2161        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2162    });
 2163    cx.assert_editor_state(
 2164        &r#"one
 2165        two
 2166        ˇ
 2167        three
 2168        four
 2169        five
 2170
 2171        six"#
 2172            .unindent(),
 2173    );
 2174
 2175    cx.update_editor(|editor, window, cx| {
 2176        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2177    });
 2178    cx.assert_editor_state(
 2179        &r#"ˇone
 2180        two
 2181
 2182        three
 2183        four
 2184        five
 2185
 2186        six"#
 2187            .unindent(),
 2188    );
 2189}
 2190
 2191#[gpui::test]
 2192async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2193    init_test(cx, |_| {});
 2194    let mut cx = EditorTestContext::new(cx).await;
 2195    let line_height = cx.editor(|editor, window, _| {
 2196        editor
 2197            .style()
 2198            .unwrap()
 2199            .text
 2200            .line_height_in_pixels(window.rem_size())
 2201    });
 2202    let window = cx.window;
 2203    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2204
 2205    cx.set_state(
 2206        r#"ˇone
 2207        two
 2208        three
 2209        four
 2210        five
 2211        six
 2212        seven
 2213        eight
 2214        nine
 2215        ten
 2216        "#,
 2217    );
 2218
 2219    cx.update_editor(|editor, window, cx| {
 2220        assert_eq!(
 2221            editor.snapshot(window, cx).scroll_position(),
 2222            gpui::Point::new(0., 0.)
 2223        );
 2224        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2225        assert_eq!(
 2226            editor.snapshot(window, cx).scroll_position(),
 2227            gpui::Point::new(0., 3.)
 2228        );
 2229        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2230        assert_eq!(
 2231            editor.snapshot(window, cx).scroll_position(),
 2232            gpui::Point::new(0., 6.)
 2233        );
 2234        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2235        assert_eq!(
 2236            editor.snapshot(window, cx).scroll_position(),
 2237            gpui::Point::new(0., 3.)
 2238        );
 2239
 2240        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2241        assert_eq!(
 2242            editor.snapshot(window, cx).scroll_position(),
 2243            gpui::Point::new(0., 1.)
 2244        );
 2245        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2246        assert_eq!(
 2247            editor.snapshot(window, cx).scroll_position(),
 2248            gpui::Point::new(0., 3.)
 2249        );
 2250    });
 2251}
 2252
 2253#[gpui::test]
 2254async fn test_autoscroll(cx: &mut TestAppContext) {
 2255    init_test(cx, |_| {});
 2256    let mut cx = EditorTestContext::new(cx).await;
 2257
 2258    let line_height = cx.update_editor(|editor, window, cx| {
 2259        editor.set_vertical_scroll_margin(2, cx);
 2260        editor
 2261            .style()
 2262            .unwrap()
 2263            .text
 2264            .line_height_in_pixels(window.rem_size())
 2265    });
 2266    let window = cx.window;
 2267    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2268
 2269    cx.set_state(
 2270        r#"ˇone
 2271            two
 2272            three
 2273            four
 2274            five
 2275            six
 2276            seven
 2277            eight
 2278            nine
 2279            ten
 2280        "#,
 2281    );
 2282    cx.update_editor(|editor, window, cx| {
 2283        assert_eq!(
 2284            editor.snapshot(window, cx).scroll_position(),
 2285            gpui::Point::new(0., 0.0)
 2286        );
 2287    });
 2288
 2289    // Add a cursor below the visible area. Since both cursors cannot fit
 2290    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2291    // allows the vertical scroll margin below that cursor.
 2292    cx.update_editor(|editor, window, cx| {
 2293        editor.change_selections(Default::default(), window, cx, |selections| {
 2294            selections.select_ranges([
 2295                Point::new(0, 0)..Point::new(0, 0),
 2296                Point::new(6, 0)..Point::new(6, 0),
 2297            ]);
 2298        })
 2299    });
 2300    cx.update_editor(|editor, window, cx| {
 2301        assert_eq!(
 2302            editor.snapshot(window, cx).scroll_position(),
 2303            gpui::Point::new(0., 3.0)
 2304        );
 2305    });
 2306
 2307    // Move down. The editor cursor scrolls down to track the newest cursor.
 2308    cx.update_editor(|editor, window, cx| {
 2309        editor.move_down(&Default::default(), window, cx);
 2310    });
 2311    cx.update_editor(|editor, window, cx| {
 2312        assert_eq!(
 2313            editor.snapshot(window, cx).scroll_position(),
 2314            gpui::Point::new(0., 4.0)
 2315        );
 2316    });
 2317
 2318    // Add a cursor above the visible area. Since both cursors fit on screen,
 2319    // the editor scrolls to show both.
 2320    cx.update_editor(|editor, window, cx| {
 2321        editor.change_selections(Default::default(), window, cx, |selections| {
 2322            selections.select_ranges([
 2323                Point::new(1, 0)..Point::new(1, 0),
 2324                Point::new(6, 0)..Point::new(6, 0),
 2325            ]);
 2326        })
 2327    });
 2328    cx.update_editor(|editor, window, cx| {
 2329        assert_eq!(
 2330            editor.snapshot(window, cx).scroll_position(),
 2331            gpui::Point::new(0., 1.0)
 2332        );
 2333    });
 2334}
 2335
 2336#[gpui::test]
 2337async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2338    init_test(cx, |_| {});
 2339    let mut cx = EditorTestContext::new(cx).await;
 2340
 2341    let line_height = cx.editor(|editor, window, _cx| {
 2342        editor
 2343            .style()
 2344            .unwrap()
 2345            .text
 2346            .line_height_in_pixels(window.rem_size())
 2347    });
 2348    let window = cx.window;
 2349    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2350    cx.set_state(
 2351        &r#"
 2352        ˇone
 2353        two
 2354        threeˇ
 2355        four
 2356        five
 2357        six
 2358        seven
 2359        eight
 2360        nine
 2361        ten
 2362        "#
 2363        .unindent(),
 2364    );
 2365
 2366    cx.update_editor(|editor, window, cx| {
 2367        editor.move_page_down(&MovePageDown::default(), window, cx)
 2368    });
 2369    cx.assert_editor_state(
 2370        &r#"
 2371        one
 2372        two
 2373        three
 2374        ˇfour
 2375        five
 2376        sixˇ
 2377        seven
 2378        eight
 2379        nine
 2380        ten
 2381        "#
 2382        .unindent(),
 2383    );
 2384
 2385    cx.update_editor(|editor, window, cx| {
 2386        editor.move_page_down(&MovePageDown::default(), window, cx)
 2387    });
 2388    cx.assert_editor_state(
 2389        &r#"
 2390        one
 2391        two
 2392        three
 2393        four
 2394        five
 2395        six
 2396        ˇseven
 2397        eight
 2398        nineˇ
 2399        ten
 2400        "#
 2401        .unindent(),
 2402    );
 2403
 2404    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2405    cx.assert_editor_state(
 2406        &r#"
 2407        one
 2408        two
 2409        three
 2410        ˇfour
 2411        five
 2412        sixˇ
 2413        seven
 2414        eight
 2415        nine
 2416        ten
 2417        "#
 2418        .unindent(),
 2419    );
 2420
 2421    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2422    cx.assert_editor_state(
 2423        &r#"
 2424        ˇone
 2425        two
 2426        threeˇ
 2427        four
 2428        five
 2429        six
 2430        seven
 2431        eight
 2432        nine
 2433        ten
 2434        "#
 2435        .unindent(),
 2436    );
 2437
 2438    // Test select collapsing
 2439    cx.update_editor(|editor, window, cx| {
 2440        editor.move_page_down(&MovePageDown::default(), window, cx);
 2441        editor.move_page_down(&MovePageDown::default(), window, cx);
 2442        editor.move_page_down(&MovePageDown::default(), window, cx);
 2443    });
 2444    cx.assert_editor_state(
 2445        &r#"
 2446        one
 2447        two
 2448        three
 2449        four
 2450        five
 2451        six
 2452        seven
 2453        eight
 2454        nine
 2455        ˇten
 2456        ˇ"#
 2457        .unindent(),
 2458    );
 2459}
 2460
 2461#[gpui::test]
 2462async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2463    init_test(cx, |_| {});
 2464    let mut cx = EditorTestContext::new(cx).await;
 2465    cx.set_state("one «two threeˇ» four");
 2466    cx.update_editor(|editor, window, cx| {
 2467        editor.delete_to_beginning_of_line(
 2468            &DeleteToBeginningOfLine {
 2469                stop_at_indent: false,
 2470            },
 2471            window,
 2472            cx,
 2473        );
 2474        assert_eq!(editor.text(cx), " four");
 2475    });
 2476}
 2477
 2478#[gpui::test]
 2479fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2480    init_test(cx, |_| {});
 2481
 2482    let editor = cx.add_window(|window, cx| {
 2483        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2484        build_editor(buffer, window, cx)
 2485    });
 2486
 2487    _ = editor.update(cx, |editor, window, cx| {
 2488        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2489            s.select_display_ranges([
 2490                // an empty selection - the preceding word fragment is deleted
 2491                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2492                // characters selected - they are deleted
 2493                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2494            ])
 2495        });
 2496        editor.delete_to_previous_word_start(
 2497            &DeleteToPreviousWordStart {
 2498                ignore_newlines: false,
 2499            },
 2500            window,
 2501            cx,
 2502        );
 2503        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2504    });
 2505
 2506    _ = editor.update(cx, |editor, window, cx| {
 2507        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2508            s.select_display_ranges([
 2509                // an empty selection - the following word fragment is deleted
 2510                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2511                // characters selected - they are deleted
 2512                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2513            ])
 2514        });
 2515        editor.delete_to_next_word_end(
 2516            &DeleteToNextWordEnd {
 2517                ignore_newlines: false,
 2518            },
 2519            window,
 2520            cx,
 2521        );
 2522        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2523    });
 2524}
 2525
 2526#[gpui::test]
 2527fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2528    init_test(cx, |_| {});
 2529
 2530    let editor = cx.add_window(|window, cx| {
 2531        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2532        build_editor(buffer, window, cx)
 2533    });
 2534    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2535        ignore_newlines: false,
 2536    };
 2537    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2538        ignore_newlines: true,
 2539    };
 2540
 2541    _ = editor.update(cx, |editor, window, cx| {
 2542        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2543            s.select_display_ranges([
 2544                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2545            ])
 2546        });
 2547        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2548        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2549        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2550        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2551        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2552        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2553        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2554        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2555        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2556        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2557        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2558        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2559    });
 2560}
 2561
 2562#[gpui::test]
 2563fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2564    init_test(cx, |_| {});
 2565
 2566    let editor = cx.add_window(|window, cx| {
 2567        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2568        build_editor(buffer, window, cx)
 2569    });
 2570    let del_to_next_word_end = DeleteToNextWordEnd {
 2571        ignore_newlines: false,
 2572    };
 2573    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2574        ignore_newlines: true,
 2575    };
 2576
 2577    _ = editor.update(cx, |editor, window, cx| {
 2578        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2579            s.select_display_ranges([
 2580                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2581            ])
 2582        });
 2583        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2584        assert_eq!(
 2585            editor.buffer.read(cx).read(cx).text(),
 2586            "one\n   two\nthree\n   four"
 2587        );
 2588        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2589        assert_eq!(
 2590            editor.buffer.read(cx).read(cx).text(),
 2591            "\n   two\nthree\n   four"
 2592        );
 2593        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2594        assert_eq!(
 2595            editor.buffer.read(cx).read(cx).text(),
 2596            "two\nthree\n   four"
 2597        );
 2598        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2599        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2600        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2601        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2602        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2603        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2604    });
 2605}
 2606
 2607#[gpui::test]
 2608fn test_newline(cx: &mut TestAppContext) {
 2609    init_test(cx, |_| {});
 2610
 2611    let editor = cx.add_window(|window, cx| {
 2612        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2613        build_editor(buffer, window, cx)
 2614    });
 2615
 2616    _ = editor.update(cx, |editor, window, cx| {
 2617        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2618            s.select_display_ranges([
 2619                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2620                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2621                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2622            ])
 2623        });
 2624
 2625        editor.newline(&Newline, window, cx);
 2626        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2627    });
 2628}
 2629
 2630#[gpui::test]
 2631fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2632    init_test(cx, |_| {});
 2633
 2634    let editor = cx.add_window(|window, cx| {
 2635        let buffer = MultiBuffer::build_simple(
 2636            "
 2637                a
 2638                b(
 2639                    X
 2640                )
 2641                c(
 2642                    X
 2643                )
 2644            "
 2645            .unindent()
 2646            .as_str(),
 2647            cx,
 2648        );
 2649        let mut editor = build_editor(buffer, window, cx);
 2650        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2651            s.select_ranges([
 2652                Point::new(2, 4)..Point::new(2, 5),
 2653                Point::new(5, 4)..Point::new(5, 5),
 2654            ])
 2655        });
 2656        editor
 2657    });
 2658
 2659    _ = editor.update(cx, |editor, window, cx| {
 2660        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2661        editor.buffer.update(cx, |buffer, cx| {
 2662            buffer.edit(
 2663                [
 2664                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2665                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2666                ],
 2667                None,
 2668                cx,
 2669            );
 2670            assert_eq!(
 2671                buffer.read(cx).text(),
 2672                "
 2673                    a
 2674                    b()
 2675                    c()
 2676                "
 2677                .unindent()
 2678            );
 2679        });
 2680        assert_eq!(
 2681            editor.selections.ranges(cx),
 2682            &[
 2683                Point::new(1, 2)..Point::new(1, 2),
 2684                Point::new(2, 2)..Point::new(2, 2),
 2685            ],
 2686        );
 2687
 2688        editor.newline(&Newline, window, cx);
 2689        assert_eq!(
 2690            editor.text(cx),
 2691            "
 2692                a
 2693                b(
 2694                )
 2695                c(
 2696                )
 2697            "
 2698            .unindent()
 2699        );
 2700
 2701        // The selections are moved after the inserted newlines
 2702        assert_eq!(
 2703            editor.selections.ranges(cx),
 2704            &[
 2705                Point::new(2, 0)..Point::new(2, 0),
 2706                Point::new(4, 0)..Point::new(4, 0),
 2707            ],
 2708        );
 2709    });
 2710}
 2711
 2712#[gpui::test]
 2713async fn test_newline_above(cx: &mut TestAppContext) {
 2714    init_test(cx, |settings| {
 2715        settings.defaults.tab_size = NonZeroU32::new(4)
 2716    });
 2717
 2718    let language = Arc::new(
 2719        Language::new(
 2720            LanguageConfig::default(),
 2721            Some(tree_sitter_rust::LANGUAGE.into()),
 2722        )
 2723        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2724        .unwrap(),
 2725    );
 2726
 2727    let mut cx = EditorTestContext::new(cx).await;
 2728    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2729    cx.set_state(indoc! {"
 2730        const a: ˇA = (
 2731 2732                «const_functionˇ»(ˇ),
 2733                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2734 2735        ˇ);ˇ
 2736    "});
 2737
 2738    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2739    cx.assert_editor_state(indoc! {"
 2740        ˇ
 2741        const a: A = (
 2742            ˇ
 2743            (
 2744                ˇ
 2745                ˇ
 2746                const_function(),
 2747                ˇ
 2748                ˇ
 2749                ˇ
 2750                ˇ
 2751                something_else,
 2752                ˇ
 2753            )
 2754            ˇ
 2755            ˇ
 2756        );
 2757    "});
 2758}
 2759
 2760#[gpui::test]
 2761async fn test_newline_below(cx: &mut TestAppContext) {
 2762    init_test(cx, |settings| {
 2763        settings.defaults.tab_size = NonZeroU32::new(4)
 2764    });
 2765
 2766    let language = Arc::new(
 2767        Language::new(
 2768            LanguageConfig::default(),
 2769            Some(tree_sitter_rust::LANGUAGE.into()),
 2770        )
 2771        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2772        .unwrap(),
 2773    );
 2774
 2775    let mut cx = EditorTestContext::new(cx).await;
 2776    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2777    cx.set_state(indoc! {"
 2778        const a: ˇA = (
 2779 2780                «const_functionˇ»(ˇ),
 2781                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2782 2783        ˇ);ˇ
 2784    "});
 2785
 2786    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2787    cx.assert_editor_state(indoc! {"
 2788        const a: A = (
 2789            ˇ
 2790            (
 2791                ˇ
 2792                const_function(),
 2793                ˇ
 2794                ˇ
 2795                something_else,
 2796                ˇ
 2797                ˇ
 2798                ˇ
 2799                ˇ
 2800            )
 2801            ˇ
 2802        );
 2803        ˇ
 2804        ˇ
 2805    "});
 2806}
 2807
 2808#[gpui::test]
 2809async fn test_newline_comments(cx: &mut TestAppContext) {
 2810    init_test(cx, |settings| {
 2811        settings.defaults.tab_size = NonZeroU32::new(4)
 2812    });
 2813
 2814    let language = Arc::new(Language::new(
 2815        LanguageConfig {
 2816            line_comments: vec!["// ".into()],
 2817            ..LanguageConfig::default()
 2818        },
 2819        None,
 2820    ));
 2821    {
 2822        let mut cx = EditorTestContext::new(cx).await;
 2823        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2824        cx.set_state(indoc! {"
 2825        // Fooˇ
 2826    "});
 2827
 2828        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2829        cx.assert_editor_state(indoc! {"
 2830        // Foo
 2831        // ˇ
 2832    "});
 2833        // Ensure that we add comment prefix when existing line contains space
 2834        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2835        cx.assert_editor_state(
 2836            indoc! {"
 2837        // Foo
 2838        //s
 2839        // ˇ
 2840    "}
 2841            .replace("s", " ") // s is used as space placeholder to prevent format on save
 2842            .as_str(),
 2843        );
 2844        // Ensure that we add comment prefix when existing line does not contain space
 2845        cx.set_state(indoc! {"
 2846        // Foo
 2847        //ˇ
 2848    "});
 2849        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2850        cx.assert_editor_state(indoc! {"
 2851        // Foo
 2852        //
 2853        // ˇ
 2854    "});
 2855        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2856        cx.set_state(indoc! {"
 2857        ˇ// Foo
 2858    "});
 2859        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2860        cx.assert_editor_state(indoc! {"
 2861
 2862        ˇ// Foo
 2863    "});
 2864    }
 2865    // Ensure that comment continuations can be disabled.
 2866    update_test_language_settings(cx, |settings| {
 2867        settings.defaults.extend_comment_on_newline = Some(false);
 2868    });
 2869    let mut cx = EditorTestContext::new(cx).await;
 2870    cx.set_state(indoc! {"
 2871        // Fooˇ
 2872    "});
 2873    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2874    cx.assert_editor_state(indoc! {"
 2875        // Foo
 2876        ˇ
 2877    "});
 2878}
 2879
 2880#[gpui::test]
 2881async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 2882    init_test(cx, |settings| {
 2883        settings.defaults.tab_size = NonZeroU32::new(4)
 2884    });
 2885
 2886    let language = Arc::new(Language::new(
 2887        LanguageConfig {
 2888            line_comments: vec!["// ".into(), "/// ".into()],
 2889            ..LanguageConfig::default()
 2890        },
 2891        None,
 2892    ));
 2893    {
 2894        let mut cx = EditorTestContext::new(cx).await;
 2895        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2896        cx.set_state(indoc! {"
 2897        //ˇ
 2898    "});
 2899        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2900        cx.assert_editor_state(indoc! {"
 2901        //
 2902        // ˇ
 2903    "});
 2904
 2905        cx.set_state(indoc! {"
 2906        ///ˇ
 2907    "});
 2908        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2909        cx.assert_editor_state(indoc! {"
 2910        ///
 2911        /// ˇ
 2912    "});
 2913    }
 2914}
 2915
 2916#[gpui::test]
 2917async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 2918    init_test(cx, |settings| {
 2919        settings.defaults.tab_size = NonZeroU32::new(4)
 2920    });
 2921
 2922    let language = Arc::new(
 2923        Language::new(
 2924            LanguageConfig {
 2925                documentation_comment: Some(language::BlockCommentConfig {
 2926                    start: "/**".into(),
 2927                    end: "*/".into(),
 2928                    prefix: "* ".into(),
 2929                    tab_size: 1,
 2930                }),
 2931
 2932                ..LanguageConfig::default()
 2933            },
 2934            Some(tree_sitter_rust::LANGUAGE.into()),
 2935        )
 2936        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 2937        .unwrap(),
 2938    );
 2939
 2940    {
 2941        let mut cx = EditorTestContext::new(cx).await;
 2942        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2943        cx.set_state(indoc! {"
 2944        /**ˇ
 2945    "});
 2946
 2947        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2948        cx.assert_editor_state(indoc! {"
 2949        /**
 2950         * ˇ
 2951    "});
 2952        // Ensure that if cursor is before the comment start,
 2953        // we do not actually insert a comment prefix.
 2954        cx.set_state(indoc! {"
 2955        ˇ/**
 2956    "});
 2957        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2958        cx.assert_editor_state(indoc! {"
 2959
 2960        ˇ/**
 2961    "});
 2962        // Ensure that if cursor is between it doesn't add comment prefix.
 2963        cx.set_state(indoc! {"
 2964        /*ˇ*
 2965    "});
 2966        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2967        cx.assert_editor_state(indoc! {"
 2968        /*
 2969        ˇ*
 2970    "});
 2971        // Ensure that if suffix exists on same line after cursor it adds new line.
 2972        cx.set_state(indoc! {"
 2973        /**ˇ*/
 2974    "});
 2975        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2976        cx.assert_editor_state(indoc! {"
 2977        /**
 2978         * ˇ
 2979         */
 2980    "});
 2981        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2982        cx.set_state(indoc! {"
 2983        /**ˇ */
 2984    "});
 2985        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2986        cx.assert_editor_state(indoc! {"
 2987        /**
 2988         * ˇ
 2989         */
 2990    "});
 2991        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 2992        cx.set_state(indoc! {"
 2993        /** ˇ*/
 2994    "});
 2995        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2996        cx.assert_editor_state(
 2997            indoc! {"
 2998        /**s
 2999         * ˇ
 3000         */
 3001    "}
 3002            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3003            .as_str(),
 3004        );
 3005        // Ensure that delimiter space is preserved when newline on already
 3006        // spaced delimiter.
 3007        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3008        cx.assert_editor_state(
 3009            indoc! {"
 3010        /**s
 3011         *s
 3012         * ˇ
 3013         */
 3014    "}
 3015            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3016            .as_str(),
 3017        );
 3018        // Ensure that delimiter space is preserved when space is not
 3019        // on existing delimiter.
 3020        cx.set_state(indoc! {"
 3021        /**
 3022 3023         */
 3024    "});
 3025        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3026        cx.assert_editor_state(indoc! {"
 3027        /**
 3028         *
 3029         * ˇ
 3030         */
 3031    "});
 3032        // Ensure that if suffix exists on same line after cursor it
 3033        // doesn't add extra new line if prefix is not on same line.
 3034        cx.set_state(indoc! {"
 3035        /**
 3036        ˇ*/
 3037    "});
 3038        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3039        cx.assert_editor_state(indoc! {"
 3040        /**
 3041
 3042        ˇ*/
 3043    "});
 3044        // Ensure that it detects suffix after existing prefix.
 3045        cx.set_state(indoc! {"
 3046        /**ˇ/
 3047    "});
 3048        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3049        cx.assert_editor_state(indoc! {"
 3050        /**
 3051        ˇ/
 3052    "});
 3053        // Ensure that if suffix exists on same line before
 3054        // cursor it does not add comment prefix.
 3055        cx.set_state(indoc! {"
 3056        /** */ˇ
 3057    "});
 3058        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3059        cx.assert_editor_state(indoc! {"
 3060        /** */
 3061        ˇ
 3062    "});
 3063        // Ensure that if suffix exists on same line before
 3064        // cursor it does not add comment prefix.
 3065        cx.set_state(indoc! {"
 3066        /**
 3067         *
 3068         */ˇ
 3069    "});
 3070        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3071        cx.assert_editor_state(indoc! {"
 3072        /**
 3073         *
 3074         */
 3075         ˇ
 3076    "});
 3077
 3078        // Ensure that inline comment followed by code
 3079        // doesn't add comment prefix on newline
 3080        cx.set_state(indoc! {"
 3081        /** */ textˇ
 3082    "});
 3083        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3084        cx.assert_editor_state(indoc! {"
 3085        /** */ text
 3086        ˇ
 3087    "});
 3088
 3089        // Ensure that text after comment end tag
 3090        // doesn't add comment prefix on newline
 3091        cx.set_state(indoc! {"
 3092        /**
 3093         *
 3094         */ˇtext
 3095    "});
 3096        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3097        cx.assert_editor_state(indoc! {"
 3098        /**
 3099         *
 3100         */
 3101         ˇtext
 3102    "});
 3103
 3104        // Ensure if not comment block it doesn't
 3105        // add comment prefix on newline
 3106        cx.set_state(indoc! {"
 3107        * textˇ
 3108    "});
 3109        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3110        cx.assert_editor_state(indoc! {"
 3111        * text
 3112        ˇ
 3113    "});
 3114    }
 3115    // Ensure that comment continuations can be disabled.
 3116    update_test_language_settings(cx, |settings| {
 3117        settings.defaults.extend_comment_on_newline = Some(false);
 3118    });
 3119    let mut cx = EditorTestContext::new(cx).await;
 3120    cx.set_state(indoc! {"
 3121        /**ˇ
 3122    "});
 3123    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3124    cx.assert_editor_state(indoc! {"
 3125        /**
 3126        ˇ
 3127    "});
 3128}
 3129
 3130#[gpui::test]
 3131async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3132    init_test(cx, |settings| {
 3133        settings.defaults.tab_size = NonZeroU32::new(4)
 3134    });
 3135
 3136    let lua_language = Arc::new(Language::new(
 3137        LanguageConfig {
 3138            line_comments: vec!["--".into()],
 3139            block_comment: Some(language::BlockCommentConfig {
 3140                start: "--[[".into(),
 3141                prefix: "".into(),
 3142                end: "]]".into(),
 3143                tab_size: 0,
 3144            }),
 3145            ..LanguageConfig::default()
 3146        },
 3147        None,
 3148    ));
 3149
 3150    let mut cx = EditorTestContext::new(cx).await;
 3151    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3152
 3153    // Line with line comment should extend
 3154    cx.set_state(indoc! {"
 3155        --ˇ
 3156    "});
 3157    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3158    cx.assert_editor_state(indoc! {"
 3159        --
 3160        --ˇ
 3161    "});
 3162
 3163    // Line with block comment that matches line comment should not extend
 3164    cx.set_state(indoc! {"
 3165        --[[ˇ
 3166    "});
 3167    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3168    cx.assert_editor_state(indoc! {"
 3169        --[[
 3170        ˇ
 3171    "});
 3172}
 3173
 3174#[gpui::test]
 3175fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3176    init_test(cx, |_| {});
 3177
 3178    let editor = cx.add_window(|window, cx| {
 3179        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3180        let mut editor = build_editor(buffer, window, cx);
 3181        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3182            s.select_ranges([3..4, 11..12, 19..20])
 3183        });
 3184        editor
 3185    });
 3186
 3187    _ = editor.update(cx, |editor, window, cx| {
 3188        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3189        editor.buffer.update(cx, |buffer, cx| {
 3190            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3191            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3192        });
 3193        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3194
 3195        editor.insert("Z", window, cx);
 3196        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3197
 3198        // The selections are moved after the inserted characters
 3199        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3200    });
 3201}
 3202
 3203#[gpui::test]
 3204async fn test_tab(cx: &mut TestAppContext) {
 3205    init_test(cx, |settings| {
 3206        settings.defaults.tab_size = NonZeroU32::new(3)
 3207    });
 3208
 3209    let mut cx = EditorTestContext::new(cx).await;
 3210    cx.set_state(indoc! {"
 3211        ˇabˇc
 3212        ˇ🏀ˇ🏀ˇefg
 3213 3214    "});
 3215    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3216    cx.assert_editor_state(indoc! {"
 3217           ˇab ˇc
 3218           ˇ🏀  ˇ🏀  ˇefg
 3219        d  ˇ
 3220    "});
 3221
 3222    cx.set_state(indoc! {"
 3223        a
 3224        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3225    "});
 3226    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3227    cx.assert_editor_state(indoc! {"
 3228        a
 3229           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3230    "});
 3231}
 3232
 3233#[gpui::test]
 3234async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3235    init_test(cx, |_| {});
 3236
 3237    let mut cx = EditorTestContext::new(cx).await;
 3238    let language = Arc::new(
 3239        Language::new(
 3240            LanguageConfig::default(),
 3241            Some(tree_sitter_rust::LANGUAGE.into()),
 3242        )
 3243        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3244        .unwrap(),
 3245    );
 3246    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3247
 3248    // test when all cursors are not at suggested indent
 3249    // then simply move to their suggested indent location
 3250    cx.set_state(indoc! {"
 3251        const a: B = (
 3252            c(
 3253        ˇ
 3254        ˇ    )
 3255        );
 3256    "});
 3257    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3258    cx.assert_editor_state(indoc! {"
 3259        const a: B = (
 3260            c(
 3261                ˇ
 3262            ˇ)
 3263        );
 3264    "});
 3265
 3266    // test cursor already at suggested indent not moving when
 3267    // other cursors are yet to reach their suggested indents
 3268    cx.set_state(indoc! {"
 3269        ˇ
 3270        const a: B = (
 3271            c(
 3272                d(
 3273        ˇ
 3274                )
 3275        ˇ
 3276        ˇ    )
 3277        );
 3278    "});
 3279    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3280    cx.assert_editor_state(indoc! {"
 3281        ˇ
 3282        const a: B = (
 3283            c(
 3284                d(
 3285                    ˇ
 3286                )
 3287                ˇ
 3288            ˇ)
 3289        );
 3290    "});
 3291    // test when all cursors are at suggested indent then tab is inserted
 3292    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3293    cx.assert_editor_state(indoc! {"
 3294            ˇ
 3295        const a: B = (
 3296            c(
 3297                d(
 3298                        ˇ
 3299                )
 3300                    ˇ
 3301                ˇ)
 3302        );
 3303    "});
 3304
 3305    // test when current indent is less than suggested indent,
 3306    // we adjust line to match suggested indent and move cursor to it
 3307    //
 3308    // when no other cursor is at word boundary, all of them should move
 3309    cx.set_state(indoc! {"
 3310        const a: B = (
 3311            c(
 3312                d(
 3313        ˇ
 3314        ˇ   )
 3315        ˇ   )
 3316        );
 3317    "});
 3318    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3319    cx.assert_editor_state(indoc! {"
 3320        const a: B = (
 3321            c(
 3322                d(
 3323                    ˇ
 3324                ˇ)
 3325            ˇ)
 3326        );
 3327    "});
 3328
 3329    // test when current indent is less than suggested indent,
 3330    // we adjust line to match suggested indent and move cursor to it
 3331    //
 3332    // when some other cursor is at word boundary, it should not move
 3333    cx.set_state(indoc! {"
 3334        const a: B = (
 3335            c(
 3336                d(
 3337        ˇ
 3338        ˇ   )
 3339           ˇ)
 3340        );
 3341    "});
 3342    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3343    cx.assert_editor_state(indoc! {"
 3344        const a: B = (
 3345            c(
 3346                d(
 3347                    ˇ
 3348                ˇ)
 3349            ˇ)
 3350        );
 3351    "});
 3352
 3353    // test when current indent is more than suggested indent,
 3354    // we just move cursor to current indent instead of suggested indent
 3355    //
 3356    // when no other cursor is at word boundary, all of them should move
 3357    cx.set_state(indoc! {"
 3358        const a: B = (
 3359            c(
 3360                d(
 3361        ˇ
 3362        ˇ                )
 3363        ˇ   )
 3364        );
 3365    "});
 3366    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3367    cx.assert_editor_state(indoc! {"
 3368        const a: B = (
 3369            c(
 3370                d(
 3371                    ˇ
 3372                        ˇ)
 3373            ˇ)
 3374        );
 3375    "});
 3376    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3377    cx.assert_editor_state(indoc! {"
 3378        const a: B = (
 3379            c(
 3380                d(
 3381                        ˇ
 3382                            ˇ)
 3383                ˇ)
 3384        );
 3385    "});
 3386
 3387    // test when current indent is more than suggested indent,
 3388    // we just move cursor to current indent instead of suggested indent
 3389    //
 3390    // when some other cursor is at word boundary, it doesn't move
 3391    cx.set_state(indoc! {"
 3392        const a: B = (
 3393            c(
 3394                d(
 3395        ˇ
 3396        ˇ                )
 3397            ˇ)
 3398        );
 3399    "});
 3400    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3401    cx.assert_editor_state(indoc! {"
 3402        const a: B = (
 3403            c(
 3404                d(
 3405                    ˇ
 3406                        ˇ)
 3407            ˇ)
 3408        );
 3409    "});
 3410
 3411    // handle auto-indent when there are multiple cursors on the same line
 3412    cx.set_state(indoc! {"
 3413        const a: B = (
 3414            c(
 3415        ˇ    ˇ
 3416        ˇ    )
 3417        );
 3418    "});
 3419    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3420    cx.assert_editor_state(indoc! {"
 3421        const a: B = (
 3422            c(
 3423                ˇ
 3424            ˇ)
 3425        );
 3426    "});
 3427}
 3428
 3429#[gpui::test]
 3430async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3431    init_test(cx, |settings| {
 3432        settings.defaults.tab_size = NonZeroU32::new(3)
 3433    });
 3434
 3435    let mut cx = EditorTestContext::new(cx).await;
 3436    cx.set_state(indoc! {"
 3437         ˇ
 3438        \t ˇ
 3439        \t  ˇ
 3440        \t   ˇ
 3441         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3442    "});
 3443
 3444    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3445    cx.assert_editor_state(indoc! {"
 3446           ˇ
 3447        \t   ˇ
 3448        \t   ˇ
 3449        \t      ˇ
 3450         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3451    "});
 3452}
 3453
 3454#[gpui::test]
 3455async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3456    init_test(cx, |settings| {
 3457        settings.defaults.tab_size = NonZeroU32::new(4)
 3458    });
 3459
 3460    let language = Arc::new(
 3461        Language::new(
 3462            LanguageConfig::default(),
 3463            Some(tree_sitter_rust::LANGUAGE.into()),
 3464        )
 3465        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3466        .unwrap(),
 3467    );
 3468
 3469    let mut cx = EditorTestContext::new(cx).await;
 3470    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3471    cx.set_state(indoc! {"
 3472        fn a() {
 3473            if b {
 3474        \t ˇc
 3475            }
 3476        }
 3477    "});
 3478
 3479    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3480    cx.assert_editor_state(indoc! {"
 3481        fn a() {
 3482            if b {
 3483                ˇc
 3484            }
 3485        }
 3486    "});
 3487}
 3488
 3489#[gpui::test]
 3490async fn test_indent_outdent(cx: &mut TestAppContext) {
 3491    init_test(cx, |settings| {
 3492        settings.defaults.tab_size = NonZeroU32::new(4);
 3493    });
 3494
 3495    let mut cx = EditorTestContext::new(cx).await;
 3496
 3497    cx.set_state(indoc! {"
 3498          «oneˇ» «twoˇ»
 3499        three
 3500         four
 3501    "});
 3502    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3503    cx.assert_editor_state(indoc! {"
 3504            «oneˇ» «twoˇ»
 3505        three
 3506         four
 3507    "});
 3508
 3509    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3510    cx.assert_editor_state(indoc! {"
 3511        «oneˇ» «twoˇ»
 3512        three
 3513         four
 3514    "});
 3515
 3516    // select across line ending
 3517    cx.set_state(indoc! {"
 3518        one two
 3519        t«hree
 3520        ˇ» four
 3521    "});
 3522    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3523    cx.assert_editor_state(indoc! {"
 3524        one two
 3525            t«hree
 3526        ˇ» four
 3527    "});
 3528
 3529    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3530    cx.assert_editor_state(indoc! {"
 3531        one two
 3532        t«hree
 3533        ˇ» four
 3534    "});
 3535
 3536    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3537    cx.set_state(indoc! {"
 3538        one two
 3539        ˇthree
 3540            four
 3541    "});
 3542    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3543    cx.assert_editor_state(indoc! {"
 3544        one two
 3545            ˇthree
 3546            four
 3547    "});
 3548
 3549    cx.set_state(indoc! {"
 3550        one two
 3551        ˇ    three
 3552            four
 3553    "});
 3554    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3555    cx.assert_editor_state(indoc! {"
 3556        one two
 3557        ˇthree
 3558            four
 3559    "});
 3560}
 3561
 3562#[gpui::test]
 3563async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3564    // This is a regression test for issue #33761
 3565    init_test(cx, |_| {});
 3566
 3567    let mut cx = EditorTestContext::new(cx).await;
 3568    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3569    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3570
 3571    cx.set_state(
 3572        r#"ˇ#     ingress:
 3573ˇ#         api:
 3574ˇ#             enabled: false
 3575ˇ#             pathType: Prefix
 3576ˇ#           console:
 3577ˇ#               enabled: false
 3578ˇ#               pathType: Prefix
 3579"#,
 3580    );
 3581
 3582    // Press tab to indent all lines
 3583    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3584
 3585    cx.assert_editor_state(
 3586        r#"    ˇ#     ingress:
 3587    ˇ#         api:
 3588    ˇ#             enabled: false
 3589    ˇ#             pathType: Prefix
 3590    ˇ#           console:
 3591    ˇ#               enabled: false
 3592    ˇ#               pathType: Prefix
 3593"#,
 3594    );
 3595}
 3596
 3597#[gpui::test]
 3598async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3599    // This is a test to make sure our fix for issue #33761 didn't break anything
 3600    init_test(cx, |_| {});
 3601
 3602    let mut cx = EditorTestContext::new(cx).await;
 3603    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3604    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3605
 3606    cx.set_state(
 3607        r#"ˇingress:
 3608ˇ  api:
 3609ˇ    enabled: false
 3610ˇ    pathType: Prefix
 3611"#,
 3612    );
 3613
 3614    // Press tab to indent all lines
 3615    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3616
 3617    cx.assert_editor_state(
 3618        r#"ˇingress:
 3619    ˇapi:
 3620        ˇenabled: false
 3621        ˇpathType: Prefix
 3622"#,
 3623    );
 3624}
 3625
 3626#[gpui::test]
 3627async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3628    init_test(cx, |settings| {
 3629        settings.defaults.hard_tabs = Some(true);
 3630    });
 3631
 3632    let mut cx = EditorTestContext::new(cx).await;
 3633
 3634    // select two ranges on one line
 3635    cx.set_state(indoc! {"
 3636        «oneˇ» «twoˇ»
 3637        three
 3638        four
 3639    "});
 3640    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3641    cx.assert_editor_state(indoc! {"
 3642        \t«oneˇ» «twoˇ»
 3643        three
 3644        four
 3645    "});
 3646    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3647    cx.assert_editor_state(indoc! {"
 3648        \t\t«oneˇ» «twoˇ»
 3649        three
 3650        four
 3651    "});
 3652    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3653    cx.assert_editor_state(indoc! {"
 3654        \t«oneˇ» «twoˇ»
 3655        three
 3656        four
 3657    "});
 3658    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3659    cx.assert_editor_state(indoc! {"
 3660        «oneˇ» «twoˇ»
 3661        three
 3662        four
 3663    "});
 3664
 3665    // select across a line ending
 3666    cx.set_state(indoc! {"
 3667        one two
 3668        t«hree
 3669        ˇ»four
 3670    "});
 3671    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3672    cx.assert_editor_state(indoc! {"
 3673        one two
 3674        \tt«hree
 3675        ˇ»four
 3676    "});
 3677    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3678    cx.assert_editor_state(indoc! {"
 3679        one two
 3680        \t\tt«hree
 3681        ˇ»four
 3682    "});
 3683    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3684    cx.assert_editor_state(indoc! {"
 3685        one two
 3686        \tt«hree
 3687        ˇ»four
 3688    "});
 3689    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3690    cx.assert_editor_state(indoc! {"
 3691        one two
 3692        t«hree
 3693        ˇ»four
 3694    "});
 3695
 3696    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3697    cx.set_state(indoc! {"
 3698        one two
 3699        ˇthree
 3700        four
 3701    "});
 3702    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3703    cx.assert_editor_state(indoc! {"
 3704        one two
 3705        ˇthree
 3706        four
 3707    "});
 3708    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3709    cx.assert_editor_state(indoc! {"
 3710        one two
 3711        \tˇthree
 3712        four
 3713    "});
 3714    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3715    cx.assert_editor_state(indoc! {"
 3716        one two
 3717        ˇthree
 3718        four
 3719    "});
 3720}
 3721
 3722#[gpui::test]
 3723fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3724    init_test(cx, |settings| {
 3725        settings.languages.0.extend([
 3726            (
 3727                "TOML".into(),
 3728                LanguageSettingsContent {
 3729                    tab_size: NonZeroU32::new(2),
 3730                    ..Default::default()
 3731                },
 3732            ),
 3733            (
 3734                "Rust".into(),
 3735                LanguageSettingsContent {
 3736                    tab_size: NonZeroU32::new(4),
 3737                    ..Default::default()
 3738                },
 3739            ),
 3740        ]);
 3741    });
 3742
 3743    let toml_language = Arc::new(Language::new(
 3744        LanguageConfig {
 3745            name: "TOML".into(),
 3746            ..Default::default()
 3747        },
 3748        None,
 3749    ));
 3750    let rust_language = Arc::new(Language::new(
 3751        LanguageConfig {
 3752            name: "Rust".into(),
 3753            ..Default::default()
 3754        },
 3755        None,
 3756    ));
 3757
 3758    let toml_buffer =
 3759        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3760    let rust_buffer =
 3761        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3762    let multibuffer = cx.new(|cx| {
 3763        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3764        multibuffer.push_excerpts(
 3765            toml_buffer.clone(),
 3766            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 3767            cx,
 3768        );
 3769        multibuffer.push_excerpts(
 3770            rust_buffer.clone(),
 3771            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 3772            cx,
 3773        );
 3774        multibuffer
 3775    });
 3776
 3777    cx.add_window(|window, cx| {
 3778        let mut editor = build_editor(multibuffer, window, cx);
 3779
 3780        assert_eq!(
 3781            editor.text(cx),
 3782            indoc! {"
 3783                a = 1
 3784                b = 2
 3785
 3786                const c: usize = 3;
 3787            "}
 3788        );
 3789
 3790        select_ranges(
 3791            &mut editor,
 3792            indoc! {"
 3793                «aˇ» = 1
 3794                b = 2
 3795
 3796                «const c:ˇ» usize = 3;
 3797            "},
 3798            window,
 3799            cx,
 3800        );
 3801
 3802        editor.tab(&Tab, window, cx);
 3803        assert_text_with_selections(
 3804            &mut editor,
 3805            indoc! {"
 3806                  «aˇ» = 1
 3807                b = 2
 3808
 3809                    «const c:ˇ» usize = 3;
 3810            "},
 3811            cx,
 3812        );
 3813        editor.backtab(&Backtab, window, cx);
 3814        assert_text_with_selections(
 3815            &mut editor,
 3816            indoc! {"
 3817                «aˇ» = 1
 3818                b = 2
 3819
 3820                «const c:ˇ» usize = 3;
 3821            "},
 3822            cx,
 3823        );
 3824
 3825        editor
 3826    });
 3827}
 3828
 3829#[gpui::test]
 3830async fn test_backspace(cx: &mut TestAppContext) {
 3831    init_test(cx, |_| {});
 3832
 3833    let mut cx = EditorTestContext::new(cx).await;
 3834
 3835    // Basic backspace
 3836    cx.set_state(indoc! {"
 3837        onˇe two three
 3838        fou«rˇ» five six
 3839        seven «ˇeight nine
 3840        »ten
 3841    "});
 3842    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3843    cx.assert_editor_state(indoc! {"
 3844        oˇe two three
 3845        fouˇ five six
 3846        seven ˇten
 3847    "});
 3848
 3849    // Test backspace inside and around indents
 3850    cx.set_state(indoc! {"
 3851        zero
 3852            ˇone
 3853                ˇtwo
 3854            ˇ ˇ ˇ  three
 3855        ˇ  ˇ  four
 3856    "});
 3857    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3858    cx.assert_editor_state(indoc! {"
 3859        zero
 3860        ˇone
 3861            ˇtwo
 3862        ˇ  threeˇ  four
 3863    "});
 3864}
 3865
 3866#[gpui::test]
 3867async fn test_delete(cx: &mut TestAppContext) {
 3868    init_test(cx, |_| {});
 3869
 3870    let mut cx = EditorTestContext::new(cx).await;
 3871    cx.set_state(indoc! {"
 3872        onˇe two three
 3873        fou«rˇ» five six
 3874        seven «ˇeight nine
 3875        »ten
 3876    "});
 3877    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3878    cx.assert_editor_state(indoc! {"
 3879        onˇ two three
 3880        fouˇ five six
 3881        seven ˇten
 3882    "});
 3883}
 3884
 3885#[gpui::test]
 3886fn test_delete_line(cx: &mut TestAppContext) {
 3887    init_test(cx, |_| {});
 3888
 3889    let editor = cx.add_window(|window, cx| {
 3890        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3891        build_editor(buffer, window, cx)
 3892    });
 3893    _ = editor.update(cx, |editor, window, cx| {
 3894        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3895            s.select_display_ranges([
 3896                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3897                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3898                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3899            ])
 3900        });
 3901        editor.delete_line(&DeleteLine, window, cx);
 3902        assert_eq!(editor.display_text(cx), "ghi");
 3903        assert_eq!(
 3904            editor.selections.display_ranges(cx),
 3905            vec![
 3906                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3907                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3908            ]
 3909        );
 3910    });
 3911
 3912    let editor = cx.add_window(|window, cx| {
 3913        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3914        build_editor(buffer, window, cx)
 3915    });
 3916    _ = editor.update(cx, |editor, window, cx| {
 3917        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3918            s.select_display_ranges([
 3919                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3920            ])
 3921        });
 3922        editor.delete_line(&DeleteLine, window, cx);
 3923        assert_eq!(editor.display_text(cx), "ghi\n");
 3924        assert_eq!(
 3925            editor.selections.display_ranges(cx),
 3926            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3927        );
 3928    });
 3929}
 3930
 3931#[gpui::test]
 3932fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3933    init_test(cx, |_| {});
 3934
 3935    cx.add_window(|window, cx| {
 3936        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3937        let mut editor = build_editor(buffer.clone(), window, cx);
 3938        let buffer = buffer.read(cx).as_singleton().unwrap();
 3939
 3940        assert_eq!(
 3941            editor.selections.ranges::<Point>(cx),
 3942            &[Point::new(0, 0)..Point::new(0, 0)]
 3943        );
 3944
 3945        // When on single line, replace newline at end by space
 3946        editor.join_lines(&JoinLines, window, cx);
 3947        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3948        assert_eq!(
 3949            editor.selections.ranges::<Point>(cx),
 3950            &[Point::new(0, 3)..Point::new(0, 3)]
 3951        );
 3952
 3953        // When multiple lines are selected, remove newlines that are spanned by the selection
 3954        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3955            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3956        });
 3957        editor.join_lines(&JoinLines, window, cx);
 3958        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3959        assert_eq!(
 3960            editor.selections.ranges::<Point>(cx),
 3961            &[Point::new(0, 11)..Point::new(0, 11)]
 3962        );
 3963
 3964        // Undo should be transactional
 3965        editor.undo(&Undo, window, cx);
 3966        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3967        assert_eq!(
 3968            editor.selections.ranges::<Point>(cx),
 3969            &[Point::new(0, 5)..Point::new(2, 2)]
 3970        );
 3971
 3972        // When joining an empty line don't insert a space
 3973        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3974            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3975        });
 3976        editor.join_lines(&JoinLines, window, cx);
 3977        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3978        assert_eq!(
 3979            editor.selections.ranges::<Point>(cx),
 3980            [Point::new(2, 3)..Point::new(2, 3)]
 3981        );
 3982
 3983        // We can remove trailing newlines
 3984        editor.join_lines(&JoinLines, window, cx);
 3985        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3986        assert_eq!(
 3987            editor.selections.ranges::<Point>(cx),
 3988            [Point::new(2, 3)..Point::new(2, 3)]
 3989        );
 3990
 3991        // We don't blow up on the last line
 3992        editor.join_lines(&JoinLines, window, cx);
 3993        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3994        assert_eq!(
 3995            editor.selections.ranges::<Point>(cx),
 3996            [Point::new(2, 3)..Point::new(2, 3)]
 3997        );
 3998
 3999        // reset to test indentation
 4000        editor.buffer.update(cx, |buffer, cx| {
 4001            buffer.edit(
 4002                [
 4003                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4004                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4005                ],
 4006                None,
 4007                cx,
 4008            )
 4009        });
 4010
 4011        // We remove any leading spaces
 4012        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4013        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4014            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4015        });
 4016        editor.join_lines(&JoinLines, window, cx);
 4017        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4018
 4019        // We don't insert a space for a line containing only spaces
 4020        editor.join_lines(&JoinLines, window, cx);
 4021        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4022
 4023        // We ignore any leading tabs
 4024        editor.join_lines(&JoinLines, window, cx);
 4025        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4026
 4027        editor
 4028    });
 4029}
 4030
 4031#[gpui::test]
 4032fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4033    init_test(cx, |_| {});
 4034
 4035    cx.add_window(|window, cx| {
 4036        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4037        let mut editor = build_editor(buffer.clone(), window, cx);
 4038        let buffer = buffer.read(cx).as_singleton().unwrap();
 4039
 4040        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4041            s.select_ranges([
 4042                Point::new(0, 2)..Point::new(1, 1),
 4043                Point::new(1, 2)..Point::new(1, 2),
 4044                Point::new(3, 1)..Point::new(3, 2),
 4045            ])
 4046        });
 4047
 4048        editor.join_lines(&JoinLines, window, cx);
 4049        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4050
 4051        assert_eq!(
 4052            editor.selections.ranges::<Point>(cx),
 4053            [
 4054                Point::new(0, 7)..Point::new(0, 7),
 4055                Point::new(1, 3)..Point::new(1, 3)
 4056            ]
 4057        );
 4058        editor
 4059    });
 4060}
 4061
 4062#[gpui::test]
 4063async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4064    init_test(cx, |_| {});
 4065
 4066    let mut cx = EditorTestContext::new(cx).await;
 4067
 4068    let diff_base = r#"
 4069        Line 0
 4070        Line 1
 4071        Line 2
 4072        Line 3
 4073        "#
 4074    .unindent();
 4075
 4076    cx.set_state(
 4077        &r#"
 4078        ˇLine 0
 4079        Line 1
 4080        Line 2
 4081        Line 3
 4082        "#
 4083        .unindent(),
 4084    );
 4085
 4086    cx.set_head_text(&diff_base);
 4087    executor.run_until_parked();
 4088
 4089    // Join lines
 4090    cx.update_editor(|editor, window, cx| {
 4091        editor.join_lines(&JoinLines, window, cx);
 4092    });
 4093    executor.run_until_parked();
 4094
 4095    cx.assert_editor_state(
 4096        &r#"
 4097        Line 0ˇ Line 1
 4098        Line 2
 4099        Line 3
 4100        "#
 4101        .unindent(),
 4102    );
 4103    // Join again
 4104    cx.update_editor(|editor, window, cx| {
 4105        editor.join_lines(&JoinLines, window, cx);
 4106    });
 4107    executor.run_until_parked();
 4108
 4109    cx.assert_editor_state(
 4110        &r#"
 4111        Line 0 Line 1ˇ Line 2
 4112        Line 3
 4113        "#
 4114        .unindent(),
 4115    );
 4116}
 4117
 4118#[gpui::test]
 4119async fn test_custom_newlines_cause_no_false_positive_diffs(
 4120    executor: BackgroundExecutor,
 4121    cx: &mut TestAppContext,
 4122) {
 4123    init_test(cx, |_| {});
 4124    let mut cx = EditorTestContext::new(cx).await;
 4125    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4126    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4127    executor.run_until_parked();
 4128
 4129    cx.update_editor(|editor, window, cx| {
 4130        let snapshot = editor.snapshot(window, cx);
 4131        assert_eq!(
 4132            snapshot
 4133                .buffer_snapshot
 4134                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4135                .collect::<Vec<_>>(),
 4136            Vec::new(),
 4137            "Should not have any diffs for files with custom newlines"
 4138        );
 4139    });
 4140}
 4141
 4142#[gpui::test]
 4143async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4144    init_test(cx, |_| {});
 4145
 4146    let mut cx = EditorTestContext::new(cx).await;
 4147
 4148    // Test sort_lines_case_insensitive()
 4149    cx.set_state(indoc! {"
 4150        «z
 4151        y
 4152        x
 4153        Z
 4154        Y
 4155        Xˇ»
 4156    "});
 4157    cx.update_editor(|e, window, cx| {
 4158        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4159    });
 4160    cx.assert_editor_state(indoc! {"
 4161        «x
 4162        X
 4163        y
 4164        Y
 4165        z
 4166        Zˇ»
 4167    "});
 4168
 4169    // Test sort_lines_by_length()
 4170    //
 4171    // Demonstrates:
 4172    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4173    // - sort is stable
 4174    cx.set_state(indoc! {"
 4175        «123
 4176        æ
 4177        12
 4178 4179        1
 4180        æˇ»
 4181    "});
 4182    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4183    cx.assert_editor_state(indoc! {"
 4184        «æ
 4185 4186        1
 4187        æ
 4188        12
 4189        123ˇ»
 4190    "});
 4191
 4192    // Test reverse_lines()
 4193    cx.set_state(indoc! {"
 4194        «5
 4195        4
 4196        3
 4197        2
 4198        1ˇ»
 4199    "});
 4200    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4201    cx.assert_editor_state(indoc! {"
 4202        «1
 4203        2
 4204        3
 4205        4
 4206        5ˇ»
 4207    "});
 4208
 4209    // Skip testing shuffle_line()
 4210
 4211    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4212    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4213
 4214    // Don't manipulate when cursor is on single line, but expand the selection
 4215    cx.set_state(indoc! {"
 4216        ddˇdd
 4217        ccc
 4218        bb
 4219        a
 4220    "});
 4221    cx.update_editor(|e, window, cx| {
 4222        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4223    });
 4224    cx.assert_editor_state(indoc! {"
 4225        «ddddˇ»
 4226        ccc
 4227        bb
 4228        a
 4229    "});
 4230
 4231    // Basic manipulate case
 4232    // Start selection moves to column 0
 4233    // End of selection shrinks to fit shorter line
 4234    cx.set_state(indoc! {"
 4235        dd«d
 4236        ccc
 4237        bb
 4238        aaaaaˇ»
 4239    "});
 4240    cx.update_editor(|e, window, cx| {
 4241        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4242    });
 4243    cx.assert_editor_state(indoc! {"
 4244        «aaaaa
 4245        bb
 4246        ccc
 4247        dddˇ»
 4248    "});
 4249
 4250    // Manipulate case with newlines
 4251    cx.set_state(indoc! {"
 4252        dd«d
 4253        ccc
 4254
 4255        bb
 4256        aaaaa
 4257
 4258        ˇ»
 4259    "});
 4260    cx.update_editor(|e, window, cx| {
 4261        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4262    });
 4263    cx.assert_editor_state(indoc! {"
 4264        «
 4265
 4266        aaaaa
 4267        bb
 4268        ccc
 4269        dddˇ»
 4270
 4271    "});
 4272
 4273    // Adding new line
 4274    cx.set_state(indoc! {"
 4275        aa«a
 4276        bbˇ»b
 4277    "});
 4278    cx.update_editor(|e, window, cx| {
 4279        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4280    });
 4281    cx.assert_editor_state(indoc! {"
 4282        «aaa
 4283        bbb
 4284        added_lineˇ»
 4285    "});
 4286
 4287    // Removing line
 4288    cx.set_state(indoc! {"
 4289        aa«a
 4290        bbbˇ»
 4291    "});
 4292    cx.update_editor(|e, window, cx| {
 4293        e.manipulate_immutable_lines(window, cx, |lines| {
 4294            lines.pop();
 4295        })
 4296    });
 4297    cx.assert_editor_state(indoc! {"
 4298        «aaaˇ»
 4299    "});
 4300
 4301    // Removing all lines
 4302    cx.set_state(indoc! {"
 4303        aa«a
 4304        bbbˇ»
 4305    "});
 4306    cx.update_editor(|e, window, cx| {
 4307        e.manipulate_immutable_lines(window, cx, |lines| {
 4308            lines.drain(..);
 4309        })
 4310    });
 4311    cx.assert_editor_state(indoc! {"
 4312        ˇ
 4313    "});
 4314}
 4315
 4316#[gpui::test]
 4317async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4318    init_test(cx, |_| {});
 4319
 4320    let mut cx = EditorTestContext::new(cx).await;
 4321
 4322    // Consider continuous selection as single selection
 4323    cx.set_state(indoc! {"
 4324        Aaa«aa
 4325        cˇ»c«c
 4326        bb
 4327        aaaˇ»aa
 4328    "});
 4329    cx.update_editor(|e, window, cx| {
 4330        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4331    });
 4332    cx.assert_editor_state(indoc! {"
 4333        «Aaaaa
 4334        ccc
 4335        bb
 4336        aaaaaˇ»
 4337    "});
 4338
 4339    cx.set_state(indoc! {"
 4340        Aaa«aa
 4341        cˇ»c«c
 4342        bb
 4343        aaaˇ»aa
 4344    "});
 4345    cx.update_editor(|e, window, cx| {
 4346        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4347    });
 4348    cx.assert_editor_state(indoc! {"
 4349        «Aaaaa
 4350        ccc
 4351        bbˇ»
 4352    "});
 4353
 4354    // Consider non continuous selection as distinct dedup operations
 4355    cx.set_state(indoc! {"
 4356        «aaaaa
 4357        bb
 4358        aaaaa
 4359        aaaaaˇ»
 4360
 4361        aaa«aaˇ»
 4362    "});
 4363    cx.update_editor(|e, window, cx| {
 4364        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4365    });
 4366    cx.assert_editor_state(indoc! {"
 4367        «aaaaa
 4368        bbˇ»
 4369
 4370        «aaaaaˇ»
 4371    "});
 4372}
 4373
 4374#[gpui::test]
 4375async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4376    init_test(cx, |_| {});
 4377
 4378    let mut cx = EditorTestContext::new(cx).await;
 4379
 4380    cx.set_state(indoc! {"
 4381        «Aaa
 4382        aAa
 4383        Aaaˇ»
 4384    "});
 4385    cx.update_editor(|e, window, cx| {
 4386        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4387    });
 4388    cx.assert_editor_state(indoc! {"
 4389        «Aaa
 4390        aAaˇ»
 4391    "});
 4392
 4393    cx.set_state(indoc! {"
 4394        «Aaa
 4395        aAa
 4396        aaAˇ»
 4397    "});
 4398    cx.update_editor(|e, window, cx| {
 4399        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4400    });
 4401    cx.assert_editor_state(indoc! {"
 4402        «Aaaˇ»
 4403    "});
 4404}
 4405
 4406#[gpui::test]
 4407async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4408    init_test(cx, |_| {});
 4409
 4410    let mut cx = EditorTestContext::new(cx).await;
 4411
 4412    let js_language = Arc::new(Language::new(
 4413        LanguageConfig {
 4414            name: "JavaScript".into(),
 4415            wrap_characters: Some(language::WrapCharactersConfig {
 4416                start_prefix: "<".into(),
 4417                start_suffix: ">".into(),
 4418                end_prefix: "</".into(),
 4419                end_suffix: ">".into(),
 4420            }),
 4421            ..LanguageConfig::default()
 4422        },
 4423        None,
 4424    ));
 4425
 4426    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4427
 4428    cx.set_state(indoc! {"
 4429        «testˇ»
 4430    "});
 4431    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4432    cx.assert_editor_state(indoc! {"
 4433        <«ˇ»>test</«ˇ»>
 4434    "});
 4435
 4436    cx.set_state(indoc! {"
 4437        «test
 4438         testˇ»
 4439    "});
 4440    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4441    cx.assert_editor_state(indoc! {"
 4442        <«ˇ»>test
 4443         test</«ˇ»>
 4444    "});
 4445
 4446    cx.set_state(indoc! {"
 4447        teˇst
 4448    "});
 4449    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4450    cx.assert_editor_state(indoc! {"
 4451        te<«ˇ»></«ˇ»>st
 4452    "});
 4453}
 4454
 4455#[gpui::test]
 4456async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4457    init_test(cx, |_| {});
 4458
 4459    let mut cx = EditorTestContext::new(cx).await;
 4460
 4461    let js_language = Arc::new(Language::new(
 4462        LanguageConfig {
 4463            name: "JavaScript".into(),
 4464            wrap_characters: Some(language::WrapCharactersConfig {
 4465                start_prefix: "<".into(),
 4466                start_suffix: ">".into(),
 4467                end_prefix: "</".into(),
 4468                end_suffix: ">".into(),
 4469            }),
 4470            ..LanguageConfig::default()
 4471        },
 4472        None,
 4473    ));
 4474
 4475    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4476
 4477    cx.set_state(indoc! {"
 4478        «testˇ»
 4479        «testˇ» «testˇ»
 4480        «testˇ»
 4481    "});
 4482    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4483    cx.assert_editor_state(indoc! {"
 4484        <«ˇ»>test</«ˇ»>
 4485        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4486        <«ˇ»>test</«ˇ»>
 4487    "});
 4488
 4489    cx.set_state(indoc! {"
 4490        «test
 4491         testˇ»
 4492        «test
 4493         testˇ»
 4494    "});
 4495    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4496    cx.assert_editor_state(indoc! {"
 4497        <«ˇ»>test
 4498         test</«ˇ»>
 4499        <«ˇ»>test
 4500         test</«ˇ»>
 4501    "});
 4502}
 4503
 4504#[gpui::test]
 4505async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4506    init_test(cx, |_| {});
 4507
 4508    let mut cx = EditorTestContext::new(cx).await;
 4509
 4510    let plaintext_language = Arc::new(Language::new(
 4511        LanguageConfig {
 4512            name: "Plain Text".into(),
 4513            ..LanguageConfig::default()
 4514        },
 4515        None,
 4516    ));
 4517
 4518    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4519
 4520    cx.set_state(indoc! {"
 4521        «testˇ»
 4522    "});
 4523    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4524    cx.assert_editor_state(indoc! {"
 4525      «testˇ»
 4526    "});
 4527}
 4528
 4529#[gpui::test]
 4530async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4531    init_test(cx, |_| {});
 4532
 4533    let mut cx = EditorTestContext::new(cx).await;
 4534
 4535    // Manipulate with multiple selections on a single line
 4536    cx.set_state(indoc! {"
 4537        dd«dd
 4538        cˇ»c«c
 4539        bb
 4540        aaaˇ»aa
 4541    "});
 4542    cx.update_editor(|e, window, cx| {
 4543        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4544    });
 4545    cx.assert_editor_state(indoc! {"
 4546        «aaaaa
 4547        bb
 4548        ccc
 4549        ddddˇ»
 4550    "});
 4551
 4552    // Manipulate with multiple disjoin selections
 4553    cx.set_state(indoc! {"
 4554 4555        4
 4556        3
 4557        2
 4558        1ˇ»
 4559
 4560        dd«dd
 4561        ccc
 4562        bb
 4563        aaaˇ»aa
 4564    "});
 4565    cx.update_editor(|e, window, cx| {
 4566        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4567    });
 4568    cx.assert_editor_state(indoc! {"
 4569        «1
 4570        2
 4571        3
 4572        4
 4573        5ˇ»
 4574
 4575        «aaaaa
 4576        bb
 4577        ccc
 4578        ddddˇ»
 4579    "});
 4580
 4581    // Adding lines on each selection
 4582    cx.set_state(indoc! {"
 4583 4584        1ˇ»
 4585
 4586        bb«bb
 4587        aaaˇ»aa
 4588    "});
 4589    cx.update_editor(|e, window, cx| {
 4590        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4591    });
 4592    cx.assert_editor_state(indoc! {"
 4593        «2
 4594        1
 4595        added lineˇ»
 4596
 4597        «bbbb
 4598        aaaaa
 4599        added lineˇ»
 4600    "});
 4601
 4602    // Removing lines on each selection
 4603    cx.set_state(indoc! {"
 4604 4605        1ˇ»
 4606
 4607        bb«bb
 4608        aaaˇ»aa
 4609    "});
 4610    cx.update_editor(|e, window, cx| {
 4611        e.manipulate_immutable_lines(window, cx, |lines| {
 4612            lines.pop();
 4613        })
 4614    });
 4615    cx.assert_editor_state(indoc! {"
 4616        «2ˇ»
 4617
 4618        «bbbbˇ»
 4619    "});
 4620}
 4621
 4622#[gpui::test]
 4623async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4624    init_test(cx, |settings| {
 4625        settings.defaults.tab_size = NonZeroU32::new(3)
 4626    });
 4627
 4628    let mut cx = EditorTestContext::new(cx).await;
 4629
 4630    // MULTI SELECTION
 4631    // Ln.1 "«" tests empty lines
 4632    // Ln.9 tests just leading whitespace
 4633    cx.set_state(indoc! {"
 4634        «
 4635        abc                 // No indentationˇ»
 4636        «\tabc              // 1 tabˇ»
 4637        \t\tabc «      ˇ»   // 2 tabs
 4638        \t ab«c             // Tab followed by space
 4639         \tabc              // Space followed by tab (3 spaces should be the result)
 4640        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4641           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4642        \t
 4643        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4644    "});
 4645    cx.update_editor(|e, window, cx| {
 4646        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4647    });
 4648    cx.assert_editor_state(
 4649        indoc! {"
 4650            «
 4651            abc                 // No indentation
 4652               abc              // 1 tab
 4653                  abc          // 2 tabs
 4654                abc             // Tab followed by space
 4655               abc              // Space followed by tab (3 spaces should be the result)
 4656                           abc   // Mixed indentation (tab conversion depends on the column)
 4657               abc         // Already space indented
 4658               ·
 4659               abc\tdef          // Only the leading tab is manipulatedˇ»
 4660        "}
 4661        .replace("·", "")
 4662        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4663    );
 4664
 4665    // Test on just a few lines, the others should remain unchanged
 4666    // Only lines (3, 5, 10, 11) should change
 4667    cx.set_state(
 4668        indoc! {"
 4669            ·
 4670            abc                 // No indentation
 4671            \tabcˇ               // 1 tab
 4672            \t\tabc             // 2 tabs
 4673            \t abcˇ              // Tab followed by space
 4674             \tabc              // Space followed by tab (3 spaces should be the result)
 4675            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4676               abc              // Already space indented
 4677            «\t
 4678            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4679        "}
 4680        .replace("·", "")
 4681        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4682    );
 4683    cx.update_editor(|e, window, cx| {
 4684        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4685    });
 4686    cx.assert_editor_state(
 4687        indoc! {"
 4688            ·
 4689            abc                 // No indentation
 4690            «   abc               // 1 tabˇ»
 4691            \t\tabc             // 2 tabs
 4692            «    abc              // Tab followed by spaceˇ»
 4693             \tabc              // Space followed by tab (3 spaces should be the result)
 4694            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4695               abc              // Already space indented
 4696            «   ·
 4697               abc\tdef          // Only the leading tab is manipulatedˇ»
 4698        "}
 4699        .replace("·", "")
 4700        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4701    );
 4702
 4703    // SINGLE SELECTION
 4704    // Ln.1 "«" tests empty lines
 4705    // Ln.9 tests just leading whitespace
 4706    cx.set_state(indoc! {"
 4707        «
 4708        abc                 // No indentation
 4709        \tabc               // 1 tab
 4710        \t\tabc             // 2 tabs
 4711        \t abc              // Tab followed by space
 4712         \tabc              // Space followed by tab (3 spaces should be the result)
 4713        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4714           abc              // Already space indented
 4715        \t
 4716        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4717    "});
 4718    cx.update_editor(|e, window, cx| {
 4719        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4720    });
 4721    cx.assert_editor_state(
 4722        indoc! {"
 4723            «
 4724            abc                 // No indentation
 4725               abc               // 1 tab
 4726                  abc             // 2 tabs
 4727                abc              // Tab followed by space
 4728               abc              // Space followed by tab (3 spaces should be the result)
 4729                           abc   // Mixed indentation (tab conversion depends on the column)
 4730               abc              // Already space indented
 4731               ·
 4732               abc\tdef          // Only the leading tab is manipulatedˇ»
 4733        "}
 4734        .replace("·", "")
 4735        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4736    );
 4737}
 4738
 4739#[gpui::test]
 4740async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 4741    init_test(cx, |settings| {
 4742        settings.defaults.tab_size = NonZeroU32::new(3)
 4743    });
 4744
 4745    let mut cx = EditorTestContext::new(cx).await;
 4746
 4747    // MULTI SELECTION
 4748    // Ln.1 "«" tests empty lines
 4749    // Ln.11 tests just leading whitespace
 4750    cx.set_state(indoc! {"
 4751        «
 4752        abˇ»ˇc                 // No indentation
 4753         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 4754          abc  «             // 2 spaces (< 3 so dont convert)
 4755           abc              // 3 spaces (convert)
 4756             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 4757        «\tˇ»\t«\tˇ»abc           // Already tab indented
 4758        «\t abc              // Tab followed by space
 4759         \tabc              // Space followed by tab (should be consumed due to tab)
 4760        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4761           \tˇ»  «\t
 4762           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 4763    "});
 4764    cx.update_editor(|e, window, cx| {
 4765        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4766    });
 4767    cx.assert_editor_state(indoc! {"
 4768        «
 4769        abc                 // No indentation
 4770         abc                // 1 space (< 3 so dont convert)
 4771          abc               // 2 spaces (< 3 so dont convert)
 4772        \tabc              // 3 spaces (convert)
 4773        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4774        \t\t\tabc           // Already tab indented
 4775        \t abc              // Tab followed by space
 4776        \tabc              // Space followed by tab (should be consumed due to tab)
 4777        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4778        \t\t\t
 4779        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4780    "});
 4781
 4782    // Test on just a few lines, the other should remain unchanged
 4783    // Only lines (4, 8, 11, 12) should change
 4784    cx.set_state(
 4785        indoc! {"
 4786            ·
 4787            abc                 // No indentation
 4788             abc                // 1 space (< 3 so dont convert)
 4789              abc               // 2 spaces (< 3 so dont convert)
 4790            «   abc              // 3 spaces (convert)ˇ»
 4791                 abc            // 5 spaces (1 tab + 2 spaces)
 4792            \t\t\tabc           // Already tab indented
 4793            \t abc              // Tab followed by space
 4794             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 4795               \t\t  \tabc      // Mixed indentation
 4796            \t \t  \t   \tabc   // Mixed indentation
 4797               \t  \tˇ
 4798            «   abc   \t         // Only the leading spaces should be convertedˇ»
 4799        "}
 4800        .replace("·", "")
 4801        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4802    );
 4803    cx.update_editor(|e, window, cx| {
 4804        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4805    });
 4806    cx.assert_editor_state(
 4807        indoc! {"
 4808            ·
 4809            abc                 // No indentation
 4810             abc                // 1 space (< 3 so dont convert)
 4811              abc               // 2 spaces (< 3 so dont convert)
 4812            «\tabc              // 3 spaces (convert)ˇ»
 4813                 abc            // 5 spaces (1 tab + 2 spaces)
 4814            \t\t\tabc           // Already tab indented
 4815            \t abc              // Tab followed by space
 4816            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 4817               \t\t  \tabc      // Mixed indentation
 4818            \t \t  \t   \tabc   // Mixed indentation
 4819            «\t\t\t
 4820            \tabc   \t         // Only the leading spaces should be convertedˇ»
 4821        "}
 4822        .replace("·", "")
 4823        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4824    );
 4825
 4826    // SINGLE SELECTION
 4827    // Ln.1 "«" tests empty lines
 4828    // Ln.11 tests just leading whitespace
 4829    cx.set_state(indoc! {"
 4830        «
 4831        abc                 // No indentation
 4832         abc                // 1 space (< 3 so dont convert)
 4833          abc               // 2 spaces (< 3 so dont convert)
 4834           abc              // 3 spaces (convert)
 4835             abc            // 5 spaces (1 tab + 2 spaces)
 4836        \t\t\tabc           // Already tab indented
 4837        \t abc              // Tab followed by space
 4838         \tabc              // Space followed by tab (should be consumed due to tab)
 4839        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4840           \t  \t
 4841           abc   \t         // Only the leading spaces should be convertedˇ»
 4842    "});
 4843    cx.update_editor(|e, window, cx| {
 4844        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 4845    });
 4846    cx.assert_editor_state(indoc! {"
 4847        «
 4848        abc                 // No indentation
 4849         abc                // 1 space (< 3 so dont convert)
 4850          abc               // 2 spaces (< 3 so dont convert)
 4851        \tabc              // 3 spaces (convert)
 4852        \t  abc            // 5 spaces (1 tab + 2 spaces)
 4853        \t\t\tabc           // Already tab indented
 4854        \t abc              // Tab followed by space
 4855        \tabc              // Space followed by tab (should be consumed due to tab)
 4856        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 4857        \t\t\t
 4858        \tabc   \t         // Only the leading spaces should be convertedˇ»
 4859    "});
 4860}
 4861
 4862#[gpui::test]
 4863async fn test_toggle_case(cx: &mut TestAppContext) {
 4864    init_test(cx, |_| {});
 4865
 4866    let mut cx = EditorTestContext::new(cx).await;
 4867
 4868    // If all lower case -> upper case
 4869    cx.set_state(indoc! {"
 4870        «hello worldˇ»
 4871    "});
 4872    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4873    cx.assert_editor_state(indoc! {"
 4874        «HELLO WORLDˇ»
 4875    "});
 4876
 4877    // If all upper case -> lower case
 4878    cx.set_state(indoc! {"
 4879        «HELLO WORLDˇ»
 4880    "});
 4881    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4882    cx.assert_editor_state(indoc! {"
 4883        «hello worldˇ»
 4884    "});
 4885
 4886    // If any upper case characters are identified -> lower case
 4887    // This matches JetBrains IDEs
 4888    cx.set_state(indoc! {"
 4889        «hEllo worldˇ»
 4890    "});
 4891    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 4892    cx.assert_editor_state(indoc! {"
 4893        «hello worldˇ»
 4894    "});
 4895}
 4896
 4897#[gpui::test]
 4898async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 4899    init_test(cx, |_| {});
 4900
 4901    let mut cx = EditorTestContext::new(cx).await;
 4902
 4903    cx.set_state(indoc! {"
 4904        «implement-windows-supportˇ»
 4905    "});
 4906    cx.update_editor(|e, window, cx| {
 4907        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 4908    });
 4909    cx.assert_editor_state(indoc! {"
 4910        «Implement windows supportˇ»
 4911    "});
 4912}
 4913
 4914#[gpui::test]
 4915async fn test_manipulate_text(cx: &mut TestAppContext) {
 4916    init_test(cx, |_| {});
 4917
 4918    let mut cx = EditorTestContext::new(cx).await;
 4919
 4920    // Test convert_to_upper_case()
 4921    cx.set_state(indoc! {"
 4922        «hello worldˇ»
 4923    "});
 4924    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4925    cx.assert_editor_state(indoc! {"
 4926        «HELLO WORLDˇ»
 4927    "});
 4928
 4929    // Test convert_to_lower_case()
 4930    cx.set_state(indoc! {"
 4931        «HELLO WORLDˇ»
 4932    "});
 4933    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 4934    cx.assert_editor_state(indoc! {"
 4935        «hello worldˇ»
 4936    "});
 4937
 4938    // Test multiple line, single selection case
 4939    cx.set_state(indoc! {"
 4940        «The quick brown
 4941        fox jumps over
 4942        the lazy dogˇ»
 4943    "});
 4944    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 4945    cx.assert_editor_state(indoc! {"
 4946        «The Quick Brown
 4947        Fox Jumps Over
 4948        The Lazy Dogˇ»
 4949    "});
 4950
 4951    // Test multiple line, single selection case
 4952    cx.set_state(indoc! {"
 4953        «The quick brown
 4954        fox jumps over
 4955        the lazy dogˇ»
 4956    "});
 4957    cx.update_editor(|e, window, cx| {
 4958        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 4959    });
 4960    cx.assert_editor_state(indoc! {"
 4961        «TheQuickBrown
 4962        FoxJumpsOver
 4963        TheLazyDogˇ»
 4964    "});
 4965
 4966    // From here on out, test more complex cases of manipulate_text()
 4967
 4968    // Test no selection case - should affect words cursors are in
 4969    // Cursor at beginning, middle, and end of word
 4970    cx.set_state(indoc! {"
 4971        ˇhello big beauˇtiful worldˇ
 4972    "});
 4973    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4974    cx.assert_editor_state(indoc! {"
 4975        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 4976    "});
 4977
 4978    // Test multiple selections on a single line and across multiple lines
 4979    cx.set_state(indoc! {"
 4980        «Theˇ» quick «brown
 4981        foxˇ» jumps «overˇ»
 4982        the «lazyˇ» dog
 4983    "});
 4984    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4985    cx.assert_editor_state(indoc! {"
 4986        «THEˇ» quick «BROWN
 4987        FOXˇ» jumps «OVERˇ»
 4988        the «LAZYˇ» dog
 4989    "});
 4990
 4991    // Test case where text length grows
 4992    cx.set_state(indoc! {"
 4993        «tschüߡ»
 4994    "});
 4995    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 4996    cx.assert_editor_state(indoc! {"
 4997        «TSCHÜSSˇ»
 4998    "});
 4999
 5000    // Test to make sure we don't crash when text shrinks
 5001    cx.set_state(indoc! {"
 5002        aaa_bbbˇ
 5003    "});
 5004    cx.update_editor(|e, window, cx| {
 5005        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5006    });
 5007    cx.assert_editor_state(indoc! {"
 5008        «aaaBbbˇ»
 5009    "});
 5010
 5011    // Test to make sure we all aware of the fact that each word can grow and shrink
 5012    // Final selections should be aware of this fact
 5013    cx.set_state(indoc! {"
 5014        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5015    "});
 5016    cx.update_editor(|e, window, cx| {
 5017        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5018    });
 5019    cx.assert_editor_state(indoc! {"
 5020        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5021    "});
 5022
 5023    cx.set_state(indoc! {"
 5024        «hElLo, WoRld!ˇ»
 5025    "});
 5026    cx.update_editor(|e, window, cx| {
 5027        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5028    });
 5029    cx.assert_editor_state(indoc! {"
 5030        «HeLlO, wOrLD!ˇ»
 5031    "});
 5032}
 5033
 5034#[gpui::test]
 5035fn test_duplicate_line(cx: &mut TestAppContext) {
 5036    init_test(cx, |_| {});
 5037
 5038    let editor = cx.add_window(|window, cx| {
 5039        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5040        build_editor(buffer, window, cx)
 5041    });
 5042    _ = editor.update(cx, |editor, window, cx| {
 5043        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5044            s.select_display_ranges([
 5045                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5046                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5047                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5048                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5049            ])
 5050        });
 5051        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5052        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5053        assert_eq!(
 5054            editor.selections.display_ranges(cx),
 5055            vec![
 5056                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5057                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5058                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5059                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5060            ]
 5061        );
 5062    });
 5063
 5064    let editor = cx.add_window(|window, cx| {
 5065        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5066        build_editor(buffer, window, cx)
 5067    });
 5068    _ = editor.update(cx, |editor, window, cx| {
 5069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5070            s.select_display_ranges([
 5071                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5072                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5073            ])
 5074        });
 5075        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5076        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5077        assert_eq!(
 5078            editor.selections.display_ranges(cx),
 5079            vec![
 5080                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5081                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5082            ]
 5083        );
 5084    });
 5085
 5086    // With `move_upwards` the selections stay in place, except for
 5087    // the lines inserted above them
 5088    let editor = cx.add_window(|window, cx| {
 5089        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5090        build_editor(buffer, window, cx)
 5091    });
 5092    _ = editor.update(cx, |editor, window, cx| {
 5093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5094            s.select_display_ranges([
 5095                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5096                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5097                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5098                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5099            ])
 5100        });
 5101        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5102        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5103        assert_eq!(
 5104            editor.selections.display_ranges(cx),
 5105            vec![
 5106                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5107                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5108                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5109                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5110            ]
 5111        );
 5112    });
 5113
 5114    let editor = cx.add_window(|window, cx| {
 5115        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5116        build_editor(buffer, window, cx)
 5117    });
 5118    _ = editor.update(cx, |editor, window, cx| {
 5119        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5120            s.select_display_ranges([
 5121                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5122                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5123            ])
 5124        });
 5125        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5126        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5127        assert_eq!(
 5128            editor.selections.display_ranges(cx),
 5129            vec![
 5130                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5131                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5132            ]
 5133        );
 5134    });
 5135
 5136    let editor = cx.add_window(|window, cx| {
 5137        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5138        build_editor(buffer, window, cx)
 5139    });
 5140    _ = editor.update(cx, |editor, window, cx| {
 5141        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5142            s.select_display_ranges([
 5143                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5144                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5145            ])
 5146        });
 5147        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5148        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5149        assert_eq!(
 5150            editor.selections.display_ranges(cx),
 5151            vec![
 5152                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5153                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5154            ]
 5155        );
 5156    });
 5157}
 5158
 5159#[gpui::test]
 5160fn test_move_line_up_down(cx: &mut TestAppContext) {
 5161    init_test(cx, |_| {});
 5162
 5163    let editor = cx.add_window(|window, cx| {
 5164        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5165        build_editor(buffer, window, cx)
 5166    });
 5167    _ = editor.update(cx, |editor, window, cx| {
 5168        editor.fold_creases(
 5169            vec![
 5170                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5171                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5172                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5173            ],
 5174            true,
 5175            window,
 5176            cx,
 5177        );
 5178        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5179            s.select_display_ranges([
 5180                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5181                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5182                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5183                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5184            ])
 5185        });
 5186        assert_eq!(
 5187            editor.display_text(cx),
 5188            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5189        );
 5190
 5191        editor.move_line_up(&MoveLineUp, window, cx);
 5192        assert_eq!(
 5193            editor.display_text(cx),
 5194            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5195        );
 5196        assert_eq!(
 5197            editor.selections.display_ranges(cx),
 5198            vec![
 5199                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5200                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5201                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5202                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5203            ]
 5204        );
 5205    });
 5206
 5207    _ = editor.update(cx, |editor, window, cx| {
 5208        editor.move_line_down(&MoveLineDown, window, cx);
 5209        assert_eq!(
 5210            editor.display_text(cx),
 5211            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5212        );
 5213        assert_eq!(
 5214            editor.selections.display_ranges(cx),
 5215            vec![
 5216                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5217                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5218                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5219                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5220            ]
 5221        );
 5222    });
 5223
 5224    _ = editor.update(cx, |editor, window, cx| {
 5225        editor.move_line_down(&MoveLineDown, window, cx);
 5226        assert_eq!(
 5227            editor.display_text(cx),
 5228            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5229        );
 5230        assert_eq!(
 5231            editor.selections.display_ranges(cx),
 5232            vec![
 5233                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5234                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5235                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5236                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5237            ]
 5238        );
 5239    });
 5240
 5241    _ = editor.update(cx, |editor, window, cx| {
 5242        editor.move_line_up(&MoveLineUp, window, cx);
 5243        assert_eq!(
 5244            editor.display_text(cx),
 5245            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5246        );
 5247        assert_eq!(
 5248            editor.selections.display_ranges(cx),
 5249            vec![
 5250                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5251                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5252                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5253                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5254            ]
 5255        );
 5256    });
 5257}
 5258
 5259#[gpui::test]
 5260fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5261    init_test(cx, |_| {});
 5262    let editor = cx.add_window(|window, cx| {
 5263        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5264        build_editor(buffer, window, cx)
 5265    });
 5266    _ = editor.update(cx, |editor, window, cx| {
 5267        editor.fold_creases(
 5268            vec![Crease::simple(
 5269                Point::new(6, 4)..Point::new(7, 4),
 5270                FoldPlaceholder::test(),
 5271            )],
 5272            true,
 5273            window,
 5274            cx,
 5275        );
 5276        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5277            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5278        });
 5279        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5280        editor.move_line_up(&MoveLineUp, window, cx);
 5281        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5282        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5283    });
 5284}
 5285
 5286#[gpui::test]
 5287fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5288    init_test(cx, |_| {});
 5289
 5290    let editor = cx.add_window(|window, cx| {
 5291        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5292        build_editor(buffer, window, cx)
 5293    });
 5294    _ = editor.update(cx, |editor, window, cx| {
 5295        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5296        editor.insert_blocks(
 5297            [BlockProperties {
 5298                style: BlockStyle::Fixed,
 5299                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5300                height: Some(1),
 5301                render: Arc::new(|_| div().into_any()),
 5302                priority: 0,
 5303            }],
 5304            Some(Autoscroll::fit()),
 5305            cx,
 5306        );
 5307        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5308            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5309        });
 5310        editor.move_line_down(&MoveLineDown, window, cx);
 5311    });
 5312}
 5313
 5314#[gpui::test]
 5315async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5316    init_test(cx, |_| {});
 5317
 5318    let mut cx = EditorTestContext::new(cx).await;
 5319    cx.set_state(
 5320        &"
 5321            ˇzero
 5322            one
 5323            two
 5324            three
 5325            four
 5326            five
 5327        "
 5328        .unindent(),
 5329    );
 5330
 5331    // Create a four-line block that replaces three lines of text.
 5332    cx.update_editor(|editor, window, cx| {
 5333        let snapshot = editor.snapshot(window, cx);
 5334        let snapshot = &snapshot.buffer_snapshot;
 5335        let placement = BlockPlacement::Replace(
 5336            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5337        );
 5338        editor.insert_blocks(
 5339            [BlockProperties {
 5340                placement,
 5341                height: Some(4),
 5342                style: BlockStyle::Sticky,
 5343                render: Arc::new(|_| gpui::div().into_any_element()),
 5344                priority: 0,
 5345            }],
 5346            None,
 5347            cx,
 5348        );
 5349    });
 5350
 5351    // Move down so that the cursor touches the block.
 5352    cx.update_editor(|editor, window, cx| {
 5353        editor.move_down(&Default::default(), window, cx);
 5354    });
 5355    cx.assert_editor_state(
 5356        &"
 5357            zero
 5358            «one
 5359            two
 5360            threeˇ»
 5361            four
 5362            five
 5363        "
 5364        .unindent(),
 5365    );
 5366
 5367    // Move down past the block.
 5368    cx.update_editor(|editor, window, cx| {
 5369        editor.move_down(&Default::default(), window, cx);
 5370    });
 5371    cx.assert_editor_state(
 5372        &"
 5373            zero
 5374            one
 5375            two
 5376            three
 5377            ˇfour
 5378            five
 5379        "
 5380        .unindent(),
 5381    );
 5382}
 5383
 5384#[gpui::test]
 5385fn test_transpose(cx: &mut TestAppContext) {
 5386    init_test(cx, |_| {});
 5387
 5388    _ = cx.add_window(|window, cx| {
 5389        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5390        editor.set_style(EditorStyle::default(), window, cx);
 5391        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5392            s.select_ranges([1..1])
 5393        });
 5394        editor.transpose(&Default::default(), window, cx);
 5395        assert_eq!(editor.text(cx), "bac");
 5396        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5397
 5398        editor.transpose(&Default::default(), window, cx);
 5399        assert_eq!(editor.text(cx), "bca");
 5400        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5401
 5402        editor.transpose(&Default::default(), window, cx);
 5403        assert_eq!(editor.text(cx), "bac");
 5404        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5405
 5406        editor
 5407    });
 5408
 5409    _ = cx.add_window(|window, cx| {
 5410        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5411        editor.set_style(EditorStyle::default(), window, cx);
 5412        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5413            s.select_ranges([3..3])
 5414        });
 5415        editor.transpose(&Default::default(), window, cx);
 5416        assert_eq!(editor.text(cx), "acb\nde");
 5417        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5418
 5419        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5420            s.select_ranges([4..4])
 5421        });
 5422        editor.transpose(&Default::default(), window, cx);
 5423        assert_eq!(editor.text(cx), "acbd\ne");
 5424        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5425
 5426        editor.transpose(&Default::default(), window, cx);
 5427        assert_eq!(editor.text(cx), "acbde\n");
 5428        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5429
 5430        editor.transpose(&Default::default(), window, cx);
 5431        assert_eq!(editor.text(cx), "acbd\ne");
 5432        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5433
 5434        editor
 5435    });
 5436
 5437    _ = cx.add_window(|window, cx| {
 5438        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5439        editor.set_style(EditorStyle::default(), window, cx);
 5440        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5441            s.select_ranges([1..1, 2..2, 4..4])
 5442        });
 5443        editor.transpose(&Default::default(), window, cx);
 5444        assert_eq!(editor.text(cx), "bacd\ne");
 5445        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5446
 5447        editor.transpose(&Default::default(), window, cx);
 5448        assert_eq!(editor.text(cx), "bcade\n");
 5449        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5450
 5451        editor.transpose(&Default::default(), window, cx);
 5452        assert_eq!(editor.text(cx), "bcda\ne");
 5453        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5454
 5455        editor.transpose(&Default::default(), window, cx);
 5456        assert_eq!(editor.text(cx), "bcade\n");
 5457        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5458
 5459        editor.transpose(&Default::default(), window, cx);
 5460        assert_eq!(editor.text(cx), "bcaed\n");
 5461        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5462
 5463        editor
 5464    });
 5465
 5466    _ = cx.add_window(|window, cx| {
 5467        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5468        editor.set_style(EditorStyle::default(), window, cx);
 5469        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5470            s.select_ranges([4..4])
 5471        });
 5472        editor.transpose(&Default::default(), window, cx);
 5473        assert_eq!(editor.text(cx), "🏀🍐✋");
 5474        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5475
 5476        editor.transpose(&Default::default(), window, cx);
 5477        assert_eq!(editor.text(cx), "🏀✋🍐");
 5478        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5479
 5480        editor.transpose(&Default::default(), window, cx);
 5481        assert_eq!(editor.text(cx), "🏀🍐✋");
 5482        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5483
 5484        editor
 5485    });
 5486}
 5487
 5488#[gpui::test]
 5489async fn test_rewrap(cx: &mut TestAppContext) {
 5490    init_test(cx, |settings| {
 5491        settings.languages.0.extend([
 5492            (
 5493                "Markdown".into(),
 5494                LanguageSettingsContent {
 5495                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5496                    preferred_line_length: Some(40),
 5497                    ..Default::default()
 5498                },
 5499            ),
 5500            (
 5501                "Plain Text".into(),
 5502                LanguageSettingsContent {
 5503                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5504                    preferred_line_length: Some(40),
 5505                    ..Default::default()
 5506                },
 5507            ),
 5508            (
 5509                "C++".into(),
 5510                LanguageSettingsContent {
 5511                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5512                    preferred_line_length: Some(40),
 5513                    ..Default::default()
 5514                },
 5515            ),
 5516            (
 5517                "Python".into(),
 5518                LanguageSettingsContent {
 5519                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5520                    preferred_line_length: Some(40),
 5521                    ..Default::default()
 5522                },
 5523            ),
 5524            (
 5525                "Rust".into(),
 5526                LanguageSettingsContent {
 5527                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5528                    preferred_line_length: Some(40),
 5529                    ..Default::default()
 5530                },
 5531            ),
 5532        ])
 5533    });
 5534
 5535    let mut cx = EditorTestContext::new(cx).await;
 5536
 5537    let cpp_language = Arc::new(Language::new(
 5538        LanguageConfig {
 5539            name: "C++".into(),
 5540            line_comments: vec!["// ".into()],
 5541            ..LanguageConfig::default()
 5542        },
 5543        None,
 5544    ));
 5545    let python_language = Arc::new(Language::new(
 5546        LanguageConfig {
 5547            name: "Python".into(),
 5548            line_comments: vec!["# ".into()],
 5549            ..LanguageConfig::default()
 5550        },
 5551        None,
 5552    ));
 5553    let markdown_language = Arc::new(Language::new(
 5554        LanguageConfig {
 5555            name: "Markdown".into(),
 5556            rewrap_prefixes: vec![
 5557                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5558                regex::Regex::new("[-*+]\\s+").unwrap(),
 5559            ],
 5560            ..LanguageConfig::default()
 5561        },
 5562        None,
 5563    ));
 5564    let rust_language = Arc::new(
 5565        Language::new(
 5566            LanguageConfig {
 5567                name: "Rust".into(),
 5568                line_comments: vec!["// ".into(), "/// ".into()],
 5569                ..LanguageConfig::default()
 5570            },
 5571            Some(tree_sitter_rust::LANGUAGE.into()),
 5572        )
 5573        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 5574        .unwrap(),
 5575    );
 5576
 5577    let plaintext_language = Arc::new(Language::new(
 5578        LanguageConfig {
 5579            name: "Plain Text".into(),
 5580            ..LanguageConfig::default()
 5581        },
 5582        None,
 5583    ));
 5584
 5585    // Test basic rewrapping of a long line with a cursor
 5586    assert_rewrap(
 5587        indoc! {"
 5588            // ˇThis is a long comment that needs to be wrapped.
 5589        "},
 5590        indoc! {"
 5591            // ˇThis is a long comment that needs to
 5592            // be wrapped.
 5593        "},
 5594        cpp_language.clone(),
 5595        &mut cx,
 5596    );
 5597
 5598    // Test rewrapping a full selection
 5599    assert_rewrap(
 5600        indoc! {"
 5601            «// This selected long comment needs to be wrapped.ˇ»"
 5602        },
 5603        indoc! {"
 5604            «// This selected long comment needs to
 5605            // be wrapped.ˇ»"
 5606        },
 5607        cpp_language.clone(),
 5608        &mut cx,
 5609    );
 5610
 5611    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5612    assert_rewrap(
 5613        indoc! {"
 5614            // ˇThis is the first line.
 5615            // Thisˇ is the second line.
 5616            // This is the thirdˇ line, all part of one paragraph.
 5617         "},
 5618        indoc! {"
 5619            // ˇThis is the first line. Thisˇ is the
 5620            // second line. This is the thirdˇ line,
 5621            // all part of one paragraph.
 5622         "},
 5623        cpp_language.clone(),
 5624        &mut cx,
 5625    );
 5626
 5627    // Test multiple cursors in different paragraphs trigger separate rewraps
 5628    assert_rewrap(
 5629        indoc! {"
 5630            // ˇThis is the first paragraph, first line.
 5631            // ˇThis is the first paragraph, second line.
 5632
 5633            // ˇThis is the second paragraph, first line.
 5634            // ˇThis is the second paragraph, second line.
 5635        "},
 5636        indoc! {"
 5637            // ˇThis is the first paragraph, first
 5638            // line. ˇThis is the first paragraph,
 5639            // second line.
 5640
 5641            // ˇThis is the second paragraph, first
 5642            // line. ˇThis is the second paragraph,
 5643            // second line.
 5644        "},
 5645        cpp_language.clone(),
 5646        &mut cx,
 5647    );
 5648
 5649    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5650    assert_rewrap(
 5651        indoc! {"
 5652            «// A regular long long comment to be wrapped.
 5653            /// A documentation long comment to be wrapped.ˇ»
 5654          "},
 5655        indoc! {"
 5656            «// A regular long long comment to be
 5657            // wrapped.
 5658            /// A documentation long comment to be
 5659            /// wrapped.ˇ»
 5660          "},
 5661        rust_language.clone(),
 5662        &mut cx,
 5663    );
 5664
 5665    // Test that change in indentation level trigger seperate rewraps
 5666    assert_rewrap(
 5667        indoc! {"
 5668            fn foo() {
 5669                «// This is a long comment at the base indent.
 5670                    // This is a long comment at the next indent.ˇ»
 5671            }
 5672        "},
 5673        indoc! {"
 5674            fn foo() {
 5675                «// This is a long comment at the
 5676                // base indent.
 5677                    // This is a long comment at the
 5678                    // next indent.ˇ»
 5679            }
 5680        "},
 5681        rust_language.clone(),
 5682        &mut cx,
 5683    );
 5684
 5685    // Test that different comment prefix characters (e.g., '#') are handled correctly
 5686    assert_rewrap(
 5687        indoc! {"
 5688            # ˇThis is a long comment using a pound sign.
 5689        "},
 5690        indoc! {"
 5691            # ˇThis is a long comment using a pound
 5692            # sign.
 5693        "},
 5694        python_language,
 5695        &mut cx,
 5696    );
 5697
 5698    // Test rewrapping only affects comments, not code even when selected
 5699    assert_rewrap(
 5700        indoc! {"
 5701            «/// This doc comment is long and should be wrapped.
 5702            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5703        "},
 5704        indoc! {"
 5705            «/// This doc comment is long and should
 5706            /// be wrapped.
 5707            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 5708        "},
 5709        rust_language.clone(),
 5710        &mut cx,
 5711    );
 5712
 5713    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 5714    assert_rewrap(
 5715        indoc! {"
 5716            # Header
 5717
 5718            A long long long line of markdown text to wrap.ˇ
 5719         "},
 5720        indoc! {"
 5721            # Header
 5722
 5723            A long long long line of markdown text
 5724            to wrap.ˇ
 5725         "},
 5726        markdown_language.clone(),
 5727        &mut cx,
 5728    );
 5729
 5730    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 5731    assert_rewrap(
 5732        indoc! {"
 5733            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 5734            2. This is a numbered list item that is very long and needs to be wrapped properly.
 5735            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 5736        "},
 5737        indoc! {"
 5738            «1. This is a numbered list item that is
 5739               very long and needs to be wrapped
 5740               properly.
 5741            2. This is a numbered list item that is
 5742               very long and needs to be wrapped
 5743               properly.
 5744            - This is an unordered list item that is
 5745              also very long and should not merge
 5746              with the numbered item.ˇ»
 5747        "},
 5748        markdown_language.clone(),
 5749        &mut cx,
 5750    );
 5751
 5752    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 5753    assert_rewrap(
 5754        indoc! {"
 5755            «1. This is a numbered list item that is
 5756            very long and needs to be wrapped
 5757            properly.
 5758            2. This is a numbered list item that is
 5759            very long and needs to be wrapped
 5760            properly.
 5761            - This is an unordered list item that is
 5762            also very long and should not merge with
 5763            the numbered item.ˇ»
 5764        "},
 5765        indoc! {"
 5766            «1. This is a numbered list item that is
 5767               very long and needs to be wrapped
 5768               properly.
 5769            2. This is a numbered list item that is
 5770               very long and needs to be wrapped
 5771               properly.
 5772            - This is an unordered list item that is
 5773              also very long and should not merge
 5774              with the numbered item.ˇ»
 5775        "},
 5776        markdown_language.clone(),
 5777        &mut cx,
 5778    );
 5779
 5780    // Test that rewrapping maintain indents even when they already exists.
 5781    assert_rewrap(
 5782        indoc! {"
 5783            «1. This is a numbered list
 5784               item that is very long and needs to be wrapped properly.
 5785            2. This is a numbered list
 5786               item that is very long and needs to be wrapped properly.
 5787            - This is an unordered list item that is also very long and
 5788              should not merge with the numbered item.ˇ»
 5789        "},
 5790        indoc! {"
 5791            «1. This is a numbered list item that is
 5792               very long and needs to be wrapped
 5793               properly.
 5794            2. This is a numbered list item that is
 5795               very long and needs to be wrapped
 5796               properly.
 5797            - This is an unordered list item that is
 5798              also very long and should not merge
 5799              with the numbered item.ˇ»
 5800        "},
 5801        markdown_language,
 5802        &mut cx,
 5803    );
 5804
 5805    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 5806    assert_rewrap(
 5807        indoc! {"
 5808            ˇThis is a very long line of plain text that will be wrapped.
 5809        "},
 5810        indoc! {"
 5811            ˇThis is a very long line of plain text
 5812            that will be wrapped.
 5813        "},
 5814        plaintext_language.clone(),
 5815        &mut cx,
 5816    );
 5817
 5818    // Test that non-commented code acts as a paragraph boundary within a selection
 5819    assert_rewrap(
 5820        indoc! {"
 5821               «// This is the first long comment block to be wrapped.
 5822               fn my_func(a: u32);
 5823               // This is the second long comment block to be wrapped.ˇ»
 5824           "},
 5825        indoc! {"
 5826               «// This is the first long comment block
 5827               // to be wrapped.
 5828               fn my_func(a: u32);
 5829               // This is the second long comment block
 5830               // to be wrapped.ˇ»
 5831           "},
 5832        rust_language,
 5833        &mut cx,
 5834    );
 5835
 5836    // Test rewrapping multiple selections, including ones with blank lines or tabs
 5837    assert_rewrap(
 5838        indoc! {"
 5839            «ˇThis is a very long line that will be wrapped.
 5840
 5841            This is another paragraph in the same selection.»
 5842
 5843            «\tThis is a very long indented line that will be wrapped.ˇ»
 5844         "},
 5845        indoc! {"
 5846            «ˇThis is a very long line that will be
 5847            wrapped.
 5848
 5849            This is another paragraph in the same
 5850            selection.»
 5851
 5852            «\tThis is a very long indented line
 5853            \tthat will be wrapped.ˇ»
 5854         "},
 5855        plaintext_language,
 5856        &mut cx,
 5857    );
 5858
 5859    // Test that an empty comment line acts as a paragraph boundary
 5860    assert_rewrap(
 5861        indoc! {"
 5862            // ˇThis is a long comment that will be wrapped.
 5863            //
 5864            // And this is another long comment that will also be wrapped.ˇ
 5865         "},
 5866        indoc! {"
 5867            // ˇThis is a long comment that will be
 5868            // wrapped.
 5869            //
 5870            // And this is another long comment that
 5871            // will also be wrapped.ˇ
 5872         "},
 5873        cpp_language,
 5874        &mut cx,
 5875    );
 5876
 5877    #[track_caller]
 5878    fn assert_rewrap(
 5879        unwrapped_text: &str,
 5880        wrapped_text: &str,
 5881        language: Arc<Language>,
 5882        cx: &mut EditorTestContext,
 5883    ) {
 5884        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 5885        cx.set_state(unwrapped_text);
 5886        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 5887        cx.assert_editor_state(wrapped_text);
 5888    }
 5889}
 5890
 5891#[gpui::test]
 5892async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 5893    init_test(cx, |settings| {
 5894        settings.languages.0.extend([(
 5895            "Rust".into(),
 5896            LanguageSettingsContent {
 5897                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5898                preferred_line_length: Some(40),
 5899                ..Default::default()
 5900            },
 5901        )])
 5902    });
 5903
 5904    let mut cx = EditorTestContext::new(cx).await;
 5905
 5906    let rust_lang = Arc::new(
 5907        Language::new(
 5908            LanguageConfig {
 5909                name: "Rust".into(),
 5910                line_comments: vec!["// ".into()],
 5911                block_comment: Some(BlockCommentConfig {
 5912                    start: "/*".into(),
 5913                    end: "*/".into(),
 5914                    prefix: "* ".into(),
 5915                    tab_size: 1,
 5916                }),
 5917                documentation_comment: Some(BlockCommentConfig {
 5918                    start: "/**".into(),
 5919                    end: "*/".into(),
 5920                    prefix: "* ".into(),
 5921                    tab_size: 1,
 5922                }),
 5923
 5924                ..LanguageConfig::default()
 5925            },
 5926            Some(tree_sitter_rust::LANGUAGE.into()),
 5927        )
 5928        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 5929        .unwrap(),
 5930    );
 5931
 5932    // regular block comment
 5933    assert_rewrap(
 5934        indoc! {"
 5935            /*
 5936             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 5937             */
 5938            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 5939        "},
 5940        indoc! {"
 5941            /*
 5942             *ˇ Lorem ipsum dolor sit amet,
 5943             * consectetur adipiscing elit.
 5944             */
 5945            /*
 5946             *ˇ Lorem ipsum dolor sit amet,
 5947             * consectetur adipiscing elit.
 5948             */
 5949        "},
 5950        rust_lang.clone(),
 5951        &mut cx,
 5952    );
 5953
 5954    // indent is respected
 5955    assert_rewrap(
 5956        indoc! {"
 5957            {}
 5958                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 5959        "},
 5960        indoc! {"
 5961            {}
 5962                /*
 5963                 *ˇ Lorem ipsum dolor sit amet,
 5964                 * consectetur adipiscing elit.
 5965                 */
 5966        "},
 5967        rust_lang.clone(),
 5968        &mut cx,
 5969    );
 5970
 5971    // short block comments with inline delimiters
 5972    assert_rewrap(
 5973        indoc! {"
 5974            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 5975            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 5976             */
 5977            /*
 5978             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 5979        "},
 5980        indoc! {"
 5981            /*
 5982             *ˇ Lorem ipsum dolor sit amet,
 5983             * consectetur adipiscing elit.
 5984             */
 5985            /*
 5986             *ˇ Lorem ipsum dolor sit amet,
 5987             * consectetur adipiscing elit.
 5988             */
 5989            /*
 5990             *ˇ Lorem ipsum dolor sit amet,
 5991             * consectetur adipiscing elit.
 5992             */
 5993        "},
 5994        rust_lang.clone(),
 5995        &mut cx,
 5996    );
 5997
 5998    // multiline block comment with inline start/end delimiters
 5999    assert_rewrap(
 6000        indoc! {"
 6001            /*ˇ Lorem ipsum dolor sit amet,
 6002             * consectetur adipiscing elit. */
 6003        "},
 6004        indoc! {"
 6005            /*
 6006             *ˇ Lorem ipsum dolor sit amet,
 6007             * consectetur adipiscing elit.
 6008             */
 6009        "},
 6010        rust_lang.clone(),
 6011        &mut cx,
 6012    );
 6013
 6014    // block comment rewrap still respects paragraph bounds
 6015    assert_rewrap(
 6016        indoc! {"
 6017            /*
 6018             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6019             *
 6020             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6021             */
 6022        "},
 6023        indoc! {"
 6024            /*
 6025             *ˇ Lorem ipsum dolor sit amet,
 6026             * consectetur adipiscing elit.
 6027             *
 6028             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6029             */
 6030        "},
 6031        rust_lang.clone(),
 6032        &mut cx,
 6033    );
 6034
 6035    // documentation comments
 6036    assert_rewrap(
 6037        indoc! {"
 6038            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6039            /**
 6040             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6041             */
 6042        "},
 6043        indoc! {"
 6044            /**
 6045             *ˇ Lorem ipsum dolor sit amet,
 6046             * consectetur adipiscing elit.
 6047             */
 6048            /**
 6049             *ˇ Lorem ipsum dolor sit amet,
 6050             * consectetur adipiscing elit.
 6051             */
 6052        "},
 6053        rust_lang.clone(),
 6054        &mut cx,
 6055    );
 6056
 6057    // different, adjacent comments
 6058    assert_rewrap(
 6059        indoc! {"
 6060            /**
 6061             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6062             */
 6063            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6064            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6065        "},
 6066        indoc! {"
 6067            /**
 6068             *ˇ Lorem ipsum dolor sit amet,
 6069             * consectetur adipiscing elit.
 6070             */
 6071            /*
 6072             *ˇ Lorem ipsum dolor sit amet,
 6073             * consectetur adipiscing elit.
 6074             */
 6075            //ˇ Lorem ipsum dolor sit amet,
 6076            // consectetur adipiscing elit.
 6077        "},
 6078        rust_lang.clone(),
 6079        &mut cx,
 6080    );
 6081
 6082    // selection w/ single short block comment
 6083    assert_rewrap(
 6084        indoc! {"
 6085            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6086        "},
 6087        indoc! {"
 6088            «/*
 6089             * Lorem ipsum dolor sit amet,
 6090             * consectetur adipiscing elit.
 6091             */ˇ»
 6092        "},
 6093        rust_lang.clone(),
 6094        &mut cx,
 6095    );
 6096
 6097    // rewrapping a single comment w/ abutting comments
 6098    assert_rewrap(
 6099        indoc! {"
 6100            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6101            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6102        "},
 6103        indoc! {"
 6104            /*
 6105             * ˇLorem ipsum dolor sit amet,
 6106             * consectetur adipiscing elit.
 6107             */
 6108            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6109        "},
 6110        rust_lang.clone(),
 6111        &mut cx,
 6112    );
 6113
 6114    // selection w/ non-abutting short block comments
 6115    assert_rewrap(
 6116        indoc! {"
 6117            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6118
 6119            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6120        "},
 6121        indoc! {"
 6122            «/*
 6123             * Lorem ipsum dolor sit amet,
 6124             * consectetur adipiscing elit.
 6125             */
 6126
 6127            /*
 6128             * Lorem ipsum dolor sit amet,
 6129             * consectetur adipiscing elit.
 6130             */ˇ»
 6131        "},
 6132        rust_lang.clone(),
 6133        &mut cx,
 6134    );
 6135
 6136    // selection of multiline block comments
 6137    assert_rewrap(
 6138        indoc! {"
 6139            «/* Lorem ipsum dolor sit amet,
 6140             * consectetur adipiscing elit. */ˇ»
 6141        "},
 6142        indoc! {"
 6143            «/*
 6144             * Lorem ipsum dolor sit amet,
 6145             * consectetur adipiscing elit.
 6146             */ˇ»
 6147        "},
 6148        rust_lang.clone(),
 6149        &mut cx,
 6150    );
 6151
 6152    // partial selection of multiline block comments
 6153    assert_rewrap(
 6154        indoc! {"
 6155            «/* Lorem ipsum dolor sit amet,ˇ»
 6156             * consectetur adipiscing elit. */
 6157            /* Lorem ipsum dolor sit amet,
 6158             «* consectetur adipiscing elit. */ˇ»
 6159        "},
 6160        indoc! {"
 6161            «/*
 6162             * Lorem ipsum dolor sit amet,ˇ»
 6163             * consectetur adipiscing elit. */
 6164            /* Lorem ipsum dolor sit amet,
 6165             «* consectetur adipiscing elit.
 6166             */ˇ»
 6167        "},
 6168        rust_lang.clone(),
 6169        &mut cx,
 6170    );
 6171
 6172    // selection w/ abutting short block comments
 6173    // TODO: should not be combined; should rewrap as 2 comments
 6174    assert_rewrap(
 6175        indoc! {"
 6176            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6177            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6178        "},
 6179        // desired behavior:
 6180        // indoc! {"
 6181        //     «/*
 6182        //      * Lorem ipsum dolor sit amet,
 6183        //      * consectetur adipiscing elit.
 6184        //      */
 6185        //     /*
 6186        //      * Lorem ipsum dolor sit amet,
 6187        //      * consectetur adipiscing elit.
 6188        //      */ˇ»
 6189        // "},
 6190        // actual behaviour:
 6191        indoc! {"
 6192            «/*
 6193             * Lorem ipsum dolor sit amet,
 6194             * consectetur adipiscing elit. Lorem
 6195             * ipsum dolor sit amet, consectetur
 6196             * adipiscing elit.
 6197             */ˇ»
 6198        "},
 6199        rust_lang.clone(),
 6200        &mut cx,
 6201    );
 6202
 6203    // TODO: same as above, but with delimiters on separate line
 6204    // assert_rewrap(
 6205    //     indoc! {"
 6206    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6207    //          */
 6208    //         /*
 6209    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6210    //     "},
 6211    //     // desired:
 6212    //     // indoc! {"
 6213    //     //     «/*
 6214    //     //      * Lorem ipsum dolor sit amet,
 6215    //     //      * consectetur adipiscing elit.
 6216    //     //      */
 6217    //     //     /*
 6218    //     //      * Lorem ipsum dolor sit amet,
 6219    //     //      * consectetur adipiscing elit.
 6220    //     //      */ˇ»
 6221    //     // "},
 6222    //     // actual: (but with trailing w/s on the empty lines)
 6223    //     indoc! {"
 6224    //         «/*
 6225    //          * Lorem ipsum dolor sit amet,
 6226    //          * consectetur adipiscing elit.
 6227    //          *
 6228    //          */
 6229    //         /*
 6230    //          *
 6231    //          * Lorem ipsum dolor sit amet,
 6232    //          * consectetur adipiscing elit.
 6233    //          */ˇ»
 6234    //     "},
 6235    //     rust_lang.clone(),
 6236    //     &mut cx,
 6237    // );
 6238
 6239    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6240    assert_rewrap(
 6241        indoc! {"
 6242            /*
 6243             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6244             */
 6245            /*
 6246             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6247            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6248        "},
 6249        // desired:
 6250        // indoc! {"
 6251        //     /*
 6252        //      *ˇ Lorem ipsum dolor sit amet,
 6253        //      * consectetur adipiscing elit.
 6254        //      */
 6255        //     /*
 6256        //      *ˇ Lorem ipsum dolor sit amet,
 6257        //      * consectetur adipiscing elit.
 6258        //      */
 6259        //     /*
 6260        //      *ˇ Lorem ipsum dolor sit amet
 6261        //      */ /* consectetur adipiscing elit. */
 6262        // "},
 6263        // actual:
 6264        indoc! {"
 6265            /*
 6266             //ˇ Lorem ipsum dolor sit amet,
 6267             // consectetur adipiscing elit.
 6268             */
 6269            /*
 6270             * //ˇ Lorem ipsum dolor sit amet,
 6271             * consectetur adipiscing elit.
 6272             */
 6273            /*
 6274             *ˇ Lorem ipsum dolor sit amet */ /*
 6275             * consectetur adipiscing elit.
 6276             */
 6277        "},
 6278        rust_lang,
 6279        &mut cx,
 6280    );
 6281
 6282    #[track_caller]
 6283    fn assert_rewrap(
 6284        unwrapped_text: &str,
 6285        wrapped_text: &str,
 6286        language: Arc<Language>,
 6287        cx: &mut EditorTestContext,
 6288    ) {
 6289        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6290        cx.set_state(unwrapped_text);
 6291        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6292        cx.assert_editor_state(wrapped_text);
 6293    }
 6294}
 6295
 6296#[gpui::test]
 6297async fn test_hard_wrap(cx: &mut TestAppContext) {
 6298    init_test(cx, |_| {});
 6299    let mut cx = EditorTestContext::new(cx).await;
 6300
 6301    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6302    cx.update_editor(|editor, _, cx| {
 6303        editor.set_hard_wrap(Some(14), cx);
 6304    });
 6305
 6306    cx.set_state(indoc!(
 6307        "
 6308        one two three ˇ
 6309        "
 6310    ));
 6311    cx.simulate_input("four");
 6312    cx.run_until_parked();
 6313
 6314    cx.assert_editor_state(indoc!(
 6315        "
 6316        one two three
 6317        fourˇ
 6318        "
 6319    ));
 6320
 6321    cx.update_editor(|editor, window, cx| {
 6322        editor.newline(&Default::default(), window, cx);
 6323    });
 6324    cx.run_until_parked();
 6325    cx.assert_editor_state(indoc!(
 6326        "
 6327        one two three
 6328        four
 6329        ˇ
 6330        "
 6331    ));
 6332
 6333    cx.simulate_input("five");
 6334    cx.run_until_parked();
 6335    cx.assert_editor_state(indoc!(
 6336        "
 6337        one two three
 6338        four
 6339        fiveˇ
 6340        "
 6341    ));
 6342
 6343    cx.update_editor(|editor, window, cx| {
 6344        editor.newline(&Default::default(), window, cx);
 6345    });
 6346    cx.run_until_parked();
 6347    cx.simulate_input("# ");
 6348    cx.run_until_parked();
 6349    cx.assert_editor_state(indoc!(
 6350        "
 6351        one two three
 6352        four
 6353        five
 6354        # ˇ
 6355        "
 6356    ));
 6357
 6358    cx.update_editor(|editor, window, cx| {
 6359        editor.newline(&Default::default(), window, cx);
 6360    });
 6361    cx.run_until_parked();
 6362    cx.assert_editor_state(indoc!(
 6363        "
 6364        one two three
 6365        four
 6366        five
 6367        #\x20
 6368 6369        "
 6370    ));
 6371
 6372    cx.simulate_input(" 6");
 6373    cx.run_until_parked();
 6374    cx.assert_editor_state(indoc!(
 6375        "
 6376        one two three
 6377        four
 6378        five
 6379        #
 6380        # 6ˇ
 6381        "
 6382    ));
 6383}
 6384
 6385#[gpui::test]
 6386async fn test_clipboard(cx: &mut TestAppContext) {
 6387    init_test(cx, |_| {});
 6388
 6389    let mut cx = EditorTestContext::new(cx).await;
 6390
 6391    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6392    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6393    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6394
 6395    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6396    cx.set_state("two ˇfour ˇsix ˇ");
 6397    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6398    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6399
 6400    // Paste again but with only two cursors. Since the number of cursors doesn't
 6401    // match the number of slices in the clipboard, the entire clipboard text
 6402    // is pasted at each cursor.
 6403    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6404    cx.update_editor(|e, window, cx| {
 6405        e.handle_input("( ", window, cx);
 6406        e.paste(&Paste, window, cx);
 6407        e.handle_input(") ", window, cx);
 6408    });
 6409    cx.assert_editor_state(
 6410        &([
 6411            "( one✅ ",
 6412            "three ",
 6413            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6414            "three ",
 6415            "five ) ˇ",
 6416        ]
 6417        .join("\n")),
 6418    );
 6419
 6420    // Cut with three selections, one of which is full-line.
 6421    cx.set_state(indoc! {"
 6422        1«2ˇ»3
 6423        4ˇ567
 6424        «8ˇ»9"});
 6425    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6426    cx.assert_editor_state(indoc! {"
 6427        1ˇ3
 6428        ˇ9"});
 6429
 6430    // Paste with three selections, noticing how the copied selection that was full-line
 6431    // gets inserted before the second cursor.
 6432    cx.set_state(indoc! {"
 6433        1ˇ3
 6434 6435        «oˇ»ne"});
 6436    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6437    cx.assert_editor_state(indoc! {"
 6438        12ˇ3
 6439        4567
 6440 6441        8ˇne"});
 6442
 6443    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6444    cx.set_state(indoc! {"
 6445        The quick brown
 6446        fox juˇmps over
 6447        the lazy dog"});
 6448    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6449    assert_eq!(
 6450        cx.read_from_clipboard()
 6451            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6452        Some("fox jumps over\n".to_string())
 6453    );
 6454
 6455    // Paste with three selections, noticing how the copied full-line selection is inserted
 6456    // before the empty selections but replaces the selection that is non-empty.
 6457    cx.set_state(indoc! {"
 6458        Tˇhe quick brown
 6459        «foˇ»x jumps over
 6460        tˇhe lazy dog"});
 6461    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6462    cx.assert_editor_state(indoc! {"
 6463        fox jumps over
 6464        Tˇhe quick brown
 6465        fox jumps over
 6466        ˇx jumps over
 6467        fox jumps over
 6468        tˇhe lazy dog"});
 6469}
 6470
 6471#[gpui::test]
 6472async fn test_copy_trim(cx: &mut TestAppContext) {
 6473    init_test(cx, |_| {});
 6474
 6475    let mut cx = EditorTestContext::new(cx).await;
 6476    cx.set_state(
 6477        r#"            «for selection in selections.iter() {
 6478            let mut start = selection.start;
 6479            let mut end = selection.end;
 6480            let is_entire_line = selection.is_empty();
 6481            if is_entire_line {
 6482                start = Point::new(start.row, 0);ˇ»
 6483                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6484            }
 6485        "#,
 6486    );
 6487    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6488    assert_eq!(
 6489        cx.read_from_clipboard()
 6490            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6491        Some(
 6492            "for selection in selections.iter() {
 6493            let mut start = selection.start;
 6494            let mut end = selection.end;
 6495            let is_entire_line = selection.is_empty();
 6496            if is_entire_line {
 6497                start = Point::new(start.row, 0);"
 6498                .to_string()
 6499        ),
 6500        "Regular copying preserves all indentation selected",
 6501    );
 6502    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6503    assert_eq!(
 6504        cx.read_from_clipboard()
 6505            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6506        Some(
 6507            "for selection in selections.iter() {
 6508let mut start = selection.start;
 6509let mut end = selection.end;
 6510let is_entire_line = selection.is_empty();
 6511if is_entire_line {
 6512    start = Point::new(start.row, 0);"
 6513                .to_string()
 6514        ),
 6515        "Copying with stripping should strip all leading whitespaces"
 6516    );
 6517
 6518    cx.set_state(
 6519        r#"       «     for selection in selections.iter() {
 6520            let mut start = selection.start;
 6521            let mut end = selection.end;
 6522            let is_entire_line = selection.is_empty();
 6523            if is_entire_line {
 6524                start = Point::new(start.row, 0);ˇ»
 6525                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6526            }
 6527        "#,
 6528    );
 6529    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6530    assert_eq!(
 6531        cx.read_from_clipboard()
 6532            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6533        Some(
 6534            "     for selection in selections.iter() {
 6535            let mut start = selection.start;
 6536            let mut end = selection.end;
 6537            let is_entire_line = selection.is_empty();
 6538            if is_entire_line {
 6539                start = Point::new(start.row, 0);"
 6540                .to_string()
 6541        ),
 6542        "Regular copying preserves all indentation selected",
 6543    );
 6544    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6545    assert_eq!(
 6546        cx.read_from_clipboard()
 6547            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6548        Some(
 6549            "for selection in selections.iter() {
 6550let mut start = selection.start;
 6551let mut end = selection.end;
 6552let is_entire_line = selection.is_empty();
 6553if is_entire_line {
 6554    start = Point::new(start.row, 0);"
 6555                .to_string()
 6556        ),
 6557        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6558    );
 6559
 6560    cx.set_state(
 6561        r#"       «ˇ     for selection in selections.iter() {
 6562            let mut start = selection.start;
 6563            let mut end = selection.end;
 6564            let is_entire_line = selection.is_empty();
 6565            if is_entire_line {
 6566                start = Point::new(start.row, 0);»
 6567                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6568            }
 6569        "#,
 6570    );
 6571    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6572    assert_eq!(
 6573        cx.read_from_clipboard()
 6574            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6575        Some(
 6576            "     for selection in selections.iter() {
 6577            let mut start = selection.start;
 6578            let mut end = selection.end;
 6579            let is_entire_line = selection.is_empty();
 6580            if is_entire_line {
 6581                start = Point::new(start.row, 0);"
 6582                .to_string()
 6583        ),
 6584        "Regular copying for reverse selection works the same",
 6585    );
 6586    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6587    assert_eq!(
 6588        cx.read_from_clipboard()
 6589            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6590        Some(
 6591            "for selection in selections.iter() {
 6592let mut start = selection.start;
 6593let mut end = selection.end;
 6594let is_entire_line = selection.is_empty();
 6595if is_entire_line {
 6596    start = Point::new(start.row, 0);"
 6597                .to_string()
 6598        ),
 6599        "Copying with stripping for reverse selection works the same"
 6600    );
 6601
 6602    cx.set_state(
 6603        r#"            for selection «in selections.iter() {
 6604            let mut start = selection.start;
 6605            let mut end = selection.end;
 6606            let is_entire_line = selection.is_empty();
 6607            if is_entire_line {
 6608                start = Point::new(start.row, 0);ˇ»
 6609                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6610            }
 6611        "#,
 6612    );
 6613    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6614    assert_eq!(
 6615        cx.read_from_clipboard()
 6616            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6617        Some(
 6618            "in selections.iter() {
 6619            let mut start = selection.start;
 6620            let mut end = selection.end;
 6621            let is_entire_line = selection.is_empty();
 6622            if is_entire_line {
 6623                start = Point::new(start.row, 0);"
 6624                .to_string()
 6625        ),
 6626        "When selecting past the indent, the copying works as usual",
 6627    );
 6628    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6629    assert_eq!(
 6630        cx.read_from_clipboard()
 6631            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6632        Some(
 6633            "in selections.iter() {
 6634            let mut start = selection.start;
 6635            let mut end = selection.end;
 6636            let is_entire_line = selection.is_empty();
 6637            if is_entire_line {
 6638                start = Point::new(start.row, 0);"
 6639                .to_string()
 6640        ),
 6641        "When selecting past the indent, nothing is trimmed"
 6642    );
 6643
 6644    cx.set_state(
 6645        r#"            «for selection in selections.iter() {
 6646            let mut start = selection.start;
 6647
 6648            let mut end = selection.end;
 6649            let is_entire_line = selection.is_empty();
 6650            if is_entire_line {
 6651                start = Point::new(start.row, 0);
 6652ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6653            }
 6654        "#,
 6655    );
 6656    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6657    assert_eq!(
 6658        cx.read_from_clipboard()
 6659            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6660        Some(
 6661            "for selection in selections.iter() {
 6662let mut start = selection.start;
 6663
 6664let mut end = selection.end;
 6665let is_entire_line = selection.is_empty();
 6666if is_entire_line {
 6667    start = Point::new(start.row, 0);
 6668"
 6669            .to_string()
 6670        ),
 6671        "Copying with stripping should ignore empty lines"
 6672    );
 6673}
 6674
 6675#[gpui::test]
 6676async fn test_paste_multiline(cx: &mut TestAppContext) {
 6677    init_test(cx, |_| {});
 6678
 6679    let mut cx = EditorTestContext::new(cx).await;
 6680    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6681
 6682    // Cut an indented block, without the leading whitespace.
 6683    cx.set_state(indoc! {"
 6684        const a: B = (
 6685            c(),
 6686            «d(
 6687                e,
 6688                f
 6689            )ˇ»
 6690        );
 6691    "});
 6692    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6693    cx.assert_editor_state(indoc! {"
 6694        const a: B = (
 6695            c(),
 6696            ˇ
 6697        );
 6698    "});
 6699
 6700    // Paste it at the same position.
 6701    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6702    cx.assert_editor_state(indoc! {"
 6703        const a: B = (
 6704            c(),
 6705            d(
 6706                e,
 6707                f
 6708 6709        );
 6710    "});
 6711
 6712    // Paste it at a line with a lower indent level.
 6713    cx.set_state(indoc! {"
 6714        ˇ
 6715        const a: B = (
 6716            c(),
 6717        );
 6718    "});
 6719    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6720    cx.assert_editor_state(indoc! {"
 6721        d(
 6722            e,
 6723            f
 6724 6725        const a: B = (
 6726            c(),
 6727        );
 6728    "});
 6729
 6730    // Cut an indented block, with the leading whitespace.
 6731    cx.set_state(indoc! {"
 6732        const a: B = (
 6733            c(),
 6734        «    d(
 6735                e,
 6736                f
 6737            )
 6738        ˇ»);
 6739    "});
 6740    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6741    cx.assert_editor_state(indoc! {"
 6742        const a: B = (
 6743            c(),
 6744        ˇ);
 6745    "});
 6746
 6747    // Paste it at the same position.
 6748    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6749    cx.assert_editor_state(indoc! {"
 6750        const a: B = (
 6751            c(),
 6752            d(
 6753                e,
 6754                f
 6755            )
 6756        ˇ);
 6757    "});
 6758
 6759    // Paste it at a line with a higher indent level.
 6760    cx.set_state(indoc! {"
 6761        const a: B = (
 6762            c(),
 6763            d(
 6764                e,
 6765 6766            )
 6767        );
 6768    "});
 6769    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6770    cx.assert_editor_state(indoc! {"
 6771        const a: B = (
 6772            c(),
 6773            d(
 6774                e,
 6775                f    d(
 6776                    e,
 6777                    f
 6778                )
 6779        ˇ
 6780            )
 6781        );
 6782    "});
 6783
 6784    // Copy an indented block, starting mid-line
 6785    cx.set_state(indoc! {"
 6786        const a: B = (
 6787            c(),
 6788            somethin«g(
 6789                e,
 6790                f
 6791            )ˇ»
 6792        );
 6793    "});
 6794    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6795
 6796    // Paste it on a line with a lower indent level
 6797    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 6798    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6799    cx.assert_editor_state(indoc! {"
 6800        const a: B = (
 6801            c(),
 6802            something(
 6803                e,
 6804                f
 6805            )
 6806        );
 6807        g(
 6808            e,
 6809            f
 6810"});
 6811}
 6812
 6813#[gpui::test]
 6814async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 6815    init_test(cx, |_| {});
 6816
 6817    cx.write_to_clipboard(ClipboardItem::new_string(
 6818        "    d(\n        e\n    );\n".into(),
 6819    ));
 6820
 6821    let mut cx = EditorTestContext::new(cx).await;
 6822    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6823
 6824    cx.set_state(indoc! {"
 6825        fn a() {
 6826            b();
 6827            if c() {
 6828                ˇ
 6829            }
 6830        }
 6831    "});
 6832
 6833    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6834    cx.assert_editor_state(indoc! {"
 6835        fn a() {
 6836            b();
 6837            if c() {
 6838                d(
 6839                    e
 6840                );
 6841        ˇ
 6842            }
 6843        }
 6844    "});
 6845
 6846    cx.set_state(indoc! {"
 6847        fn a() {
 6848            b();
 6849            ˇ
 6850        }
 6851    "});
 6852
 6853    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6854    cx.assert_editor_state(indoc! {"
 6855        fn a() {
 6856            b();
 6857            d(
 6858                e
 6859            );
 6860        ˇ
 6861        }
 6862    "});
 6863}
 6864
 6865#[gpui::test]
 6866fn test_select_all(cx: &mut TestAppContext) {
 6867    init_test(cx, |_| {});
 6868
 6869    let editor = cx.add_window(|window, cx| {
 6870        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 6871        build_editor(buffer, window, cx)
 6872    });
 6873    _ = editor.update(cx, |editor, window, cx| {
 6874        editor.select_all(&SelectAll, window, cx);
 6875        assert_eq!(
 6876            editor.selections.display_ranges(cx),
 6877            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 6878        );
 6879    });
 6880}
 6881
 6882#[gpui::test]
 6883fn test_select_line(cx: &mut TestAppContext) {
 6884    init_test(cx, |_| {});
 6885
 6886    let editor = cx.add_window(|window, cx| {
 6887        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 6888        build_editor(buffer, window, cx)
 6889    });
 6890    _ = editor.update(cx, |editor, window, cx| {
 6891        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6892            s.select_display_ranges([
 6893                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 6894                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 6895                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 6896                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 6897            ])
 6898        });
 6899        editor.select_line(&SelectLine, window, cx);
 6900        assert_eq!(
 6901            editor.selections.display_ranges(cx),
 6902            vec![
 6903                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 6904                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 6905            ]
 6906        );
 6907    });
 6908
 6909    _ = editor.update(cx, |editor, window, cx| {
 6910        editor.select_line(&SelectLine, window, cx);
 6911        assert_eq!(
 6912            editor.selections.display_ranges(cx),
 6913            vec![
 6914                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 6915                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 6916            ]
 6917        );
 6918    });
 6919
 6920    _ = editor.update(cx, |editor, window, cx| {
 6921        editor.select_line(&SelectLine, window, cx);
 6922        assert_eq!(
 6923            editor.selections.display_ranges(cx),
 6924            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 6925        );
 6926    });
 6927}
 6928
 6929#[gpui::test]
 6930async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 6931    init_test(cx, |_| {});
 6932    let mut cx = EditorTestContext::new(cx).await;
 6933
 6934    #[track_caller]
 6935    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 6936        cx.set_state(initial_state);
 6937        cx.update_editor(|e, window, cx| {
 6938            e.split_selection_into_lines(&Default::default(), window, cx)
 6939        });
 6940        cx.assert_editor_state(expected_state);
 6941    }
 6942
 6943    // Selection starts and ends at the middle of lines, left-to-right
 6944    test(
 6945        &mut cx,
 6946        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 6947        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6948    );
 6949    // Same thing, right-to-left
 6950    test(
 6951        &mut cx,
 6952        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 6953        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 6954    );
 6955
 6956    // Whole buffer, left-to-right, last line *doesn't* end with newline
 6957    test(
 6958        &mut cx,
 6959        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 6960        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6961    );
 6962    // Same thing, right-to-left
 6963    test(
 6964        &mut cx,
 6965        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 6966        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 6967    );
 6968
 6969    // Whole buffer, left-to-right, last line ends with newline
 6970    test(
 6971        &mut cx,
 6972        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 6973        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6974    );
 6975    // Same thing, right-to-left
 6976    test(
 6977        &mut cx,
 6978        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 6979        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 6980    );
 6981
 6982    // Starts at the end of a line, ends at the start of another
 6983    test(
 6984        &mut cx,
 6985        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 6986        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 6987    );
 6988}
 6989
 6990#[gpui::test]
 6991async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 6992    init_test(cx, |_| {});
 6993
 6994    let editor = cx.add_window(|window, cx| {
 6995        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 6996        build_editor(buffer, window, cx)
 6997    });
 6998
 6999    // setup
 7000    _ = editor.update(cx, |editor, window, cx| {
 7001        editor.fold_creases(
 7002            vec![
 7003                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7004                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7005                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7006            ],
 7007            true,
 7008            window,
 7009            cx,
 7010        );
 7011        assert_eq!(
 7012            editor.display_text(cx),
 7013            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7014        );
 7015    });
 7016
 7017    _ = editor.update(cx, |editor, window, cx| {
 7018        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7019            s.select_display_ranges([
 7020                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7021                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7022                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7023                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7024            ])
 7025        });
 7026        editor.split_selection_into_lines(&Default::default(), window, cx);
 7027        assert_eq!(
 7028            editor.display_text(cx),
 7029            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7030        );
 7031    });
 7032    EditorTestContext::for_editor(editor, cx)
 7033        .await
 7034        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7035
 7036    _ = editor.update(cx, |editor, window, cx| {
 7037        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7038            s.select_display_ranges([
 7039                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7040            ])
 7041        });
 7042        editor.split_selection_into_lines(&Default::default(), window, cx);
 7043        assert_eq!(
 7044            editor.display_text(cx),
 7045            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7046        );
 7047        assert_eq!(
 7048            editor.selections.display_ranges(cx),
 7049            [
 7050                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7051                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7052                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7053                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7054                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7055                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7056                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7057            ]
 7058        );
 7059    });
 7060    EditorTestContext::for_editor(editor, cx)
 7061        .await
 7062        .assert_editor_state(
 7063            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7064        );
 7065}
 7066
 7067#[gpui::test]
 7068async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7069    init_test(cx, |_| {});
 7070
 7071    let mut cx = EditorTestContext::new(cx).await;
 7072
 7073    cx.set_state(indoc!(
 7074        r#"abc
 7075           defˇghi
 7076
 7077           jk
 7078           nlmo
 7079           "#
 7080    ));
 7081
 7082    cx.update_editor(|editor, window, cx| {
 7083        editor.add_selection_above(&Default::default(), window, cx);
 7084    });
 7085
 7086    cx.assert_editor_state(indoc!(
 7087        r#"abcˇ
 7088           defˇghi
 7089
 7090           jk
 7091           nlmo
 7092           "#
 7093    ));
 7094
 7095    cx.update_editor(|editor, window, cx| {
 7096        editor.add_selection_above(&Default::default(), window, cx);
 7097    });
 7098
 7099    cx.assert_editor_state(indoc!(
 7100        r#"abcˇ
 7101            defˇghi
 7102
 7103            jk
 7104            nlmo
 7105            "#
 7106    ));
 7107
 7108    cx.update_editor(|editor, window, cx| {
 7109        editor.add_selection_below(&Default::default(), window, cx);
 7110    });
 7111
 7112    cx.assert_editor_state(indoc!(
 7113        r#"abc
 7114           defˇghi
 7115
 7116           jk
 7117           nlmo
 7118           "#
 7119    ));
 7120
 7121    cx.update_editor(|editor, window, cx| {
 7122        editor.undo_selection(&Default::default(), window, cx);
 7123    });
 7124
 7125    cx.assert_editor_state(indoc!(
 7126        r#"abcˇ
 7127           defˇghi
 7128
 7129           jk
 7130           nlmo
 7131           "#
 7132    ));
 7133
 7134    cx.update_editor(|editor, window, cx| {
 7135        editor.redo_selection(&Default::default(), window, cx);
 7136    });
 7137
 7138    cx.assert_editor_state(indoc!(
 7139        r#"abc
 7140           defˇghi
 7141
 7142           jk
 7143           nlmo
 7144           "#
 7145    ));
 7146
 7147    cx.update_editor(|editor, window, cx| {
 7148        editor.add_selection_below(&Default::default(), window, cx);
 7149    });
 7150
 7151    cx.assert_editor_state(indoc!(
 7152        r#"abc
 7153           defˇghi
 7154           ˇ
 7155           jk
 7156           nlmo
 7157           "#
 7158    ));
 7159
 7160    cx.update_editor(|editor, window, cx| {
 7161        editor.add_selection_below(&Default::default(), window, cx);
 7162    });
 7163
 7164    cx.assert_editor_state(indoc!(
 7165        r#"abc
 7166           defˇghi
 7167           ˇ
 7168           jkˇ
 7169           nlmo
 7170           "#
 7171    ));
 7172
 7173    cx.update_editor(|editor, window, cx| {
 7174        editor.add_selection_below(&Default::default(), window, cx);
 7175    });
 7176
 7177    cx.assert_editor_state(indoc!(
 7178        r#"abc
 7179           defˇghi
 7180           ˇ
 7181           jkˇ
 7182           nlmˇo
 7183           "#
 7184    ));
 7185
 7186    cx.update_editor(|editor, window, cx| {
 7187        editor.add_selection_below(&Default::default(), window, cx);
 7188    });
 7189
 7190    cx.assert_editor_state(indoc!(
 7191        r#"abc
 7192           defˇghi
 7193           ˇ
 7194           jkˇ
 7195           nlmˇo
 7196           ˇ"#
 7197    ));
 7198
 7199    // change selections
 7200    cx.set_state(indoc!(
 7201        r#"abc
 7202           def«ˇg»hi
 7203
 7204           jk
 7205           nlmo
 7206           "#
 7207    ));
 7208
 7209    cx.update_editor(|editor, window, cx| {
 7210        editor.add_selection_below(&Default::default(), window, cx);
 7211    });
 7212
 7213    cx.assert_editor_state(indoc!(
 7214        r#"abc
 7215           def«ˇg»hi
 7216
 7217           jk
 7218           nlm«ˇo»
 7219           "#
 7220    ));
 7221
 7222    cx.update_editor(|editor, window, cx| {
 7223        editor.add_selection_below(&Default::default(), window, cx);
 7224    });
 7225
 7226    cx.assert_editor_state(indoc!(
 7227        r#"abc
 7228           def«ˇg»hi
 7229
 7230           jk
 7231           nlm«ˇo»
 7232           "#
 7233    ));
 7234
 7235    cx.update_editor(|editor, window, cx| {
 7236        editor.add_selection_above(&Default::default(), window, cx);
 7237    });
 7238
 7239    cx.assert_editor_state(indoc!(
 7240        r#"abc
 7241           def«ˇg»hi
 7242
 7243           jk
 7244           nlmo
 7245           "#
 7246    ));
 7247
 7248    cx.update_editor(|editor, window, cx| {
 7249        editor.add_selection_above(&Default::default(), window, cx);
 7250    });
 7251
 7252    cx.assert_editor_state(indoc!(
 7253        r#"abc
 7254           def«ˇg»hi
 7255
 7256           jk
 7257           nlmo
 7258           "#
 7259    ));
 7260
 7261    // Change selections again
 7262    cx.set_state(indoc!(
 7263        r#"a«bc
 7264           defgˇ»hi
 7265
 7266           jk
 7267           nlmo
 7268           "#
 7269    ));
 7270
 7271    cx.update_editor(|editor, window, cx| {
 7272        editor.add_selection_below(&Default::default(), window, cx);
 7273    });
 7274
 7275    cx.assert_editor_state(indoc!(
 7276        r#"a«bcˇ»
 7277           d«efgˇ»hi
 7278
 7279           j«kˇ»
 7280           nlmo
 7281           "#
 7282    ));
 7283
 7284    cx.update_editor(|editor, window, cx| {
 7285        editor.add_selection_below(&Default::default(), window, cx);
 7286    });
 7287    cx.assert_editor_state(indoc!(
 7288        r#"a«bcˇ»
 7289           d«efgˇ»hi
 7290
 7291           j«kˇ»
 7292           n«lmoˇ»
 7293           "#
 7294    ));
 7295    cx.update_editor(|editor, window, cx| {
 7296        editor.add_selection_above(&Default::default(), window, cx);
 7297    });
 7298
 7299    cx.assert_editor_state(indoc!(
 7300        r#"a«bcˇ»
 7301           d«efgˇ»hi
 7302
 7303           j«kˇ»
 7304           nlmo
 7305           "#
 7306    ));
 7307
 7308    // Change selections again
 7309    cx.set_state(indoc!(
 7310        r#"abc
 7311           d«ˇefghi
 7312
 7313           jk
 7314           nlm»o
 7315           "#
 7316    ));
 7317
 7318    cx.update_editor(|editor, window, cx| {
 7319        editor.add_selection_above(&Default::default(), window, cx);
 7320    });
 7321
 7322    cx.assert_editor_state(indoc!(
 7323        r#"a«ˇbc»
 7324           d«ˇef»ghi
 7325
 7326           j«ˇk»
 7327           n«ˇlm»o
 7328           "#
 7329    ));
 7330
 7331    cx.update_editor(|editor, window, cx| {
 7332        editor.add_selection_below(&Default::default(), window, cx);
 7333    });
 7334
 7335    cx.assert_editor_state(indoc!(
 7336        r#"abc
 7337           d«ˇef»ghi
 7338
 7339           j«ˇk»
 7340           n«ˇlm»o
 7341           "#
 7342    ));
 7343}
 7344
 7345#[gpui::test]
 7346async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7347    init_test(cx, |_| {});
 7348    let mut cx = EditorTestContext::new(cx).await;
 7349
 7350    cx.set_state(indoc!(
 7351        r#"line onˇe
 7352           liˇne two
 7353           line three
 7354           line four"#
 7355    ));
 7356
 7357    cx.update_editor(|editor, window, cx| {
 7358        editor.add_selection_below(&Default::default(), window, cx);
 7359    });
 7360
 7361    // test multiple cursors expand in the same direction
 7362    cx.assert_editor_state(indoc!(
 7363        r#"line onˇe
 7364           liˇne twˇo
 7365           liˇne three
 7366           line four"#
 7367    ));
 7368
 7369    cx.update_editor(|editor, window, cx| {
 7370        editor.add_selection_below(&Default::default(), window, cx);
 7371    });
 7372
 7373    cx.update_editor(|editor, window, cx| {
 7374        editor.add_selection_below(&Default::default(), window, cx);
 7375    });
 7376
 7377    // test multiple cursors expand below overflow
 7378    cx.assert_editor_state(indoc!(
 7379        r#"line onˇe
 7380           liˇne twˇo
 7381           liˇne thˇree
 7382           liˇne foˇur"#
 7383    ));
 7384
 7385    cx.update_editor(|editor, window, cx| {
 7386        editor.add_selection_above(&Default::default(), window, cx);
 7387    });
 7388
 7389    // test multiple cursors retrieves back correctly
 7390    cx.assert_editor_state(indoc!(
 7391        r#"line onˇe
 7392           liˇne twˇo
 7393           liˇne thˇree
 7394           line four"#
 7395    ));
 7396
 7397    cx.update_editor(|editor, window, cx| {
 7398        editor.add_selection_above(&Default::default(), window, cx);
 7399    });
 7400
 7401    cx.update_editor(|editor, window, cx| {
 7402        editor.add_selection_above(&Default::default(), window, cx);
 7403    });
 7404
 7405    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7406    cx.assert_editor_state(indoc!(
 7407        r#"liˇne onˇe
 7408           liˇne two
 7409           line three
 7410           line four"#
 7411    ));
 7412
 7413    cx.update_editor(|editor, window, cx| {
 7414        editor.undo_selection(&Default::default(), window, cx);
 7415    });
 7416
 7417    // test undo
 7418    cx.assert_editor_state(indoc!(
 7419        r#"line onˇe
 7420           liˇne twˇo
 7421           line three
 7422           line four"#
 7423    ));
 7424
 7425    cx.update_editor(|editor, window, cx| {
 7426        editor.redo_selection(&Default::default(), window, cx);
 7427    });
 7428
 7429    // test redo
 7430    cx.assert_editor_state(indoc!(
 7431        r#"liˇne onˇe
 7432           liˇne two
 7433           line three
 7434           line four"#
 7435    ));
 7436
 7437    cx.set_state(indoc!(
 7438        r#"abcd
 7439           ef«ghˇ»
 7440           ijkl
 7441           «mˇ»nop"#
 7442    ));
 7443
 7444    cx.update_editor(|editor, window, cx| {
 7445        editor.add_selection_above(&Default::default(), window, cx);
 7446    });
 7447
 7448    // test multiple selections expand in the same direction
 7449    cx.assert_editor_state(indoc!(
 7450        r#"ab«cdˇ»
 7451           ef«ghˇ»
 7452           «iˇ»jkl
 7453           «mˇ»nop"#
 7454    ));
 7455
 7456    cx.update_editor(|editor, window, cx| {
 7457        editor.add_selection_above(&Default::default(), window, cx);
 7458    });
 7459
 7460    // test multiple selection upward overflow
 7461    cx.assert_editor_state(indoc!(
 7462        r#"ab«cdˇ»
 7463           «eˇ»f«ghˇ»
 7464           «iˇ»jkl
 7465           «mˇ»nop"#
 7466    ));
 7467
 7468    cx.update_editor(|editor, window, cx| {
 7469        editor.add_selection_below(&Default::default(), window, cx);
 7470    });
 7471
 7472    // test multiple selection retrieves back correctly
 7473    cx.assert_editor_state(indoc!(
 7474        r#"abcd
 7475           ef«ghˇ»
 7476           «iˇ»jkl
 7477           «mˇ»nop"#
 7478    ));
 7479
 7480    cx.update_editor(|editor, window, cx| {
 7481        editor.add_selection_below(&Default::default(), window, cx);
 7482    });
 7483
 7484    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7485    cx.assert_editor_state(indoc!(
 7486        r#"abcd
 7487           ef«ghˇ»
 7488           ij«klˇ»
 7489           «mˇ»nop"#
 7490    ));
 7491
 7492    cx.update_editor(|editor, window, cx| {
 7493        editor.undo_selection(&Default::default(), window, cx);
 7494    });
 7495
 7496    // test undo
 7497    cx.assert_editor_state(indoc!(
 7498        r#"abcd
 7499           ef«ghˇ»
 7500           «iˇ»jkl
 7501           «mˇ»nop"#
 7502    ));
 7503
 7504    cx.update_editor(|editor, window, cx| {
 7505        editor.redo_selection(&Default::default(), window, cx);
 7506    });
 7507
 7508    // test redo
 7509    cx.assert_editor_state(indoc!(
 7510        r#"abcd
 7511           ef«ghˇ»
 7512           ij«klˇ»
 7513           «mˇ»nop"#
 7514    ));
 7515}
 7516
 7517#[gpui::test]
 7518async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7519    init_test(cx, |_| {});
 7520    let mut cx = EditorTestContext::new(cx).await;
 7521
 7522    cx.set_state(indoc!(
 7523        r#"line onˇe
 7524           liˇne two
 7525           line three
 7526           line four"#
 7527    ));
 7528
 7529    cx.update_editor(|editor, window, cx| {
 7530        editor.add_selection_below(&Default::default(), window, cx);
 7531        editor.add_selection_below(&Default::default(), window, cx);
 7532        editor.add_selection_below(&Default::default(), window, cx);
 7533    });
 7534
 7535    // initial state with two multi cursor groups
 7536    cx.assert_editor_state(indoc!(
 7537        r#"line onˇe
 7538           liˇne twˇo
 7539           liˇne thˇree
 7540           liˇne foˇur"#
 7541    ));
 7542
 7543    // add single cursor in middle - simulate opt click
 7544    cx.update_editor(|editor, window, cx| {
 7545        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7546        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7547        editor.end_selection(window, cx);
 7548    });
 7549
 7550    cx.assert_editor_state(indoc!(
 7551        r#"line onˇe
 7552           liˇne twˇo
 7553           liˇneˇ thˇree
 7554           liˇne foˇur"#
 7555    ));
 7556
 7557    cx.update_editor(|editor, window, cx| {
 7558        editor.add_selection_above(&Default::default(), window, cx);
 7559    });
 7560
 7561    // test new added selection expands above and existing selection shrinks
 7562    cx.assert_editor_state(indoc!(
 7563        r#"line onˇe
 7564           liˇneˇ twˇo
 7565           liˇneˇ thˇree
 7566           line four"#
 7567    ));
 7568
 7569    cx.update_editor(|editor, window, cx| {
 7570        editor.add_selection_above(&Default::default(), window, cx);
 7571    });
 7572
 7573    // test new added selection expands above and existing selection shrinks
 7574    cx.assert_editor_state(indoc!(
 7575        r#"lineˇ onˇe
 7576           liˇneˇ twˇo
 7577           lineˇ three
 7578           line four"#
 7579    ));
 7580
 7581    // intial state with two selection groups
 7582    cx.set_state(indoc!(
 7583        r#"abcd
 7584           ef«ghˇ»
 7585           ijkl
 7586           «mˇ»nop"#
 7587    ));
 7588
 7589    cx.update_editor(|editor, window, cx| {
 7590        editor.add_selection_above(&Default::default(), window, cx);
 7591        editor.add_selection_above(&Default::default(), window, cx);
 7592    });
 7593
 7594    cx.assert_editor_state(indoc!(
 7595        r#"ab«cdˇ»
 7596           «eˇ»f«ghˇ»
 7597           «iˇ»jkl
 7598           «mˇ»nop"#
 7599    ));
 7600
 7601    // add single selection in middle - simulate opt drag
 7602    cx.update_editor(|editor, window, cx| {
 7603        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 7604        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7605        editor.update_selection(
 7606            DisplayPoint::new(DisplayRow(2), 4),
 7607            0,
 7608            gpui::Point::<f32>::default(),
 7609            window,
 7610            cx,
 7611        );
 7612        editor.end_selection(window, cx);
 7613    });
 7614
 7615    cx.assert_editor_state(indoc!(
 7616        r#"ab«cdˇ»
 7617           «eˇ»f«ghˇ»
 7618           «iˇ»jk«lˇ»
 7619           «mˇ»nop"#
 7620    ));
 7621
 7622    cx.update_editor(|editor, window, cx| {
 7623        editor.add_selection_below(&Default::default(), window, cx);
 7624    });
 7625
 7626    // test new added selection expands below, others shrinks from above
 7627    cx.assert_editor_state(indoc!(
 7628        r#"abcd
 7629           ef«ghˇ»
 7630           «iˇ»jk«lˇ»
 7631           «mˇ»no«pˇ»"#
 7632    ));
 7633}
 7634
 7635#[gpui::test]
 7636async fn test_select_next(cx: &mut TestAppContext) {
 7637    init_test(cx, |_| {});
 7638
 7639    let mut cx = EditorTestContext::new(cx).await;
 7640    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7641
 7642    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7643        .unwrap();
 7644    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7645
 7646    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7647        .unwrap();
 7648    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7649
 7650    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 7651    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 7652
 7653    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 7654    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 7655
 7656    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7657        .unwrap();
 7658    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7659
 7660    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7661        .unwrap();
 7662    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7663
 7664    // Test selection direction should be preserved
 7665    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 7666
 7667    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7668        .unwrap();
 7669    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 7670}
 7671
 7672#[gpui::test]
 7673async fn test_select_all_matches(cx: &mut TestAppContext) {
 7674    init_test(cx, |_| {});
 7675
 7676    let mut cx = EditorTestContext::new(cx).await;
 7677
 7678    // Test caret-only selections
 7679    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 7680    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7681        .unwrap();
 7682    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 7683
 7684    // Test left-to-right selections
 7685    cx.set_state("abc\n«abcˇ»\nabc");
 7686    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7687        .unwrap();
 7688    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 7689
 7690    // Test right-to-left selections
 7691    cx.set_state("abc\n«ˇabc»\nabc");
 7692    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7693        .unwrap();
 7694    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 7695
 7696    // Test selecting whitespace with caret selection
 7697    cx.set_state("abc\nˇ   abc\nabc");
 7698    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7699        .unwrap();
 7700    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 7701
 7702    // Test selecting whitespace with left-to-right selection
 7703    cx.set_state("abc\n«ˇ  »abc\nabc");
 7704    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7705        .unwrap();
 7706    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 7707
 7708    // Test no matches with right-to-left selection
 7709    cx.set_state("abc\n«  ˇ»abc\nabc");
 7710    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7711        .unwrap();
 7712    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 7713
 7714    // Test with a single word and clip_at_line_ends=true (#29823)
 7715    cx.set_state("aˇbc");
 7716    cx.update_editor(|e, window, cx| {
 7717        e.set_clip_at_line_ends(true, cx);
 7718        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 7719        e.set_clip_at_line_ends(false, cx);
 7720    });
 7721    cx.assert_editor_state("«abcˇ»");
 7722}
 7723
 7724#[gpui::test]
 7725async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 7726    init_test(cx, |_| {});
 7727
 7728    let mut cx = EditorTestContext::new(cx).await;
 7729
 7730    let large_body_1 = "\nd".repeat(200);
 7731    let large_body_2 = "\ne".repeat(200);
 7732
 7733    cx.set_state(&format!(
 7734        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 7735    ));
 7736    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 7737        let scroll_position = editor.scroll_position(cx);
 7738        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 7739        scroll_position
 7740    });
 7741
 7742    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 7743        .unwrap();
 7744    cx.assert_editor_state(&format!(
 7745        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 7746    ));
 7747    let scroll_position_after_selection =
 7748        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 7749    assert_eq!(
 7750        initial_scroll_position, scroll_position_after_selection,
 7751        "Scroll position should not change after selecting all matches"
 7752    );
 7753}
 7754
 7755#[gpui::test]
 7756async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 7757    init_test(cx, |_| {});
 7758
 7759    let mut cx = EditorLspTestContext::new_rust(
 7760        lsp::ServerCapabilities {
 7761            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7762            ..Default::default()
 7763        },
 7764        cx,
 7765    )
 7766    .await;
 7767
 7768    cx.set_state(indoc! {"
 7769        line 1
 7770        line 2
 7771        linˇe 3
 7772        line 4
 7773        line 5
 7774    "});
 7775
 7776    // Make an edit
 7777    cx.update_editor(|editor, window, cx| {
 7778        editor.handle_input("X", window, cx);
 7779    });
 7780
 7781    // Move cursor to a different position
 7782    cx.update_editor(|editor, window, cx| {
 7783        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7784            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 7785        });
 7786    });
 7787
 7788    cx.assert_editor_state(indoc! {"
 7789        line 1
 7790        line 2
 7791        linXe 3
 7792        line 4
 7793        liˇne 5
 7794    "});
 7795
 7796    cx.lsp
 7797        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 7798            Ok(Some(vec![lsp::TextEdit::new(
 7799                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 7800                "PREFIX ".to_string(),
 7801            )]))
 7802        });
 7803
 7804    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 7805        .unwrap()
 7806        .await
 7807        .unwrap();
 7808
 7809    cx.assert_editor_state(indoc! {"
 7810        PREFIX line 1
 7811        line 2
 7812        linXe 3
 7813        line 4
 7814        liˇne 5
 7815    "});
 7816
 7817    // Undo formatting
 7818    cx.update_editor(|editor, window, cx| {
 7819        editor.undo(&Default::default(), window, cx);
 7820    });
 7821
 7822    // Verify cursor moved back to position after edit
 7823    cx.assert_editor_state(indoc! {"
 7824        line 1
 7825        line 2
 7826        linXˇe 3
 7827        line 4
 7828        line 5
 7829    "});
 7830}
 7831
 7832#[gpui::test]
 7833async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 7834    init_test(cx, |_| {});
 7835
 7836    let mut cx = EditorTestContext::new(cx).await;
 7837
 7838    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 7839    cx.update_editor(|editor, window, cx| {
 7840        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 7841    });
 7842
 7843    cx.set_state(indoc! {"
 7844        line 1
 7845        line 2
 7846        linˇe 3
 7847        line 4
 7848        line 5
 7849        line 6
 7850        line 7
 7851        line 8
 7852        line 9
 7853        line 10
 7854    "});
 7855
 7856    let snapshot = cx.buffer_snapshot();
 7857    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 7858
 7859    cx.update(|_, cx| {
 7860        provider.update(cx, |provider, _| {
 7861            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 7862                id: None,
 7863                edits: vec![(edit_position..edit_position, "X".into())],
 7864                edit_preview: None,
 7865            }))
 7866        })
 7867    });
 7868
 7869    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 7870    cx.update_editor(|editor, window, cx| {
 7871        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 7872    });
 7873
 7874    cx.assert_editor_state(indoc! {"
 7875        line 1
 7876        line 2
 7877        lineXˇ 3
 7878        line 4
 7879        line 5
 7880        line 6
 7881        line 7
 7882        line 8
 7883        line 9
 7884        line 10
 7885    "});
 7886
 7887    cx.update_editor(|editor, window, cx| {
 7888        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7889            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 7890        });
 7891    });
 7892
 7893    cx.assert_editor_state(indoc! {"
 7894        line 1
 7895        line 2
 7896        lineX 3
 7897        line 4
 7898        line 5
 7899        line 6
 7900        line 7
 7901        line 8
 7902        line 9
 7903        liˇne 10
 7904    "});
 7905
 7906    cx.update_editor(|editor, window, cx| {
 7907        editor.undo(&Default::default(), window, cx);
 7908    });
 7909
 7910    cx.assert_editor_state(indoc! {"
 7911        line 1
 7912        line 2
 7913        lineˇ 3
 7914        line 4
 7915        line 5
 7916        line 6
 7917        line 7
 7918        line 8
 7919        line 9
 7920        line 10
 7921    "});
 7922}
 7923
 7924#[gpui::test]
 7925async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 7926    init_test(cx, |_| {});
 7927
 7928    let mut cx = EditorTestContext::new(cx).await;
 7929    cx.set_state(
 7930        r#"let foo = 2;
 7931lˇet foo = 2;
 7932let fooˇ = 2;
 7933let foo = 2;
 7934let foo = ˇ2;"#,
 7935    );
 7936
 7937    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7938        .unwrap();
 7939    cx.assert_editor_state(
 7940        r#"let foo = 2;
 7941«letˇ» foo = 2;
 7942let «fooˇ» = 2;
 7943let foo = 2;
 7944let foo = «2ˇ»;"#,
 7945    );
 7946
 7947    // noop for multiple selections with different contents
 7948    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7949        .unwrap();
 7950    cx.assert_editor_state(
 7951        r#"let foo = 2;
 7952«letˇ» foo = 2;
 7953let «fooˇ» = 2;
 7954let foo = 2;
 7955let foo = «2ˇ»;"#,
 7956    );
 7957
 7958    // Test last selection direction should be preserved
 7959    cx.set_state(
 7960        r#"let foo = 2;
 7961let foo = 2;
 7962let «fooˇ» = 2;
 7963let «ˇfoo» = 2;
 7964let foo = 2;"#,
 7965    );
 7966
 7967    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 7968        .unwrap();
 7969    cx.assert_editor_state(
 7970        r#"let foo = 2;
 7971let foo = 2;
 7972let «fooˇ» = 2;
 7973let «ˇfoo» = 2;
 7974let «ˇfoo» = 2;"#,
 7975    );
 7976}
 7977
 7978#[gpui::test]
 7979async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 7980    init_test(cx, |_| {});
 7981
 7982    let mut cx =
 7983        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 7984
 7985    cx.assert_editor_state(indoc! {"
 7986        ˇbbb
 7987        ccc
 7988
 7989        bbb
 7990        ccc
 7991        "});
 7992    cx.dispatch_action(SelectPrevious::default());
 7993    cx.assert_editor_state(indoc! {"
 7994                «bbbˇ»
 7995                ccc
 7996
 7997                bbb
 7998                ccc
 7999                "});
 8000    cx.dispatch_action(SelectPrevious::default());
 8001    cx.assert_editor_state(indoc! {"
 8002                «bbbˇ»
 8003                ccc
 8004
 8005                «bbbˇ»
 8006                ccc
 8007                "});
 8008}
 8009
 8010#[gpui::test]
 8011async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8012    init_test(cx, |_| {});
 8013
 8014    let mut cx = EditorTestContext::new(cx).await;
 8015    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8016
 8017    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8018        .unwrap();
 8019    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8020
 8021    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8022        .unwrap();
 8023    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8024
 8025    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8026    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8027
 8028    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8029    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8030
 8031    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8032        .unwrap();
 8033    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8034
 8035    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8036        .unwrap();
 8037    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8038}
 8039
 8040#[gpui::test]
 8041async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8042    init_test(cx, |_| {});
 8043
 8044    let mut cx = EditorTestContext::new(cx).await;
 8045    cx.set_state("");
 8046
 8047    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8048        .unwrap();
 8049    cx.assert_editor_state("«aˇ»");
 8050    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8051        .unwrap();
 8052    cx.assert_editor_state("«aˇ»");
 8053}
 8054
 8055#[gpui::test]
 8056async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8057    init_test(cx, |_| {});
 8058
 8059    let mut cx = EditorTestContext::new(cx).await;
 8060    cx.set_state(
 8061        r#"let foo = 2;
 8062lˇet foo = 2;
 8063let fooˇ = 2;
 8064let foo = 2;
 8065let foo = ˇ2;"#,
 8066    );
 8067
 8068    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8069        .unwrap();
 8070    cx.assert_editor_state(
 8071        r#"let foo = 2;
 8072«letˇ» foo = 2;
 8073let «fooˇ» = 2;
 8074let foo = 2;
 8075let foo = «2ˇ»;"#,
 8076    );
 8077
 8078    // noop for multiple selections with different contents
 8079    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8080        .unwrap();
 8081    cx.assert_editor_state(
 8082        r#"let foo = 2;
 8083«letˇ» foo = 2;
 8084let «fooˇ» = 2;
 8085let foo = 2;
 8086let foo = «2ˇ»;"#,
 8087    );
 8088}
 8089
 8090#[gpui::test]
 8091async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8092    init_test(cx, |_| {});
 8093
 8094    let mut cx = EditorTestContext::new(cx).await;
 8095    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8096
 8097    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8098        .unwrap();
 8099    // selection direction is preserved
 8100    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8101
 8102    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8103        .unwrap();
 8104    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8105
 8106    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8107    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8108
 8109    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8110    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8111
 8112    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8113        .unwrap();
 8114    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8115
 8116    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8117        .unwrap();
 8118    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8119}
 8120
 8121#[gpui::test]
 8122async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8123    init_test(cx, |_| {});
 8124
 8125    let language = Arc::new(Language::new(
 8126        LanguageConfig::default(),
 8127        Some(tree_sitter_rust::LANGUAGE.into()),
 8128    ));
 8129
 8130    let text = r#"
 8131        use mod1::mod2::{mod3, mod4};
 8132
 8133        fn fn_1(param1: bool, param2: &str) {
 8134            let var1 = "text";
 8135        }
 8136    "#
 8137    .unindent();
 8138
 8139    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8140    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8141    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8142
 8143    editor
 8144        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8145        .await;
 8146
 8147    editor.update_in(cx, |editor, window, cx| {
 8148        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8149            s.select_display_ranges([
 8150                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8151                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8152                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8153            ]);
 8154        });
 8155        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8156    });
 8157    editor.update(cx, |editor, cx| {
 8158        assert_text_with_selections(
 8159            editor,
 8160            indoc! {r#"
 8161                use mod1::mod2::{mod3, «mod4ˇ»};
 8162
 8163                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8164                    let var1 = "«ˇtext»";
 8165                }
 8166            "#},
 8167            cx,
 8168        );
 8169    });
 8170
 8171    editor.update_in(cx, |editor, window, cx| {
 8172        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8173    });
 8174    editor.update(cx, |editor, cx| {
 8175        assert_text_with_selections(
 8176            editor,
 8177            indoc! {r#"
 8178                use mod1::mod2::«{mod3, mod4}ˇ»;
 8179
 8180                «ˇfn fn_1(param1: bool, param2: &str) {
 8181                    let var1 = "text";
 8182 8183            "#},
 8184            cx,
 8185        );
 8186    });
 8187
 8188    editor.update_in(cx, |editor, window, cx| {
 8189        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8190    });
 8191    assert_eq!(
 8192        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8193        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8194    );
 8195
 8196    // Trying to expand the selected syntax node one more time has no effect.
 8197    editor.update_in(cx, |editor, window, cx| {
 8198        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8199    });
 8200    assert_eq!(
 8201        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8202        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8203    );
 8204
 8205    editor.update_in(cx, |editor, window, cx| {
 8206        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8207    });
 8208    editor.update(cx, |editor, cx| {
 8209        assert_text_with_selections(
 8210            editor,
 8211            indoc! {r#"
 8212                use mod1::mod2::«{mod3, mod4}ˇ»;
 8213
 8214                «ˇfn fn_1(param1: bool, param2: &str) {
 8215                    let var1 = "text";
 8216 8217            "#},
 8218            cx,
 8219        );
 8220    });
 8221
 8222    editor.update_in(cx, |editor, window, cx| {
 8223        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8224    });
 8225    editor.update(cx, |editor, cx| {
 8226        assert_text_with_selections(
 8227            editor,
 8228            indoc! {r#"
 8229                use mod1::mod2::{mod3, «mod4ˇ»};
 8230
 8231                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8232                    let var1 = "«ˇtext»";
 8233                }
 8234            "#},
 8235            cx,
 8236        );
 8237    });
 8238
 8239    editor.update_in(cx, |editor, window, cx| {
 8240        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8241    });
 8242    editor.update(cx, |editor, cx| {
 8243        assert_text_with_selections(
 8244            editor,
 8245            indoc! {r#"
 8246                use mod1::mod2::{mod3, mo«ˇ»d4};
 8247
 8248                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8249                    let var1 = "te«ˇ»xt";
 8250                }
 8251            "#},
 8252            cx,
 8253        );
 8254    });
 8255
 8256    // Trying to shrink the selected syntax node one more time has no effect.
 8257    editor.update_in(cx, |editor, window, cx| {
 8258        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8259    });
 8260    editor.update_in(cx, |editor, _, cx| {
 8261        assert_text_with_selections(
 8262            editor,
 8263            indoc! {r#"
 8264                use mod1::mod2::{mod3, mo«ˇ»d4};
 8265
 8266                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8267                    let var1 = "te«ˇ»xt";
 8268                }
 8269            "#},
 8270            cx,
 8271        );
 8272    });
 8273
 8274    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8275    // a fold.
 8276    editor.update_in(cx, |editor, window, cx| {
 8277        editor.fold_creases(
 8278            vec![
 8279                Crease::simple(
 8280                    Point::new(0, 21)..Point::new(0, 24),
 8281                    FoldPlaceholder::test(),
 8282                ),
 8283                Crease::simple(
 8284                    Point::new(3, 20)..Point::new(3, 22),
 8285                    FoldPlaceholder::test(),
 8286                ),
 8287            ],
 8288            true,
 8289            window,
 8290            cx,
 8291        );
 8292        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8293    });
 8294    editor.update(cx, |editor, cx| {
 8295        assert_text_with_selections(
 8296            editor,
 8297            indoc! {r#"
 8298                use mod1::mod2::«{mod3, mod4}ˇ»;
 8299
 8300                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8301                    let var1 = "«ˇtext»";
 8302                }
 8303            "#},
 8304            cx,
 8305        );
 8306    });
 8307}
 8308
 8309#[gpui::test]
 8310async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8311    init_test(cx, |_| {});
 8312
 8313    let language = Arc::new(Language::new(
 8314        LanguageConfig::default(),
 8315        Some(tree_sitter_rust::LANGUAGE.into()),
 8316    ));
 8317
 8318    let text = "let a = 2;";
 8319
 8320    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8321    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8322    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8323
 8324    editor
 8325        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8326        .await;
 8327
 8328    // Test case 1: Cursor at end of word
 8329    editor.update_in(cx, |editor, window, cx| {
 8330        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8331            s.select_display_ranges([
 8332                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8333            ]);
 8334        });
 8335    });
 8336    editor.update(cx, |editor, cx| {
 8337        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8338    });
 8339    editor.update_in(cx, |editor, window, cx| {
 8340        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8341    });
 8342    editor.update(cx, |editor, cx| {
 8343        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8344    });
 8345    editor.update_in(cx, |editor, window, cx| {
 8346        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8347    });
 8348    editor.update(cx, |editor, cx| {
 8349        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8350    });
 8351
 8352    // Test case 2: Cursor at end of statement
 8353    editor.update_in(cx, |editor, window, cx| {
 8354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8355            s.select_display_ranges([
 8356                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8357            ]);
 8358        });
 8359    });
 8360    editor.update(cx, |editor, cx| {
 8361        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8362    });
 8363    editor.update_in(cx, |editor, window, cx| {
 8364        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8365    });
 8366    editor.update(cx, |editor, cx| {
 8367        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8368    });
 8369}
 8370
 8371#[gpui::test]
 8372async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8373    init_test(cx, |_| {});
 8374
 8375    let language = Arc::new(Language::new(
 8376        LanguageConfig::default(),
 8377        Some(tree_sitter_rust::LANGUAGE.into()),
 8378    ));
 8379
 8380    let text = r#"
 8381        use mod1::mod2::{mod3, mod4};
 8382
 8383        fn fn_1(param1: bool, param2: &str) {
 8384            let var1 = "hello world";
 8385        }
 8386    "#
 8387    .unindent();
 8388
 8389    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8390    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8391    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8392
 8393    editor
 8394        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8395        .await;
 8396
 8397    // Test 1: Cursor on a letter of a string word
 8398    editor.update_in(cx, |editor, window, cx| {
 8399        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8400            s.select_display_ranges([
 8401                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8402            ]);
 8403        });
 8404    });
 8405    editor.update_in(cx, |editor, window, cx| {
 8406        assert_text_with_selections(
 8407            editor,
 8408            indoc! {r#"
 8409                use mod1::mod2::{mod3, mod4};
 8410
 8411                fn fn_1(param1: bool, param2: &str) {
 8412                    let var1 = "hˇello world";
 8413                }
 8414            "#},
 8415            cx,
 8416        );
 8417        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8418        assert_text_with_selections(
 8419            editor,
 8420            indoc! {r#"
 8421                use mod1::mod2::{mod3, mod4};
 8422
 8423                fn fn_1(param1: bool, param2: &str) {
 8424                    let var1 = "«ˇhello» world";
 8425                }
 8426            "#},
 8427            cx,
 8428        );
 8429    });
 8430
 8431    // Test 2: Partial selection within a word
 8432    editor.update_in(cx, |editor, window, cx| {
 8433        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8434            s.select_display_ranges([
 8435                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 8436            ]);
 8437        });
 8438    });
 8439    editor.update_in(cx, |editor, window, cx| {
 8440        assert_text_with_selections(
 8441            editor,
 8442            indoc! {r#"
 8443                use mod1::mod2::{mod3, mod4};
 8444
 8445                fn fn_1(param1: bool, param2: &str) {
 8446                    let var1 = "h«elˇ»lo world";
 8447                }
 8448            "#},
 8449            cx,
 8450        );
 8451        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8452        assert_text_with_selections(
 8453            editor,
 8454            indoc! {r#"
 8455                use mod1::mod2::{mod3, mod4};
 8456
 8457                fn fn_1(param1: bool, param2: &str) {
 8458                    let var1 = "«ˇhello» world";
 8459                }
 8460            "#},
 8461            cx,
 8462        );
 8463    });
 8464
 8465    // Test 3: Complete word already selected
 8466    editor.update_in(cx, |editor, window, cx| {
 8467        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8468            s.select_display_ranges([
 8469                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 8470            ]);
 8471        });
 8472    });
 8473    editor.update_in(cx, |editor, window, cx| {
 8474        assert_text_with_selections(
 8475            editor,
 8476            indoc! {r#"
 8477                use mod1::mod2::{mod3, mod4};
 8478
 8479                fn fn_1(param1: bool, param2: &str) {
 8480                    let var1 = "«helloˇ» world";
 8481                }
 8482            "#},
 8483            cx,
 8484        );
 8485        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8486        assert_text_with_selections(
 8487            editor,
 8488            indoc! {r#"
 8489                use mod1::mod2::{mod3, mod4};
 8490
 8491                fn fn_1(param1: bool, param2: &str) {
 8492                    let var1 = "«hello worldˇ»";
 8493                }
 8494            "#},
 8495            cx,
 8496        );
 8497    });
 8498
 8499    // Test 4: Selection spanning across words
 8500    editor.update_in(cx, |editor, window, cx| {
 8501        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8502            s.select_display_ranges([
 8503                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 8504            ]);
 8505        });
 8506    });
 8507    editor.update_in(cx, |editor, window, cx| {
 8508        assert_text_with_selections(
 8509            editor,
 8510            indoc! {r#"
 8511                use mod1::mod2::{mod3, mod4};
 8512
 8513                fn fn_1(param1: bool, param2: &str) {
 8514                    let var1 = "hel«lo woˇ»rld";
 8515                }
 8516            "#},
 8517            cx,
 8518        );
 8519        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8520        assert_text_with_selections(
 8521            editor,
 8522            indoc! {r#"
 8523                use mod1::mod2::{mod3, mod4};
 8524
 8525                fn fn_1(param1: bool, param2: &str) {
 8526                    let var1 = "«ˇhello world»";
 8527                }
 8528            "#},
 8529            cx,
 8530        );
 8531    });
 8532
 8533    // Test 5: Expansion beyond string
 8534    editor.update_in(cx, |editor, window, cx| {
 8535        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8536        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8537        assert_text_with_selections(
 8538            editor,
 8539            indoc! {r#"
 8540                use mod1::mod2::{mod3, mod4};
 8541
 8542                fn fn_1(param1: bool, param2: &str) {
 8543                    «ˇlet var1 = "hello world";»
 8544                }
 8545            "#},
 8546            cx,
 8547        );
 8548    });
 8549}
 8550
 8551#[gpui::test]
 8552async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 8553    init_test(cx, |_| {});
 8554
 8555    let mut cx = EditorTestContext::new(cx).await;
 8556
 8557    let language = Arc::new(Language::new(
 8558        LanguageConfig::default(),
 8559        Some(tree_sitter_rust::LANGUAGE.into()),
 8560    ));
 8561
 8562    cx.update_buffer(|buffer, cx| {
 8563        buffer.set_language(Some(language), cx);
 8564    });
 8565
 8566    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 8567    cx.update_editor(|editor, window, cx| {
 8568        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 8569    });
 8570
 8571    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 8572}
 8573
 8574#[gpui::test]
 8575async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 8576    init_test(cx, |_| {});
 8577
 8578    let base_text = r#"
 8579        impl A {
 8580            // this is an uncommitted comment
 8581
 8582            fn b() {
 8583                c();
 8584            }
 8585
 8586            // this is another uncommitted comment
 8587
 8588            fn d() {
 8589                // e
 8590                // f
 8591            }
 8592        }
 8593
 8594        fn g() {
 8595            // h
 8596        }
 8597    "#
 8598    .unindent();
 8599
 8600    let text = r#"
 8601        ˇimpl A {
 8602
 8603            fn b() {
 8604                c();
 8605            }
 8606
 8607            fn d() {
 8608                // e
 8609                // f
 8610            }
 8611        }
 8612
 8613        fn g() {
 8614            // h
 8615        }
 8616    "#
 8617    .unindent();
 8618
 8619    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8620    cx.set_state(&text);
 8621    cx.set_head_text(&base_text);
 8622    cx.update_editor(|editor, window, cx| {
 8623        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 8624    });
 8625
 8626    cx.assert_state_with_diff(
 8627        "
 8628        ˇimpl A {
 8629      -     // this is an uncommitted comment
 8630
 8631            fn b() {
 8632                c();
 8633            }
 8634
 8635      -     // this is another uncommitted comment
 8636      -
 8637            fn d() {
 8638                // e
 8639                // f
 8640            }
 8641        }
 8642
 8643        fn g() {
 8644            // h
 8645        }
 8646    "
 8647        .unindent(),
 8648    );
 8649
 8650    let expected_display_text = "
 8651        impl A {
 8652            // this is an uncommitted comment
 8653
 8654            fn b() {
 8655 8656            }
 8657
 8658            // this is another uncommitted comment
 8659
 8660            fn d() {
 8661 8662            }
 8663        }
 8664
 8665        fn g() {
 8666 8667        }
 8668        "
 8669    .unindent();
 8670
 8671    cx.update_editor(|editor, window, cx| {
 8672        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 8673        assert_eq!(editor.display_text(cx), expected_display_text);
 8674    });
 8675}
 8676
 8677#[gpui::test]
 8678async fn test_autoindent(cx: &mut TestAppContext) {
 8679    init_test(cx, |_| {});
 8680
 8681    let language = Arc::new(
 8682        Language::new(
 8683            LanguageConfig {
 8684                brackets: BracketPairConfig {
 8685                    pairs: vec![
 8686                        BracketPair {
 8687                            start: "{".to_string(),
 8688                            end: "}".to_string(),
 8689                            close: false,
 8690                            surround: false,
 8691                            newline: true,
 8692                        },
 8693                        BracketPair {
 8694                            start: "(".to_string(),
 8695                            end: ")".to_string(),
 8696                            close: false,
 8697                            surround: false,
 8698                            newline: true,
 8699                        },
 8700                    ],
 8701                    ..Default::default()
 8702                },
 8703                ..Default::default()
 8704            },
 8705            Some(tree_sitter_rust::LANGUAGE.into()),
 8706        )
 8707        .with_indents_query(
 8708            r#"
 8709                (_ "(" ")" @end) @indent
 8710                (_ "{" "}" @end) @indent
 8711            "#,
 8712        )
 8713        .unwrap(),
 8714    );
 8715
 8716    let text = "fn a() {}";
 8717
 8718    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8719    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8720    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8721    editor
 8722        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8723        .await;
 8724
 8725    editor.update_in(cx, |editor, window, cx| {
 8726        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8727            s.select_ranges([5..5, 8..8, 9..9])
 8728        });
 8729        editor.newline(&Newline, window, cx);
 8730        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 8731        assert_eq!(
 8732            editor.selections.ranges(cx),
 8733            &[
 8734                Point::new(1, 4)..Point::new(1, 4),
 8735                Point::new(3, 4)..Point::new(3, 4),
 8736                Point::new(5, 0)..Point::new(5, 0)
 8737            ]
 8738        );
 8739    });
 8740}
 8741
 8742#[gpui::test]
 8743async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 8744    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 8745
 8746    let language = Arc::new(
 8747        Language::new(
 8748            LanguageConfig {
 8749                brackets: BracketPairConfig {
 8750                    pairs: vec![
 8751                        BracketPair {
 8752                            start: "{".to_string(),
 8753                            end: "}".to_string(),
 8754                            close: false,
 8755                            surround: false,
 8756                            newline: true,
 8757                        },
 8758                        BracketPair {
 8759                            start: "(".to_string(),
 8760                            end: ")".to_string(),
 8761                            close: false,
 8762                            surround: false,
 8763                            newline: true,
 8764                        },
 8765                    ],
 8766                    ..Default::default()
 8767                },
 8768                ..Default::default()
 8769            },
 8770            Some(tree_sitter_rust::LANGUAGE.into()),
 8771        )
 8772        .with_indents_query(
 8773            r#"
 8774                (_ "(" ")" @end) @indent
 8775                (_ "{" "}" @end) @indent
 8776            "#,
 8777        )
 8778        .unwrap(),
 8779    );
 8780
 8781    let text = "fn a() {}";
 8782
 8783    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8784    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8785    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8786    editor
 8787        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8788        .await;
 8789
 8790    editor.update_in(cx, |editor, window, cx| {
 8791        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8792            s.select_ranges([5..5, 8..8, 9..9])
 8793        });
 8794        editor.newline(&Newline, window, cx);
 8795        assert_eq!(
 8796            editor.text(cx),
 8797            indoc!(
 8798                "
 8799                fn a(
 8800
 8801                ) {
 8802
 8803                }
 8804                "
 8805            )
 8806        );
 8807        assert_eq!(
 8808            editor.selections.ranges(cx),
 8809            &[
 8810                Point::new(1, 0)..Point::new(1, 0),
 8811                Point::new(3, 0)..Point::new(3, 0),
 8812                Point::new(5, 0)..Point::new(5, 0)
 8813            ]
 8814        );
 8815    });
 8816}
 8817
 8818#[gpui::test]
 8819async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 8820    init_test(cx, |settings| {
 8821        settings.defaults.auto_indent = Some(true);
 8822        settings.languages.0.insert(
 8823            "python".into(),
 8824            LanguageSettingsContent {
 8825                auto_indent: Some(false),
 8826                ..Default::default()
 8827            },
 8828        );
 8829    });
 8830
 8831    let mut cx = EditorTestContext::new(cx).await;
 8832
 8833    let injected_language = Arc::new(
 8834        Language::new(
 8835            LanguageConfig {
 8836                brackets: BracketPairConfig {
 8837                    pairs: vec![
 8838                        BracketPair {
 8839                            start: "{".to_string(),
 8840                            end: "}".to_string(),
 8841                            close: false,
 8842                            surround: false,
 8843                            newline: true,
 8844                        },
 8845                        BracketPair {
 8846                            start: "(".to_string(),
 8847                            end: ")".to_string(),
 8848                            close: true,
 8849                            surround: false,
 8850                            newline: true,
 8851                        },
 8852                    ],
 8853                    ..Default::default()
 8854                },
 8855                name: "python".into(),
 8856                ..Default::default()
 8857            },
 8858            Some(tree_sitter_python::LANGUAGE.into()),
 8859        )
 8860        .with_indents_query(
 8861            r#"
 8862                (_ "(" ")" @end) @indent
 8863                (_ "{" "}" @end) @indent
 8864            "#,
 8865        )
 8866        .unwrap(),
 8867    );
 8868
 8869    let language = Arc::new(
 8870        Language::new(
 8871            LanguageConfig {
 8872                brackets: BracketPairConfig {
 8873                    pairs: vec![
 8874                        BracketPair {
 8875                            start: "{".to_string(),
 8876                            end: "}".to_string(),
 8877                            close: false,
 8878                            surround: false,
 8879                            newline: true,
 8880                        },
 8881                        BracketPair {
 8882                            start: "(".to_string(),
 8883                            end: ")".to_string(),
 8884                            close: true,
 8885                            surround: false,
 8886                            newline: true,
 8887                        },
 8888                    ],
 8889                    ..Default::default()
 8890                },
 8891                name: LanguageName::new("rust"),
 8892                ..Default::default()
 8893            },
 8894            Some(tree_sitter_rust::LANGUAGE.into()),
 8895        )
 8896        .with_indents_query(
 8897            r#"
 8898                (_ "(" ")" @end) @indent
 8899                (_ "{" "}" @end) @indent
 8900            "#,
 8901        )
 8902        .unwrap()
 8903        .with_injection_query(
 8904            r#"
 8905            (macro_invocation
 8906                macro: (identifier) @_macro_name
 8907                (token_tree) @injection.content
 8908                (#set! injection.language "python"))
 8909           "#,
 8910        )
 8911        .unwrap(),
 8912    );
 8913
 8914    cx.language_registry().add(injected_language);
 8915    cx.language_registry().add(language.clone());
 8916
 8917    cx.update_buffer(|buffer, cx| {
 8918        buffer.set_language(Some(language), cx);
 8919    });
 8920
 8921    cx.set_state(r#"struct A {ˇ}"#);
 8922
 8923    cx.update_editor(|editor, window, cx| {
 8924        editor.newline(&Default::default(), window, cx);
 8925    });
 8926
 8927    cx.assert_editor_state(indoc!(
 8928        "struct A {
 8929            ˇ
 8930        }"
 8931    ));
 8932
 8933    cx.set_state(r#"select_biased!(ˇ)"#);
 8934
 8935    cx.update_editor(|editor, window, cx| {
 8936        editor.newline(&Default::default(), window, cx);
 8937        editor.handle_input("def ", window, cx);
 8938        editor.handle_input("(", window, cx);
 8939        editor.newline(&Default::default(), window, cx);
 8940        editor.handle_input("a", window, cx);
 8941    });
 8942
 8943    cx.assert_editor_state(indoc!(
 8944        "select_biased!(
 8945        def (
 8946 8947        )
 8948        )"
 8949    ));
 8950}
 8951
 8952#[gpui::test]
 8953async fn test_autoindent_selections(cx: &mut TestAppContext) {
 8954    init_test(cx, |_| {});
 8955
 8956    {
 8957        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 8958        cx.set_state(indoc! {"
 8959            impl A {
 8960
 8961                fn b() {}
 8962
 8963            «fn c() {
 8964
 8965            }ˇ»
 8966            }
 8967        "});
 8968
 8969        cx.update_editor(|editor, window, cx| {
 8970            editor.autoindent(&Default::default(), window, cx);
 8971        });
 8972
 8973        cx.assert_editor_state(indoc! {"
 8974            impl A {
 8975
 8976                fn b() {}
 8977
 8978                «fn c() {
 8979
 8980                }ˇ»
 8981            }
 8982        "});
 8983    }
 8984
 8985    {
 8986        let mut cx = EditorTestContext::new_multibuffer(
 8987            cx,
 8988            [indoc! { "
 8989                impl A {
 8990                «
 8991                // a
 8992                fn b(){}
 8993                »
 8994                «
 8995                    }
 8996                    fn c(){}
 8997                »
 8998            "}],
 8999        );
 9000
 9001        let buffer = cx.update_editor(|editor, _, cx| {
 9002            let buffer = editor.buffer().update(cx, |buffer, _| {
 9003                buffer.all_buffers().iter().next().unwrap().clone()
 9004            });
 9005            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9006            buffer
 9007        });
 9008
 9009        cx.run_until_parked();
 9010        cx.update_editor(|editor, window, cx| {
 9011            editor.select_all(&Default::default(), window, cx);
 9012            editor.autoindent(&Default::default(), window, cx)
 9013        });
 9014        cx.run_until_parked();
 9015
 9016        cx.update(|_, cx| {
 9017            assert_eq!(
 9018                buffer.read(cx).text(),
 9019                indoc! { "
 9020                    impl A {
 9021
 9022                        // a
 9023                        fn b(){}
 9024
 9025
 9026                    }
 9027                    fn c(){}
 9028
 9029                " }
 9030            )
 9031        });
 9032    }
 9033}
 9034
 9035#[gpui::test]
 9036async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9037    init_test(cx, |_| {});
 9038
 9039    let mut cx = EditorTestContext::new(cx).await;
 9040
 9041    let language = Arc::new(Language::new(
 9042        LanguageConfig {
 9043            brackets: BracketPairConfig {
 9044                pairs: vec![
 9045                    BracketPair {
 9046                        start: "{".to_string(),
 9047                        end: "}".to_string(),
 9048                        close: true,
 9049                        surround: true,
 9050                        newline: true,
 9051                    },
 9052                    BracketPair {
 9053                        start: "(".to_string(),
 9054                        end: ")".to_string(),
 9055                        close: true,
 9056                        surround: true,
 9057                        newline: true,
 9058                    },
 9059                    BracketPair {
 9060                        start: "/*".to_string(),
 9061                        end: " */".to_string(),
 9062                        close: true,
 9063                        surround: true,
 9064                        newline: true,
 9065                    },
 9066                    BracketPair {
 9067                        start: "[".to_string(),
 9068                        end: "]".to_string(),
 9069                        close: false,
 9070                        surround: false,
 9071                        newline: true,
 9072                    },
 9073                    BracketPair {
 9074                        start: "\"".to_string(),
 9075                        end: "\"".to_string(),
 9076                        close: true,
 9077                        surround: true,
 9078                        newline: false,
 9079                    },
 9080                    BracketPair {
 9081                        start: "<".to_string(),
 9082                        end: ">".to_string(),
 9083                        close: false,
 9084                        surround: true,
 9085                        newline: true,
 9086                    },
 9087                ],
 9088                ..Default::default()
 9089            },
 9090            autoclose_before: "})]".to_string(),
 9091            ..Default::default()
 9092        },
 9093        Some(tree_sitter_rust::LANGUAGE.into()),
 9094    ));
 9095
 9096    cx.language_registry().add(language.clone());
 9097    cx.update_buffer(|buffer, cx| {
 9098        buffer.set_language(Some(language), cx);
 9099    });
 9100
 9101    cx.set_state(
 9102        &r#"
 9103            🏀ˇ
 9104            εˇ
 9105            ❤️ˇ
 9106        "#
 9107        .unindent(),
 9108    );
 9109
 9110    // autoclose multiple nested brackets at multiple cursors
 9111    cx.update_editor(|editor, window, cx| {
 9112        editor.handle_input("{", window, cx);
 9113        editor.handle_input("{", window, cx);
 9114        editor.handle_input("{", window, cx);
 9115    });
 9116    cx.assert_editor_state(
 9117        &"
 9118            🏀{{{ˇ}}}
 9119            ε{{{ˇ}}}
 9120            ❤️{{{ˇ}}}
 9121        "
 9122        .unindent(),
 9123    );
 9124
 9125    // insert a different closing bracket
 9126    cx.update_editor(|editor, window, cx| {
 9127        editor.handle_input(")", window, cx);
 9128    });
 9129    cx.assert_editor_state(
 9130        &"
 9131            🏀{{{)ˇ}}}
 9132            ε{{{)ˇ}}}
 9133            ❤️{{{)ˇ}}}
 9134        "
 9135        .unindent(),
 9136    );
 9137
 9138    // skip over the auto-closed brackets when typing a closing bracket
 9139    cx.update_editor(|editor, window, cx| {
 9140        editor.move_right(&MoveRight, window, cx);
 9141        editor.handle_input("}", window, cx);
 9142        editor.handle_input("}", window, cx);
 9143        editor.handle_input("}", window, cx);
 9144    });
 9145    cx.assert_editor_state(
 9146        &"
 9147            🏀{{{)}}}}ˇ
 9148            ε{{{)}}}}ˇ
 9149            ❤️{{{)}}}}ˇ
 9150        "
 9151        .unindent(),
 9152    );
 9153
 9154    // autoclose multi-character pairs
 9155    cx.set_state(
 9156        &"
 9157            ˇ
 9158            ˇ
 9159        "
 9160        .unindent(),
 9161    );
 9162    cx.update_editor(|editor, window, cx| {
 9163        editor.handle_input("/", window, cx);
 9164        editor.handle_input("*", window, cx);
 9165    });
 9166    cx.assert_editor_state(
 9167        &"
 9168            /*ˇ */
 9169            /*ˇ */
 9170        "
 9171        .unindent(),
 9172    );
 9173
 9174    // one cursor autocloses a multi-character pair, one cursor
 9175    // does not autoclose.
 9176    cx.set_state(
 9177        &"
 9178 9179            ˇ
 9180        "
 9181        .unindent(),
 9182    );
 9183    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9184    cx.assert_editor_state(
 9185        &"
 9186            /*ˇ */
 9187 9188        "
 9189        .unindent(),
 9190    );
 9191
 9192    // Don't autoclose if the next character isn't whitespace and isn't
 9193    // listed in the language's "autoclose_before" section.
 9194    cx.set_state("ˇa b");
 9195    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9196    cx.assert_editor_state("{ˇa b");
 9197
 9198    // Don't autoclose if `close` is false for the bracket pair
 9199    cx.set_state("ˇ");
 9200    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9201    cx.assert_editor_state("");
 9202
 9203    // Surround with brackets if text is selected
 9204    cx.set_state("«aˇ» b");
 9205    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9206    cx.assert_editor_state("{«aˇ»} b");
 9207
 9208    // Autoclose when not immediately after a word character
 9209    cx.set_state("a ˇ");
 9210    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9211    cx.assert_editor_state("a \"ˇ\"");
 9212
 9213    // Autoclose pair where the start and end characters are the same
 9214    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9215    cx.assert_editor_state("a \"\"ˇ");
 9216
 9217    // Don't autoclose when immediately after a word character
 9218    cx.set_state("");
 9219    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9220    cx.assert_editor_state("a\"ˇ");
 9221
 9222    // Do autoclose when after a non-word character
 9223    cx.set_state("");
 9224    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9225    cx.assert_editor_state("{\"ˇ\"");
 9226
 9227    // Non identical pairs autoclose regardless of preceding character
 9228    cx.set_state("");
 9229    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9230    cx.assert_editor_state("a{ˇ}");
 9231
 9232    // Don't autoclose pair if autoclose is disabled
 9233    cx.set_state("ˇ");
 9234    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9235    cx.assert_editor_state("");
 9236
 9237    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9238    cx.set_state("«aˇ» b");
 9239    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9240    cx.assert_editor_state("<«aˇ»> b");
 9241}
 9242
 9243#[gpui::test]
 9244async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9245    init_test(cx, |settings| {
 9246        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9247    });
 9248
 9249    let mut cx = EditorTestContext::new(cx).await;
 9250
 9251    let language = Arc::new(Language::new(
 9252        LanguageConfig {
 9253            brackets: BracketPairConfig {
 9254                pairs: vec![
 9255                    BracketPair {
 9256                        start: "{".to_string(),
 9257                        end: "}".to_string(),
 9258                        close: true,
 9259                        surround: true,
 9260                        newline: true,
 9261                    },
 9262                    BracketPair {
 9263                        start: "(".to_string(),
 9264                        end: ")".to_string(),
 9265                        close: true,
 9266                        surround: true,
 9267                        newline: true,
 9268                    },
 9269                    BracketPair {
 9270                        start: "[".to_string(),
 9271                        end: "]".to_string(),
 9272                        close: false,
 9273                        surround: false,
 9274                        newline: true,
 9275                    },
 9276                ],
 9277                ..Default::default()
 9278            },
 9279            autoclose_before: "})]".to_string(),
 9280            ..Default::default()
 9281        },
 9282        Some(tree_sitter_rust::LANGUAGE.into()),
 9283    ));
 9284
 9285    cx.language_registry().add(language.clone());
 9286    cx.update_buffer(|buffer, cx| {
 9287        buffer.set_language(Some(language), cx);
 9288    });
 9289
 9290    cx.set_state(
 9291        &"
 9292            ˇ
 9293            ˇ
 9294            ˇ
 9295        "
 9296        .unindent(),
 9297    );
 9298
 9299    // ensure only matching closing brackets are skipped over
 9300    cx.update_editor(|editor, window, cx| {
 9301        editor.handle_input("}", window, cx);
 9302        editor.move_left(&MoveLeft, window, cx);
 9303        editor.handle_input(")", window, cx);
 9304        editor.move_left(&MoveLeft, window, cx);
 9305    });
 9306    cx.assert_editor_state(
 9307        &"
 9308            ˇ)}
 9309            ˇ)}
 9310            ˇ)}
 9311        "
 9312        .unindent(),
 9313    );
 9314
 9315    // skip-over closing brackets at multiple cursors
 9316    cx.update_editor(|editor, window, cx| {
 9317        editor.handle_input(")", window, cx);
 9318        editor.handle_input("}", window, cx);
 9319    });
 9320    cx.assert_editor_state(
 9321        &"
 9322            )}ˇ
 9323            )}ˇ
 9324            )}ˇ
 9325        "
 9326        .unindent(),
 9327    );
 9328
 9329    // ignore non-close brackets
 9330    cx.update_editor(|editor, window, cx| {
 9331        editor.handle_input("]", window, cx);
 9332        editor.move_left(&MoveLeft, window, cx);
 9333        editor.handle_input("]", window, cx);
 9334    });
 9335    cx.assert_editor_state(
 9336        &"
 9337            )}]ˇ]
 9338            )}]ˇ]
 9339            )}]ˇ]
 9340        "
 9341        .unindent(),
 9342    );
 9343}
 9344
 9345#[gpui::test]
 9346async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9347    init_test(cx, |_| {});
 9348
 9349    let mut cx = EditorTestContext::new(cx).await;
 9350
 9351    let html_language = Arc::new(
 9352        Language::new(
 9353            LanguageConfig {
 9354                name: "HTML".into(),
 9355                brackets: BracketPairConfig {
 9356                    pairs: vec![
 9357                        BracketPair {
 9358                            start: "<".into(),
 9359                            end: ">".into(),
 9360                            close: true,
 9361                            ..Default::default()
 9362                        },
 9363                        BracketPair {
 9364                            start: "{".into(),
 9365                            end: "}".into(),
 9366                            close: true,
 9367                            ..Default::default()
 9368                        },
 9369                        BracketPair {
 9370                            start: "(".into(),
 9371                            end: ")".into(),
 9372                            close: true,
 9373                            ..Default::default()
 9374                        },
 9375                    ],
 9376                    ..Default::default()
 9377                },
 9378                autoclose_before: "})]>".into(),
 9379                ..Default::default()
 9380            },
 9381            Some(tree_sitter_html::LANGUAGE.into()),
 9382        )
 9383        .with_injection_query(
 9384            r#"
 9385            (script_element
 9386                (raw_text) @injection.content
 9387                (#set! injection.language "javascript"))
 9388            "#,
 9389        )
 9390        .unwrap(),
 9391    );
 9392
 9393    let javascript_language = Arc::new(Language::new(
 9394        LanguageConfig {
 9395            name: "JavaScript".into(),
 9396            brackets: BracketPairConfig {
 9397                pairs: vec![
 9398                    BracketPair {
 9399                        start: "/*".into(),
 9400                        end: " */".into(),
 9401                        close: true,
 9402                        ..Default::default()
 9403                    },
 9404                    BracketPair {
 9405                        start: "{".into(),
 9406                        end: "}".into(),
 9407                        close: true,
 9408                        ..Default::default()
 9409                    },
 9410                    BracketPair {
 9411                        start: "(".into(),
 9412                        end: ")".into(),
 9413                        close: true,
 9414                        ..Default::default()
 9415                    },
 9416                ],
 9417                ..Default::default()
 9418            },
 9419            autoclose_before: "})]>".into(),
 9420            ..Default::default()
 9421        },
 9422        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9423    ));
 9424
 9425    cx.language_registry().add(html_language.clone());
 9426    cx.language_registry().add(javascript_language);
 9427    cx.executor().run_until_parked();
 9428
 9429    cx.update_buffer(|buffer, cx| {
 9430        buffer.set_language(Some(html_language), cx);
 9431    });
 9432
 9433    cx.set_state(
 9434        &r#"
 9435            <body>ˇ
 9436                <script>
 9437                    var x = 1;ˇ
 9438                </script>
 9439            </body>ˇ
 9440        "#
 9441        .unindent(),
 9442    );
 9443
 9444    // Precondition: different languages are active at different locations.
 9445    cx.update_editor(|editor, window, cx| {
 9446        let snapshot = editor.snapshot(window, cx);
 9447        let cursors = editor.selections.ranges::<usize>(cx);
 9448        let languages = cursors
 9449            .iter()
 9450            .map(|c| snapshot.language_at(c.start).unwrap().name())
 9451            .collect::<Vec<_>>();
 9452        assert_eq!(
 9453            languages,
 9454            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 9455        );
 9456    });
 9457
 9458    // Angle brackets autoclose in HTML, but not JavaScript.
 9459    cx.update_editor(|editor, window, cx| {
 9460        editor.handle_input("<", window, cx);
 9461        editor.handle_input("a", window, cx);
 9462    });
 9463    cx.assert_editor_state(
 9464        &r#"
 9465            <body><aˇ>
 9466                <script>
 9467                    var x = 1;<aˇ
 9468                </script>
 9469            </body><aˇ>
 9470        "#
 9471        .unindent(),
 9472    );
 9473
 9474    // Curly braces and parens autoclose in both HTML and JavaScript.
 9475    cx.update_editor(|editor, window, cx| {
 9476        editor.handle_input(" b=", window, cx);
 9477        editor.handle_input("{", window, cx);
 9478        editor.handle_input("c", window, cx);
 9479        editor.handle_input("(", window, cx);
 9480    });
 9481    cx.assert_editor_state(
 9482        &r#"
 9483            <body><a b={c(ˇ)}>
 9484                <script>
 9485                    var x = 1;<a b={c(ˇ)}
 9486                </script>
 9487            </body><a b={c(ˇ)}>
 9488        "#
 9489        .unindent(),
 9490    );
 9491
 9492    // Brackets that were already autoclosed are skipped.
 9493    cx.update_editor(|editor, window, cx| {
 9494        editor.handle_input(")", window, cx);
 9495        editor.handle_input("d", window, cx);
 9496        editor.handle_input("}", window, cx);
 9497    });
 9498    cx.assert_editor_state(
 9499        &r#"
 9500            <body><a b={c()d}ˇ>
 9501                <script>
 9502                    var x = 1;<a b={c()d}ˇ
 9503                </script>
 9504            </body><a b={c()d}ˇ>
 9505        "#
 9506        .unindent(),
 9507    );
 9508    cx.update_editor(|editor, window, cx| {
 9509        editor.handle_input(">", window, cx);
 9510    });
 9511    cx.assert_editor_state(
 9512        &r#"
 9513            <body><a b={c()d}>ˇ
 9514                <script>
 9515                    var x = 1;<a b={c()d}>ˇ
 9516                </script>
 9517            </body><a b={c()d}>ˇ
 9518        "#
 9519        .unindent(),
 9520    );
 9521
 9522    // Reset
 9523    cx.set_state(
 9524        &r#"
 9525            <body>ˇ
 9526                <script>
 9527                    var x = 1;ˇ
 9528                </script>
 9529            </body>ˇ
 9530        "#
 9531        .unindent(),
 9532    );
 9533
 9534    cx.update_editor(|editor, window, cx| {
 9535        editor.handle_input("<", window, cx);
 9536    });
 9537    cx.assert_editor_state(
 9538        &r#"
 9539            <body><ˇ>
 9540                <script>
 9541                    var x = 1;<ˇ
 9542                </script>
 9543            </body><ˇ>
 9544        "#
 9545        .unindent(),
 9546    );
 9547
 9548    // When backspacing, the closing angle brackets are removed.
 9549    cx.update_editor(|editor, window, cx| {
 9550        editor.backspace(&Backspace, window, cx);
 9551    });
 9552    cx.assert_editor_state(
 9553        &r#"
 9554            <body>ˇ
 9555                <script>
 9556                    var x = 1;ˇ
 9557                </script>
 9558            </body>ˇ
 9559        "#
 9560        .unindent(),
 9561    );
 9562
 9563    // Block comments autoclose in JavaScript, but not HTML.
 9564    cx.update_editor(|editor, window, cx| {
 9565        editor.handle_input("/", window, cx);
 9566        editor.handle_input("*", window, cx);
 9567    });
 9568    cx.assert_editor_state(
 9569        &r#"
 9570            <body>/*ˇ
 9571                <script>
 9572                    var x = 1;/*ˇ */
 9573                </script>
 9574            </body>/*ˇ
 9575        "#
 9576        .unindent(),
 9577    );
 9578}
 9579
 9580#[gpui::test]
 9581async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 9582    init_test(cx, |_| {});
 9583
 9584    let mut cx = EditorTestContext::new(cx).await;
 9585
 9586    let rust_language = Arc::new(
 9587        Language::new(
 9588            LanguageConfig {
 9589                name: "Rust".into(),
 9590                brackets: serde_json::from_value(json!([
 9591                    { "start": "{", "end": "}", "close": true, "newline": true },
 9592                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 9593                ]))
 9594                .unwrap(),
 9595                autoclose_before: "})]>".into(),
 9596                ..Default::default()
 9597            },
 9598            Some(tree_sitter_rust::LANGUAGE.into()),
 9599        )
 9600        .with_override_query("(string_literal) @string")
 9601        .unwrap(),
 9602    );
 9603
 9604    cx.language_registry().add(rust_language.clone());
 9605    cx.update_buffer(|buffer, cx| {
 9606        buffer.set_language(Some(rust_language), cx);
 9607    });
 9608
 9609    cx.set_state(
 9610        &r#"
 9611            let x = ˇ
 9612        "#
 9613        .unindent(),
 9614    );
 9615
 9616    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 9617    cx.update_editor(|editor, window, cx| {
 9618        editor.handle_input("\"", window, cx);
 9619    });
 9620    cx.assert_editor_state(
 9621        &r#"
 9622            let x = "ˇ"
 9623        "#
 9624        .unindent(),
 9625    );
 9626
 9627    // Inserting another quotation mark. The cursor moves across the existing
 9628    // automatically-inserted quotation mark.
 9629    cx.update_editor(|editor, window, cx| {
 9630        editor.handle_input("\"", window, cx);
 9631    });
 9632    cx.assert_editor_state(
 9633        &r#"
 9634            let x = ""ˇ
 9635        "#
 9636        .unindent(),
 9637    );
 9638
 9639    // Reset
 9640    cx.set_state(
 9641        &r#"
 9642            let x = ˇ
 9643        "#
 9644        .unindent(),
 9645    );
 9646
 9647    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 9648    cx.update_editor(|editor, window, cx| {
 9649        editor.handle_input("\"", window, cx);
 9650        editor.handle_input(" ", window, cx);
 9651        editor.move_left(&Default::default(), window, cx);
 9652        editor.handle_input("\\", window, cx);
 9653        editor.handle_input("\"", window, cx);
 9654    });
 9655    cx.assert_editor_state(
 9656        &r#"
 9657            let x = "\"ˇ "
 9658        "#
 9659        .unindent(),
 9660    );
 9661
 9662    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 9663    // mark. Nothing is inserted.
 9664    cx.update_editor(|editor, window, cx| {
 9665        editor.move_right(&Default::default(), window, cx);
 9666        editor.handle_input("\"", window, cx);
 9667    });
 9668    cx.assert_editor_state(
 9669        &r#"
 9670            let x = "\" "ˇ
 9671        "#
 9672        .unindent(),
 9673    );
 9674}
 9675
 9676#[gpui::test]
 9677async fn test_surround_with_pair(cx: &mut TestAppContext) {
 9678    init_test(cx, |_| {});
 9679
 9680    let language = Arc::new(Language::new(
 9681        LanguageConfig {
 9682            brackets: BracketPairConfig {
 9683                pairs: vec![
 9684                    BracketPair {
 9685                        start: "{".to_string(),
 9686                        end: "}".to_string(),
 9687                        close: true,
 9688                        surround: true,
 9689                        newline: true,
 9690                    },
 9691                    BracketPair {
 9692                        start: "/* ".to_string(),
 9693                        end: "*/".to_string(),
 9694                        close: true,
 9695                        surround: true,
 9696                        ..Default::default()
 9697                    },
 9698                ],
 9699                ..Default::default()
 9700            },
 9701            ..Default::default()
 9702        },
 9703        Some(tree_sitter_rust::LANGUAGE.into()),
 9704    ));
 9705
 9706    let text = r#"
 9707        a
 9708        b
 9709        c
 9710    "#
 9711    .unindent();
 9712
 9713    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9714    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9715    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9716    editor
 9717        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9718        .await;
 9719
 9720    editor.update_in(cx, |editor, window, cx| {
 9721        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9722            s.select_display_ranges([
 9723                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9724                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9725                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 9726            ])
 9727        });
 9728
 9729        editor.handle_input("{", window, cx);
 9730        editor.handle_input("{", window, cx);
 9731        editor.handle_input("{", window, cx);
 9732        assert_eq!(
 9733            editor.text(cx),
 9734            "
 9735                {{{a}}}
 9736                {{{b}}}
 9737                {{{c}}}
 9738            "
 9739            .unindent()
 9740        );
 9741        assert_eq!(
 9742            editor.selections.display_ranges(cx),
 9743            [
 9744                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 9745                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 9746                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 9747            ]
 9748        );
 9749
 9750        editor.undo(&Undo, window, cx);
 9751        editor.undo(&Undo, window, cx);
 9752        editor.undo(&Undo, window, cx);
 9753        assert_eq!(
 9754            editor.text(cx),
 9755            "
 9756                a
 9757                b
 9758                c
 9759            "
 9760            .unindent()
 9761        );
 9762        assert_eq!(
 9763            editor.selections.display_ranges(cx),
 9764            [
 9765                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9766                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9767                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9768            ]
 9769        );
 9770
 9771        // Ensure inserting the first character of a multi-byte bracket pair
 9772        // doesn't surround the selections with the bracket.
 9773        editor.handle_input("/", window, cx);
 9774        assert_eq!(
 9775            editor.text(cx),
 9776            "
 9777                /
 9778                /
 9779                /
 9780            "
 9781            .unindent()
 9782        );
 9783        assert_eq!(
 9784            editor.selections.display_ranges(cx),
 9785            [
 9786                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9787                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9788                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9789            ]
 9790        );
 9791
 9792        editor.undo(&Undo, window, cx);
 9793        assert_eq!(
 9794            editor.text(cx),
 9795            "
 9796                a
 9797                b
 9798                c
 9799            "
 9800            .unindent()
 9801        );
 9802        assert_eq!(
 9803            editor.selections.display_ranges(cx),
 9804            [
 9805                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 9806                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 9807                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 9808            ]
 9809        );
 9810
 9811        // Ensure inserting the last character of a multi-byte bracket pair
 9812        // doesn't surround the selections with the bracket.
 9813        editor.handle_input("*", window, cx);
 9814        assert_eq!(
 9815            editor.text(cx),
 9816            "
 9817                *
 9818                *
 9819                *
 9820            "
 9821            .unindent()
 9822        );
 9823        assert_eq!(
 9824            editor.selections.display_ranges(cx),
 9825            [
 9826                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 9827                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 9828                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 9829            ]
 9830        );
 9831    });
 9832}
 9833
 9834#[gpui::test]
 9835async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 9836    init_test(cx, |_| {});
 9837
 9838    let language = Arc::new(Language::new(
 9839        LanguageConfig {
 9840            brackets: BracketPairConfig {
 9841                pairs: vec![BracketPair {
 9842                    start: "{".to_string(),
 9843                    end: "}".to_string(),
 9844                    close: true,
 9845                    surround: true,
 9846                    newline: true,
 9847                }],
 9848                ..Default::default()
 9849            },
 9850            autoclose_before: "}".to_string(),
 9851            ..Default::default()
 9852        },
 9853        Some(tree_sitter_rust::LANGUAGE.into()),
 9854    ));
 9855
 9856    let text = r#"
 9857        a
 9858        b
 9859        c
 9860    "#
 9861    .unindent();
 9862
 9863    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9864    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9865    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9866    editor
 9867        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9868        .await;
 9869
 9870    editor.update_in(cx, |editor, window, cx| {
 9871        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9872            s.select_ranges([
 9873                Point::new(0, 1)..Point::new(0, 1),
 9874                Point::new(1, 1)..Point::new(1, 1),
 9875                Point::new(2, 1)..Point::new(2, 1),
 9876            ])
 9877        });
 9878
 9879        editor.handle_input("{", window, cx);
 9880        editor.handle_input("{", window, cx);
 9881        editor.handle_input("_", window, cx);
 9882        assert_eq!(
 9883            editor.text(cx),
 9884            "
 9885                a{{_}}
 9886                b{{_}}
 9887                c{{_}}
 9888            "
 9889            .unindent()
 9890        );
 9891        assert_eq!(
 9892            editor.selections.ranges::<Point>(cx),
 9893            [
 9894                Point::new(0, 4)..Point::new(0, 4),
 9895                Point::new(1, 4)..Point::new(1, 4),
 9896                Point::new(2, 4)..Point::new(2, 4)
 9897            ]
 9898        );
 9899
 9900        editor.backspace(&Default::default(), window, cx);
 9901        editor.backspace(&Default::default(), window, cx);
 9902        assert_eq!(
 9903            editor.text(cx),
 9904            "
 9905                a{}
 9906                b{}
 9907                c{}
 9908            "
 9909            .unindent()
 9910        );
 9911        assert_eq!(
 9912            editor.selections.ranges::<Point>(cx),
 9913            [
 9914                Point::new(0, 2)..Point::new(0, 2),
 9915                Point::new(1, 2)..Point::new(1, 2),
 9916                Point::new(2, 2)..Point::new(2, 2)
 9917            ]
 9918        );
 9919
 9920        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 9921        assert_eq!(
 9922            editor.text(cx),
 9923            "
 9924                a
 9925                b
 9926                c
 9927            "
 9928            .unindent()
 9929        );
 9930        assert_eq!(
 9931            editor.selections.ranges::<Point>(cx),
 9932            [
 9933                Point::new(0, 1)..Point::new(0, 1),
 9934                Point::new(1, 1)..Point::new(1, 1),
 9935                Point::new(2, 1)..Point::new(2, 1)
 9936            ]
 9937        );
 9938    });
 9939}
 9940
 9941#[gpui::test]
 9942async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 9943    init_test(cx, |settings| {
 9944        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9945    });
 9946
 9947    let mut cx = EditorTestContext::new(cx).await;
 9948
 9949    let language = Arc::new(Language::new(
 9950        LanguageConfig {
 9951            brackets: BracketPairConfig {
 9952                pairs: vec![
 9953                    BracketPair {
 9954                        start: "{".to_string(),
 9955                        end: "}".to_string(),
 9956                        close: true,
 9957                        surround: true,
 9958                        newline: true,
 9959                    },
 9960                    BracketPair {
 9961                        start: "(".to_string(),
 9962                        end: ")".to_string(),
 9963                        close: true,
 9964                        surround: true,
 9965                        newline: true,
 9966                    },
 9967                    BracketPair {
 9968                        start: "[".to_string(),
 9969                        end: "]".to_string(),
 9970                        close: false,
 9971                        surround: true,
 9972                        newline: true,
 9973                    },
 9974                ],
 9975                ..Default::default()
 9976            },
 9977            autoclose_before: "})]".to_string(),
 9978            ..Default::default()
 9979        },
 9980        Some(tree_sitter_rust::LANGUAGE.into()),
 9981    ));
 9982
 9983    cx.language_registry().add(language.clone());
 9984    cx.update_buffer(|buffer, cx| {
 9985        buffer.set_language(Some(language), cx);
 9986    });
 9987
 9988    cx.set_state(
 9989        &"
 9990            {(ˇ)}
 9991            [[ˇ]]
 9992            {(ˇ)}
 9993        "
 9994        .unindent(),
 9995    );
 9996
 9997    cx.update_editor(|editor, window, cx| {
 9998        editor.backspace(&Default::default(), window, cx);
 9999        editor.backspace(&Default::default(), window, cx);
10000    });
10001
10002    cx.assert_editor_state(
10003        &"
10004            ˇ
10005            ˇ]]
10006            ˇ
10007        "
10008        .unindent(),
10009    );
10010
10011    cx.update_editor(|editor, window, cx| {
10012        editor.handle_input("{", window, cx);
10013        editor.handle_input("{", window, cx);
10014        editor.move_right(&MoveRight, window, cx);
10015        editor.move_right(&MoveRight, window, cx);
10016        editor.move_left(&MoveLeft, window, cx);
10017        editor.move_left(&MoveLeft, window, cx);
10018        editor.backspace(&Default::default(), window, cx);
10019    });
10020
10021    cx.assert_editor_state(
10022        &"
10023            {ˇ}
10024            {ˇ}]]
10025            {ˇ}
10026        "
10027        .unindent(),
10028    );
10029
10030    cx.update_editor(|editor, window, cx| {
10031        editor.backspace(&Default::default(), window, cx);
10032    });
10033
10034    cx.assert_editor_state(
10035        &"
10036            ˇ
10037            ˇ]]
10038            ˇ
10039        "
10040        .unindent(),
10041    );
10042}
10043
10044#[gpui::test]
10045async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10046    init_test(cx, |_| {});
10047
10048    let language = Arc::new(Language::new(
10049        LanguageConfig::default(),
10050        Some(tree_sitter_rust::LANGUAGE.into()),
10051    ));
10052
10053    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10054    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10055    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10056    editor
10057        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10058        .await;
10059
10060    editor.update_in(cx, |editor, window, cx| {
10061        editor.set_auto_replace_emoji_shortcode(true);
10062
10063        editor.handle_input("Hello ", window, cx);
10064        editor.handle_input(":wave", window, cx);
10065        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10066
10067        editor.handle_input(":", window, cx);
10068        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10069
10070        editor.handle_input(" :smile", window, cx);
10071        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10072
10073        editor.handle_input(":", window, cx);
10074        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10075
10076        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10077        editor.handle_input(":wave", window, cx);
10078        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10079
10080        editor.handle_input(":", window, cx);
10081        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10082
10083        editor.handle_input(":1", window, cx);
10084        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10085
10086        editor.handle_input(":", window, cx);
10087        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10088
10089        // Ensure shortcode does not get replaced when it is part of a word
10090        editor.handle_input(" Test:wave", window, cx);
10091        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10092
10093        editor.handle_input(":", window, cx);
10094        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10095
10096        editor.set_auto_replace_emoji_shortcode(false);
10097
10098        // Ensure shortcode does not get replaced when auto replace is off
10099        editor.handle_input(" :wave", window, cx);
10100        assert_eq!(
10101            editor.text(cx),
10102            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10103        );
10104
10105        editor.handle_input(":", window, cx);
10106        assert_eq!(
10107            editor.text(cx),
10108            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10109        );
10110    });
10111}
10112
10113#[gpui::test]
10114async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10115    init_test(cx, |_| {});
10116
10117    let (text, insertion_ranges) = marked_text_ranges(
10118        indoc! {"
10119            ˇ
10120        "},
10121        false,
10122    );
10123
10124    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10125    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10126
10127    _ = editor.update_in(cx, |editor, window, cx| {
10128        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10129
10130        editor
10131            .insert_snippet(&insertion_ranges, snippet, window, cx)
10132            .unwrap();
10133
10134        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10135            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10136            assert_eq!(editor.text(cx), expected_text);
10137            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10138        }
10139
10140        assert(
10141            editor,
10142            cx,
10143            indoc! {"
10144            type «» =•
10145            "},
10146        );
10147
10148        assert!(editor.context_menu_visible(), "There should be a matches");
10149    });
10150}
10151
10152#[gpui::test]
10153async fn test_snippets(cx: &mut TestAppContext) {
10154    init_test(cx, |_| {});
10155
10156    let mut cx = EditorTestContext::new(cx).await;
10157
10158    cx.set_state(indoc! {"
10159        a.ˇ b
10160        a.ˇ b
10161        a.ˇ b
10162    "});
10163
10164    cx.update_editor(|editor, window, cx| {
10165        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10166        let insertion_ranges = editor
10167            .selections
10168            .all(cx)
10169            .iter()
10170            .map(|s| s.range())
10171            .collect::<Vec<_>>();
10172        editor
10173            .insert_snippet(&insertion_ranges, snippet, window, cx)
10174            .unwrap();
10175    });
10176
10177    cx.assert_editor_state(indoc! {"
10178        a.f(«oneˇ», two, «threeˇ») b
10179        a.f(«oneˇ», two, «threeˇ») b
10180        a.f(«oneˇ», two, «threeˇ») b
10181    "});
10182
10183    // Can't move earlier than the first tab stop
10184    cx.update_editor(|editor, window, cx| {
10185        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10186    });
10187    cx.assert_editor_state(indoc! {"
10188        a.f(«oneˇ», two, «threeˇ») b
10189        a.f(«oneˇ», two, «threeˇ») b
10190        a.f(«oneˇ», two, «threeˇ») b
10191    "});
10192
10193    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10194    cx.assert_editor_state(indoc! {"
10195        a.f(one, «twoˇ», three) b
10196        a.f(one, «twoˇ», three) b
10197        a.f(one, «twoˇ», three) b
10198    "});
10199
10200    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10201    cx.assert_editor_state(indoc! {"
10202        a.f(«oneˇ», two, «threeˇ») b
10203        a.f(«oneˇ», two, «threeˇ») b
10204        a.f(«oneˇ», two, «threeˇ») b
10205    "});
10206
10207    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10208    cx.assert_editor_state(indoc! {"
10209        a.f(one, «twoˇ», three) b
10210        a.f(one, «twoˇ», three) b
10211        a.f(one, «twoˇ», three) b
10212    "});
10213    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10214    cx.assert_editor_state(indoc! {"
10215        a.f(one, two, three)ˇ b
10216        a.f(one, two, three)ˇ b
10217        a.f(one, two, three)ˇ b
10218    "});
10219
10220    // As soon as the last tab stop is reached, snippet state is gone
10221    cx.update_editor(|editor, window, cx| {
10222        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10223    });
10224    cx.assert_editor_state(indoc! {"
10225        a.f(one, two, three)ˇ b
10226        a.f(one, two, three)ˇ b
10227        a.f(one, two, three)ˇ b
10228    "});
10229}
10230
10231#[gpui::test]
10232async fn test_snippet_indentation(cx: &mut TestAppContext) {
10233    init_test(cx, |_| {});
10234
10235    let mut cx = EditorTestContext::new(cx).await;
10236
10237    cx.update_editor(|editor, window, cx| {
10238        let snippet = Snippet::parse(indoc! {"
10239            /*
10240             * Multiline comment with leading indentation
10241             *
10242             * $1
10243             */
10244            $0"})
10245        .unwrap();
10246        let insertion_ranges = editor
10247            .selections
10248            .all(cx)
10249            .iter()
10250            .map(|s| s.range())
10251            .collect::<Vec<_>>();
10252        editor
10253            .insert_snippet(&insertion_ranges, snippet, window, cx)
10254            .unwrap();
10255    });
10256
10257    cx.assert_editor_state(indoc! {"
10258        /*
10259         * Multiline comment with leading indentation
10260         *
10261         * ˇ
10262         */
10263    "});
10264
10265    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10266    cx.assert_editor_state(indoc! {"
10267        /*
10268         * Multiline comment with leading indentation
10269         *
10270         *•
10271         */
10272        ˇ"});
10273}
10274
10275#[gpui::test]
10276async fn test_document_format_during_save(cx: &mut TestAppContext) {
10277    init_test(cx, |_| {});
10278
10279    let fs = FakeFs::new(cx.executor());
10280    fs.insert_file(path!("/file.rs"), Default::default()).await;
10281
10282    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10283
10284    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10285    language_registry.add(rust_lang());
10286    let mut fake_servers = language_registry.register_fake_lsp(
10287        "Rust",
10288        FakeLspAdapter {
10289            capabilities: lsp::ServerCapabilities {
10290                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10291                ..Default::default()
10292            },
10293            ..Default::default()
10294        },
10295    );
10296
10297    let buffer = project
10298        .update(cx, |project, cx| {
10299            project.open_local_buffer(path!("/file.rs"), cx)
10300        })
10301        .await
10302        .unwrap();
10303
10304    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10305    let (editor, cx) = cx.add_window_view(|window, cx| {
10306        build_editor_with_project(project.clone(), buffer, window, cx)
10307    });
10308    editor.update_in(cx, |editor, window, cx| {
10309        editor.set_text("one\ntwo\nthree\n", window, cx)
10310    });
10311    assert!(cx.read(|cx| editor.is_dirty(cx)));
10312
10313    cx.executor().start_waiting();
10314    let fake_server = fake_servers.next().await.unwrap();
10315
10316    {
10317        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10318            move |params, _| async move {
10319                assert_eq!(
10320                    params.text_document.uri,
10321                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10322                );
10323                assert_eq!(params.options.tab_size, 4);
10324                Ok(Some(vec![lsp::TextEdit::new(
10325                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10326                    ", ".to_string(),
10327                )]))
10328            },
10329        );
10330        let save = editor
10331            .update_in(cx, |editor, window, cx| {
10332                editor.save(
10333                    SaveOptions {
10334                        format: true,
10335                        autosave: false,
10336                    },
10337                    project.clone(),
10338                    window,
10339                    cx,
10340                )
10341            })
10342            .unwrap();
10343        cx.executor().start_waiting();
10344        save.await;
10345
10346        assert_eq!(
10347            editor.update(cx, |editor, cx| editor.text(cx)),
10348            "one, two\nthree\n"
10349        );
10350        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10351    }
10352
10353    {
10354        editor.update_in(cx, |editor, window, cx| {
10355            editor.set_text("one\ntwo\nthree\n", window, cx)
10356        });
10357        assert!(cx.read(|cx| editor.is_dirty(cx)));
10358
10359        // Ensure we can still save even if formatting hangs.
10360        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10361            move |params, _| async move {
10362                assert_eq!(
10363                    params.text_document.uri,
10364                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10365                );
10366                futures::future::pending::<()>().await;
10367                unreachable!()
10368            },
10369        );
10370        let save = editor
10371            .update_in(cx, |editor, window, cx| {
10372                editor.save(
10373                    SaveOptions {
10374                        format: true,
10375                        autosave: false,
10376                    },
10377                    project.clone(),
10378                    window,
10379                    cx,
10380                )
10381            })
10382            .unwrap();
10383        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10384        cx.executor().start_waiting();
10385        save.await;
10386        assert_eq!(
10387            editor.update(cx, |editor, cx| editor.text(cx)),
10388            "one\ntwo\nthree\n"
10389        );
10390    }
10391
10392    // Set rust language override and assert overridden tabsize is sent to language server
10393    update_test_language_settings(cx, |settings| {
10394        settings.languages.0.insert(
10395            "Rust".into(),
10396            LanguageSettingsContent {
10397                tab_size: NonZeroU32::new(8),
10398                ..Default::default()
10399            },
10400        );
10401    });
10402
10403    {
10404        editor.update_in(cx, |editor, window, cx| {
10405            editor.set_text("somehting_new\n", window, cx)
10406        });
10407        assert!(cx.read(|cx| editor.is_dirty(cx)));
10408        let _formatting_request_signal = fake_server
10409            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10410                assert_eq!(
10411                    params.text_document.uri,
10412                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10413                );
10414                assert_eq!(params.options.tab_size, 8);
10415                Ok(Some(vec![]))
10416            });
10417        let save = editor
10418            .update_in(cx, |editor, window, cx| {
10419                editor.save(
10420                    SaveOptions {
10421                        format: true,
10422                        autosave: false,
10423                    },
10424                    project.clone(),
10425                    window,
10426                    cx,
10427                )
10428            })
10429            .unwrap();
10430        cx.executor().start_waiting();
10431        save.await;
10432    }
10433}
10434
10435#[gpui::test]
10436async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
10437    init_test(cx, |settings| {
10438        settings.defaults.ensure_final_newline_on_save = Some(false);
10439    });
10440
10441    let fs = FakeFs::new(cx.executor());
10442    fs.insert_file(path!("/file.txt"), "foo".into()).await;
10443
10444    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
10445
10446    let buffer = project
10447        .update(cx, |project, cx| {
10448            project.open_local_buffer(path!("/file.txt"), cx)
10449        })
10450        .await
10451        .unwrap();
10452
10453    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10454    let (editor, cx) = cx.add_window_view(|window, cx| {
10455        build_editor_with_project(project.clone(), buffer, window, cx)
10456    });
10457    editor.update_in(cx, |editor, window, cx| {
10458        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
10459            s.select_ranges([0..0])
10460        });
10461    });
10462    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10463
10464    editor.update_in(cx, |editor, window, cx| {
10465        editor.handle_input("\n", window, cx)
10466    });
10467    cx.run_until_parked();
10468    save(&editor, &project, cx).await;
10469    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10470
10471    editor.update_in(cx, |editor, window, cx| {
10472        editor.undo(&Default::default(), window, cx);
10473    });
10474    save(&editor, &project, cx).await;
10475    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10476
10477    editor.update_in(cx, |editor, window, cx| {
10478        editor.redo(&Default::default(), window, cx);
10479    });
10480    cx.run_until_parked();
10481    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
10482
10483    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
10484        let save = editor
10485            .update_in(cx, |editor, window, cx| {
10486                editor.save(
10487                    SaveOptions {
10488                        format: true,
10489                        autosave: false,
10490                    },
10491                    project.clone(),
10492                    window,
10493                    cx,
10494                )
10495            })
10496            .unwrap();
10497        cx.executor().start_waiting();
10498        save.await;
10499        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10500    }
10501}
10502
10503#[gpui::test]
10504async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
10505    init_test(cx, |_| {});
10506
10507    let cols = 4;
10508    let rows = 10;
10509    let sample_text_1 = sample_text(rows, cols, 'a');
10510    assert_eq!(
10511        sample_text_1,
10512        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
10513    );
10514    let sample_text_2 = sample_text(rows, cols, 'l');
10515    assert_eq!(
10516        sample_text_2,
10517        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
10518    );
10519    let sample_text_3 = sample_text(rows, cols, 'v');
10520    assert_eq!(
10521        sample_text_3,
10522        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
10523    );
10524
10525    let fs = FakeFs::new(cx.executor());
10526    fs.insert_tree(
10527        path!("/a"),
10528        json!({
10529            "main.rs": sample_text_1,
10530            "other.rs": sample_text_2,
10531            "lib.rs": sample_text_3,
10532        }),
10533    )
10534    .await;
10535
10536    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
10537    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10538    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10539
10540    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10541    language_registry.add(rust_lang());
10542    let mut fake_servers = language_registry.register_fake_lsp(
10543        "Rust",
10544        FakeLspAdapter {
10545            capabilities: lsp::ServerCapabilities {
10546                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10547                ..Default::default()
10548            },
10549            ..Default::default()
10550        },
10551    );
10552
10553    let worktree = project.update(cx, |project, cx| {
10554        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
10555        assert_eq!(worktrees.len(), 1);
10556        worktrees.pop().unwrap()
10557    });
10558    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10559
10560    let buffer_1 = project
10561        .update(cx, |project, cx| {
10562            project.open_buffer((worktree_id, "main.rs"), cx)
10563        })
10564        .await
10565        .unwrap();
10566    let buffer_2 = project
10567        .update(cx, |project, cx| {
10568            project.open_buffer((worktree_id, "other.rs"), cx)
10569        })
10570        .await
10571        .unwrap();
10572    let buffer_3 = project
10573        .update(cx, |project, cx| {
10574            project.open_buffer((worktree_id, "lib.rs"), cx)
10575        })
10576        .await
10577        .unwrap();
10578
10579    let multi_buffer = cx.new(|cx| {
10580        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10581        multi_buffer.push_excerpts(
10582            buffer_1.clone(),
10583            [
10584                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10585                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10586                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10587            ],
10588            cx,
10589        );
10590        multi_buffer.push_excerpts(
10591            buffer_2.clone(),
10592            [
10593                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10594                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10595                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10596            ],
10597            cx,
10598        );
10599        multi_buffer.push_excerpts(
10600            buffer_3.clone(),
10601            [
10602                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
10603                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
10604                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
10605            ],
10606            cx,
10607        );
10608        multi_buffer
10609    });
10610    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
10611        Editor::new(
10612            EditorMode::full(),
10613            multi_buffer,
10614            Some(project.clone()),
10615            window,
10616            cx,
10617        )
10618    });
10619
10620    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10621        editor.change_selections(
10622            SelectionEffects::scroll(Autoscroll::Next),
10623            window,
10624            cx,
10625            |s| s.select_ranges(Some(1..2)),
10626        );
10627        editor.insert("|one|two|three|", window, cx);
10628    });
10629    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10630    multi_buffer_editor.update_in(cx, |editor, window, cx| {
10631        editor.change_selections(
10632            SelectionEffects::scroll(Autoscroll::Next),
10633            window,
10634            cx,
10635            |s| s.select_ranges(Some(60..70)),
10636        );
10637        editor.insert("|four|five|six|", window, cx);
10638    });
10639    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
10640
10641    // First two buffers should be edited, but not the third one.
10642    assert_eq!(
10643        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10644        "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}",
10645    );
10646    buffer_1.update(cx, |buffer, _| {
10647        assert!(buffer.is_dirty());
10648        assert_eq!(
10649            buffer.text(),
10650            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
10651        )
10652    });
10653    buffer_2.update(cx, |buffer, _| {
10654        assert!(buffer.is_dirty());
10655        assert_eq!(
10656            buffer.text(),
10657            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
10658        )
10659    });
10660    buffer_3.update(cx, |buffer, _| {
10661        assert!(!buffer.is_dirty());
10662        assert_eq!(buffer.text(), sample_text_3,)
10663    });
10664    cx.executor().run_until_parked();
10665
10666    cx.executor().start_waiting();
10667    let save = multi_buffer_editor
10668        .update_in(cx, |editor, window, cx| {
10669            editor.save(
10670                SaveOptions {
10671                    format: true,
10672                    autosave: false,
10673                },
10674                project.clone(),
10675                window,
10676                cx,
10677            )
10678        })
10679        .unwrap();
10680
10681    let fake_server = fake_servers.next().await.unwrap();
10682    fake_server
10683        .server
10684        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
10685            Ok(Some(vec![lsp::TextEdit::new(
10686                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10687                format!("[{} formatted]", params.text_document.uri),
10688            )]))
10689        })
10690        .detach();
10691    save.await;
10692
10693    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
10694    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
10695    assert_eq!(
10696        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
10697        uri!(
10698            "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}"
10699        ),
10700    );
10701    buffer_1.update(cx, |buffer, _| {
10702        assert!(!buffer.is_dirty());
10703        assert_eq!(
10704            buffer.text(),
10705            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
10706        )
10707    });
10708    buffer_2.update(cx, |buffer, _| {
10709        assert!(!buffer.is_dirty());
10710        assert_eq!(
10711            buffer.text(),
10712            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
10713        )
10714    });
10715    buffer_3.update(cx, |buffer, _| {
10716        assert!(!buffer.is_dirty());
10717        assert_eq!(buffer.text(), sample_text_3,)
10718    });
10719}
10720
10721#[gpui::test]
10722async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
10723    init_test(cx, |_| {});
10724
10725    let fs = FakeFs::new(cx.executor());
10726    fs.insert_tree(
10727        path!("/dir"),
10728        json!({
10729            "file1.rs": "fn main() { println!(\"hello\"); }",
10730            "file2.rs": "fn test() { println!(\"test\"); }",
10731            "file3.rs": "fn other() { println!(\"other\"); }\n",
10732        }),
10733    )
10734    .await;
10735
10736    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
10737    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
10738    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
10739
10740    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10741    language_registry.add(rust_lang());
10742
10743    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
10744    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
10745
10746    // Open three buffers
10747    let buffer_1 = project
10748        .update(cx, |project, cx| {
10749            project.open_buffer((worktree_id, "file1.rs"), cx)
10750        })
10751        .await
10752        .unwrap();
10753    let buffer_2 = project
10754        .update(cx, |project, cx| {
10755            project.open_buffer((worktree_id, "file2.rs"), cx)
10756        })
10757        .await
10758        .unwrap();
10759    let buffer_3 = project
10760        .update(cx, |project, cx| {
10761            project.open_buffer((worktree_id, "file3.rs"), cx)
10762        })
10763        .await
10764        .unwrap();
10765
10766    // Create a multi-buffer with all three buffers
10767    let multi_buffer = cx.new(|cx| {
10768        let mut multi_buffer = MultiBuffer::new(ReadWrite);
10769        multi_buffer.push_excerpts(
10770            buffer_1.clone(),
10771            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10772            cx,
10773        );
10774        multi_buffer.push_excerpts(
10775            buffer_2.clone(),
10776            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10777            cx,
10778        );
10779        multi_buffer.push_excerpts(
10780            buffer_3.clone(),
10781            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
10782            cx,
10783        );
10784        multi_buffer
10785    });
10786
10787    let editor = cx.new_window_entity(|window, cx| {
10788        Editor::new(
10789            EditorMode::full(),
10790            multi_buffer,
10791            Some(project.clone()),
10792            window,
10793            cx,
10794        )
10795    });
10796
10797    // Edit only the first buffer
10798    editor.update_in(cx, |editor, window, cx| {
10799        editor.change_selections(
10800            SelectionEffects::scroll(Autoscroll::Next),
10801            window,
10802            cx,
10803            |s| s.select_ranges(Some(10..10)),
10804        );
10805        editor.insert("// edited", window, cx);
10806    });
10807
10808    // Verify that only buffer 1 is dirty
10809    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
10810    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10811    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10812
10813    // Get write counts after file creation (files were created with initial content)
10814    // We expect each file to have been written once during creation
10815    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
10816    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
10817    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
10818
10819    // Perform autosave
10820    let save_task = editor.update_in(cx, |editor, window, cx| {
10821        editor.save(
10822            SaveOptions {
10823                format: true,
10824                autosave: true,
10825            },
10826            project.clone(),
10827            window,
10828            cx,
10829        )
10830    });
10831    save_task.await.unwrap();
10832
10833    // Only the dirty buffer should have been saved
10834    assert_eq!(
10835        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10836        1,
10837        "Buffer 1 was dirty, so it should have been written once during autosave"
10838    );
10839    assert_eq!(
10840        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10841        0,
10842        "Buffer 2 was clean, so it should not have been written during autosave"
10843    );
10844    assert_eq!(
10845        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10846        0,
10847        "Buffer 3 was clean, so it should not have been written during autosave"
10848    );
10849
10850    // Verify buffer states after autosave
10851    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10852    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10853    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
10854
10855    // Now perform a manual save (format = true)
10856    let save_task = editor.update_in(cx, |editor, window, cx| {
10857        editor.save(
10858            SaveOptions {
10859                format: true,
10860                autosave: false,
10861            },
10862            project.clone(),
10863            window,
10864            cx,
10865        )
10866    });
10867    save_task.await.unwrap();
10868
10869    // During manual save, clean buffers don't get written to disk
10870    // They just get did_save called for language server notifications
10871    assert_eq!(
10872        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
10873        1,
10874        "Buffer 1 should only have been written once total (during autosave, not manual save)"
10875    );
10876    assert_eq!(
10877        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
10878        0,
10879        "Buffer 2 should not have been written at all"
10880    );
10881    assert_eq!(
10882        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
10883        0,
10884        "Buffer 3 should not have been written at all"
10885    );
10886}
10887
10888async fn setup_range_format_test(
10889    cx: &mut TestAppContext,
10890) -> (
10891    Entity<Project>,
10892    Entity<Editor>,
10893    &mut gpui::VisualTestContext,
10894    lsp::FakeLanguageServer,
10895) {
10896    init_test(cx, |_| {});
10897
10898    let fs = FakeFs::new(cx.executor());
10899    fs.insert_file(path!("/file.rs"), Default::default()).await;
10900
10901    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
10902
10903    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10904    language_registry.add(rust_lang());
10905    let mut fake_servers = language_registry.register_fake_lsp(
10906        "Rust",
10907        FakeLspAdapter {
10908            capabilities: lsp::ServerCapabilities {
10909                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
10910                ..lsp::ServerCapabilities::default()
10911            },
10912            ..FakeLspAdapter::default()
10913        },
10914    );
10915
10916    let buffer = project
10917        .update(cx, |project, cx| {
10918            project.open_local_buffer(path!("/file.rs"), cx)
10919        })
10920        .await
10921        .unwrap();
10922
10923    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10924    let (editor, cx) = cx.add_window_view(|window, cx| {
10925        build_editor_with_project(project.clone(), buffer, window, cx)
10926    });
10927
10928    cx.executor().start_waiting();
10929    let fake_server = fake_servers.next().await.unwrap();
10930
10931    (project, editor, cx, fake_server)
10932}
10933
10934#[gpui::test]
10935async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
10936    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10937
10938    editor.update_in(cx, |editor, window, cx| {
10939        editor.set_text("one\ntwo\nthree\n", window, cx)
10940    });
10941    assert!(cx.read(|cx| editor.is_dirty(cx)));
10942
10943    let save = editor
10944        .update_in(cx, |editor, window, cx| {
10945            editor.save(
10946                SaveOptions {
10947                    format: true,
10948                    autosave: false,
10949                },
10950                project.clone(),
10951                window,
10952                cx,
10953            )
10954        })
10955        .unwrap();
10956    fake_server
10957        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
10958            assert_eq!(
10959                params.text_document.uri,
10960                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10961            );
10962            assert_eq!(params.options.tab_size, 4);
10963            Ok(Some(vec![lsp::TextEdit::new(
10964                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10965                ", ".to_string(),
10966            )]))
10967        })
10968        .next()
10969        .await;
10970    cx.executor().start_waiting();
10971    save.await;
10972    assert_eq!(
10973        editor.update(cx, |editor, cx| editor.text(cx)),
10974        "one, two\nthree\n"
10975    );
10976    assert!(!cx.read(|cx| editor.is_dirty(cx)));
10977}
10978
10979#[gpui::test]
10980async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
10981    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
10982
10983    editor.update_in(cx, |editor, window, cx| {
10984        editor.set_text("one\ntwo\nthree\n", window, cx)
10985    });
10986    assert!(cx.read(|cx| editor.is_dirty(cx)));
10987
10988    // Test that save still works when formatting hangs
10989    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
10990        move |params, _| async move {
10991            assert_eq!(
10992                params.text_document.uri,
10993                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10994            );
10995            futures::future::pending::<()>().await;
10996            unreachable!()
10997        },
10998    );
10999    let save = editor
11000        .update_in(cx, |editor, window, cx| {
11001            editor.save(
11002                SaveOptions {
11003                    format: true,
11004                    autosave: false,
11005                },
11006                project.clone(),
11007                window,
11008                cx,
11009            )
11010        })
11011        .unwrap();
11012    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11013    cx.executor().start_waiting();
11014    save.await;
11015    assert_eq!(
11016        editor.update(cx, |editor, cx| editor.text(cx)),
11017        "one\ntwo\nthree\n"
11018    );
11019    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11020}
11021
11022#[gpui::test]
11023async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11024    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11025
11026    // Buffer starts clean, no formatting should be requested
11027    let save = editor
11028        .update_in(cx, |editor, window, cx| {
11029            editor.save(
11030                SaveOptions {
11031                    format: false,
11032                    autosave: false,
11033                },
11034                project.clone(),
11035                window,
11036                cx,
11037            )
11038        })
11039        .unwrap();
11040    let _pending_format_request = fake_server
11041        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11042            panic!("Should not be invoked");
11043        })
11044        .next();
11045    cx.executor().start_waiting();
11046    save.await;
11047    cx.run_until_parked();
11048}
11049
11050#[gpui::test]
11051async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11052    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11053
11054    // Set Rust language override and assert overridden tabsize is sent to language server
11055    update_test_language_settings(cx, |settings| {
11056        settings.languages.0.insert(
11057            "Rust".into(),
11058            LanguageSettingsContent {
11059                tab_size: NonZeroU32::new(8),
11060                ..Default::default()
11061            },
11062        );
11063    });
11064
11065    editor.update_in(cx, |editor, window, cx| {
11066        editor.set_text("something_new\n", window, cx)
11067    });
11068    assert!(cx.read(|cx| editor.is_dirty(cx)));
11069    let save = editor
11070        .update_in(cx, |editor, window, cx| {
11071            editor.save(
11072                SaveOptions {
11073                    format: true,
11074                    autosave: false,
11075                },
11076                project.clone(),
11077                window,
11078                cx,
11079            )
11080        })
11081        .unwrap();
11082    fake_server
11083        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11084            assert_eq!(
11085                params.text_document.uri,
11086                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11087            );
11088            assert_eq!(params.options.tab_size, 8);
11089            Ok(Some(Vec::new()))
11090        })
11091        .next()
11092        .await;
11093    save.await;
11094}
11095
11096#[gpui::test]
11097async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11098    init_test(cx, |settings| {
11099        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11100            Formatter::LanguageServer { name: None },
11101        )))
11102    });
11103
11104    let fs = FakeFs::new(cx.executor());
11105    fs.insert_file(path!("/file.rs"), Default::default()).await;
11106
11107    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11108
11109    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11110    language_registry.add(Arc::new(Language::new(
11111        LanguageConfig {
11112            name: "Rust".into(),
11113            matcher: LanguageMatcher {
11114                path_suffixes: vec!["rs".to_string()],
11115                ..Default::default()
11116            },
11117            ..LanguageConfig::default()
11118        },
11119        Some(tree_sitter_rust::LANGUAGE.into()),
11120    )));
11121    update_test_language_settings(cx, |settings| {
11122        // Enable Prettier formatting for the same buffer, and ensure
11123        // LSP is called instead of Prettier.
11124        settings.defaults.prettier = Some(PrettierSettings {
11125            allowed: true,
11126            ..PrettierSettings::default()
11127        });
11128    });
11129    let mut fake_servers = language_registry.register_fake_lsp(
11130        "Rust",
11131        FakeLspAdapter {
11132            capabilities: lsp::ServerCapabilities {
11133                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11134                ..Default::default()
11135            },
11136            ..Default::default()
11137        },
11138    );
11139
11140    let buffer = project
11141        .update(cx, |project, cx| {
11142            project.open_local_buffer(path!("/file.rs"), cx)
11143        })
11144        .await
11145        .unwrap();
11146
11147    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11148    let (editor, cx) = cx.add_window_view(|window, cx| {
11149        build_editor_with_project(project.clone(), buffer, window, cx)
11150    });
11151    editor.update_in(cx, |editor, window, cx| {
11152        editor.set_text("one\ntwo\nthree\n", window, cx)
11153    });
11154
11155    cx.executor().start_waiting();
11156    let fake_server = fake_servers.next().await.unwrap();
11157
11158    let format = editor
11159        .update_in(cx, |editor, window, cx| {
11160            editor.perform_format(
11161                project.clone(),
11162                FormatTrigger::Manual,
11163                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11164                window,
11165                cx,
11166            )
11167        })
11168        .unwrap();
11169    fake_server
11170        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11171            assert_eq!(
11172                params.text_document.uri,
11173                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11174            );
11175            assert_eq!(params.options.tab_size, 4);
11176            Ok(Some(vec![lsp::TextEdit::new(
11177                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11178                ", ".to_string(),
11179            )]))
11180        })
11181        .next()
11182        .await;
11183    cx.executor().start_waiting();
11184    format.await;
11185    assert_eq!(
11186        editor.update(cx, |editor, cx| editor.text(cx)),
11187        "one, two\nthree\n"
11188    );
11189
11190    editor.update_in(cx, |editor, window, cx| {
11191        editor.set_text("one\ntwo\nthree\n", window, cx)
11192    });
11193    // Ensure we don't lock if formatting hangs.
11194    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11195        move |params, _| async move {
11196            assert_eq!(
11197                params.text_document.uri,
11198                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11199            );
11200            futures::future::pending::<()>().await;
11201            unreachable!()
11202        },
11203    );
11204    let format = editor
11205        .update_in(cx, |editor, window, cx| {
11206            editor.perform_format(
11207                project,
11208                FormatTrigger::Manual,
11209                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11210                window,
11211                cx,
11212            )
11213        })
11214        .unwrap();
11215    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11216    cx.executor().start_waiting();
11217    format.await;
11218    assert_eq!(
11219        editor.update(cx, |editor, cx| editor.text(cx)),
11220        "one\ntwo\nthree\n"
11221    );
11222}
11223
11224#[gpui::test]
11225async fn test_multiple_formatters(cx: &mut TestAppContext) {
11226    init_test(cx, |settings| {
11227        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11228        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11229            Formatter::LanguageServer { name: None },
11230            Formatter::CodeActions(
11231                [
11232                    ("code-action-1".into(), true),
11233                    ("code-action-2".into(), true),
11234                ]
11235                .into_iter()
11236                .collect(),
11237            ),
11238        ])))
11239    });
11240
11241    let fs = FakeFs::new(cx.executor());
11242    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11243        .await;
11244
11245    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11246    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11247    language_registry.add(rust_lang());
11248
11249    let mut fake_servers = language_registry.register_fake_lsp(
11250        "Rust",
11251        FakeLspAdapter {
11252            capabilities: lsp::ServerCapabilities {
11253                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11254                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11255                    commands: vec!["the-command-for-code-action-1".into()],
11256                    ..Default::default()
11257                }),
11258                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11259                ..Default::default()
11260            },
11261            ..Default::default()
11262        },
11263    );
11264
11265    let buffer = project
11266        .update(cx, |project, cx| {
11267            project.open_local_buffer(path!("/file.rs"), cx)
11268        })
11269        .await
11270        .unwrap();
11271
11272    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11273    let (editor, cx) = cx.add_window_view(|window, cx| {
11274        build_editor_with_project(project.clone(), buffer, window, cx)
11275    });
11276
11277    cx.executor().start_waiting();
11278
11279    let fake_server = fake_servers.next().await.unwrap();
11280    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11281        move |_params, _| async move {
11282            Ok(Some(vec![lsp::TextEdit::new(
11283                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11284                "applied-formatting\n".to_string(),
11285            )]))
11286        },
11287    );
11288    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11289        move |params, _| async move {
11290            assert_eq!(
11291                params.context.only,
11292                Some(vec!["code-action-1".into(), "code-action-2".into()])
11293            );
11294            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11295            Ok(Some(vec![
11296                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11297                    kind: Some("code-action-1".into()),
11298                    edit: Some(lsp::WorkspaceEdit::new(
11299                        [(
11300                            uri.clone(),
11301                            vec![lsp::TextEdit::new(
11302                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11303                                "applied-code-action-1-edit\n".to_string(),
11304                            )],
11305                        )]
11306                        .into_iter()
11307                        .collect(),
11308                    )),
11309                    command: Some(lsp::Command {
11310                        command: "the-command-for-code-action-1".into(),
11311                        ..Default::default()
11312                    }),
11313                    ..Default::default()
11314                }),
11315                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11316                    kind: Some("code-action-2".into()),
11317                    edit: Some(lsp::WorkspaceEdit::new(
11318                        [(
11319                            uri,
11320                            vec![lsp::TextEdit::new(
11321                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11322                                "applied-code-action-2-edit\n".to_string(),
11323                            )],
11324                        )]
11325                        .into_iter()
11326                        .collect(),
11327                    )),
11328                    ..Default::default()
11329                }),
11330            ]))
11331        },
11332    );
11333
11334    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11335        move |params, _| async move { Ok(params) }
11336    });
11337
11338    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11339    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11340        let fake = fake_server.clone();
11341        let lock = command_lock.clone();
11342        move |params, _| {
11343            assert_eq!(params.command, "the-command-for-code-action-1");
11344            let fake = fake.clone();
11345            let lock = lock.clone();
11346            async move {
11347                lock.lock().await;
11348                fake.server
11349                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11350                        label: None,
11351                        edit: lsp::WorkspaceEdit {
11352                            changes: Some(
11353                                [(
11354                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11355                                    vec![lsp::TextEdit {
11356                                        range: lsp::Range::new(
11357                                            lsp::Position::new(0, 0),
11358                                            lsp::Position::new(0, 0),
11359                                        ),
11360                                        new_text: "applied-code-action-1-command\n".into(),
11361                                    }],
11362                                )]
11363                                .into_iter()
11364                                .collect(),
11365                            ),
11366                            ..Default::default()
11367                        },
11368                    })
11369                    .await
11370                    .into_response()
11371                    .unwrap();
11372                Ok(Some(json!(null)))
11373            }
11374        }
11375    });
11376
11377    cx.executor().start_waiting();
11378    editor
11379        .update_in(cx, |editor, window, cx| {
11380            editor.perform_format(
11381                project.clone(),
11382                FormatTrigger::Manual,
11383                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11384                window,
11385                cx,
11386            )
11387        })
11388        .unwrap()
11389        .await;
11390    editor.update(cx, |editor, cx| {
11391        assert_eq!(
11392            editor.text(cx),
11393            r#"
11394                applied-code-action-2-edit
11395                applied-code-action-1-command
11396                applied-code-action-1-edit
11397                applied-formatting
11398                one
11399                two
11400                three
11401            "#
11402            .unindent()
11403        );
11404    });
11405
11406    editor.update_in(cx, |editor, window, cx| {
11407        editor.undo(&Default::default(), window, cx);
11408        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11409    });
11410
11411    // Perform a manual edit while waiting for an LSP command
11412    // that's being run as part of a formatting code action.
11413    let lock_guard = command_lock.lock().await;
11414    let format = editor
11415        .update_in(cx, |editor, window, cx| {
11416            editor.perform_format(
11417                project.clone(),
11418                FormatTrigger::Manual,
11419                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11420                window,
11421                cx,
11422            )
11423        })
11424        .unwrap();
11425    cx.run_until_parked();
11426    editor.update(cx, |editor, cx| {
11427        assert_eq!(
11428            editor.text(cx),
11429            r#"
11430                applied-code-action-1-edit
11431                applied-formatting
11432                one
11433                two
11434                three
11435            "#
11436            .unindent()
11437        );
11438
11439        editor.buffer.update(cx, |buffer, cx| {
11440            let ix = buffer.len(cx);
11441            buffer.edit([(ix..ix, "edited\n")], None, cx);
11442        });
11443    });
11444
11445    // Allow the LSP command to proceed. Because the buffer was edited,
11446    // the second code action will not be run.
11447    drop(lock_guard);
11448    format.await;
11449    editor.update_in(cx, |editor, window, cx| {
11450        assert_eq!(
11451            editor.text(cx),
11452            r#"
11453                applied-code-action-1-command
11454                applied-code-action-1-edit
11455                applied-formatting
11456                one
11457                two
11458                three
11459                edited
11460            "#
11461            .unindent()
11462        );
11463
11464        // The manual edit is undone first, because it is the last thing the user did
11465        // (even though the command completed afterwards).
11466        editor.undo(&Default::default(), window, cx);
11467        assert_eq!(
11468            editor.text(cx),
11469            r#"
11470                applied-code-action-1-command
11471                applied-code-action-1-edit
11472                applied-formatting
11473                one
11474                two
11475                three
11476            "#
11477            .unindent()
11478        );
11479
11480        // All the formatting (including the command, which completed after the manual edit)
11481        // is undone together.
11482        editor.undo(&Default::default(), window, cx);
11483        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11484    });
11485}
11486
11487#[gpui::test]
11488async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
11489    init_test(cx, |settings| {
11490        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11491            Formatter::LanguageServer { name: None },
11492        ])))
11493    });
11494
11495    let fs = FakeFs::new(cx.executor());
11496    fs.insert_file(path!("/file.ts"), Default::default()).await;
11497
11498    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11499
11500    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11501    language_registry.add(Arc::new(Language::new(
11502        LanguageConfig {
11503            name: "TypeScript".into(),
11504            matcher: LanguageMatcher {
11505                path_suffixes: vec!["ts".to_string()],
11506                ..Default::default()
11507            },
11508            ..LanguageConfig::default()
11509        },
11510        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
11511    )));
11512    update_test_language_settings(cx, |settings| {
11513        settings.defaults.prettier = Some(PrettierSettings {
11514            allowed: true,
11515            ..PrettierSettings::default()
11516        });
11517    });
11518    let mut fake_servers = language_registry.register_fake_lsp(
11519        "TypeScript",
11520        FakeLspAdapter {
11521            capabilities: lsp::ServerCapabilities {
11522                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11523                ..Default::default()
11524            },
11525            ..Default::default()
11526        },
11527    );
11528
11529    let buffer = project
11530        .update(cx, |project, cx| {
11531            project.open_local_buffer(path!("/file.ts"), cx)
11532        })
11533        .await
11534        .unwrap();
11535
11536    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11537    let (editor, cx) = cx.add_window_view(|window, cx| {
11538        build_editor_with_project(project.clone(), buffer, window, cx)
11539    });
11540    editor.update_in(cx, |editor, window, cx| {
11541        editor.set_text(
11542            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11543            window,
11544            cx,
11545        )
11546    });
11547
11548    cx.executor().start_waiting();
11549    let fake_server = fake_servers.next().await.unwrap();
11550
11551    let format = editor
11552        .update_in(cx, |editor, window, cx| {
11553            editor.perform_code_action_kind(
11554                project.clone(),
11555                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11556                window,
11557                cx,
11558            )
11559        })
11560        .unwrap();
11561    fake_server
11562        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
11563            assert_eq!(
11564                params.text_document.uri,
11565                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
11566            );
11567            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
11568                lsp::CodeAction {
11569                    title: "Organize Imports".to_string(),
11570                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
11571                    edit: Some(lsp::WorkspaceEdit {
11572                        changes: Some(
11573                            [(
11574                                params.text_document.uri.clone(),
11575                                vec![lsp::TextEdit::new(
11576                                    lsp::Range::new(
11577                                        lsp::Position::new(1, 0),
11578                                        lsp::Position::new(2, 0),
11579                                    ),
11580                                    "".to_string(),
11581                                )],
11582                            )]
11583                            .into_iter()
11584                            .collect(),
11585                        ),
11586                        ..Default::default()
11587                    }),
11588                    ..Default::default()
11589                },
11590            )]))
11591        })
11592        .next()
11593        .await;
11594    cx.executor().start_waiting();
11595    format.await;
11596    assert_eq!(
11597        editor.update(cx, |editor, cx| editor.text(cx)),
11598        "import { a } from 'module';\n\nconst x = a;\n"
11599    );
11600
11601    editor.update_in(cx, |editor, window, cx| {
11602        editor.set_text(
11603            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
11604            window,
11605            cx,
11606        )
11607    });
11608    // Ensure we don't lock if code action hangs.
11609    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11610        move |params, _| async move {
11611            assert_eq!(
11612                params.text_document.uri,
11613                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
11614            );
11615            futures::future::pending::<()>().await;
11616            unreachable!()
11617        },
11618    );
11619    let format = editor
11620        .update_in(cx, |editor, window, cx| {
11621            editor.perform_code_action_kind(
11622                project,
11623                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
11624                window,
11625                cx,
11626            )
11627        })
11628        .unwrap();
11629    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
11630    cx.executor().start_waiting();
11631    format.await;
11632    assert_eq!(
11633        editor.update(cx, |editor, cx| editor.text(cx)),
11634        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
11635    );
11636}
11637
11638#[gpui::test]
11639async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
11640    init_test(cx, |_| {});
11641
11642    let mut cx = EditorLspTestContext::new_rust(
11643        lsp::ServerCapabilities {
11644            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11645            ..Default::default()
11646        },
11647        cx,
11648    )
11649    .await;
11650
11651    cx.set_state(indoc! {"
11652        one.twoˇ
11653    "});
11654
11655    // The format request takes a long time. When it completes, it inserts
11656    // a newline and an indent before the `.`
11657    cx.lsp
11658        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
11659            let executor = cx.background_executor().clone();
11660            async move {
11661                executor.timer(Duration::from_millis(100)).await;
11662                Ok(Some(vec![lsp::TextEdit {
11663                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
11664                    new_text: "\n    ".into(),
11665                }]))
11666            }
11667        });
11668
11669    // Submit a format request.
11670    let format_1 = cx
11671        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11672        .unwrap();
11673    cx.executor().run_until_parked();
11674
11675    // Submit a second format request.
11676    let format_2 = cx
11677        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11678        .unwrap();
11679    cx.executor().run_until_parked();
11680
11681    // Wait for both format requests to complete
11682    cx.executor().advance_clock(Duration::from_millis(200));
11683    cx.executor().start_waiting();
11684    format_1.await.unwrap();
11685    cx.executor().start_waiting();
11686    format_2.await.unwrap();
11687
11688    // The formatting edits only happens once.
11689    cx.assert_editor_state(indoc! {"
11690        one
11691            .twoˇ
11692    "});
11693}
11694
11695#[gpui::test]
11696async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
11697    init_test(cx, |settings| {
11698        settings.defaults.formatter = Some(SelectedFormatter::Auto)
11699    });
11700
11701    let mut cx = EditorLspTestContext::new_rust(
11702        lsp::ServerCapabilities {
11703            document_formatting_provider: Some(lsp::OneOf::Left(true)),
11704            ..Default::default()
11705        },
11706        cx,
11707    )
11708    .await;
11709
11710    // Set up a buffer white some trailing whitespace and no trailing newline.
11711    cx.set_state(
11712        &[
11713            "one ",   //
11714            "twoˇ",   //
11715            "three ", //
11716            "four",   //
11717        ]
11718        .join("\n"),
11719    );
11720
11721    // Submit a format request.
11722    let format = cx
11723        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
11724        .unwrap();
11725
11726    // Record which buffer changes have been sent to the language server
11727    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
11728    cx.lsp
11729        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
11730            let buffer_changes = buffer_changes.clone();
11731            move |params, _| {
11732                buffer_changes.lock().extend(
11733                    params
11734                        .content_changes
11735                        .into_iter()
11736                        .map(|e| (e.range.unwrap(), e.text)),
11737                );
11738            }
11739        });
11740
11741    // Handle formatting requests to the language server.
11742    cx.lsp
11743        .set_request_handler::<lsp::request::Formatting, _, _>({
11744            let buffer_changes = buffer_changes.clone();
11745            move |_, _| {
11746                // When formatting is requested, trailing whitespace has already been stripped,
11747                // and the trailing newline has already been added.
11748                assert_eq!(
11749                    &buffer_changes.lock()[1..],
11750                    &[
11751                        (
11752                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
11753                            "".into()
11754                        ),
11755                        (
11756                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
11757                            "".into()
11758                        ),
11759                        (
11760                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
11761                            "\n".into()
11762                        ),
11763                    ]
11764                );
11765
11766                // Insert blank lines between each line of the buffer.
11767                async move {
11768                    Ok(Some(vec![
11769                        lsp::TextEdit {
11770                            range: lsp::Range::new(
11771                                lsp::Position::new(1, 0),
11772                                lsp::Position::new(1, 0),
11773                            ),
11774                            new_text: "\n".into(),
11775                        },
11776                        lsp::TextEdit {
11777                            range: lsp::Range::new(
11778                                lsp::Position::new(2, 0),
11779                                lsp::Position::new(2, 0),
11780                            ),
11781                            new_text: "\n".into(),
11782                        },
11783                    ]))
11784                }
11785            }
11786        });
11787
11788    // After formatting the buffer, the trailing whitespace is stripped,
11789    // a newline is appended, and the edits provided by the language server
11790    // have been applied.
11791    format.await.unwrap();
11792    cx.assert_editor_state(
11793        &[
11794            "one",   //
11795            "",      //
11796            "twoˇ",  //
11797            "",      //
11798            "three", //
11799            "four",  //
11800            "",      //
11801        ]
11802        .join("\n"),
11803    );
11804
11805    // Undoing the formatting undoes the trailing whitespace removal, the
11806    // trailing newline, and the LSP edits.
11807    cx.update_buffer(|buffer, cx| buffer.undo(cx));
11808    cx.assert_editor_state(
11809        &[
11810            "one ",   //
11811            "twoˇ",   //
11812            "three ", //
11813            "four",   //
11814        ]
11815        .join("\n"),
11816    );
11817}
11818
11819#[gpui::test]
11820async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
11821    cx: &mut TestAppContext,
11822) {
11823    init_test(cx, |_| {});
11824
11825    cx.update(|cx| {
11826        cx.update_global::<SettingsStore, _>(|settings, cx| {
11827            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11828                settings.auto_signature_help = Some(true);
11829            });
11830        });
11831    });
11832
11833    let mut cx = EditorLspTestContext::new_rust(
11834        lsp::ServerCapabilities {
11835            signature_help_provider: Some(lsp::SignatureHelpOptions {
11836                ..Default::default()
11837            }),
11838            ..Default::default()
11839        },
11840        cx,
11841    )
11842    .await;
11843
11844    let language = Language::new(
11845        LanguageConfig {
11846            name: "Rust".into(),
11847            brackets: BracketPairConfig {
11848                pairs: vec![
11849                    BracketPair {
11850                        start: "{".to_string(),
11851                        end: "}".to_string(),
11852                        close: true,
11853                        surround: true,
11854                        newline: true,
11855                    },
11856                    BracketPair {
11857                        start: "(".to_string(),
11858                        end: ")".to_string(),
11859                        close: true,
11860                        surround: true,
11861                        newline: true,
11862                    },
11863                    BracketPair {
11864                        start: "/*".to_string(),
11865                        end: " */".to_string(),
11866                        close: true,
11867                        surround: true,
11868                        newline: true,
11869                    },
11870                    BracketPair {
11871                        start: "[".to_string(),
11872                        end: "]".to_string(),
11873                        close: false,
11874                        surround: false,
11875                        newline: true,
11876                    },
11877                    BracketPair {
11878                        start: "\"".to_string(),
11879                        end: "\"".to_string(),
11880                        close: true,
11881                        surround: true,
11882                        newline: false,
11883                    },
11884                    BracketPair {
11885                        start: "<".to_string(),
11886                        end: ">".to_string(),
11887                        close: false,
11888                        surround: true,
11889                        newline: true,
11890                    },
11891                ],
11892                ..Default::default()
11893            },
11894            autoclose_before: "})]".to_string(),
11895            ..Default::default()
11896        },
11897        Some(tree_sitter_rust::LANGUAGE.into()),
11898    );
11899    let language = Arc::new(language);
11900
11901    cx.language_registry().add(language.clone());
11902    cx.update_buffer(|buffer, cx| {
11903        buffer.set_language(Some(language), cx);
11904    });
11905
11906    cx.set_state(
11907        &r#"
11908            fn main() {
11909                sampleˇ
11910            }
11911        "#
11912        .unindent(),
11913    );
11914
11915    cx.update_editor(|editor, window, cx| {
11916        editor.handle_input("(", window, cx);
11917    });
11918    cx.assert_editor_state(
11919        &"
11920            fn main() {
11921                sample(ˇ)
11922            }
11923        "
11924        .unindent(),
11925    );
11926
11927    let mocked_response = lsp::SignatureHelp {
11928        signatures: vec![lsp::SignatureInformation {
11929            label: "fn sample(param1: u8, param2: u8)".to_string(),
11930            documentation: None,
11931            parameters: Some(vec![
11932                lsp::ParameterInformation {
11933                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
11934                    documentation: None,
11935                },
11936                lsp::ParameterInformation {
11937                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
11938                    documentation: None,
11939                },
11940            ]),
11941            active_parameter: None,
11942        }],
11943        active_signature: Some(0),
11944        active_parameter: Some(0),
11945    };
11946    handle_signature_help_request(&mut cx, mocked_response).await;
11947
11948    cx.condition(|editor, _| editor.signature_help_state.is_shown())
11949        .await;
11950
11951    cx.editor(|editor, _, _| {
11952        let signature_help_state = editor.signature_help_state.popover().cloned();
11953        let signature = signature_help_state.unwrap();
11954        assert_eq!(
11955            signature.signatures[signature.current_signature].label,
11956            "fn sample(param1: u8, param2: u8)"
11957        );
11958    });
11959}
11960
11961#[gpui::test]
11962async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
11963    init_test(cx, |_| {});
11964
11965    cx.update(|cx| {
11966        cx.update_global::<SettingsStore, _>(|settings, cx| {
11967            settings.update_user_settings::<EditorSettings>(cx, |settings| {
11968                settings.auto_signature_help = Some(false);
11969                settings.show_signature_help_after_edits = Some(false);
11970            });
11971        });
11972    });
11973
11974    let mut cx = EditorLspTestContext::new_rust(
11975        lsp::ServerCapabilities {
11976            signature_help_provider: Some(lsp::SignatureHelpOptions {
11977                ..Default::default()
11978            }),
11979            ..Default::default()
11980        },
11981        cx,
11982    )
11983    .await;
11984
11985    let language = Language::new(
11986        LanguageConfig {
11987            name: "Rust".into(),
11988            brackets: BracketPairConfig {
11989                pairs: vec![
11990                    BracketPair {
11991                        start: "{".to_string(),
11992                        end: "}".to_string(),
11993                        close: true,
11994                        surround: true,
11995                        newline: true,
11996                    },
11997                    BracketPair {
11998                        start: "(".to_string(),
11999                        end: ")".to_string(),
12000                        close: true,
12001                        surround: true,
12002                        newline: true,
12003                    },
12004                    BracketPair {
12005                        start: "/*".to_string(),
12006                        end: " */".to_string(),
12007                        close: true,
12008                        surround: true,
12009                        newline: true,
12010                    },
12011                    BracketPair {
12012                        start: "[".to_string(),
12013                        end: "]".to_string(),
12014                        close: false,
12015                        surround: false,
12016                        newline: true,
12017                    },
12018                    BracketPair {
12019                        start: "\"".to_string(),
12020                        end: "\"".to_string(),
12021                        close: true,
12022                        surround: true,
12023                        newline: false,
12024                    },
12025                    BracketPair {
12026                        start: "<".to_string(),
12027                        end: ">".to_string(),
12028                        close: false,
12029                        surround: true,
12030                        newline: true,
12031                    },
12032                ],
12033                ..Default::default()
12034            },
12035            autoclose_before: "})]".to_string(),
12036            ..Default::default()
12037        },
12038        Some(tree_sitter_rust::LANGUAGE.into()),
12039    );
12040    let language = Arc::new(language);
12041
12042    cx.language_registry().add(language.clone());
12043    cx.update_buffer(|buffer, cx| {
12044        buffer.set_language(Some(language), cx);
12045    });
12046
12047    // Ensure that signature_help is not called when no signature help is enabled.
12048    cx.set_state(
12049        &r#"
12050            fn main() {
12051                sampleˇ
12052            }
12053        "#
12054        .unindent(),
12055    );
12056    cx.update_editor(|editor, window, cx| {
12057        editor.handle_input("(", window, cx);
12058    });
12059    cx.assert_editor_state(
12060        &"
12061            fn main() {
12062                sample(ˇ)
12063            }
12064        "
12065        .unindent(),
12066    );
12067    cx.editor(|editor, _, _| {
12068        assert!(editor.signature_help_state.task().is_none());
12069    });
12070
12071    let mocked_response = lsp::SignatureHelp {
12072        signatures: vec![lsp::SignatureInformation {
12073            label: "fn sample(param1: u8, param2: u8)".to_string(),
12074            documentation: None,
12075            parameters: Some(vec![
12076                lsp::ParameterInformation {
12077                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12078                    documentation: None,
12079                },
12080                lsp::ParameterInformation {
12081                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12082                    documentation: None,
12083                },
12084            ]),
12085            active_parameter: None,
12086        }],
12087        active_signature: Some(0),
12088        active_parameter: Some(0),
12089    };
12090
12091    // Ensure that signature_help is called when enabled afte edits
12092    cx.update(|_, cx| {
12093        cx.update_global::<SettingsStore, _>(|settings, cx| {
12094            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12095                settings.auto_signature_help = Some(false);
12096                settings.show_signature_help_after_edits = Some(true);
12097            });
12098        });
12099    });
12100    cx.set_state(
12101        &r#"
12102            fn main() {
12103                sampleˇ
12104            }
12105        "#
12106        .unindent(),
12107    );
12108    cx.update_editor(|editor, window, cx| {
12109        editor.handle_input("(", window, cx);
12110    });
12111    cx.assert_editor_state(
12112        &"
12113            fn main() {
12114                sample(ˇ)
12115            }
12116        "
12117        .unindent(),
12118    );
12119    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12120    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12121        .await;
12122    cx.update_editor(|editor, _, _| {
12123        let signature_help_state = editor.signature_help_state.popover().cloned();
12124        assert!(signature_help_state.is_some());
12125        let signature = signature_help_state.unwrap();
12126        assert_eq!(
12127            signature.signatures[signature.current_signature].label,
12128            "fn sample(param1: u8, param2: u8)"
12129        );
12130        editor.signature_help_state = SignatureHelpState::default();
12131    });
12132
12133    // Ensure that signature_help is called when auto signature help override is enabled
12134    cx.update(|_, cx| {
12135        cx.update_global::<SettingsStore, _>(|settings, cx| {
12136            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12137                settings.auto_signature_help = Some(true);
12138                settings.show_signature_help_after_edits = Some(false);
12139            });
12140        });
12141    });
12142    cx.set_state(
12143        &r#"
12144            fn main() {
12145                sampleˇ
12146            }
12147        "#
12148        .unindent(),
12149    );
12150    cx.update_editor(|editor, window, cx| {
12151        editor.handle_input("(", window, cx);
12152    });
12153    cx.assert_editor_state(
12154        &"
12155            fn main() {
12156                sample(ˇ)
12157            }
12158        "
12159        .unindent(),
12160    );
12161    handle_signature_help_request(&mut cx, mocked_response).await;
12162    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12163        .await;
12164    cx.editor(|editor, _, _| {
12165        let signature_help_state = editor.signature_help_state.popover().cloned();
12166        assert!(signature_help_state.is_some());
12167        let signature = signature_help_state.unwrap();
12168        assert_eq!(
12169            signature.signatures[signature.current_signature].label,
12170            "fn sample(param1: u8, param2: u8)"
12171        );
12172    });
12173}
12174
12175#[gpui::test]
12176async fn test_signature_help(cx: &mut TestAppContext) {
12177    init_test(cx, |_| {});
12178    cx.update(|cx| {
12179        cx.update_global::<SettingsStore, _>(|settings, cx| {
12180            settings.update_user_settings::<EditorSettings>(cx, |settings| {
12181                settings.auto_signature_help = Some(true);
12182            });
12183        });
12184    });
12185
12186    let mut cx = EditorLspTestContext::new_rust(
12187        lsp::ServerCapabilities {
12188            signature_help_provider: Some(lsp::SignatureHelpOptions {
12189                ..Default::default()
12190            }),
12191            ..Default::default()
12192        },
12193        cx,
12194    )
12195    .await;
12196
12197    // A test that directly calls `show_signature_help`
12198    cx.update_editor(|editor, window, cx| {
12199        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12200    });
12201
12202    let mocked_response = lsp::SignatureHelp {
12203        signatures: vec![lsp::SignatureInformation {
12204            label: "fn sample(param1: u8, param2: u8)".to_string(),
12205            documentation: None,
12206            parameters: Some(vec![
12207                lsp::ParameterInformation {
12208                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12209                    documentation: None,
12210                },
12211                lsp::ParameterInformation {
12212                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12213                    documentation: None,
12214                },
12215            ]),
12216            active_parameter: None,
12217        }],
12218        active_signature: Some(0),
12219        active_parameter: Some(0),
12220    };
12221    handle_signature_help_request(&mut cx, mocked_response).await;
12222
12223    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12224        .await;
12225
12226    cx.editor(|editor, _, _| {
12227        let signature_help_state = editor.signature_help_state.popover().cloned();
12228        assert!(signature_help_state.is_some());
12229        let signature = signature_help_state.unwrap();
12230        assert_eq!(
12231            signature.signatures[signature.current_signature].label,
12232            "fn sample(param1: u8, param2: u8)"
12233        );
12234    });
12235
12236    // When exiting outside from inside the brackets, `signature_help` is closed.
12237    cx.set_state(indoc! {"
12238        fn main() {
12239            sample(ˇ);
12240        }
12241
12242        fn sample(param1: u8, param2: u8) {}
12243    "});
12244
12245    cx.update_editor(|editor, window, cx| {
12246        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12247            s.select_ranges([0..0])
12248        });
12249    });
12250
12251    let mocked_response = lsp::SignatureHelp {
12252        signatures: Vec::new(),
12253        active_signature: None,
12254        active_parameter: None,
12255    };
12256    handle_signature_help_request(&mut cx, mocked_response).await;
12257
12258    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12259        .await;
12260
12261    cx.editor(|editor, _, _| {
12262        assert!(!editor.signature_help_state.is_shown());
12263    });
12264
12265    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12266    cx.set_state(indoc! {"
12267        fn main() {
12268            sample(ˇ);
12269        }
12270
12271        fn sample(param1: u8, param2: u8) {}
12272    "});
12273
12274    let mocked_response = lsp::SignatureHelp {
12275        signatures: vec![lsp::SignatureInformation {
12276            label: "fn sample(param1: u8, param2: u8)".to_string(),
12277            documentation: None,
12278            parameters: Some(vec![
12279                lsp::ParameterInformation {
12280                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12281                    documentation: None,
12282                },
12283                lsp::ParameterInformation {
12284                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12285                    documentation: None,
12286                },
12287            ]),
12288            active_parameter: None,
12289        }],
12290        active_signature: Some(0),
12291        active_parameter: Some(0),
12292    };
12293    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12294    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12295        .await;
12296    cx.editor(|editor, _, _| {
12297        assert!(editor.signature_help_state.is_shown());
12298    });
12299
12300    // Restore the popover with more parameter input
12301    cx.set_state(indoc! {"
12302        fn main() {
12303            sample(param1, param2ˇ);
12304        }
12305
12306        fn sample(param1: u8, param2: u8) {}
12307    "});
12308
12309    let mocked_response = lsp::SignatureHelp {
12310        signatures: vec![lsp::SignatureInformation {
12311            label: "fn sample(param1: u8, param2: u8)".to_string(),
12312            documentation: None,
12313            parameters: Some(vec![
12314                lsp::ParameterInformation {
12315                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12316                    documentation: None,
12317                },
12318                lsp::ParameterInformation {
12319                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12320                    documentation: None,
12321                },
12322            ]),
12323            active_parameter: None,
12324        }],
12325        active_signature: Some(0),
12326        active_parameter: Some(1),
12327    };
12328    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12329    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12330        .await;
12331
12332    // When selecting a range, the popover is gone.
12333    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12334    cx.update_editor(|editor, window, cx| {
12335        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12336            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12337        })
12338    });
12339    cx.assert_editor_state(indoc! {"
12340        fn main() {
12341            sample(param1, «ˇparam2»);
12342        }
12343
12344        fn sample(param1: u8, param2: u8) {}
12345    "});
12346    cx.editor(|editor, _, _| {
12347        assert!(!editor.signature_help_state.is_shown());
12348    });
12349
12350    // When unselecting again, the popover is back if within the brackets.
12351    cx.update_editor(|editor, window, cx| {
12352        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12353            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12354        })
12355    });
12356    cx.assert_editor_state(indoc! {"
12357        fn main() {
12358            sample(param1, ˇparam2);
12359        }
12360
12361        fn sample(param1: u8, param2: u8) {}
12362    "});
12363    handle_signature_help_request(&mut cx, mocked_response).await;
12364    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12365        .await;
12366    cx.editor(|editor, _, _| {
12367        assert!(editor.signature_help_state.is_shown());
12368    });
12369
12370    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
12371    cx.update_editor(|editor, window, cx| {
12372        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12373            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
12374            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12375        })
12376    });
12377    cx.assert_editor_state(indoc! {"
12378        fn main() {
12379            sample(param1, ˇparam2);
12380        }
12381
12382        fn sample(param1: u8, param2: u8) {}
12383    "});
12384
12385    let mocked_response = lsp::SignatureHelp {
12386        signatures: vec![lsp::SignatureInformation {
12387            label: "fn sample(param1: u8, param2: u8)".to_string(),
12388            documentation: None,
12389            parameters: Some(vec![
12390                lsp::ParameterInformation {
12391                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12392                    documentation: None,
12393                },
12394                lsp::ParameterInformation {
12395                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12396                    documentation: None,
12397                },
12398            ]),
12399            active_parameter: None,
12400        }],
12401        active_signature: Some(0),
12402        active_parameter: Some(1),
12403    };
12404    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12405    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12406        .await;
12407    cx.update_editor(|editor, _, cx| {
12408        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
12409    });
12410    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12411        .await;
12412    cx.update_editor(|editor, window, cx| {
12413        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12414            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12415        })
12416    });
12417    cx.assert_editor_state(indoc! {"
12418        fn main() {
12419            sample(param1, «ˇparam2»);
12420        }
12421
12422        fn sample(param1: u8, param2: u8) {}
12423    "});
12424    cx.update_editor(|editor, window, cx| {
12425        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12426            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12427        })
12428    });
12429    cx.assert_editor_state(indoc! {"
12430        fn main() {
12431            sample(param1, ˇparam2);
12432        }
12433
12434        fn sample(param1: u8, param2: u8) {}
12435    "});
12436    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
12437        .await;
12438}
12439
12440#[gpui::test]
12441async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
12442    init_test(cx, |_| {});
12443
12444    let mut cx = EditorLspTestContext::new_rust(
12445        lsp::ServerCapabilities {
12446            signature_help_provider: Some(lsp::SignatureHelpOptions {
12447                ..Default::default()
12448            }),
12449            ..Default::default()
12450        },
12451        cx,
12452    )
12453    .await;
12454
12455    cx.set_state(indoc! {"
12456        fn main() {
12457            overloadedˇ
12458        }
12459    "});
12460
12461    cx.update_editor(|editor, window, cx| {
12462        editor.handle_input("(", window, cx);
12463        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12464    });
12465
12466    // Mock response with 3 signatures
12467    let mocked_response = lsp::SignatureHelp {
12468        signatures: vec![
12469            lsp::SignatureInformation {
12470                label: "fn overloaded(x: i32)".to_string(),
12471                documentation: None,
12472                parameters: Some(vec![lsp::ParameterInformation {
12473                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12474                    documentation: None,
12475                }]),
12476                active_parameter: None,
12477            },
12478            lsp::SignatureInformation {
12479                label: "fn overloaded(x: i32, y: i32)".to_string(),
12480                documentation: None,
12481                parameters: Some(vec![
12482                    lsp::ParameterInformation {
12483                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12484                        documentation: None,
12485                    },
12486                    lsp::ParameterInformation {
12487                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
12488                        documentation: None,
12489                    },
12490                ]),
12491                active_parameter: None,
12492            },
12493            lsp::SignatureInformation {
12494                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
12495                documentation: None,
12496                parameters: Some(vec![
12497                    lsp::ParameterInformation {
12498                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
12499                        documentation: None,
12500                    },
12501                    lsp::ParameterInformation {
12502                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
12503                        documentation: None,
12504                    },
12505                    lsp::ParameterInformation {
12506                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
12507                        documentation: None,
12508                    },
12509                ]),
12510                active_parameter: None,
12511            },
12512        ],
12513        active_signature: Some(1),
12514        active_parameter: Some(0),
12515    };
12516    handle_signature_help_request(&mut cx, mocked_response).await;
12517
12518    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12519        .await;
12520
12521    // Verify we have multiple signatures and the right one is selected
12522    cx.editor(|editor, _, _| {
12523        let popover = editor.signature_help_state.popover().cloned().unwrap();
12524        assert_eq!(popover.signatures.len(), 3);
12525        // active_signature was 1, so that should be the current
12526        assert_eq!(popover.current_signature, 1);
12527        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
12528        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
12529        assert_eq!(
12530            popover.signatures[2].label,
12531            "fn overloaded(x: i32, y: i32, z: i32)"
12532        );
12533    });
12534
12535    // Test navigation functionality
12536    cx.update_editor(|editor, window, cx| {
12537        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12538    });
12539
12540    cx.editor(|editor, _, _| {
12541        let popover = editor.signature_help_state.popover().cloned().unwrap();
12542        assert_eq!(popover.current_signature, 2);
12543    });
12544
12545    // Test wrap around
12546    cx.update_editor(|editor, window, cx| {
12547        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
12548    });
12549
12550    cx.editor(|editor, _, _| {
12551        let popover = editor.signature_help_state.popover().cloned().unwrap();
12552        assert_eq!(popover.current_signature, 0);
12553    });
12554
12555    // Test previous navigation
12556    cx.update_editor(|editor, window, cx| {
12557        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
12558    });
12559
12560    cx.editor(|editor, _, _| {
12561        let popover = editor.signature_help_state.popover().cloned().unwrap();
12562        assert_eq!(popover.current_signature, 2);
12563    });
12564}
12565
12566#[gpui::test]
12567async fn test_completion_mode(cx: &mut TestAppContext) {
12568    init_test(cx, |_| {});
12569    let mut cx = EditorLspTestContext::new_rust(
12570        lsp::ServerCapabilities {
12571            completion_provider: Some(lsp::CompletionOptions {
12572                resolve_provider: Some(true),
12573                ..Default::default()
12574            }),
12575            ..Default::default()
12576        },
12577        cx,
12578    )
12579    .await;
12580
12581    struct Run {
12582        run_description: &'static str,
12583        initial_state: String,
12584        buffer_marked_text: String,
12585        completion_label: &'static str,
12586        completion_text: &'static str,
12587        expected_with_insert_mode: String,
12588        expected_with_replace_mode: String,
12589        expected_with_replace_subsequence_mode: String,
12590        expected_with_replace_suffix_mode: String,
12591    }
12592
12593    let runs = [
12594        Run {
12595            run_description: "Start of word matches completion text",
12596            initial_state: "before ediˇ after".into(),
12597            buffer_marked_text: "before <edi|> after".into(),
12598            completion_label: "editor",
12599            completion_text: "editor",
12600            expected_with_insert_mode: "before editorˇ after".into(),
12601            expected_with_replace_mode: "before editorˇ after".into(),
12602            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12603            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12604        },
12605        Run {
12606            run_description: "Accept same text at the middle of the word",
12607            initial_state: "before ediˇtor after".into(),
12608            buffer_marked_text: "before <edi|tor> after".into(),
12609            completion_label: "editor",
12610            completion_text: "editor",
12611            expected_with_insert_mode: "before editorˇtor after".into(),
12612            expected_with_replace_mode: "before editorˇ after".into(),
12613            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12614            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12615        },
12616        Run {
12617            run_description: "End of word matches completion text -- cursor at end",
12618            initial_state: "before torˇ after".into(),
12619            buffer_marked_text: "before <tor|> after".into(),
12620            completion_label: "editor",
12621            completion_text: "editor",
12622            expected_with_insert_mode: "before editorˇ after".into(),
12623            expected_with_replace_mode: "before editorˇ after".into(),
12624            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12625            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12626        },
12627        Run {
12628            run_description: "End of word matches completion text -- cursor at start",
12629            initial_state: "before ˇtor after".into(),
12630            buffer_marked_text: "before <|tor> after".into(),
12631            completion_label: "editor",
12632            completion_text: "editor",
12633            expected_with_insert_mode: "before editorˇtor after".into(),
12634            expected_with_replace_mode: "before editorˇ after".into(),
12635            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12636            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12637        },
12638        Run {
12639            run_description: "Prepend text containing whitespace",
12640            initial_state: "pˇfield: bool".into(),
12641            buffer_marked_text: "<p|field>: bool".into(),
12642            completion_label: "pub ",
12643            completion_text: "pub ",
12644            expected_with_insert_mode: "pub ˇfield: bool".into(),
12645            expected_with_replace_mode: "pub ˇ: bool".into(),
12646            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
12647            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
12648        },
12649        Run {
12650            run_description: "Add element to start of list",
12651            initial_state: "[element_ˇelement_2]".into(),
12652            buffer_marked_text: "[<element_|element_2>]".into(),
12653            completion_label: "element_1",
12654            completion_text: "element_1",
12655            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
12656            expected_with_replace_mode: "[element_1ˇ]".into(),
12657            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
12658            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
12659        },
12660        Run {
12661            run_description: "Add element to start of list -- first and second elements are equal",
12662            initial_state: "[elˇelement]".into(),
12663            buffer_marked_text: "[<el|element>]".into(),
12664            completion_label: "element",
12665            completion_text: "element",
12666            expected_with_insert_mode: "[elementˇelement]".into(),
12667            expected_with_replace_mode: "[elementˇ]".into(),
12668            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
12669            expected_with_replace_suffix_mode: "[elementˇ]".into(),
12670        },
12671        Run {
12672            run_description: "Ends with matching suffix",
12673            initial_state: "SubˇError".into(),
12674            buffer_marked_text: "<Sub|Error>".into(),
12675            completion_label: "SubscriptionError",
12676            completion_text: "SubscriptionError",
12677            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
12678            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12679            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12680            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
12681        },
12682        Run {
12683            run_description: "Suffix is a subsequence -- contiguous",
12684            initial_state: "SubˇErr".into(),
12685            buffer_marked_text: "<Sub|Err>".into(),
12686            completion_label: "SubscriptionError",
12687            completion_text: "SubscriptionError",
12688            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
12689            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12690            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12691            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
12692        },
12693        Run {
12694            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
12695            initial_state: "Suˇscrirr".into(),
12696            buffer_marked_text: "<Su|scrirr>".into(),
12697            completion_label: "SubscriptionError",
12698            completion_text: "SubscriptionError",
12699            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
12700            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
12701            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
12702            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
12703        },
12704        Run {
12705            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
12706            initial_state: "foo(indˇix)".into(),
12707            buffer_marked_text: "foo(<ind|ix>)".into(),
12708            completion_label: "node_index",
12709            completion_text: "node_index",
12710            expected_with_insert_mode: "foo(node_indexˇix)".into(),
12711            expected_with_replace_mode: "foo(node_indexˇ)".into(),
12712            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
12713            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
12714        },
12715        Run {
12716            run_description: "Replace range ends before cursor - should extend to cursor",
12717            initial_state: "before editˇo after".into(),
12718            buffer_marked_text: "before <{ed}>it|o after".into(),
12719            completion_label: "editor",
12720            completion_text: "editor",
12721            expected_with_insert_mode: "before editorˇo after".into(),
12722            expected_with_replace_mode: "before editorˇo after".into(),
12723            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
12724            expected_with_replace_suffix_mode: "before editorˇo after".into(),
12725        },
12726        Run {
12727            run_description: "Uses label for suffix matching",
12728            initial_state: "before ediˇtor after".into(),
12729            buffer_marked_text: "before <edi|tor> after".into(),
12730            completion_label: "editor",
12731            completion_text: "editor()",
12732            expected_with_insert_mode: "before editor()ˇtor after".into(),
12733            expected_with_replace_mode: "before editor()ˇ after".into(),
12734            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
12735            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
12736        },
12737        Run {
12738            run_description: "Case insensitive subsequence and suffix matching",
12739            initial_state: "before EDiˇtoR after".into(),
12740            buffer_marked_text: "before <EDi|toR> after".into(),
12741            completion_label: "editor",
12742            completion_text: "editor",
12743            expected_with_insert_mode: "before editorˇtoR after".into(),
12744            expected_with_replace_mode: "before editorˇ after".into(),
12745            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
12746            expected_with_replace_suffix_mode: "before editorˇ after".into(),
12747        },
12748    ];
12749
12750    for run in runs {
12751        let run_variations = [
12752            (LspInsertMode::Insert, run.expected_with_insert_mode),
12753            (LspInsertMode::Replace, run.expected_with_replace_mode),
12754            (
12755                LspInsertMode::ReplaceSubsequence,
12756                run.expected_with_replace_subsequence_mode,
12757            ),
12758            (
12759                LspInsertMode::ReplaceSuffix,
12760                run.expected_with_replace_suffix_mode,
12761            ),
12762        ];
12763
12764        for (lsp_insert_mode, expected_text) in run_variations {
12765            eprintln!(
12766                "run = {:?}, mode = {lsp_insert_mode:.?}",
12767                run.run_description,
12768            );
12769
12770            update_test_language_settings(&mut cx, |settings| {
12771                settings.defaults.completions = Some(CompletionSettings {
12772                    lsp_insert_mode,
12773                    words: WordsCompletionMode::Disabled,
12774                    words_min_length: 0,
12775                    lsp: true,
12776                    lsp_fetch_timeout_ms: 0,
12777                });
12778            });
12779
12780            cx.set_state(&run.initial_state);
12781            cx.update_editor(|editor, window, cx| {
12782                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12783            });
12784
12785            let counter = Arc::new(AtomicUsize::new(0));
12786            handle_completion_request_with_insert_and_replace(
12787                &mut cx,
12788                &run.buffer_marked_text,
12789                vec![(run.completion_label, run.completion_text)],
12790                counter.clone(),
12791            )
12792            .await;
12793            cx.condition(|editor, _| editor.context_menu_visible())
12794                .await;
12795            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12796
12797            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12798                editor
12799                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
12800                    .unwrap()
12801            });
12802            cx.assert_editor_state(&expected_text);
12803            handle_resolve_completion_request(&mut cx, None).await;
12804            apply_additional_edits.await.unwrap();
12805        }
12806    }
12807}
12808
12809#[gpui::test]
12810async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
12811    init_test(cx, |_| {});
12812    let mut cx = EditorLspTestContext::new_rust(
12813        lsp::ServerCapabilities {
12814            completion_provider: Some(lsp::CompletionOptions {
12815                resolve_provider: Some(true),
12816                ..Default::default()
12817            }),
12818            ..Default::default()
12819        },
12820        cx,
12821    )
12822    .await;
12823
12824    let initial_state = "SubˇError";
12825    let buffer_marked_text = "<Sub|Error>";
12826    let completion_text = "SubscriptionError";
12827    let expected_with_insert_mode = "SubscriptionErrorˇError";
12828    let expected_with_replace_mode = "SubscriptionErrorˇ";
12829
12830    update_test_language_settings(&mut cx, |settings| {
12831        settings.defaults.completions = Some(CompletionSettings {
12832            words: WordsCompletionMode::Disabled,
12833            words_min_length: 0,
12834            // set the opposite here to ensure that the action is overriding the default behavior
12835            lsp_insert_mode: LspInsertMode::Insert,
12836            lsp: true,
12837            lsp_fetch_timeout_ms: 0,
12838        });
12839    });
12840
12841    cx.set_state(initial_state);
12842    cx.update_editor(|editor, window, cx| {
12843        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12844    });
12845
12846    let counter = Arc::new(AtomicUsize::new(0));
12847    handle_completion_request_with_insert_and_replace(
12848        &mut cx,
12849        buffer_marked_text,
12850        vec![(completion_text, completion_text)],
12851        counter.clone(),
12852    )
12853    .await;
12854    cx.condition(|editor, _| editor.context_menu_visible())
12855        .await;
12856    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
12857
12858    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12859        editor
12860            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12861            .unwrap()
12862    });
12863    cx.assert_editor_state(expected_with_replace_mode);
12864    handle_resolve_completion_request(&mut cx, None).await;
12865    apply_additional_edits.await.unwrap();
12866
12867    update_test_language_settings(&mut cx, |settings| {
12868        settings.defaults.completions = Some(CompletionSettings {
12869            words: WordsCompletionMode::Disabled,
12870            words_min_length: 0,
12871            // set the opposite here to ensure that the action is overriding the default behavior
12872            lsp_insert_mode: LspInsertMode::Replace,
12873            lsp: true,
12874            lsp_fetch_timeout_ms: 0,
12875        });
12876    });
12877
12878    cx.set_state(initial_state);
12879    cx.update_editor(|editor, window, cx| {
12880        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12881    });
12882    handle_completion_request_with_insert_and_replace(
12883        &mut cx,
12884        buffer_marked_text,
12885        vec![(completion_text, completion_text)],
12886        counter.clone(),
12887    )
12888    .await;
12889    cx.condition(|editor, _| editor.context_menu_visible())
12890        .await;
12891    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
12892
12893    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12894        editor
12895            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
12896            .unwrap()
12897    });
12898    cx.assert_editor_state(expected_with_insert_mode);
12899    handle_resolve_completion_request(&mut cx, None).await;
12900    apply_additional_edits.await.unwrap();
12901}
12902
12903#[gpui::test]
12904async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
12905    init_test(cx, |_| {});
12906    let mut cx = EditorLspTestContext::new_rust(
12907        lsp::ServerCapabilities {
12908            completion_provider: Some(lsp::CompletionOptions {
12909                resolve_provider: Some(true),
12910                ..Default::default()
12911            }),
12912            ..Default::default()
12913        },
12914        cx,
12915    )
12916    .await;
12917
12918    // scenario: surrounding text matches completion text
12919    let completion_text = "to_offset";
12920    let initial_state = indoc! {"
12921        1. buf.to_offˇsuffix
12922        2. buf.to_offˇsuf
12923        3. buf.to_offˇfix
12924        4. buf.to_offˇ
12925        5. into_offˇensive
12926        6. ˇsuffix
12927        7. let ˇ //
12928        8. aaˇzz
12929        9. buf.to_off«zzzzzˇ»suffix
12930        10. buf.«ˇzzzzz»suffix
12931        11. to_off«ˇzzzzz»
12932
12933        buf.to_offˇsuffix  // newest cursor
12934    "};
12935    let completion_marked_buffer = indoc! {"
12936        1. buf.to_offsuffix
12937        2. buf.to_offsuf
12938        3. buf.to_offfix
12939        4. buf.to_off
12940        5. into_offensive
12941        6. suffix
12942        7. let  //
12943        8. aazz
12944        9. buf.to_offzzzzzsuffix
12945        10. buf.zzzzzsuffix
12946        11. to_offzzzzz
12947
12948        buf.<to_off|suffix>  // newest cursor
12949    "};
12950    let expected = indoc! {"
12951        1. buf.to_offsetˇ
12952        2. buf.to_offsetˇsuf
12953        3. buf.to_offsetˇfix
12954        4. buf.to_offsetˇ
12955        5. into_offsetˇensive
12956        6. to_offsetˇsuffix
12957        7. let to_offsetˇ //
12958        8. aato_offsetˇzz
12959        9. buf.to_offsetˇ
12960        10. buf.to_offsetˇsuffix
12961        11. to_offsetˇ
12962
12963        buf.to_offsetˇ  // newest cursor
12964    "};
12965    cx.set_state(initial_state);
12966    cx.update_editor(|editor, window, cx| {
12967        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
12968    });
12969    handle_completion_request_with_insert_and_replace(
12970        &mut cx,
12971        completion_marked_buffer,
12972        vec![(completion_text, completion_text)],
12973        Arc::new(AtomicUsize::new(0)),
12974    )
12975    .await;
12976    cx.condition(|editor, _| editor.context_menu_visible())
12977        .await;
12978    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12979        editor
12980            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
12981            .unwrap()
12982    });
12983    cx.assert_editor_state(expected);
12984    handle_resolve_completion_request(&mut cx, None).await;
12985    apply_additional_edits.await.unwrap();
12986
12987    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
12988    let completion_text = "foo_and_bar";
12989    let initial_state = indoc! {"
12990        1. ooanbˇ
12991        2. zooanbˇ
12992        3. ooanbˇz
12993        4. zooanbˇz
12994        5. ooanˇ
12995        6. oanbˇ
12996
12997        ooanbˇ
12998    "};
12999    let completion_marked_buffer = indoc! {"
13000        1. ooanb
13001        2. zooanb
13002        3. ooanbz
13003        4. zooanbz
13004        5. ooan
13005        6. oanb
13006
13007        <ooanb|>
13008    "};
13009    let expected = indoc! {"
13010        1. foo_and_barˇ
13011        2. zfoo_and_barˇ
13012        3. foo_and_barˇz
13013        4. zfoo_and_barˇz
13014        5. ooanfoo_and_barˇ
13015        6. oanbfoo_and_barˇ
13016
13017        foo_and_barˇ
13018    "};
13019    cx.set_state(initial_state);
13020    cx.update_editor(|editor, window, cx| {
13021        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13022    });
13023    handle_completion_request_with_insert_and_replace(
13024        &mut cx,
13025        completion_marked_buffer,
13026        vec![(completion_text, completion_text)],
13027        Arc::new(AtomicUsize::new(0)),
13028    )
13029    .await;
13030    cx.condition(|editor, _| editor.context_menu_visible())
13031        .await;
13032    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13033        editor
13034            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13035            .unwrap()
13036    });
13037    cx.assert_editor_state(expected);
13038    handle_resolve_completion_request(&mut cx, None).await;
13039    apply_additional_edits.await.unwrap();
13040
13041    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13042    // (expects the same as if it was inserted at the end)
13043    let completion_text = "foo_and_bar";
13044    let initial_state = indoc! {"
13045        1. ooˇanb
13046        2. zooˇanb
13047        3. ooˇanbz
13048        4. zooˇanbz
13049
13050        ooˇanb
13051    "};
13052    let completion_marked_buffer = indoc! {"
13053        1. ooanb
13054        2. zooanb
13055        3. ooanbz
13056        4. zooanbz
13057
13058        <oo|anb>
13059    "};
13060    let expected = indoc! {"
13061        1. foo_and_barˇ
13062        2. zfoo_and_barˇ
13063        3. foo_and_barˇz
13064        4. zfoo_and_barˇz
13065
13066        foo_and_barˇ
13067    "};
13068    cx.set_state(initial_state);
13069    cx.update_editor(|editor, window, cx| {
13070        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13071    });
13072    handle_completion_request_with_insert_and_replace(
13073        &mut cx,
13074        completion_marked_buffer,
13075        vec![(completion_text, completion_text)],
13076        Arc::new(AtomicUsize::new(0)),
13077    )
13078    .await;
13079    cx.condition(|editor, _| editor.context_menu_visible())
13080        .await;
13081    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13082        editor
13083            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13084            .unwrap()
13085    });
13086    cx.assert_editor_state(expected);
13087    handle_resolve_completion_request(&mut cx, None).await;
13088    apply_additional_edits.await.unwrap();
13089}
13090
13091// This used to crash
13092#[gpui::test]
13093async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13094    init_test(cx, |_| {});
13095
13096    let buffer_text = indoc! {"
13097        fn main() {
13098            10.satu;
13099
13100            //
13101            // separate cursors so they open in different excerpts (manually reproducible)
13102            //
13103
13104            10.satu20;
13105        }
13106    "};
13107    let multibuffer_text_with_selections = indoc! {"
13108        fn main() {
13109            10.satuˇ;
13110
13111            //
13112
13113            //
13114
13115            10.satuˇ20;
13116        }
13117    "};
13118    let expected_multibuffer = indoc! {"
13119        fn main() {
13120            10.saturating_sub()ˇ;
13121
13122            //
13123
13124            //
13125
13126            10.saturating_sub()ˇ;
13127        }
13128    "};
13129
13130    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13131    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13132
13133    let fs = FakeFs::new(cx.executor());
13134    fs.insert_tree(
13135        path!("/a"),
13136        json!({
13137            "main.rs": buffer_text,
13138        }),
13139    )
13140    .await;
13141
13142    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13143    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13144    language_registry.add(rust_lang());
13145    let mut fake_servers = language_registry.register_fake_lsp(
13146        "Rust",
13147        FakeLspAdapter {
13148            capabilities: lsp::ServerCapabilities {
13149                completion_provider: Some(lsp::CompletionOptions {
13150                    resolve_provider: None,
13151                    ..lsp::CompletionOptions::default()
13152                }),
13153                ..lsp::ServerCapabilities::default()
13154            },
13155            ..FakeLspAdapter::default()
13156        },
13157    );
13158    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13159    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13160    let buffer = project
13161        .update(cx, |project, cx| {
13162            project.open_local_buffer(path!("/a/main.rs"), cx)
13163        })
13164        .await
13165        .unwrap();
13166
13167    let multi_buffer = cx.new(|cx| {
13168        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13169        multi_buffer.push_excerpts(
13170            buffer.clone(),
13171            [ExcerptRange::new(0..first_excerpt_end)],
13172            cx,
13173        );
13174        multi_buffer.push_excerpts(
13175            buffer.clone(),
13176            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13177            cx,
13178        );
13179        multi_buffer
13180    });
13181
13182    let editor = workspace
13183        .update(cx, |_, window, cx| {
13184            cx.new(|cx| {
13185                Editor::new(
13186                    EditorMode::Full {
13187                        scale_ui_elements_with_buffer_font_size: false,
13188                        show_active_line_background: false,
13189                        sized_by_content: false,
13190                    },
13191                    multi_buffer.clone(),
13192                    Some(project.clone()),
13193                    window,
13194                    cx,
13195                )
13196            })
13197        })
13198        .unwrap();
13199
13200    let pane = workspace
13201        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13202        .unwrap();
13203    pane.update_in(cx, |pane, window, cx| {
13204        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13205    });
13206
13207    let fake_server = fake_servers.next().await.unwrap();
13208
13209    editor.update_in(cx, |editor, window, cx| {
13210        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13211            s.select_ranges([
13212                Point::new(1, 11)..Point::new(1, 11),
13213                Point::new(7, 11)..Point::new(7, 11),
13214            ])
13215        });
13216
13217        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13218    });
13219
13220    editor.update_in(cx, |editor, window, cx| {
13221        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13222    });
13223
13224    fake_server
13225        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13226            let completion_item = lsp::CompletionItem {
13227                label: "saturating_sub()".into(),
13228                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13229                    lsp::InsertReplaceEdit {
13230                        new_text: "saturating_sub()".to_owned(),
13231                        insert: lsp::Range::new(
13232                            lsp::Position::new(7, 7),
13233                            lsp::Position::new(7, 11),
13234                        ),
13235                        replace: lsp::Range::new(
13236                            lsp::Position::new(7, 7),
13237                            lsp::Position::new(7, 13),
13238                        ),
13239                    },
13240                )),
13241                ..lsp::CompletionItem::default()
13242            };
13243
13244            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13245        })
13246        .next()
13247        .await
13248        .unwrap();
13249
13250    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13251        .await;
13252
13253    editor
13254        .update_in(cx, |editor, window, cx| {
13255            editor
13256                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13257                .unwrap()
13258        })
13259        .await
13260        .unwrap();
13261
13262    editor.update(cx, |editor, cx| {
13263        assert_text_with_selections(editor, expected_multibuffer, cx);
13264    })
13265}
13266
13267#[gpui::test]
13268async fn test_completion(cx: &mut TestAppContext) {
13269    init_test(cx, |_| {});
13270
13271    let mut cx = EditorLspTestContext::new_rust(
13272        lsp::ServerCapabilities {
13273            completion_provider: Some(lsp::CompletionOptions {
13274                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13275                resolve_provider: Some(true),
13276                ..Default::default()
13277            }),
13278            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13279            ..Default::default()
13280        },
13281        cx,
13282    )
13283    .await;
13284    let counter = Arc::new(AtomicUsize::new(0));
13285
13286    cx.set_state(indoc! {"
13287        oneˇ
13288        two
13289        three
13290    "});
13291    cx.simulate_keystroke(".");
13292    handle_completion_request(
13293        indoc! {"
13294            one.|<>
13295            two
13296            three
13297        "},
13298        vec!["first_completion", "second_completion"],
13299        true,
13300        counter.clone(),
13301        &mut cx,
13302    )
13303    .await;
13304    cx.condition(|editor, _| editor.context_menu_visible())
13305        .await;
13306    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13307
13308    let _handler = handle_signature_help_request(
13309        &mut cx,
13310        lsp::SignatureHelp {
13311            signatures: vec![lsp::SignatureInformation {
13312                label: "test signature".to_string(),
13313                documentation: None,
13314                parameters: Some(vec![lsp::ParameterInformation {
13315                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13316                    documentation: None,
13317                }]),
13318                active_parameter: None,
13319            }],
13320            active_signature: None,
13321            active_parameter: None,
13322        },
13323    );
13324    cx.update_editor(|editor, window, cx| {
13325        assert!(
13326            !editor.signature_help_state.is_shown(),
13327            "No signature help was called for"
13328        );
13329        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13330    });
13331    cx.run_until_parked();
13332    cx.update_editor(|editor, _, _| {
13333        assert!(
13334            !editor.signature_help_state.is_shown(),
13335            "No signature help should be shown when completions menu is open"
13336        );
13337    });
13338
13339    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13340        editor.context_menu_next(&Default::default(), window, cx);
13341        editor
13342            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13343            .unwrap()
13344    });
13345    cx.assert_editor_state(indoc! {"
13346        one.second_completionˇ
13347        two
13348        three
13349    "});
13350
13351    handle_resolve_completion_request(
13352        &mut cx,
13353        Some(vec![
13354            (
13355                //This overlaps with the primary completion edit which is
13356                //misbehavior from the LSP spec, test that we filter it out
13357                indoc! {"
13358                    one.second_ˇcompletion
13359                    two
13360                    threeˇ
13361                "},
13362                "overlapping additional edit",
13363            ),
13364            (
13365                indoc! {"
13366                    one.second_completion
13367                    two
13368                    threeˇ
13369                "},
13370                "\nadditional edit",
13371            ),
13372        ]),
13373    )
13374    .await;
13375    apply_additional_edits.await.unwrap();
13376    cx.assert_editor_state(indoc! {"
13377        one.second_completionˇ
13378        two
13379        three
13380        additional edit
13381    "});
13382
13383    cx.set_state(indoc! {"
13384        one.second_completion
13385        twoˇ
13386        threeˇ
13387        additional edit
13388    "});
13389    cx.simulate_keystroke(" ");
13390    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13391    cx.simulate_keystroke("s");
13392    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13393
13394    cx.assert_editor_state(indoc! {"
13395        one.second_completion
13396        two sˇ
13397        three sˇ
13398        additional edit
13399    "});
13400    handle_completion_request(
13401        indoc! {"
13402            one.second_completion
13403            two s
13404            three <s|>
13405            additional edit
13406        "},
13407        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13408        true,
13409        counter.clone(),
13410        &mut cx,
13411    )
13412    .await;
13413    cx.condition(|editor, _| editor.context_menu_visible())
13414        .await;
13415    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13416
13417    cx.simulate_keystroke("i");
13418
13419    handle_completion_request(
13420        indoc! {"
13421            one.second_completion
13422            two si
13423            three <si|>
13424            additional edit
13425        "},
13426        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13427        true,
13428        counter.clone(),
13429        &mut cx,
13430    )
13431    .await;
13432    cx.condition(|editor, _| editor.context_menu_visible())
13433        .await;
13434    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13435
13436    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13437        editor
13438            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13439            .unwrap()
13440    });
13441    cx.assert_editor_state(indoc! {"
13442        one.second_completion
13443        two sixth_completionˇ
13444        three sixth_completionˇ
13445        additional edit
13446    "});
13447
13448    apply_additional_edits.await.unwrap();
13449
13450    update_test_language_settings(&mut cx, |settings| {
13451        settings.defaults.show_completions_on_input = Some(false);
13452    });
13453    cx.set_state("editorˇ");
13454    cx.simulate_keystroke(".");
13455    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13456    cx.simulate_keystrokes("c l o");
13457    cx.assert_editor_state("editor.cloˇ");
13458    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13459    cx.update_editor(|editor, window, cx| {
13460        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13461    });
13462    handle_completion_request(
13463        "editor.<clo|>",
13464        vec!["close", "clobber"],
13465        true,
13466        counter.clone(),
13467        &mut cx,
13468    )
13469    .await;
13470    cx.condition(|editor, _| editor.context_menu_visible())
13471        .await;
13472    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
13473
13474    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13475        editor
13476            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13477            .unwrap()
13478    });
13479    cx.assert_editor_state("editor.clobberˇ");
13480    handle_resolve_completion_request(&mut cx, None).await;
13481    apply_additional_edits.await.unwrap();
13482}
13483
13484#[gpui::test]
13485async fn test_completion_reuse(cx: &mut TestAppContext) {
13486    init_test(cx, |_| {});
13487
13488    let mut cx = EditorLspTestContext::new_rust(
13489        lsp::ServerCapabilities {
13490            completion_provider: Some(lsp::CompletionOptions {
13491                trigger_characters: Some(vec![".".to_string()]),
13492                ..Default::default()
13493            }),
13494            ..Default::default()
13495        },
13496        cx,
13497    )
13498    .await;
13499
13500    let counter = Arc::new(AtomicUsize::new(0));
13501    cx.set_state("objˇ");
13502    cx.simulate_keystroke(".");
13503
13504    // Initial completion request returns complete results
13505    let is_incomplete = false;
13506    handle_completion_request(
13507        "obj.|<>",
13508        vec!["a", "ab", "abc"],
13509        is_incomplete,
13510        counter.clone(),
13511        &mut cx,
13512    )
13513    .await;
13514    cx.run_until_parked();
13515    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13516    cx.assert_editor_state("obj.ˇ");
13517    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13518
13519    // Type "a" - filters existing completions
13520    cx.simulate_keystroke("a");
13521    cx.run_until_parked();
13522    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13523    cx.assert_editor_state("obj.aˇ");
13524    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13525
13526    // Type "b" - filters existing completions
13527    cx.simulate_keystroke("b");
13528    cx.run_until_parked();
13529    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13530    cx.assert_editor_state("obj.abˇ");
13531    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13532
13533    // Type "c" - filters existing completions
13534    cx.simulate_keystroke("c");
13535    cx.run_until_parked();
13536    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13537    cx.assert_editor_state("obj.abcˇ");
13538    check_displayed_completions(vec!["abc"], &mut cx);
13539
13540    // Backspace to delete "c" - filters existing completions
13541    cx.update_editor(|editor, window, cx| {
13542        editor.backspace(&Backspace, window, cx);
13543    });
13544    cx.run_until_parked();
13545    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13546    cx.assert_editor_state("obj.abˇ");
13547    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13548
13549    // Moving cursor to the left dismisses menu.
13550    cx.update_editor(|editor, window, cx| {
13551        editor.move_left(&MoveLeft, window, cx);
13552    });
13553    cx.run_until_parked();
13554    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13555    cx.assert_editor_state("obj.aˇb");
13556    cx.update_editor(|editor, _, _| {
13557        assert_eq!(editor.context_menu_visible(), false);
13558    });
13559
13560    // Type "b" - new request
13561    cx.simulate_keystroke("b");
13562    let is_incomplete = false;
13563    handle_completion_request(
13564        "obj.<ab|>a",
13565        vec!["ab", "abc"],
13566        is_incomplete,
13567        counter.clone(),
13568        &mut cx,
13569    )
13570    .await;
13571    cx.run_until_parked();
13572    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13573    cx.assert_editor_state("obj.abˇb");
13574    check_displayed_completions(vec!["ab", "abc"], &mut cx);
13575
13576    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
13577    cx.update_editor(|editor, window, cx| {
13578        editor.backspace(&Backspace, window, cx);
13579    });
13580    let is_incomplete = false;
13581    handle_completion_request(
13582        "obj.<a|>b",
13583        vec!["a", "ab", "abc"],
13584        is_incomplete,
13585        counter.clone(),
13586        &mut cx,
13587    )
13588    .await;
13589    cx.run_until_parked();
13590    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13591    cx.assert_editor_state("obj.aˇb");
13592    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
13593
13594    // Backspace to delete "a" - dismisses menu.
13595    cx.update_editor(|editor, window, cx| {
13596        editor.backspace(&Backspace, window, cx);
13597    });
13598    cx.run_until_parked();
13599    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
13600    cx.assert_editor_state("obj.ˇb");
13601    cx.update_editor(|editor, _, _| {
13602        assert_eq!(editor.context_menu_visible(), false);
13603    });
13604}
13605
13606#[gpui::test]
13607async fn test_word_completion(cx: &mut TestAppContext) {
13608    let lsp_fetch_timeout_ms = 10;
13609    init_test(cx, |language_settings| {
13610        language_settings.defaults.completions = Some(CompletionSettings {
13611            words: WordsCompletionMode::Fallback,
13612            words_min_length: 0,
13613            lsp: true,
13614            lsp_fetch_timeout_ms: 10,
13615            lsp_insert_mode: LspInsertMode::Insert,
13616        });
13617    });
13618
13619    let mut cx = EditorLspTestContext::new_rust(
13620        lsp::ServerCapabilities {
13621            completion_provider: Some(lsp::CompletionOptions {
13622                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13623                ..lsp::CompletionOptions::default()
13624            }),
13625            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13626            ..lsp::ServerCapabilities::default()
13627        },
13628        cx,
13629    )
13630    .await;
13631
13632    let throttle_completions = Arc::new(AtomicBool::new(false));
13633
13634    let lsp_throttle_completions = throttle_completions.clone();
13635    let _completion_requests_handler =
13636        cx.lsp
13637            .server
13638            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
13639                let lsp_throttle_completions = lsp_throttle_completions.clone();
13640                let cx = cx.clone();
13641                async move {
13642                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
13643                        cx.background_executor()
13644                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
13645                            .await;
13646                    }
13647                    Ok(Some(lsp::CompletionResponse::Array(vec![
13648                        lsp::CompletionItem {
13649                            label: "first".into(),
13650                            ..lsp::CompletionItem::default()
13651                        },
13652                        lsp::CompletionItem {
13653                            label: "last".into(),
13654                            ..lsp::CompletionItem::default()
13655                        },
13656                    ])))
13657                }
13658            });
13659
13660    cx.set_state(indoc! {"
13661        oneˇ
13662        two
13663        three
13664    "});
13665    cx.simulate_keystroke(".");
13666    cx.executor().run_until_parked();
13667    cx.condition(|editor, _| editor.context_menu_visible())
13668        .await;
13669    cx.update_editor(|editor, window, cx| {
13670        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13671        {
13672            assert_eq!(
13673                completion_menu_entries(menu),
13674                &["first", "last"],
13675                "When LSP server is fast to reply, no fallback word completions are used"
13676            );
13677        } else {
13678            panic!("expected completion menu to be open");
13679        }
13680        editor.cancel(&Cancel, window, cx);
13681    });
13682    cx.executor().run_until_parked();
13683    cx.condition(|editor, _| !editor.context_menu_visible())
13684        .await;
13685
13686    throttle_completions.store(true, atomic::Ordering::Release);
13687    cx.simulate_keystroke(".");
13688    cx.executor()
13689        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
13690    cx.executor().run_until_parked();
13691    cx.condition(|editor, _| editor.context_menu_visible())
13692        .await;
13693    cx.update_editor(|editor, _, _| {
13694        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13695        {
13696            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
13697                "When LSP server is slow, document words can be shown instead, if configured accordingly");
13698        } else {
13699            panic!("expected completion menu to be open");
13700        }
13701    });
13702}
13703
13704#[gpui::test]
13705async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
13706    init_test(cx, |language_settings| {
13707        language_settings.defaults.completions = Some(CompletionSettings {
13708            words: WordsCompletionMode::Enabled,
13709            words_min_length: 0,
13710            lsp: true,
13711            lsp_fetch_timeout_ms: 0,
13712            lsp_insert_mode: LspInsertMode::Insert,
13713        });
13714    });
13715
13716    let mut cx = EditorLspTestContext::new_rust(
13717        lsp::ServerCapabilities {
13718            completion_provider: Some(lsp::CompletionOptions {
13719                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13720                ..lsp::CompletionOptions::default()
13721            }),
13722            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13723            ..lsp::ServerCapabilities::default()
13724        },
13725        cx,
13726    )
13727    .await;
13728
13729    let _completion_requests_handler =
13730        cx.lsp
13731            .server
13732            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13733                Ok(Some(lsp::CompletionResponse::Array(vec![
13734                    lsp::CompletionItem {
13735                        label: "first".into(),
13736                        ..lsp::CompletionItem::default()
13737                    },
13738                    lsp::CompletionItem {
13739                        label: "last".into(),
13740                        ..lsp::CompletionItem::default()
13741                    },
13742                ])))
13743            });
13744
13745    cx.set_state(indoc! {"ˇ
13746        first
13747        last
13748        second
13749    "});
13750    cx.simulate_keystroke(".");
13751    cx.executor().run_until_parked();
13752    cx.condition(|editor, _| editor.context_menu_visible())
13753        .await;
13754    cx.update_editor(|editor, _, _| {
13755        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13756        {
13757            assert_eq!(
13758                completion_menu_entries(menu),
13759                &["first", "last", "second"],
13760                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
13761            );
13762        } else {
13763            panic!("expected completion menu to be open");
13764        }
13765    });
13766}
13767
13768#[gpui::test]
13769async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
13770    init_test(cx, |language_settings| {
13771        language_settings.defaults.completions = Some(CompletionSettings {
13772            words: WordsCompletionMode::Disabled,
13773            words_min_length: 0,
13774            lsp: true,
13775            lsp_fetch_timeout_ms: 0,
13776            lsp_insert_mode: LspInsertMode::Insert,
13777        });
13778    });
13779
13780    let mut cx = EditorLspTestContext::new_rust(
13781        lsp::ServerCapabilities {
13782            completion_provider: Some(lsp::CompletionOptions {
13783                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13784                ..lsp::CompletionOptions::default()
13785            }),
13786            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13787            ..lsp::ServerCapabilities::default()
13788        },
13789        cx,
13790    )
13791    .await;
13792
13793    let _completion_requests_handler =
13794        cx.lsp
13795            .server
13796            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13797                panic!("LSP completions should not be queried when dealing with word completions")
13798            });
13799
13800    cx.set_state(indoc! {"ˇ
13801        first
13802        last
13803        second
13804    "});
13805    cx.update_editor(|editor, window, cx| {
13806        editor.show_word_completions(&ShowWordCompletions, window, cx);
13807    });
13808    cx.executor().run_until_parked();
13809    cx.condition(|editor, _| editor.context_menu_visible())
13810        .await;
13811    cx.update_editor(|editor, _, _| {
13812        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13813        {
13814            assert_eq!(
13815                completion_menu_entries(menu),
13816                &["first", "last", "second"],
13817                "`ShowWordCompletions` action should show word completions"
13818            );
13819        } else {
13820            panic!("expected completion menu to be open");
13821        }
13822    });
13823
13824    cx.simulate_keystroke("l");
13825    cx.executor().run_until_parked();
13826    cx.condition(|editor, _| editor.context_menu_visible())
13827        .await;
13828    cx.update_editor(|editor, _, _| {
13829        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13830        {
13831            assert_eq!(
13832                completion_menu_entries(menu),
13833                &["last"],
13834                "After showing word completions, further editing should filter them and not query the LSP"
13835            );
13836        } else {
13837            panic!("expected completion menu to be open");
13838        }
13839    });
13840}
13841
13842#[gpui::test]
13843async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
13844    init_test(cx, |language_settings| {
13845        language_settings.defaults.completions = Some(CompletionSettings {
13846            words: WordsCompletionMode::Fallback,
13847            words_min_length: 0,
13848            lsp: false,
13849            lsp_fetch_timeout_ms: 0,
13850            lsp_insert_mode: LspInsertMode::Insert,
13851        });
13852    });
13853
13854    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13855
13856    cx.set_state(indoc! {"ˇ
13857        0_usize
13858        let
13859        33
13860        4.5f32
13861    "});
13862    cx.update_editor(|editor, window, cx| {
13863        editor.show_completions(&ShowCompletions::default(), window, cx);
13864    });
13865    cx.executor().run_until_parked();
13866    cx.condition(|editor, _| editor.context_menu_visible())
13867        .await;
13868    cx.update_editor(|editor, window, cx| {
13869        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13870        {
13871            assert_eq!(
13872                completion_menu_entries(menu),
13873                &["let"],
13874                "With no digits in the completion query, no digits should be in the word completions"
13875            );
13876        } else {
13877            panic!("expected completion menu to be open");
13878        }
13879        editor.cancel(&Cancel, window, cx);
13880    });
13881
13882    cx.set_state(indoc! {"13883        0_usize
13884        let
13885        3
13886        33.35f32
13887    "});
13888    cx.update_editor(|editor, window, cx| {
13889        editor.show_completions(&ShowCompletions::default(), window, cx);
13890    });
13891    cx.executor().run_until_parked();
13892    cx.condition(|editor, _| editor.context_menu_visible())
13893        .await;
13894    cx.update_editor(|editor, _, _| {
13895        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13896        {
13897            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
13898                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
13899        } else {
13900            panic!("expected completion menu to be open");
13901        }
13902    });
13903}
13904
13905#[gpui::test]
13906async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
13907    init_test(cx, |language_settings| {
13908        language_settings.defaults.completions = Some(CompletionSettings {
13909            words: WordsCompletionMode::Enabled,
13910            words_min_length: 3,
13911            lsp: true,
13912            lsp_fetch_timeout_ms: 0,
13913            lsp_insert_mode: LspInsertMode::Insert,
13914        });
13915    });
13916
13917    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13918    cx.set_state(indoc! {"ˇ
13919        wow
13920        wowen
13921        wowser
13922    "});
13923    cx.simulate_keystroke("w");
13924    cx.executor().run_until_parked();
13925    cx.update_editor(|editor, _, _| {
13926        if editor.context_menu.borrow_mut().is_some() {
13927            panic!(
13928                "expected completion menu to be hidden, as words completion threshold is not met"
13929            );
13930        }
13931    });
13932
13933    cx.simulate_keystroke("o");
13934    cx.executor().run_until_parked();
13935    cx.update_editor(|editor, _, _| {
13936        if editor.context_menu.borrow_mut().is_some() {
13937            panic!(
13938                "expected completion menu to be hidden, as words completion threshold is not met still"
13939            );
13940        }
13941    });
13942
13943    cx.simulate_keystroke("w");
13944    cx.executor().run_until_parked();
13945    cx.update_editor(|editor, _, _| {
13946        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13947        {
13948            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
13949        } else {
13950            panic!("expected completion menu to be open after the word completions threshold is met");
13951        }
13952    });
13953}
13954
13955fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
13956    let position = || lsp::Position {
13957        line: params.text_document_position.position.line,
13958        character: params.text_document_position.position.character,
13959    };
13960    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
13961        range: lsp::Range {
13962            start: position(),
13963            end: position(),
13964        },
13965        new_text: text.to_string(),
13966    }))
13967}
13968
13969#[gpui::test]
13970async fn test_multiline_completion(cx: &mut TestAppContext) {
13971    init_test(cx, |_| {});
13972
13973    let fs = FakeFs::new(cx.executor());
13974    fs.insert_tree(
13975        path!("/a"),
13976        json!({
13977            "main.ts": "a",
13978        }),
13979    )
13980    .await;
13981
13982    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13983    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13984    let typescript_language = Arc::new(Language::new(
13985        LanguageConfig {
13986            name: "TypeScript".into(),
13987            matcher: LanguageMatcher {
13988                path_suffixes: vec!["ts".to_string()],
13989                ..LanguageMatcher::default()
13990            },
13991            line_comments: vec!["// ".into()],
13992            ..LanguageConfig::default()
13993        },
13994        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13995    ));
13996    language_registry.add(typescript_language.clone());
13997    let mut fake_servers = language_registry.register_fake_lsp(
13998        "TypeScript",
13999        FakeLspAdapter {
14000            capabilities: lsp::ServerCapabilities {
14001                completion_provider: Some(lsp::CompletionOptions {
14002                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14003                    ..lsp::CompletionOptions::default()
14004                }),
14005                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14006                ..lsp::ServerCapabilities::default()
14007            },
14008            // Emulate vtsls label generation
14009            label_for_completion: Some(Box::new(|item, _| {
14010                let text = if let Some(description) = item
14011                    .label_details
14012                    .as_ref()
14013                    .and_then(|label_details| label_details.description.as_ref())
14014                {
14015                    format!("{} {}", item.label, description)
14016                } else if let Some(detail) = &item.detail {
14017                    format!("{} {}", item.label, detail)
14018                } else {
14019                    item.label.clone()
14020                };
14021                let len = text.len();
14022                Some(language::CodeLabel {
14023                    text,
14024                    runs: Vec::new(),
14025                    filter_range: 0..len,
14026                })
14027            })),
14028            ..FakeLspAdapter::default()
14029        },
14030    );
14031    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14032    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14033    let worktree_id = workspace
14034        .update(cx, |workspace, _window, cx| {
14035            workspace.project().update(cx, |project, cx| {
14036                project.worktrees(cx).next().unwrap().read(cx).id()
14037            })
14038        })
14039        .unwrap();
14040    let _buffer = project
14041        .update(cx, |project, cx| {
14042            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14043        })
14044        .await
14045        .unwrap();
14046    let editor = workspace
14047        .update(cx, |workspace, window, cx| {
14048            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14049        })
14050        .unwrap()
14051        .await
14052        .unwrap()
14053        .downcast::<Editor>()
14054        .unwrap();
14055    let fake_server = fake_servers.next().await.unwrap();
14056
14057    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14058    let multiline_label_2 = "a\nb\nc\n";
14059    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14060    let multiline_description = "d\ne\nf\n";
14061    let multiline_detail_2 = "g\nh\ni\n";
14062
14063    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14064        move |params, _| async move {
14065            Ok(Some(lsp::CompletionResponse::Array(vec![
14066                lsp::CompletionItem {
14067                    label: multiline_label.to_string(),
14068                    text_edit: gen_text_edit(&params, "new_text_1"),
14069                    ..lsp::CompletionItem::default()
14070                },
14071                lsp::CompletionItem {
14072                    label: "single line label 1".to_string(),
14073                    detail: Some(multiline_detail.to_string()),
14074                    text_edit: gen_text_edit(&params, "new_text_2"),
14075                    ..lsp::CompletionItem::default()
14076                },
14077                lsp::CompletionItem {
14078                    label: "single line label 2".to_string(),
14079                    label_details: Some(lsp::CompletionItemLabelDetails {
14080                        description: Some(multiline_description.to_string()),
14081                        detail: None,
14082                    }),
14083                    text_edit: gen_text_edit(&params, "new_text_2"),
14084                    ..lsp::CompletionItem::default()
14085                },
14086                lsp::CompletionItem {
14087                    label: multiline_label_2.to_string(),
14088                    detail: Some(multiline_detail_2.to_string()),
14089                    text_edit: gen_text_edit(&params, "new_text_3"),
14090                    ..lsp::CompletionItem::default()
14091                },
14092                lsp::CompletionItem {
14093                    label: "Label with many     spaces and \t but without newlines".to_string(),
14094                    detail: Some(
14095                        "Details with many     spaces and \t but without newlines".to_string(),
14096                    ),
14097                    text_edit: gen_text_edit(&params, "new_text_4"),
14098                    ..lsp::CompletionItem::default()
14099                },
14100            ])))
14101        },
14102    );
14103
14104    editor.update_in(cx, |editor, window, cx| {
14105        cx.focus_self(window);
14106        editor.move_to_end(&MoveToEnd, window, cx);
14107        editor.handle_input(".", window, cx);
14108    });
14109    cx.run_until_parked();
14110    completion_handle.next().await.unwrap();
14111
14112    editor.update(cx, |editor, _| {
14113        assert!(editor.context_menu_visible());
14114        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14115        {
14116            let completion_labels = menu
14117                .completions
14118                .borrow()
14119                .iter()
14120                .map(|c| c.label.text.clone())
14121                .collect::<Vec<_>>();
14122            assert_eq!(
14123                completion_labels,
14124                &[
14125                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14126                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14127                    "single line label 2 d e f ",
14128                    "a b c g h i ",
14129                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14130                ],
14131                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14132            );
14133
14134            for completion in menu
14135                .completions
14136                .borrow()
14137                .iter() {
14138                    assert_eq!(
14139                        completion.label.filter_range,
14140                        0..completion.label.text.len(),
14141                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14142                    );
14143                }
14144        } else {
14145            panic!("expected completion menu to be open");
14146        }
14147    });
14148}
14149
14150#[gpui::test]
14151async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14152    init_test(cx, |_| {});
14153    let mut cx = EditorLspTestContext::new_rust(
14154        lsp::ServerCapabilities {
14155            completion_provider: Some(lsp::CompletionOptions {
14156                trigger_characters: Some(vec![".".to_string()]),
14157                ..Default::default()
14158            }),
14159            ..Default::default()
14160        },
14161        cx,
14162    )
14163    .await;
14164    cx.lsp
14165        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14166            Ok(Some(lsp::CompletionResponse::Array(vec![
14167                lsp::CompletionItem {
14168                    label: "first".into(),
14169                    ..Default::default()
14170                },
14171                lsp::CompletionItem {
14172                    label: "last".into(),
14173                    ..Default::default()
14174                },
14175            ])))
14176        });
14177    cx.set_state("variableˇ");
14178    cx.simulate_keystroke(".");
14179    cx.executor().run_until_parked();
14180
14181    cx.update_editor(|editor, _, _| {
14182        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14183        {
14184            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14185        } else {
14186            panic!("expected completion menu to be open");
14187        }
14188    });
14189
14190    cx.update_editor(|editor, window, cx| {
14191        editor.move_page_down(&MovePageDown::default(), window, cx);
14192        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14193        {
14194            assert!(
14195                menu.selected_item == 1,
14196                "expected PageDown to select the last item from the context menu"
14197            );
14198        } else {
14199            panic!("expected completion menu to stay open after PageDown");
14200        }
14201    });
14202
14203    cx.update_editor(|editor, window, cx| {
14204        editor.move_page_up(&MovePageUp::default(), window, cx);
14205        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14206        {
14207            assert!(
14208                menu.selected_item == 0,
14209                "expected PageUp to select the first item from the context menu"
14210            );
14211        } else {
14212            panic!("expected completion menu to stay open after PageUp");
14213        }
14214    });
14215}
14216
14217#[gpui::test]
14218async fn test_as_is_completions(cx: &mut TestAppContext) {
14219    init_test(cx, |_| {});
14220    let mut cx = EditorLspTestContext::new_rust(
14221        lsp::ServerCapabilities {
14222            completion_provider: Some(lsp::CompletionOptions {
14223                ..Default::default()
14224            }),
14225            ..Default::default()
14226        },
14227        cx,
14228    )
14229    .await;
14230    cx.lsp
14231        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14232            Ok(Some(lsp::CompletionResponse::Array(vec![
14233                lsp::CompletionItem {
14234                    label: "unsafe".into(),
14235                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14236                        range: lsp::Range {
14237                            start: lsp::Position {
14238                                line: 1,
14239                                character: 2,
14240                            },
14241                            end: lsp::Position {
14242                                line: 1,
14243                                character: 3,
14244                            },
14245                        },
14246                        new_text: "unsafe".to_string(),
14247                    })),
14248                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14249                    ..Default::default()
14250                },
14251            ])))
14252        });
14253    cx.set_state("fn a() {}\n");
14254    cx.executor().run_until_parked();
14255    cx.update_editor(|editor, window, cx| {
14256        editor.show_completions(
14257            &ShowCompletions {
14258                trigger: Some("\n".into()),
14259            },
14260            window,
14261            cx,
14262        );
14263    });
14264    cx.executor().run_until_parked();
14265
14266    cx.update_editor(|editor, window, cx| {
14267        editor.confirm_completion(&Default::default(), window, cx)
14268    });
14269    cx.executor().run_until_parked();
14270    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14271}
14272
14273#[gpui::test]
14274async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14275    init_test(cx, |_| {});
14276    let language =
14277        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14278    let mut cx = EditorLspTestContext::new(
14279        language,
14280        lsp::ServerCapabilities {
14281            completion_provider: Some(lsp::CompletionOptions {
14282                ..lsp::CompletionOptions::default()
14283            }),
14284            ..lsp::ServerCapabilities::default()
14285        },
14286        cx,
14287    )
14288    .await;
14289
14290    cx.set_state(
14291        "#ifndef BAR_H
14292#define BAR_H
14293
14294#include <stdbool.h>
14295
14296int fn_branch(bool do_branch1, bool do_branch2);
14297
14298#endif // BAR_H
14299ˇ",
14300    );
14301    cx.executor().run_until_parked();
14302    cx.update_editor(|editor, window, cx| {
14303        editor.handle_input("#", window, cx);
14304    });
14305    cx.executor().run_until_parked();
14306    cx.update_editor(|editor, window, cx| {
14307        editor.handle_input("i", window, cx);
14308    });
14309    cx.executor().run_until_parked();
14310    cx.update_editor(|editor, window, cx| {
14311        editor.handle_input("n", window, cx);
14312    });
14313    cx.executor().run_until_parked();
14314    cx.assert_editor_state(
14315        "#ifndef BAR_H
14316#define BAR_H
14317
14318#include <stdbool.h>
14319
14320int fn_branch(bool do_branch1, bool do_branch2);
14321
14322#endif // BAR_H
14323#inˇ",
14324    );
14325
14326    cx.lsp
14327        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14328            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14329                is_incomplete: false,
14330                item_defaults: None,
14331                items: vec![lsp::CompletionItem {
14332                    kind: Some(lsp::CompletionItemKind::SNIPPET),
14333                    label_details: Some(lsp::CompletionItemLabelDetails {
14334                        detail: Some("header".to_string()),
14335                        description: None,
14336                    }),
14337                    label: " include".to_string(),
14338                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14339                        range: lsp::Range {
14340                            start: lsp::Position {
14341                                line: 8,
14342                                character: 1,
14343                            },
14344                            end: lsp::Position {
14345                                line: 8,
14346                                character: 1,
14347                            },
14348                        },
14349                        new_text: "include \"$0\"".to_string(),
14350                    })),
14351                    sort_text: Some("40b67681include".to_string()),
14352                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14353                    filter_text: Some("include".to_string()),
14354                    insert_text: Some("include \"$0\"".to_string()),
14355                    ..lsp::CompletionItem::default()
14356                }],
14357            })))
14358        });
14359    cx.update_editor(|editor, window, cx| {
14360        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14361    });
14362    cx.executor().run_until_parked();
14363    cx.update_editor(|editor, window, cx| {
14364        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14365    });
14366    cx.executor().run_until_parked();
14367    cx.assert_editor_state(
14368        "#ifndef BAR_H
14369#define BAR_H
14370
14371#include <stdbool.h>
14372
14373int fn_branch(bool do_branch1, bool do_branch2);
14374
14375#endif // BAR_H
14376#include \"ˇ\"",
14377    );
14378
14379    cx.lsp
14380        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14381            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14382                is_incomplete: true,
14383                item_defaults: None,
14384                items: vec![lsp::CompletionItem {
14385                    kind: Some(lsp::CompletionItemKind::FILE),
14386                    label: "AGL/".to_string(),
14387                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14388                        range: lsp::Range {
14389                            start: lsp::Position {
14390                                line: 8,
14391                                character: 10,
14392                            },
14393                            end: lsp::Position {
14394                                line: 8,
14395                                character: 11,
14396                            },
14397                        },
14398                        new_text: "AGL/".to_string(),
14399                    })),
14400                    sort_text: Some("40b67681AGL/".to_string()),
14401                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
14402                    filter_text: Some("AGL/".to_string()),
14403                    insert_text: Some("AGL/".to_string()),
14404                    ..lsp::CompletionItem::default()
14405                }],
14406            })))
14407        });
14408    cx.update_editor(|editor, window, cx| {
14409        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14410    });
14411    cx.executor().run_until_parked();
14412    cx.update_editor(|editor, window, cx| {
14413        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14414    });
14415    cx.executor().run_until_parked();
14416    cx.assert_editor_state(
14417        r##"#ifndef BAR_H
14418#define BAR_H
14419
14420#include <stdbool.h>
14421
14422int fn_branch(bool do_branch1, bool do_branch2);
14423
14424#endif // BAR_H
14425#include "AGL/ˇ"##,
14426    );
14427
14428    cx.update_editor(|editor, window, cx| {
14429        editor.handle_input("\"", window, cx);
14430    });
14431    cx.executor().run_until_parked();
14432    cx.assert_editor_state(
14433        r##"#ifndef BAR_H
14434#define BAR_H
14435
14436#include <stdbool.h>
14437
14438int fn_branch(bool do_branch1, bool do_branch2);
14439
14440#endif // BAR_H
14441#include "AGL/"ˇ"##,
14442    );
14443}
14444
14445#[gpui::test]
14446async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
14447    init_test(cx, |_| {});
14448
14449    let mut cx = EditorLspTestContext::new_rust(
14450        lsp::ServerCapabilities {
14451            completion_provider: Some(lsp::CompletionOptions {
14452                trigger_characters: Some(vec![".".to_string()]),
14453                resolve_provider: Some(true),
14454                ..Default::default()
14455            }),
14456            ..Default::default()
14457        },
14458        cx,
14459    )
14460    .await;
14461
14462    cx.set_state("fn main() { let a = 2ˇ; }");
14463    cx.simulate_keystroke(".");
14464    let completion_item = lsp::CompletionItem {
14465        label: "Some".into(),
14466        kind: Some(lsp::CompletionItemKind::SNIPPET),
14467        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
14468        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
14469            kind: lsp::MarkupKind::Markdown,
14470            value: "```rust\nSome(2)\n```".to_string(),
14471        })),
14472        deprecated: Some(false),
14473        sort_text: Some("Some".to_string()),
14474        filter_text: Some("Some".to_string()),
14475        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14476        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14477            range: lsp::Range {
14478                start: lsp::Position {
14479                    line: 0,
14480                    character: 22,
14481                },
14482                end: lsp::Position {
14483                    line: 0,
14484                    character: 22,
14485                },
14486            },
14487            new_text: "Some(2)".to_string(),
14488        })),
14489        additional_text_edits: Some(vec![lsp::TextEdit {
14490            range: lsp::Range {
14491                start: lsp::Position {
14492                    line: 0,
14493                    character: 20,
14494                },
14495                end: lsp::Position {
14496                    line: 0,
14497                    character: 22,
14498                },
14499            },
14500            new_text: "".to_string(),
14501        }]),
14502        ..Default::default()
14503    };
14504
14505    let closure_completion_item = completion_item.clone();
14506    let counter = Arc::new(AtomicUsize::new(0));
14507    let counter_clone = counter.clone();
14508    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
14509        let task_completion_item = closure_completion_item.clone();
14510        counter_clone.fetch_add(1, atomic::Ordering::Release);
14511        async move {
14512            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14513                is_incomplete: true,
14514                item_defaults: None,
14515                items: vec![task_completion_item],
14516            })))
14517        }
14518    });
14519
14520    cx.condition(|editor, _| editor.context_menu_visible())
14521        .await;
14522    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
14523    assert!(request.next().await.is_some());
14524    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14525
14526    cx.simulate_keystrokes("S o m");
14527    cx.condition(|editor, _| editor.context_menu_visible())
14528        .await;
14529    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
14530    assert!(request.next().await.is_some());
14531    assert!(request.next().await.is_some());
14532    assert!(request.next().await.is_some());
14533    request.close();
14534    assert!(request.next().await.is_none());
14535    assert_eq!(
14536        counter.load(atomic::Ordering::Acquire),
14537        4,
14538        "With the completions menu open, only one LSP request should happen per input"
14539    );
14540}
14541
14542#[gpui::test]
14543async fn test_toggle_comment(cx: &mut TestAppContext) {
14544    init_test(cx, |_| {});
14545    let mut cx = EditorTestContext::new(cx).await;
14546    let language = Arc::new(Language::new(
14547        LanguageConfig {
14548            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14549            ..Default::default()
14550        },
14551        Some(tree_sitter_rust::LANGUAGE.into()),
14552    ));
14553    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14554
14555    // If multiple selections intersect a line, the line is only toggled once.
14556    cx.set_state(indoc! {"
14557        fn a() {
14558            «//b();
14559            ˇ»// «c();
14560            //ˇ»  d();
14561        }
14562    "});
14563
14564    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14565
14566    cx.assert_editor_state(indoc! {"
14567        fn a() {
14568            «b();
14569            c();
14570            ˇ» d();
14571        }
14572    "});
14573
14574    // The comment prefix is inserted at the same column for every line in a
14575    // selection.
14576    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14577
14578    cx.assert_editor_state(indoc! {"
14579        fn a() {
14580            // «b();
14581            // c();
14582            ˇ»//  d();
14583        }
14584    "});
14585
14586    // If a selection ends at the beginning of a line, that line is not toggled.
14587    cx.set_selections_state(indoc! {"
14588        fn a() {
14589            // b();
14590            «// c();
14591        ˇ»    //  d();
14592        }
14593    "});
14594
14595    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14596
14597    cx.assert_editor_state(indoc! {"
14598        fn a() {
14599            // b();
14600            «c();
14601        ˇ»    //  d();
14602        }
14603    "});
14604
14605    // If a selection span a single line and is empty, the line is toggled.
14606    cx.set_state(indoc! {"
14607        fn a() {
14608            a();
14609            b();
14610        ˇ
14611        }
14612    "});
14613
14614    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14615
14616    cx.assert_editor_state(indoc! {"
14617        fn a() {
14618            a();
14619            b();
14620        //•ˇ
14621        }
14622    "});
14623
14624    // If a selection span multiple lines, empty lines are not toggled.
14625    cx.set_state(indoc! {"
14626        fn a() {
14627            «a();
14628
14629            c();ˇ»
14630        }
14631    "});
14632
14633    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14634
14635    cx.assert_editor_state(indoc! {"
14636        fn a() {
14637            // «a();
14638
14639            // c();ˇ»
14640        }
14641    "});
14642
14643    // If a selection includes multiple comment prefixes, all lines are uncommented.
14644    cx.set_state(indoc! {"
14645        fn a() {
14646            «// a();
14647            /// b();
14648            //! c();ˇ»
14649        }
14650    "});
14651
14652    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
14653
14654    cx.assert_editor_state(indoc! {"
14655        fn a() {
14656            «a();
14657            b();
14658            c();ˇ»
14659        }
14660    "});
14661}
14662
14663#[gpui::test]
14664async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
14665    init_test(cx, |_| {});
14666    let mut cx = EditorTestContext::new(cx).await;
14667    let language = Arc::new(Language::new(
14668        LanguageConfig {
14669            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
14670            ..Default::default()
14671        },
14672        Some(tree_sitter_rust::LANGUAGE.into()),
14673    ));
14674    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
14675
14676    let toggle_comments = &ToggleComments {
14677        advance_downwards: false,
14678        ignore_indent: true,
14679    };
14680
14681    // If multiple selections intersect a line, the line is only toggled once.
14682    cx.set_state(indoc! {"
14683        fn a() {
14684        //    «b();
14685        //    c();
14686        //    ˇ» d();
14687        }
14688    "});
14689
14690    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14691
14692    cx.assert_editor_state(indoc! {"
14693        fn a() {
14694            «b();
14695            c();
14696            ˇ» d();
14697        }
14698    "});
14699
14700    // The comment prefix is inserted at the beginning of each line
14701    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14702
14703    cx.assert_editor_state(indoc! {"
14704        fn a() {
14705        //    «b();
14706        //    c();
14707        //    ˇ» d();
14708        }
14709    "});
14710
14711    // If a selection ends at the beginning of a line, that line is not toggled.
14712    cx.set_selections_state(indoc! {"
14713        fn a() {
14714        //    b();
14715        //    «c();
14716        ˇ»//     d();
14717        }
14718    "});
14719
14720    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14721
14722    cx.assert_editor_state(indoc! {"
14723        fn a() {
14724        //    b();
14725            «c();
14726        ˇ»//     d();
14727        }
14728    "});
14729
14730    // If a selection span a single line and is empty, the line is toggled.
14731    cx.set_state(indoc! {"
14732        fn a() {
14733            a();
14734            b();
14735        ˇ
14736        }
14737    "});
14738
14739    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14740
14741    cx.assert_editor_state(indoc! {"
14742        fn a() {
14743            a();
14744            b();
14745        //ˇ
14746        }
14747    "});
14748
14749    // If a selection span multiple lines, empty lines are not toggled.
14750    cx.set_state(indoc! {"
14751        fn a() {
14752            «a();
14753
14754            c();ˇ»
14755        }
14756    "});
14757
14758    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14759
14760    cx.assert_editor_state(indoc! {"
14761        fn a() {
14762        //    «a();
14763
14764        //    c();ˇ»
14765        }
14766    "});
14767
14768    // If a selection includes multiple comment prefixes, all lines are uncommented.
14769    cx.set_state(indoc! {"
14770        fn a() {
14771        //    «a();
14772        ///    b();
14773        //!    c();ˇ»
14774        }
14775    "});
14776
14777    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
14778
14779    cx.assert_editor_state(indoc! {"
14780        fn a() {
14781            «a();
14782            b();
14783            c();ˇ»
14784        }
14785    "});
14786}
14787
14788#[gpui::test]
14789async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
14790    init_test(cx, |_| {});
14791
14792    let language = Arc::new(Language::new(
14793        LanguageConfig {
14794            line_comments: vec!["// ".into()],
14795            ..Default::default()
14796        },
14797        Some(tree_sitter_rust::LANGUAGE.into()),
14798    ));
14799
14800    let mut cx = EditorTestContext::new(cx).await;
14801
14802    cx.language_registry().add(language.clone());
14803    cx.update_buffer(|buffer, cx| {
14804        buffer.set_language(Some(language), cx);
14805    });
14806
14807    let toggle_comments = &ToggleComments {
14808        advance_downwards: true,
14809        ignore_indent: false,
14810    };
14811
14812    // Single cursor on one line -> advance
14813    // Cursor moves horizontally 3 characters as well on non-blank line
14814    cx.set_state(indoc!(
14815        "fn a() {
14816             ˇdog();
14817             cat();
14818        }"
14819    ));
14820    cx.update_editor(|editor, window, cx| {
14821        editor.toggle_comments(toggle_comments, window, cx);
14822    });
14823    cx.assert_editor_state(indoc!(
14824        "fn a() {
14825             // dog();
14826             catˇ();
14827        }"
14828    ));
14829
14830    // Single selection on one line -> don't advance
14831    cx.set_state(indoc!(
14832        "fn a() {
14833             «dog()ˇ»;
14834             cat();
14835        }"
14836    ));
14837    cx.update_editor(|editor, window, cx| {
14838        editor.toggle_comments(toggle_comments, window, cx);
14839    });
14840    cx.assert_editor_state(indoc!(
14841        "fn a() {
14842             // «dog()ˇ»;
14843             cat();
14844        }"
14845    ));
14846
14847    // Multiple cursors on one line -> advance
14848    cx.set_state(indoc!(
14849        "fn a() {
14850             ˇdˇog();
14851             cat();
14852        }"
14853    ));
14854    cx.update_editor(|editor, window, cx| {
14855        editor.toggle_comments(toggle_comments, window, cx);
14856    });
14857    cx.assert_editor_state(indoc!(
14858        "fn a() {
14859             // dog();
14860             catˇ(ˇ);
14861        }"
14862    ));
14863
14864    // Multiple cursors on one line, with selection -> don't advance
14865    cx.set_state(indoc!(
14866        "fn a() {
14867             ˇdˇog«()ˇ»;
14868             cat();
14869        }"
14870    ));
14871    cx.update_editor(|editor, window, cx| {
14872        editor.toggle_comments(toggle_comments, window, cx);
14873    });
14874    cx.assert_editor_state(indoc!(
14875        "fn a() {
14876             // ˇdˇog«()ˇ»;
14877             cat();
14878        }"
14879    ));
14880
14881    // Single cursor on one line -> advance
14882    // Cursor moves to column 0 on blank line
14883    cx.set_state(indoc!(
14884        "fn a() {
14885             ˇdog();
14886
14887             cat();
14888        }"
14889    ));
14890    cx.update_editor(|editor, window, cx| {
14891        editor.toggle_comments(toggle_comments, window, cx);
14892    });
14893    cx.assert_editor_state(indoc!(
14894        "fn a() {
14895             // dog();
14896        ˇ
14897             cat();
14898        }"
14899    ));
14900
14901    // Single cursor on one line -> advance
14902    // Cursor starts and ends at column 0
14903    cx.set_state(indoc!(
14904        "fn a() {
14905         ˇ    dog();
14906             cat();
14907        }"
14908    ));
14909    cx.update_editor(|editor, window, cx| {
14910        editor.toggle_comments(toggle_comments, window, cx);
14911    });
14912    cx.assert_editor_state(indoc!(
14913        "fn a() {
14914             // dog();
14915         ˇ    cat();
14916        }"
14917    ));
14918}
14919
14920#[gpui::test]
14921async fn test_toggle_block_comment(cx: &mut TestAppContext) {
14922    init_test(cx, |_| {});
14923
14924    let mut cx = EditorTestContext::new(cx).await;
14925
14926    let html_language = Arc::new(
14927        Language::new(
14928            LanguageConfig {
14929                name: "HTML".into(),
14930                block_comment: Some(BlockCommentConfig {
14931                    start: "<!-- ".into(),
14932                    prefix: "".into(),
14933                    end: " -->".into(),
14934                    tab_size: 0,
14935                }),
14936                ..Default::default()
14937            },
14938            Some(tree_sitter_html::LANGUAGE.into()),
14939        )
14940        .with_injection_query(
14941            r#"
14942            (script_element
14943                (raw_text) @injection.content
14944                (#set! injection.language "javascript"))
14945            "#,
14946        )
14947        .unwrap(),
14948    );
14949
14950    let javascript_language = Arc::new(Language::new(
14951        LanguageConfig {
14952            name: "JavaScript".into(),
14953            line_comments: vec!["// ".into()],
14954            ..Default::default()
14955        },
14956        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
14957    ));
14958
14959    cx.language_registry().add(html_language.clone());
14960    cx.language_registry().add(javascript_language);
14961    cx.update_buffer(|buffer, cx| {
14962        buffer.set_language(Some(html_language), cx);
14963    });
14964
14965    // Toggle comments for empty selections
14966    cx.set_state(
14967        &r#"
14968            <p>A</p>ˇ
14969            <p>B</p>ˇ
14970            <p>C</p>ˇ
14971        "#
14972        .unindent(),
14973    );
14974    cx.update_editor(|editor, window, cx| {
14975        editor.toggle_comments(&ToggleComments::default(), window, cx)
14976    });
14977    cx.assert_editor_state(
14978        &r#"
14979            <!-- <p>A</p>ˇ -->
14980            <!-- <p>B</p>ˇ -->
14981            <!-- <p>C</p>ˇ -->
14982        "#
14983        .unindent(),
14984    );
14985    cx.update_editor(|editor, window, cx| {
14986        editor.toggle_comments(&ToggleComments::default(), window, cx)
14987    });
14988    cx.assert_editor_state(
14989        &r#"
14990            <p>A</p>ˇ
14991            <p>B</p>ˇ
14992            <p>C</p>ˇ
14993        "#
14994        .unindent(),
14995    );
14996
14997    // Toggle comments for mixture of empty and non-empty selections, where
14998    // multiple selections occupy a given line.
14999    cx.set_state(
15000        &r#"
15001            <p>A«</p>
15002            <p>ˇ»B</p>ˇ
15003            <p>C«</p>
15004            <p>ˇ»D</p>ˇ
15005        "#
15006        .unindent(),
15007    );
15008
15009    cx.update_editor(|editor, window, cx| {
15010        editor.toggle_comments(&ToggleComments::default(), window, cx)
15011    });
15012    cx.assert_editor_state(
15013        &r#"
15014            <!-- <p>A«</p>
15015            <p>ˇ»B</p>ˇ -->
15016            <!-- <p>C«</p>
15017            <p>ˇ»D</p>ˇ -->
15018        "#
15019        .unindent(),
15020    );
15021    cx.update_editor(|editor, window, cx| {
15022        editor.toggle_comments(&ToggleComments::default(), window, cx)
15023    });
15024    cx.assert_editor_state(
15025        &r#"
15026            <p>A«</p>
15027            <p>ˇ»B</p>ˇ
15028            <p>C«</p>
15029            <p>ˇ»D</p>ˇ
15030        "#
15031        .unindent(),
15032    );
15033
15034    // Toggle comments when different languages are active for different
15035    // selections.
15036    cx.set_state(
15037        &r#"
15038            ˇ<script>
15039                ˇvar x = new Y();
15040            ˇ</script>
15041        "#
15042        .unindent(),
15043    );
15044    cx.executor().run_until_parked();
15045    cx.update_editor(|editor, window, cx| {
15046        editor.toggle_comments(&ToggleComments::default(), window, cx)
15047    });
15048    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15049    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15050    cx.assert_editor_state(
15051        &r#"
15052            <!-- ˇ<script> -->
15053                // ˇvar x = new Y();
15054            <!-- ˇ</script> -->
15055        "#
15056        .unindent(),
15057    );
15058}
15059
15060#[gpui::test]
15061fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15062    init_test(cx, |_| {});
15063
15064    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15065    let multibuffer = cx.new(|cx| {
15066        let mut multibuffer = MultiBuffer::new(ReadWrite);
15067        multibuffer.push_excerpts(
15068            buffer.clone(),
15069            [
15070                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15071                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15072            ],
15073            cx,
15074        );
15075        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15076        multibuffer
15077    });
15078
15079    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15080    editor.update_in(cx, |editor, window, cx| {
15081        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15082        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15083            s.select_ranges([
15084                Point::new(0, 0)..Point::new(0, 0),
15085                Point::new(1, 0)..Point::new(1, 0),
15086            ])
15087        });
15088
15089        editor.handle_input("X", window, cx);
15090        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15091        assert_eq!(
15092            editor.selections.ranges(cx),
15093            [
15094                Point::new(0, 1)..Point::new(0, 1),
15095                Point::new(1, 1)..Point::new(1, 1),
15096            ]
15097        );
15098
15099        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15100        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15101            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15102        });
15103        editor.backspace(&Default::default(), window, cx);
15104        assert_eq!(editor.text(cx), "Xa\nbbb");
15105        assert_eq!(
15106            editor.selections.ranges(cx),
15107            [Point::new(1, 0)..Point::new(1, 0)]
15108        );
15109
15110        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15111            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15112        });
15113        editor.backspace(&Default::default(), window, cx);
15114        assert_eq!(editor.text(cx), "X\nbb");
15115        assert_eq!(
15116            editor.selections.ranges(cx),
15117            [Point::new(0, 1)..Point::new(0, 1)]
15118        );
15119    });
15120}
15121
15122#[gpui::test]
15123fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15124    init_test(cx, |_| {});
15125
15126    let markers = vec![('[', ']').into(), ('(', ')').into()];
15127    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15128        indoc! {"
15129            [aaaa
15130            (bbbb]
15131            cccc)",
15132        },
15133        markers.clone(),
15134    );
15135    let excerpt_ranges = markers.into_iter().map(|marker| {
15136        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15137        ExcerptRange::new(context)
15138    });
15139    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15140    let multibuffer = cx.new(|cx| {
15141        let mut multibuffer = MultiBuffer::new(ReadWrite);
15142        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15143        multibuffer
15144    });
15145
15146    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15147    editor.update_in(cx, |editor, window, cx| {
15148        let (expected_text, selection_ranges) = marked_text_ranges(
15149            indoc! {"
15150                aaaa
15151                bˇbbb
15152                bˇbbˇb
15153                cccc"
15154            },
15155            true,
15156        );
15157        assert_eq!(editor.text(cx), expected_text);
15158        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15159            s.select_ranges(selection_ranges)
15160        });
15161
15162        editor.handle_input("X", window, cx);
15163
15164        let (expected_text, expected_selections) = marked_text_ranges(
15165            indoc! {"
15166                aaaa
15167                bXˇbbXb
15168                bXˇbbXˇb
15169                cccc"
15170            },
15171            false,
15172        );
15173        assert_eq!(editor.text(cx), expected_text);
15174        assert_eq!(editor.selections.ranges(cx), expected_selections);
15175
15176        editor.newline(&Newline, window, cx);
15177        let (expected_text, expected_selections) = marked_text_ranges(
15178            indoc! {"
15179                aaaa
15180                bX
15181                ˇbbX
15182                b
15183                bX
15184                ˇbbX
15185                ˇb
15186                cccc"
15187            },
15188            false,
15189        );
15190        assert_eq!(editor.text(cx), expected_text);
15191        assert_eq!(editor.selections.ranges(cx), expected_selections);
15192    });
15193}
15194
15195#[gpui::test]
15196fn test_refresh_selections(cx: &mut TestAppContext) {
15197    init_test(cx, |_| {});
15198
15199    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15200    let mut excerpt1_id = None;
15201    let multibuffer = cx.new(|cx| {
15202        let mut multibuffer = MultiBuffer::new(ReadWrite);
15203        excerpt1_id = multibuffer
15204            .push_excerpts(
15205                buffer.clone(),
15206                [
15207                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15208                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15209                ],
15210                cx,
15211            )
15212            .into_iter()
15213            .next();
15214        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15215        multibuffer
15216    });
15217
15218    let editor = cx.add_window(|window, cx| {
15219        let mut editor = build_editor(multibuffer.clone(), window, cx);
15220        let snapshot = editor.snapshot(window, cx);
15221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15222            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15223        });
15224        editor.begin_selection(
15225            Point::new(2, 1).to_display_point(&snapshot),
15226            true,
15227            1,
15228            window,
15229            cx,
15230        );
15231        assert_eq!(
15232            editor.selections.ranges(cx),
15233            [
15234                Point::new(1, 3)..Point::new(1, 3),
15235                Point::new(2, 1)..Point::new(2, 1),
15236            ]
15237        );
15238        editor
15239    });
15240
15241    // Refreshing selections is a no-op when excerpts haven't changed.
15242    _ = editor.update(cx, |editor, window, cx| {
15243        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15244        assert_eq!(
15245            editor.selections.ranges(cx),
15246            [
15247                Point::new(1, 3)..Point::new(1, 3),
15248                Point::new(2, 1)..Point::new(2, 1),
15249            ]
15250        );
15251    });
15252
15253    multibuffer.update(cx, |multibuffer, cx| {
15254        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15255    });
15256    _ = editor.update(cx, |editor, window, cx| {
15257        // Removing an excerpt causes the first selection to become degenerate.
15258        assert_eq!(
15259            editor.selections.ranges(cx),
15260            [
15261                Point::new(0, 0)..Point::new(0, 0),
15262                Point::new(0, 1)..Point::new(0, 1)
15263            ]
15264        );
15265
15266        // Refreshing selections will relocate the first selection to the original buffer
15267        // location.
15268        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15269        assert_eq!(
15270            editor.selections.ranges(cx),
15271            [
15272                Point::new(0, 1)..Point::new(0, 1),
15273                Point::new(0, 3)..Point::new(0, 3)
15274            ]
15275        );
15276        assert!(editor.selections.pending_anchor().is_some());
15277    });
15278}
15279
15280#[gpui::test]
15281fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15282    init_test(cx, |_| {});
15283
15284    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15285    let mut excerpt1_id = None;
15286    let multibuffer = cx.new(|cx| {
15287        let mut multibuffer = MultiBuffer::new(ReadWrite);
15288        excerpt1_id = multibuffer
15289            .push_excerpts(
15290                buffer.clone(),
15291                [
15292                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15293                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15294                ],
15295                cx,
15296            )
15297            .into_iter()
15298            .next();
15299        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15300        multibuffer
15301    });
15302
15303    let editor = cx.add_window(|window, cx| {
15304        let mut editor = build_editor(multibuffer.clone(), window, cx);
15305        let snapshot = editor.snapshot(window, cx);
15306        editor.begin_selection(
15307            Point::new(1, 3).to_display_point(&snapshot),
15308            false,
15309            1,
15310            window,
15311            cx,
15312        );
15313        assert_eq!(
15314            editor.selections.ranges(cx),
15315            [Point::new(1, 3)..Point::new(1, 3)]
15316        );
15317        editor
15318    });
15319
15320    multibuffer.update(cx, |multibuffer, cx| {
15321        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15322    });
15323    _ = editor.update(cx, |editor, window, cx| {
15324        assert_eq!(
15325            editor.selections.ranges(cx),
15326            [Point::new(0, 0)..Point::new(0, 0)]
15327        );
15328
15329        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
15330        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15331        assert_eq!(
15332            editor.selections.ranges(cx),
15333            [Point::new(0, 3)..Point::new(0, 3)]
15334        );
15335        assert!(editor.selections.pending_anchor().is_some());
15336    });
15337}
15338
15339#[gpui::test]
15340async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
15341    init_test(cx, |_| {});
15342
15343    let language = Arc::new(
15344        Language::new(
15345            LanguageConfig {
15346                brackets: BracketPairConfig {
15347                    pairs: vec![
15348                        BracketPair {
15349                            start: "{".to_string(),
15350                            end: "}".to_string(),
15351                            close: true,
15352                            surround: true,
15353                            newline: true,
15354                        },
15355                        BracketPair {
15356                            start: "/* ".to_string(),
15357                            end: " */".to_string(),
15358                            close: true,
15359                            surround: true,
15360                            newline: true,
15361                        },
15362                    ],
15363                    ..Default::default()
15364                },
15365                ..Default::default()
15366            },
15367            Some(tree_sitter_rust::LANGUAGE.into()),
15368        )
15369        .with_indents_query("")
15370        .unwrap(),
15371    );
15372
15373    let text = concat!(
15374        "{   }\n",     //
15375        "  x\n",       //
15376        "  /*   */\n", //
15377        "x\n",         //
15378        "{{} }\n",     //
15379    );
15380
15381    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
15382    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
15383    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
15384    editor
15385        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
15386        .await;
15387
15388    editor.update_in(cx, |editor, window, cx| {
15389        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15390            s.select_display_ranges([
15391                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
15392                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
15393                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
15394            ])
15395        });
15396        editor.newline(&Newline, window, cx);
15397
15398        assert_eq!(
15399            editor.buffer().read(cx).read(cx).text(),
15400            concat!(
15401                "{ \n",    // Suppress rustfmt
15402                "\n",      //
15403                "}\n",     //
15404                "  x\n",   //
15405                "  /* \n", //
15406                "  \n",    //
15407                "  */\n",  //
15408                "x\n",     //
15409                "{{} \n",  //
15410                "}\n",     //
15411            )
15412        );
15413    });
15414}
15415
15416#[gpui::test]
15417fn test_highlighted_ranges(cx: &mut TestAppContext) {
15418    init_test(cx, |_| {});
15419
15420    let editor = cx.add_window(|window, cx| {
15421        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
15422        build_editor(buffer, window, cx)
15423    });
15424
15425    _ = editor.update(cx, |editor, window, cx| {
15426        struct Type1;
15427        struct Type2;
15428
15429        let buffer = editor.buffer.read(cx).snapshot(cx);
15430
15431        let anchor_range =
15432            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
15433
15434        editor.highlight_background::<Type1>(
15435            &[
15436                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
15437                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
15438                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
15439                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
15440            ],
15441            |_| Hsla::red(),
15442            cx,
15443        );
15444        editor.highlight_background::<Type2>(
15445            &[
15446                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
15447                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
15448                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
15449                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
15450            ],
15451            |_| Hsla::green(),
15452            cx,
15453        );
15454
15455        let snapshot = editor.snapshot(window, cx);
15456        let mut highlighted_ranges = editor.background_highlights_in_range(
15457            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
15458            &snapshot,
15459            cx.theme(),
15460        );
15461        // Enforce a consistent ordering based on color without relying on the ordering of the
15462        // highlight's `TypeId` which is non-executor.
15463        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
15464        assert_eq!(
15465            highlighted_ranges,
15466            &[
15467                (
15468                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
15469                    Hsla::red(),
15470                ),
15471                (
15472                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
15473                    Hsla::red(),
15474                ),
15475                (
15476                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
15477                    Hsla::green(),
15478                ),
15479                (
15480                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
15481                    Hsla::green(),
15482                ),
15483            ]
15484        );
15485        assert_eq!(
15486            editor.background_highlights_in_range(
15487                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
15488                &snapshot,
15489                cx.theme(),
15490            ),
15491            &[(
15492                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
15493                Hsla::red(),
15494            )]
15495        );
15496    });
15497}
15498
15499#[gpui::test]
15500async fn test_following(cx: &mut TestAppContext) {
15501    init_test(cx, |_| {});
15502
15503    let fs = FakeFs::new(cx.executor());
15504    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
15505
15506    let buffer = project.update(cx, |project, cx| {
15507        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
15508        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
15509    });
15510    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
15511    let follower = cx.update(|cx| {
15512        cx.open_window(
15513            WindowOptions {
15514                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
15515                    gpui::Point::new(px(0.), px(0.)),
15516                    gpui::Point::new(px(10.), px(80.)),
15517                ))),
15518                ..Default::default()
15519            },
15520            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
15521        )
15522        .unwrap()
15523    });
15524
15525    let is_still_following = Rc::new(RefCell::new(true));
15526    let follower_edit_event_count = Rc::new(RefCell::new(0));
15527    let pending_update = Rc::new(RefCell::new(None));
15528    let leader_entity = leader.root(cx).unwrap();
15529    let follower_entity = follower.root(cx).unwrap();
15530    _ = follower.update(cx, {
15531        let update = pending_update.clone();
15532        let is_still_following = is_still_following.clone();
15533        let follower_edit_event_count = follower_edit_event_count.clone();
15534        |_, window, cx| {
15535            cx.subscribe_in(
15536                &leader_entity,
15537                window,
15538                move |_, leader, event, window, cx| {
15539                    leader.read(cx).add_event_to_update_proto(
15540                        event,
15541                        &mut update.borrow_mut(),
15542                        window,
15543                        cx,
15544                    );
15545                },
15546            )
15547            .detach();
15548
15549            cx.subscribe_in(
15550                &follower_entity,
15551                window,
15552                move |_, _, event: &EditorEvent, _window, _cx| {
15553                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
15554                        *is_still_following.borrow_mut() = false;
15555                    }
15556
15557                    if let EditorEvent::BufferEdited = event {
15558                        *follower_edit_event_count.borrow_mut() += 1;
15559                    }
15560                },
15561            )
15562            .detach();
15563        }
15564    });
15565
15566    // Update the selections only
15567    _ = leader.update(cx, |leader, window, cx| {
15568        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15569            s.select_ranges([1..1])
15570        });
15571    });
15572    follower
15573        .update(cx, |follower, window, cx| {
15574            follower.apply_update_proto(
15575                &project,
15576                pending_update.borrow_mut().take().unwrap(),
15577                window,
15578                cx,
15579            )
15580        })
15581        .unwrap()
15582        .await
15583        .unwrap();
15584    _ = follower.update(cx, |follower, _, cx| {
15585        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
15586    });
15587    assert!(*is_still_following.borrow());
15588    assert_eq!(*follower_edit_event_count.borrow(), 0);
15589
15590    // Update the scroll position only
15591    _ = leader.update(cx, |leader, window, cx| {
15592        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15593    });
15594    follower
15595        .update(cx, |follower, window, cx| {
15596            follower.apply_update_proto(
15597                &project,
15598                pending_update.borrow_mut().take().unwrap(),
15599                window,
15600                cx,
15601            )
15602        })
15603        .unwrap()
15604        .await
15605        .unwrap();
15606    assert_eq!(
15607        follower
15608            .update(cx, |follower, _, cx| follower.scroll_position(cx))
15609            .unwrap(),
15610        gpui::Point::new(1.5, 3.5)
15611    );
15612    assert!(*is_still_following.borrow());
15613    assert_eq!(*follower_edit_event_count.borrow(), 0);
15614
15615    // Update the selections and scroll position. The follower's scroll position is updated
15616    // via autoscroll, not via the leader's exact scroll position.
15617    _ = leader.update(cx, |leader, window, cx| {
15618        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15619            s.select_ranges([0..0])
15620        });
15621        leader.request_autoscroll(Autoscroll::newest(), cx);
15622        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
15623    });
15624    follower
15625        .update(cx, |follower, window, cx| {
15626            follower.apply_update_proto(
15627                &project,
15628                pending_update.borrow_mut().take().unwrap(),
15629                window,
15630                cx,
15631            )
15632        })
15633        .unwrap()
15634        .await
15635        .unwrap();
15636    _ = follower.update(cx, |follower, _, cx| {
15637        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
15638        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
15639    });
15640    assert!(*is_still_following.borrow());
15641
15642    // Creating a pending selection that precedes another selection
15643    _ = leader.update(cx, |leader, window, cx| {
15644        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15645            s.select_ranges([1..1])
15646        });
15647        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
15648    });
15649    follower
15650        .update(cx, |follower, window, cx| {
15651            follower.apply_update_proto(
15652                &project,
15653                pending_update.borrow_mut().take().unwrap(),
15654                window,
15655                cx,
15656            )
15657        })
15658        .unwrap()
15659        .await
15660        .unwrap();
15661    _ = follower.update(cx, |follower, _, cx| {
15662        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
15663    });
15664    assert!(*is_still_following.borrow());
15665
15666    // Extend the pending selection so that it surrounds another selection
15667    _ = leader.update(cx, |leader, window, cx| {
15668        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
15669    });
15670    follower
15671        .update(cx, |follower, window, cx| {
15672            follower.apply_update_proto(
15673                &project,
15674                pending_update.borrow_mut().take().unwrap(),
15675                window,
15676                cx,
15677            )
15678        })
15679        .unwrap()
15680        .await
15681        .unwrap();
15682    _ = follower.update(cx, |follower, _, cx| {
15683        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
15684    });
15685
15686    // Scrolling locally breaks the follow
15687    _ = follower.update(cx, |follower, window, cx| {
15688        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
15689        follower.set_scroll_anchor(
15690            ScrollAnchor {
15691                anchor: top_anchor,
15692                offset: gpui::Point::new(0.0, 0.5),
15693            },
15694            window,
15695            cx,
15696        );
15697    });
15698    assert!(!(*is_still_following.borrow()));
15699}
15700
15701#[gpui::test]
15702async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
15703    init_test(cx, |_| {});
15704
15705    let fs = FakeFs::new(cx.executor());
15706    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
15707    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15708    let pane = workspace
15709        .update(cx, |workspace, _, _| workspace.active_pane().clone())
15710        .unwrap();
15711
15712    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
15713
15714    let leader = pane.update_in(cx, |_, window, cx| {
15715        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
15716        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
15717    });
15718
15719    // Start following the editor when it has no excerpts.
15720    let mut state_message =
15721        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
15722    let workspace_entity = workspace.root(cx).unwrap();
15723    let follower_1 = cx
15724        .update_window(*workspace.deref(), |_, window, cx| {
15725            Editor::from_state_proto(
15726                workspace_entity,
15727                ViewId {
15728                    creator: CollaboratorId::PeerId(PeerId::default()),
15729                    id: 0,
15730                },
15731                &mut state_message,
15732                window,
15733                cx,
15734            )
15735        })
15736        .unwrap()
15737        .unwrap()
15738        .await
15739        .unwrap();
15740
15741    let update_message = Rc::new(RefCell::new(None));
15742    follower_1.update_in(cx, {
15743        let update = update_message.clone();
15744        |_, window, cx| {
15745            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
15746                leader.read(cx).add_event_to_update_proto(
15747                    event,
15748                    &mut update.borrow_mut(),
15749                    window,
15750                    cx,
15751                );
15752            })
15753            .detach();
15754        }
15755    });
15756
15757    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
15758        (
15759            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
15760            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
15761        )
15762    });
15763
15764    // Insert some excerpts.
15765    leader.update(cx, |leader, cx| {
15766        leader.buffer.update(cx, |multibuffer, cx| {
15767            multibuffer.set_excerpts_for_path(
15768                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
15769                buffer_1.clone(),
15770                vec![
15771                    Point::row_range(0..3),
15772                    Point::row_range(1..6),
15773                    Point::row_range(12..15),
15774                ],
15775                0,
15776                cx,
15777            );
15778            multibuffer.set_excerpts_for_path(
15779                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
15780                buffer_2.clone(),
15781                vec![Point::row_range(0..6), Point::row_range(8..12)],
15782                0,
15783                cx,
15784            );
15785        });
15786    });
15787
15788    // Apply the update of adding the excerpts.
15789    follower_1
15790        .update_in(cx, |follower, window, cx| {
15791            follower.apply_update_proto(
15792                &project,
15793                update_message.borrow().clone().unwrap(),
15794                window,
15795                cx,
15796            )
15797        })
15798        .await
15799        .unwrap();
15800    assert_eq!(
15801        follower_1.update(cx, |editor, cx| editor.text(cx)),
15802        leader.update(cx, |editor, cx| editor.text(cx))
15803    );
15804    update_message.borrow_mut().take();
15805
15806    // Start following separately after it already has excerpts.
15807    let mut state_message =
15808        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
15809    let workspace_entity = workspace.root(cx).unwrap();
15810    let follower_2 = cx
15811        .update_window(*workspace.deref(), |_, window, cx| {
15812            Editor::from_state_proto(
15813                workspace_entity,
15814                ViewId {
15815                    creator: CollaboratorId::PeerId(PeerId::default()),
15816                    id: 0,
15817                },
15818                &mut state_message,
15819                window,
15820                cx,
15821            )
15822        })
15823        .unwrap()
15824        .unwrap()
15825        .await
15826        .unwrap();
15827    assert_eq!(
15828        follower_2.update(cx, |editor, cx| editor.text(cx)),
15829        leader.update(cx, |editor, cx| editor.text(cx))
15830    );
15831
15832    // Remove some excerpts.
15833    leader.update(cx, |leader, cx| {
15834        leader.buffer.update(cx, |multibuffer, cx| {
15835            let excerpt_ids = multibuffer.excerpt_ids();
15836            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
15837            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
15838        });
15839    });
15840
15841    // Apply the update of removing the excerpts.
15842    follower_1
15843        .update_in(cx, |follower, window, cx| {
15844            follower.apply_update_proto(
15845                &project,
15846                update_message.borrow().clone().unwrap(),
15847                window,
15848                cx,
15849            )
15850        })
15851        .await
15852        .unwrap();
15853    follower_2
15854        .update_in(cx, |follower, window, cx| {
15855            follower.apply_update_proto(
15856                &project,
15857                update_message.borrow().clone().unwrap(),
15858                window,
15859                cx,
15860            )
15861        })
15862        .await
15863        .unwrap();
15864    update_message.borrow_mut().take();
15865    assert_eq!(
15866        follower_1.update(cx, |editor, cx| editor.text(cx)),
15867        leader.update(cx, |editor, cx| editor.text(cx))
15868    );
15869}
15870
15871#[gpui::test]
15872async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15873    init_test(cx, |_| {});
15874
15875    let mut cx = EditorTestContext::new(cx).await;
15876    let lsp_store =
15877        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
15878
15879    cx.set_state(indoc! {"
15880        ˇfn func(abc def: i32) -> u32 {
15881        }
15882    "});
15883
15884    cx.update(|_, cx| {
15885        lsp_store.update(cx, |lsp_store, cx| {
15886            lsp_store
15887                .update_diagnostics(
15888                    LanguageServerId(0),
15889                    lsp::PublishDiagnosticsParams {
15890                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
15891                        version: None,
15892                        diagnostics: vec![
15893                            lsp::Diagnostic {
15894                                range: lsp::Range::new(
15895                                    lsp::Position::new(0, 11),
15896                                    lsp::Position::new(0, 12),
15897                                ),
15898                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15899                                ..Default::default()
15900                            },
15901                            lsp::Diagnostic {
15902                                range: lsp::Range::new(
15903                                    lsp::Position::new(0, 12),
15904                                    lsp::Position::new(0, 15),
15905                                ),
15906                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15907                                ..Default::default()
15908                            },
15909                            lsp::Diagnostic {
15910                                range: lsp::Range::new(
15911                                    lsp::Position::new(0, 25),
15912                                    lsp::Position::new(0, 28),
15913                                ),
15914                                severity: Some(lsp::DiagnosticSeverity::ERROR),
15915                                ..Default::default()
15916                            },
15917                        ],
15918                    },
15919                    None,
15920                    DiagnosticSourceKind::Pushed,
15921                    &[],
15922                    cx,
15923                )
15924                .unwrap()
15925        });
15926    });
15927
15928    executor.run_until_parked();
15929
15930    cx.update_editor(|editor, window, cx| {
15931        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15932    });
15933
15934    cx.assert_editor_state(indoc! {"
15935        fn func(abc def: i32) -> ˇu32 {
15936        }
15937    "});
15938
15939    cx.update_editor(|editor, window, cx| {
15940        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15941    });
15942
15943    cx.assert_editor_state(indoc! {"
15944        fn func(abc ˇdef: i32) -> u32 {
15945        }
15946    "});
15947
15948    cx.update_editor(|editor, window, cx| {
15949        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15950    });
15951
15952    cx.assert_editor_state(indoc! {"
15953        fn func(abcˇ def: i32) -> u32 {
15954        }
15955    "});
15956
15957    cx.update_editor(|editor, window, cx| {
15958        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
15959    });
15960
15961    cx.assert_editor_state(indoc! {"
15962        fn func(abc def: i32) -> ˇu32 {
15963        }
15964    "});
15965}
15966
15967#[gpui::test]
15968async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15969    init_test(cx, |_| {});
15970
15971    let mut cx = EditorTestContext::new(cx).await;
15972
15973    let diff_base = r#"
15974        use some::mod;
15975
15976        const A: u32 = 42;
15977
15978        fn main() {
15979            println!("hello");
15980
15981            println!("world");
15982        }
15983        "#
15984    .unindent();
15985
15986    // Edits are modified, removed, modified, added
15987    cx.set_state(
15988        &r#"
15989        use some::modified;
15990
15991        ˇ
15992        fn main() {
15993            println!("hello there");
15994
15995            println!("around the");
15996            println!("world");
15997        }
15998        "#
15999        .unindent(),
16000    );
16001
16002    cx.set_head_text(&diff_base);
16003    executor.run_until_parked();
16004
16005    cx.update_editor(|editor, window, cx| {
16006        //Wrap around the bottom of the buffer
16007        for _ in 0..3 {
16008            editor.go_to_next_hunk(&GoToHunk, window, cx);
16009        }
16010    });
16011
16012    cx.assert_editor_state(
16013        &r#"
16014        ˇuse some::modified;
16015
16016
16017        fn main() {
16018            println!("hello there");
16019
16020            println!("around the");
16021            println!("world");
16022        }
16023        "#
16024        .unindent(),
16025    );
16026
16027    cx.update_editor(|editor, window, cx| {
16028        //Wrap around the top of the buffer
16029        for _ in 0..2 {
16030            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16031        }
16032    });
16033
16034    cx.assert_editor_state(
16035        &r#"
16036        use some::modified;
16037
16038
16039        fn main() {
16040        ˇ    println!("hello there");
16041
16042            println!("around the");
16043            println!("world");
16044        }
16045        "#
16046        .unindent(),
16047    );
16048
16049    cx.update_editor(|editor, window, cx| {
16050        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16051    });
16052
16053    cx.assert_editor_state(
16054        &r#"
16055        use some::modified;
16056
16057        ˇ
16058        fn main() {
16059            println!("hello there");
16060
16061            println!("around the");
16062            println!("world");
16063        }
16064        "#
16065        .unindent(),
16066    );
16067
16068    cx.update_editor(|editor, window, cx| {
16069        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16070    });
16071
16072    cx.assert_editor_state(
16073        &r#"
16074        ˇuse some::modified;
16075
16076
16077        fn main() {
16078            println!("hello there");
16079
16080            println!("around the");
16081            println!("world");
16082        }
16083        "#
16084        .unindent(),
16085    );
16086
16087    cx.update_editor(|editor, window, cx| {
16088        for _ in 0..2 {
16089            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16090        }
16091    });
16092
16093    cx.assert_editor_state(
16094        &r#"
16095        use some::modified;
16096
16097
16098        fn main() {
16099        ˇ    println!("hello there");
16100
16101            println!("around the");
16102            println!("world");
16103        }
16104        "#
16105        .unindent(),
16106    );
16107
16108    cx.update_editor(|editor, window, cx| {
16109        editor.fold(&Fold, window, cx);
16110    });
16111
16112    cx.update_editor(|editor, window, cx| {
16113        editor.go_to_next_hunk(&GoToHunk, window, cx);
16114    });
16115
16116    cx.assert_editor_state(
16117        &r#"
16118        ˇuse some::modified;
16119
16120
16121        fn main() {
16122            println!("hello there");
16123
16124            println!("around the");
16125            println!("world");
16126        }
16127        "#
16128        .unindent(),
16129    );
16130}
16131
16132#[test]
16133fn test_split_words() {
16134    fn split(text: &str) -> Vec<&str> {
16135        split_words(text).collect()
16136    }
16137
16138    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16139    assert_eq!(split("hello_world"), &["hello_", "world"]);
16140    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16141    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16142    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16143    assert_eq!(split("helloworld"), &["helloworld"]);
16144
16145    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16146}
16147
16148#[gpui::test]
16149async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16150    init_test(cx, |_| {});
16151
16152    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16153    let mut assert = |before, after| {
16154        let _state_context = cx.set_state(before);
16155        cx.run_until_parked();
16156        cx.update_editor(|editor, window, cx| {
16157            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16158        });
16159        cx.run_until_parked();
16160        cx.assert_editor_state(after);
16161    };
16162
16163    // Outside bracket jumps to outside of matching bracket
16164    assert("console.logˇ(var);", "console.log(var)ˇ;");
16165    assert("console.log(var)ˇ;", "console.logˇ(var);");
16166
16167    // Inside bracket jumps to inside of matching bracket
16168    assert("console.log(ˇvar);", "console.log(varˇ);");
16169    assert("console.log(varˇ);", "console.log(ˇvar);");
16170
16171    // When outside a bracket and inside, favor jumping to the inside bracket
16172    assert(
16173        "console.log('foo', [1, 2, 3]ˇ);",
16174        "console.log(ˇ'foo', [1, 2, 3]);",
16175    );
16176    assert(
16177        "console.log(ˇ'foo', [1, 2, 3]);",
16178        "console.log('foo', [1, 2, 3]ˇ);",
16179    );
16180
16181    // Bias forward if two options are equally likely
16182    assert(
16183        "let result = curried_fun()ˇ();",
16184        "let result = curried_fun()()ˇ;",
16185    );
16186
16187    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16188    assert(
16189        indoc! {"
16190            function test() {
16191                console.log('test')ˇ
16192            }"},
16193        indoc! {"
16194            function test() {
16195                console.logˇ('test')
16196            }"},
16197    );
16198}
16199
16200#[gpui::test]
16201async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16202    init_test(cx, |_| {});
16203
16204    let fs = FakeFs::new(cx.executor());
16205    fs.insert_tree(
16206        path!("/a"),
16207        json!({
16208            "main.rs": "fn main() { let a = 5; }",
16209            "other.rs": "// Test file",
16210        }),
16211    )
16212    .await;
16213    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16214
16215    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16216    language_registry.add(Arc::new(Language::new(
16217        LanguageConfig {
16218            name: "Rust".into(),
16219            matcher: LanguageMatcher {
16220                path_suffixes: vec!["rs".to_string()],
16221                ..Default::default()
16222            },
16223            brackets: BracketPairConfig {
16224                pairs: vec![BracketPair {
16225                    start: "{".to_string(),
16226                    end: "}".to_string(),
16227                    close: true,
16228                    surround: true,
16229                    newline: true,
16230                }],
16231                disabled_scopes_by_bracket_ix: Vec::new(),
16232            },
16233            ..Default::default()
16234        },
16235        Some(tree_sitter_rust::LANGUAGE.into()),
16236    )));
16237    let mut fake_servers = language_registry.register_fake_lsp(
16238        "Rust",
16239        FakeLspAdapter {
16240            capabilities: lsp::ServerCapabilities {
16241                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16242                    first_trigger_character: "{".to_string(),
16243                    more_trigger_character: None,
16244                }),
16245                ..Default::default()
16246            },
16247            ..Default::default()
16248        },
16249    );
16250
16251    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16252
16253    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16254
16255    let worktree_id = workspace
16256        .update(cx, |workspace, _, cx| {
16257            workspace.project().update(cx, |project, cx| {
16258                project.worktrees(cx).next().unwrap().read(cx).id()
16259            })
16260        })
16261        .unwrap();
16262
16263    let buffer = project
16264        .update(cx, |project, cx| {
16265            project.open_local_buffer(path!("/a/main.rs"), cx)
16266        })
16267        .await
16268        .unwrap();
16269    let editor_handle = workspace
16270        .update(cx, |workspace, window, cx| {
16271            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16272        })
16273        .unwrap()
16274        .await
16275        .unwrap()
16276        .downcast::<Editor>()
16277        .unwrap();
16278
16279    cx.executor().start_waiting();
16280    let fake_server = fake_servers.next().await.unwrap();
16281
16282    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16283        |params, _| async move {
16284            assert_eq!(
16285                params.text_document_position.text_document.uri,
16286                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16287            );
16288            assert_eq!(
16289                params.text_document_position.position,
16290                lsp::Position::new(0, 21),
16291            );
16292
16293            Ok(Some(vec![lsp::TextEdit {
16294                new_text: "]".to_string(),
16295                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16296            }]))
16297        },
16298    );
16299
16300    editor_handle.update_in(cx, |editor, window, cx| {
16301        window.focus(&editor.focus_handle(cx));
16302        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16303            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16304        });
16305        editor.handle_input("{", window, cx);
16306    });
16307
16308    cx.executor().run_until_parked();
16309
16310    buffer.update(cx, |buffer, _| {
16311        assert_eq!(
16312            buffer.text(),
16313            "fn main() { let a = {5}; }",
16314            "No extra braces from on type formatting should appear in the buffer"
16315        )
16316    });
16317}
16318
16319#[gpui::test(iterations = 20, seeds(31))]
16320async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
16321    init_test(cx, |_| {});
16322
16323    let mut cx = EditorLspTestContext::new_rust(
16324        lsp::ServerCapabilities {
16325            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16326                first_trigger_character: ".".to_string(),
16327                more_trigger_character: None,
16328            }),
16329            ..Default::default()
16330        },
16331        cx,
16332    )
16333    .await;
16334
16335    cx.update_buffer(|buffer, _| {
16336        // This causes autoindent to be async.
16337        buffer.set_sync_parse_timeout(Duration::ZERO)
16338    });
16339
16340    cx.set_state("fn c() {\n    d()ˇ\n}\n");
16341    cx.simulate_keystroke("\n");
16342    cx.run_until_parked();
16343
16344    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
16345    let mut request =
16346        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
16347            let buffer_cloned = buffer_cloned.clone();
16348            async move {
16349                buffer_cloned.update(&mut cx, |buffer, _| {
16350                    assert_eq!(
16351                        buffer.text(),
16352                        "fn c() {\n    d()\n        .\n}\n",
16353                        "OnTypeFormatting should triggered after autoindent applied"
16354                    )
16355                })?;
16356
16357                Ok(Some(vec![]))
16358            }
16359        });
16360
16361    cx.simulate_keystroke(".");
16362    cx.run_until_parked();
16363
16364    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
16365    assert!(request.next().await.is_some());
16366    request.close();
16367    assert!(request.next().await.is_none());
16368}
16369
16370#[gpui::test]
16371async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
16372    init_test(cx, |_| {});
16373
16374    let fs = FakeFs::new(cx.executor());
16375    fs.insert_tree(
16376        path!("/a"),
16377        json!({
16378            "main.rs": "fn main() { let a = 5; }",
16379            "other.rs": "// Test file",
16380        }),
16381    )
16382    .await;
16383
16384    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16385
16386    let server_restarts = Arc::new(AtomicUsize::new(0));
16387    let closure_restarts = Arc::clone(&server_restarts);
16388    let language_server_name = "test language server";
16389    let language_name: LanguageName = "Rust".into();
16390
16391    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16392    language_registry.add(Arc::new(Language::new(
16393        LanguageConfig {
16394            name: language_name.clone(),
16395            matcher: LanguageMatcher {
16396                path_suffixes: vec!["rs".to_string()],
16397                ..Default::default()
16398            },
16399            ..Default::default()
16400        },
16401        Some(tree_sitter_rust::LANGUAGE.into()),
16402    )));
16403    let mut fake_servers = language_registry.register_fake_lsp(
16404        "Rust",
16405        FakeLspAdapter {
16406            name: language_server_name,
16407            initialization_options: Some(json!({
16408                "testOptionValue": true
16409            })),
16410            initializer: Some(Box::new(move |fake_server| {
16411                let task_restarts = Arc::clone(&closure_restarts);
16412                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
16413                    task_restarts.fetch_add(1, atomic::Ordering::Release);
16414                    futures::future::ready(Ok(()))
16415                });
16416            })),
16417            ..Default::default()
16418        },
16419    );
16420
16421    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16422    let _buffer = project
16423        .update(cx, |project, cx| {
16424            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
16425        })
16426        .await
16427        .unwrap();
16428    let _fake_server = fake_servers.next().await.unwrap();
16429    update_test_language_settings(cx, |language_settings| {
16430        language_settings.languages.0.insert(
16431            language_name.clone(),
16432            LanguageSettingsContent {
16433                tab_size: NonZeroU32::new(8),
16434                ..Default::default()
16435            },
16436        );
16437    });
16438    cx.executor().run_until_parked();
16439    assert_eq!(
16440        server_restarts.load(atomic::Ordering::Acquire),
16441        0,
16442        "Should not restart LSP server on an unrelated change"
16443    );
16444
16445    update_test_project_settings(cx, |project_settings| {
16446        project_settings.lsp.insert(
16447            "Some other server name".into(),
16448            LspSettings {
16449                binary: None,
16450                settings: None,
16451                initialization_options: Some(json!({
16452                    "some other init value": false
16453                })),
16454                enable_lsp_tasks: false,
16455            },
16456        );
16457    });
16458    cx.executor().run_until_parked();
16459    assert_eq!(
16460        server_restarts.load(atomic::Ordering::Acquire),
16461        0,
16462        "Should not restart LSP server on an unrelated LSP settings change"
16463    );
16464
16465    update_test_project_settings(cx, |project_settings| {
16466        project_settings.lsp.insert(
16467            language_server_name.into(),
16468            LspSettings {
16469                binary: None,
16470                settings: None,
16471                initialization_options: Some(json!({
16472                    "anotherInitValue": false
16473                })),
16474                enable_lsp_tasks: false,
16475            },
16476        );
16477    });
16478    cx.executor().run_until_parked();
16479    assert_eq!(
16480        server_restarts.load(atomic::Ordering::Acquire),
16481        1,
16482        "Should restart LSP server on a related LSP settings change"
16483    );
16484
16485    update_test_project_settings(cx, |project_settings| {
16486        project_settings.lsp.insert(
16487            language_server_name.into(),
16488            LspSettings {
16489                binary: None,
16490                settings: None,
16491                initialization_options: Some(json!({
16492                    "anotherInitValue": false
16493                })),
16494                enable_lsp_tasks: false,
16495            },
16496        );
16497    });
16498    cx.executor().run_until_parked();
16499    assert_eq!(
16500        server_restarts.load(atomic::Ordering::Acquire),
16501        1,
16502        "Should not restart LSP server on a related LSP settings change that is the same"
16503    );
16504
16505    update_test_project_settings(cx, |project_settings| {
16506        project_settings.lsp.insert(
16507            language_server_name.into(),
16508            LspSettings {
16509                binary: None,
16510                settings: None,
16511                initialization_options: None,
16512                enable_lsp_tasks: false,
16513            },
16514        );
16515    });
16516    cx.executor().run_until_parked();
16517    assert_eq!(
16518        server_restarts.load(atomic::Ordering::Acquire),
16519        2,
16520        "Should restart LSP server on another related LSP settings change"
16521    );
16522}
16523
16524#[gpui::test]
16525async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
16526    init_test(cx, |_| {});
16527
16528    let mut cx = EditorLspTestContext::new_rust(
16529        lsp::ServerCapabilities {
16530            completion_provider: Some(lsp::CompletionOptions {
16531                trigger_characters: Some(vec![".".to_string()]),
16532                resolve_provider: Some(true),
16533                ..Default::default()
16534            }),
16535            ..Default::default()
16536        },
16537        cx,
16538    )
16539    .await;
16540
16541    cx.set_state("fn main() { let a = 2ˇ; }");
16542    cx.simulate_keystroke(".");
16543    let completion_item = lsp::CompletionItem {
16544        label: "some".into(),
16545        kind: Some(lsp::CompletionItemKind::SNIPPET),
16546        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16547        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16548            kind: lsp::MarkupKind::Markdown,
16549            value: "```rust\nSome(2)\n```".to_string(),
16550        })),
16551        deprecated: Some(false),
16552        sort_text: Some("fffffff2".to_string()),
16553        filter_text: Some("some".to_string()),
16554        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16555        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16556            range: lsp::Range {
16557                start: lsp::Position {
16558                    line: 0,
16559                    character: 22,
16560                },
16561                end: lsp::Position {
16562                    line: 0,
16563                    character: 22,
16564                },
16565            },
16566            new_text: "Some(2)".to_string(),
16567        })),
16568        additional_text_edits: Some(vec![lsp::TextEdit {
16569            range: lsp::Range {
16570                start: lsp::Position {
16571                    line: 0,
16572                    character: 20,
16573                },
16574                end: lsp::Position {
16575                    line: 0,
16576                    character: 22,
16577                },
16578            },
16579            new_text: "".to_string(),
16580        }]),
16581        ..Default::default()
16582    };
16583
16584    let closure_completion_item = completion_item.clone();
16585    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16586        let task_completion_item = closure_completion_item.clone();
16587        async move {
16588            Ok(Some(lsp::CompletionResponse::Array(vec![
16589                task_completion_item,
16590            ])))
16591        }
16592    });
16593
16594    request.next().await;
16595
16596    cx.condition(|editor, _| editor.context_menu_visible())
16597        .await;
16598    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
16599        editor
16600            .confirm_completion(&ConfirmCompletion::default(), window, cx)
16601            .unwrap()
16602    });
16603    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
16604
16605    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
16606        let task_completion_item = completion_item.clone();
16607        async move { Ok(task_completion_item) }
16608    })
16609    .next()
16610    .await
16611    .unwrap();
16612    apply_additional_edits.await.unwrap();
16613    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
16614}
16615
16616#[gpui::test]
16617async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
16618    init_test(cx, |_| {});
16619
16620    let mut cx = EditorLspTestContext::new_rust(
16621        lsp::ServerCapabilities {
16622            completion_provider: Some(lsp::CompletionOptions {
16623                trigger_characters: Some(vec![".".to_string()]),
16624                resolve_provider: Some(true),
16625                ..Default::default()
16626            }),
16627            ..Default::default()
16628        },
16629        cx,
16630    )
16631    .await;
16632
16633    cx.set_state("fn main() { let a = 2ˇ; }");
16634    cx.simulate_keystroke(".");
16635
16636    let item1 = lsp::CompletionItem {
16637        label: "method id()".to_string(),
16638        filter_text: Some("id".to_string()),
16639        detail: None,
16640        documentation: None,
16641        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16642            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16643            new_text: ".id".to_string(),
16644        })),
16645        ..lsp::CompletionItem::default()
16646    };
16647
16648    let item2 = lsp::CompletionItem {
16649        label: "other".to_string(),
16650        filter_text: Some("other".to_string()),
16651        detail: None,
16652        documentation: None,
16653        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16654            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16655            new_text: ".other".to_string(),
16656        })),
16657        ..lsp::CompletionItem::default()
16658    };
16659
16660    let item1 = item1.clone();
16661    cx.set_request_handler::<lsp::request::Completion, _, _>({
16662        let item1 = item1.clone();
16663        move |_, _, _| {
16664            let item1 = item1.clone();
16665            let item2 = item2.clone();
16666            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
16667        }
16668    })
16669    .next()
16670    .await;
16671
16672    cx.condition(|editor, _| editor.context_menu_visible())
16673        .await;
16674    cx.update_editor(|editor, _, _| {
16675        let context_menu = editor.context_menu.borrow_mut();
16676        let context_menu = context_menu
16677            .as_ref()
16678            .expect("Should have the context menu deployed");
16679        match context_menu {
16680            CodeContextMenu::Completions(completions_menu) => {
16681                let completions = completions_menu.completions.borrow_mut();
16682                assert_eq!(
16683                    completions
16684                        .iter()
16685                        .map(|completion| &completion.label.text)
16686                        .collect::<Vec<_>>(),
16687                    vec!["method id()", "other"]
16688                )
16689            }
16690            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16691        }
16692    });
16693
16694    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
16695        let item1 = item1.clone();
16696        move |_, item_to_resolve, _| {
16697            let item1 = item1.clone();
16698            async move {
16699                if item1 == item_to_resolve {
16700                    Ok(lsp::CompletionItem {
16701                        label: "method id()".to_string(),
16702                        filter_text: Some("id".to_string()),
16703                        detail: Some("Now resolved!".to_string()),
16704                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
16705                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16706                            range: lsp::Range::new(
16707                                lsp::Position::new(0, 22),
16708                                lsp::Position::new(0, 22),
16709                            ),
16710                            new_text: ".id".to_string(),
16711                        })),
16712                        ..lsp::CompletionItem::default()
16713                    })
16714                } else {
16715                    Ok(item_to_resolve)
16716                }
16717            }
16718        }
16719    })
16720    .next()
16721    .await
16722    .unwrap();
16723    cx.run_until_parked();
16724
16725    cx.update_editor(|editor, window, cx| {
16726        editor.context_menu_next(&Default::default(), window, cx);
16727    });
16728
16729    cx.update_editor(|editor, _, _| {
16730        let context_menu = editor.context_menu.borrow_mut();
16731        let context_menu = context_menu
16732            .as_ref()
16733            .expect("Should have the context menu deployed");
16734        match context_menu {
16735            CodeContextMenu::Completions(completions_menu) => {
16736                let completions = completions_menu.completions.borrow_mut();
16737                assert_eq!(
16738                    completions
16739                        .iter()
16740                        .map(|completion| &completion.label.text)
16741                        .collect::<Vec<_>>(),
16742                    vec!["method id() Now resolved!", "other"],
16743                    "Should update first completion label, but not second as the filter text did not match."
16744                );
16745            }
16746            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
16747        }
16748    });
16749}
16750
16751#[gpui::test]
16752async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
16753    init_test(cx, |_| {});
16754    let mut cx = EditorLspTestContext::new_rust(
16755        lsp::ServerCapabilities {
16756            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
16757            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
16758            completion_provider: Some(lsp::CompletionOptions {
16759                resolve_provider: Some(true),
16760                ..Default::default()
16761            }),
16762            ..Default::default()
16763        },
16764        cx,
16765    )
16766    .await;
16767    cx.set_state(indoc! {"
16768        struct TestStruct {
16769            field: i32
16770        }
16771
16772        fn mainˇ() {
16773            let unused_var = 42;
16774            let test_struct = TestStruct { field: 42 };
16775        }
16776    "});
16777    let symbol_range = cx.lsp_range(indoc! {"
16778        struct TestStruct {
16779            field: i32
16780        }
16781
16782        «fn main»() {
16783            let unused_var = 42;
16784            let test_struct = TestStruct { field: 42 };
16785        }
16786    "});
16787    let mut hover_requests =
16788        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
16789            Ok(Some(lsp::Hover {
16790                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
16791                    kind: lsp::MarkupKind::Markdown,
16792                    value: "Function documentation".to_string(),
16793                }),
16794                range: Some(symbol_range),
16795            }))
16796        });
16797
16798    // Case 1: Test that code action menu hide hover popover
16799    cx.dispatch_action(Hover);
16800    hover_requests.next().await;
16801    cx.condition(|editor, _| editor.hover_state.visible()).await;
16802    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
16803        move |_, _, _| async move {
16804            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
16805                lsp::CodeAction {
16806                    title: "Remove unused variable".to_string(),
16807                    kind: Some(CodeActionKind::QUICKFIX),
16808                    edit: Some(lsp::WorkspaceEdit {
16809                        changes: Some(
16810                            [(
16811                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
16812                                vec![lsp::TextEdit {
16813                                    range: lsp::Range::new(
16814                                        lsp::Position::new(5, 4),
16815                                        lsp::Position::new(5, 27),
16816                                    ),
16817                                    new_text: "".to_string(),
16818                                }],
16819                            )]
16820                            .into_iter()
16821                            .collect(),
16822                        ),
16823                        ..Default::default()
16824                    }),
16825                    ..Default::default()
16826                },
16827            )]))
16828        },
16829    );
16830    cx.update_editor(|editor, window, cx| {
16831        editor.toggle_code_actions(
16832            &ToggleCodeActions {
16833                deployed_from: None,
16834                quick_launch: false,
16835            },
16836            window,
16837            cx,
16838        );
16839    });
16840    code_action_requests.next().await;
16841    cx.run_until_parked();
16842    cx.condition(|editor, _| editor.context_menu_visible())
16843        .await;
16844    cx.update_editor(|editor, _, _| {
16845        assert!(
16846            !editor.hover_state.visible(),
16847            "Hover popover should be hidden when code action menu is shown"
16848        );
16849        // Hide code actions
16850        editor.context_menu.take();
16851    });
16852
16853    // Case 2: Test that code completions hide hover popover
16854    cx.dispatch_action(Hover);
16855    hover_requests.next().await;
16856    cx.condition(|editor, _| editor.hover_state.visible()).await;
16857    let counter = Arc::new(AtomicUsize::new(0));
16858    let mut completion_requests =
16859        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16860            let counter = counter.clone();
16861            async move {
16862                counter.fetch_add(1, atomic::Ordering::Release);
16863                Ok(Some(lsp::CompletionResponse::Array(vec![
16864                    lsp::CompletionItem {
16865                        label: "main".into(),
16866                        kind: Some(lsp::CompletionItemKind::FUNCTION),
16867                        detail: Some("() -> ()".to_string()),
16868                        ..Default::default()
16869                    },
16870                    lsp::CompletionItem {
16871                        label: "TestStruct".into(),
16872                        kind: Some(lsp::CompletionItemKind::STRUCT),
16873                        detail: Some("struct TestStruct".to_string()),
16874                        ..Default::default()
16875                    },
16876                ])))
16877            }
16878        });
16879    cx.update_editor(|editor, window, cx| {
16880        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
16881    });
16882    completion_requests.next().await;
16883    cx.condition(|editor, _| editor.context_menu_visible())
16884        .await;
16885    cx.update_editor(|editor, _, _| {
16886        assert!(
16887            !editor.hover_state.visible(),
16888            "Hover popover should be hidden when completion menu is shown"
16889        );
16890    });
16891}
16892
16893#[gpui::test]
16894async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
16895    init_test(cx, |_| {});
16896
16897    let mut cx = EditorLspTestContext::new_rust(
16898        lsp::ServerCapabilities {
16899            completion_provider: Some(lsp::CompletionOptions {
16900                trigger_characters: Some(vec![".".to_string()]),
16901                resolve_provider: Some(true),
16902                ..Default::default()
16903            }),
16904            ..Default::default()
16905        },
16906        cx,
16907    )
16908    .await;
16909
16910    cx.set_state("fn main() { let a = 2ˇ; }");
16911    cx.simulate_keystroke(".");
16912
16913    let unresolved_item_1 = lsp::CompletionItem {
16914        label: "id".to_string(),
16915        filter_text: Some("id".to_string()),
16916        detail: None,
16917        documentation: None,
16918        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16919            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16920            new_text: ".id".to_string(),
16921        })),
16922        ..lsp::CompletionItem::default()
16923    };
16924    let resolved_item_1 = lsp::CompletionItem {
16925        additional_text_edits: Some(vec![lsp::TextEdit {
16926            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16927            new_text: "!!".to_string(),
16928        }]),
16929        ..unresolved_item_1.clone()
16930    };
16931    let unresolved_item_2 = lsp::CompletionItem {
16932        label: "other".to_string(),
16933        filter_text: Some("other".to_string()),
16934        detail: None,
16935        documentation: None,
16936        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16937            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16938            new_text: ".other".to_string(),
16939        })),
16940        ..lsp::CompletionItem::default()
16941    };
16942    let resolved_item_2 = lsp::CompletionItem {
16943        additional_text_edits: Some(vec![lsp::TextEdit {
16944            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
16945            new_text: "??".to_string(),
16946        }]),
16947        ..unresolved_item_2.clone()
16948    };
16949
16950    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
16951    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
16952    cx.lsp
16953        .server
16954        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
16955            let unresolved_item_1 = unresolved_item_1.clone();
16956            let resolved_item_1 = resolved_item_1.clone();
16957            let unresolved_item_2 = unresolved_item_2.clone();
16958            let resolved_item_2 = resolved_item_2.clone();
16959            let resolve_requests_1 = resolve_requests_1.clone();
16960            let resolve_requests_2 = resolve_requests_2.clone();
16961            move |unresolved_request, _| {
16962                let unresolved_item_1 = unresolved_item_1.clone();
16963                let resolved_item_1 = resolved_item_1.clone();
16964                let unresolved_item_2 = unresolved_item_2.clone();
16965                let resolved_item_2 = resolved_item_2.clone();
16966                let resolve_requests_1 = resolve_requests_1.clone();
16967                let resolve_requests_2 = resolve_requests_2.clone();
16968                async move {
16969                    if unresolved_request == unresolved_item_1 {
16970                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
16971                        Ok(resolved_item_1.clone())
16972                    } else if unresolved_request == unresolved_item_2 {
16973                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
16974                        Ok(resolved_item_2.clone())
16975                    } else {
16976                        panic!("Unexpected completion item {unresolved_request:?}")
16977                    }
16978                }
16979            }
16980        })
16981        .detach();
16982
16983    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16984        let unresolved_item_1 = unresolved_item_1.clone();
16985        let unresolved_item_2 = unresolved_item_2.clone();
16986        async move {
16987            Ok(Some(lsp::CompletionResponse::Array(vec![
16988                unresolved_item_1,
16989                unresolved_item_2,
16990            ])))
16991        }
16992    })
16993    .next()
16994    .await;
16995
16996    cx.condition(|editor, _| editor.context_menu_visible())
16997        .await;
16998    cx.update_editor(|editor, _, _| {
16999        let context_menu = editor.context_menu.borrow_mut();
17000        let context_menu = context_menu
17001            .as_ref()
17002            .expect("Should have the context menu deployed");
17003        match context_menu {
17004            CodeContextMenu::Completions(completions_menu) => {
17005                let completions = completions_menu.completions.borrow_mut();
17006                assert_eq!(
17007                    completions
17008                        .iter()
17009                        .map(|completion| &completion.label.text)
17010                        .collect::<Vec<_>>(),
17011                    vec!["id", "other"]
17012                )
17013            }
17014            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17015        }
17016    });
17017    cx.run_until_parked();
17018
17019    cx.update_editor(|editor, window, cx| {
17020        editor.context_menu_next(&ContextMenuNext, window, cx);
17021    });
17022    cx.run_until_parked();
17023    cx.update_editor(|editor, window, cx| {
17024        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17025    });
17026    cx.run_until_parked();
17027    cx.update_editor(|editor, window, cx| {
17028        editor.context_menu_next(&ContextMenuNext, window, cx);
17029    });
17030    cx.run_until_parked();
17031    cx.update_editor(|editor, window, cx| {
17032        editor
17033            .compose_completion(&ComposeCompletion::default(), window, cx)
17034            .expect("No task returned")
17035    })
17036    .await
17037    .expect("Completion failed");
17038    cx.run_until_parked();
17039
17040    cx.update_editor(|editor, _, cx| {
17041        assert_eq!(
17042            resolve_requests_1.load(atomic::Ordering::Acquire),
17043            1,
17044            "Should always resolve once despite multiple selections"
17045        );
17046        assert_eq!(
17047            resolve_requests_2.load(atomic::Ordering::Acquire),
17048            1,
17049            "Should always resolve once after multiple selections and applying the completion"
17050        );
17051        assert_eq!(
17052            editor.text(cx),
17053            "fn main() { let a = ??.other; }",
17054            "Should use resolved data when applying the completion"
17055        );
17056    });
17057}
17058
17059#[gpui::test]
17060async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17061    init_test(cx, |_| {});
17062
17063    let item_0 = lsp::CompletionItem {
17064        label: "abs".into(),
17065        insert_text: Some("abs".into()),
17066        data: Some(json!({ "very": "special"})),
17067        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17068        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17069            lsp::InsertReplaceEdit {
17070                new_text: "abs".to_string(),
17071                insert: lsp::Range::default(),
17072                replace: lsp::Range::default(),
17073            },
17074        )),
17075        ..lsp::CompletionItem::default()
17076    };
17077    let items = iter::once(item_0.clone())
17078        .chain((11..51).map(|i| lsp::CompletionItem {
17079            label: format!("item_{}", i),
17080            insert_text: Some(format!("item_{}", i)),
17081            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17082            ..lsp::CompletionItem::default()
17083        }))
17084        .collect::<Vec<_>>();
17085
17086    let default_commit_characters = vec!["?".to_string()];
17087    let default_data = json!({ "default": "data"});
17088    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17089    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17090    let default_edit_range = lsp::Range {
17091        start: lsp::Position {
17092            line: 0,
17093            character: 5,
17094        },
17095        end: lsp::Position {
17096            line: 0,
17097            character: 5,
17098        },
17099    };
17100
17101    let mut cx = EditorLspTestContext::new_rust(
17102        lsp::ServerCapabilities {
17103            completion_provider: Some(lsp::CompletionOptions {
17104                trigger_characters: Some(vec![".".to_string()]),
17105                resolve_provider: Some(true),
17106                ..Default::default()
17107            }),
17108            ..Default::default()
17109        },
17110        cx,
17111    )
17112    .await;
17113
17114    cx.set_state("fn main() { let a = 2ˇ; }");
17115    cx.simulate_keystroke(".");
17116
17117    let completion_data = default_data.clone();
17118    let completion_characters = default_commit_characters.clone();
17119    let completion_items = items.clone();
17120    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17121        let default_data = completion_data.clone();
17122        let default_commit_characters = completion_characters.clone();
17123        let items = completion_items.clone();
17124        async move {
17125            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17126                items,
17127                item_defaults: Some(lsp::CompletionListItemDefaults {
17128                    data: Some(default_data.clone()),
17129                    commit_characters: Some(default_commit_characters.clone()),
17130                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17131                        default_edit_range,
17132                    )),
17133                    insert_text_format: Some(default_insert_text_format),
17134                    insert_text_mode: Some(default_insert_text_mode),
17135                }),
17136                ..lsp::CompletionList::default()
17137            })))
17138        }
17139    })
17140    .next()
17141    .await;
17142
17143    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17144    cx.lsp
17145        .server
17146        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17147            let closure_resolved_items = resolved_items.clone();
17148            move |item_to_resolve, _| {
17149                let closure_resolved_items = closure_resolved_items.clone();
17150                async move {
17151                    closure_resolved_items.lock().push(item_to_resolve.clone());
17152                    Ok(item_to_resolve)
17153                }
17154            }
17155        })
17156        .detach();
17157
17158    cx.condition(|editor, _| editor.context_menu_visible())
17159        .await;
17160    cx.run_until_parked();
17161    cx.update_editor(|editor, _, _| {
17162        let menu = editor.context_menu.borrow_mut();
17163        match menu.as_ref().expect("should have the completions menu") {
17164            CodeContextMenu::Completions(completions_menu) => {
17165                assert_eq!(
17166                    completions_menu
17167                        .entries
17168                        .borrow()
17169                        .iter()
17170                        .map(|mat| mat.string.clone())
17171                        .collect::<Vec<String>>(),
17172                    items
17173                        .iter()
17174                        .map(|completion| completion.label.clone())
17175                        .collect::<Vec<String>>()
17176                );
17177            }
17178            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17179        }
17180    });
17181    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17182    // with 4 from the end.
17183    assert_eq!(
17184        *resolved_items.lock(),
17185        [&items[0..16], &items[items.len() - 4..items.len()]]
17186            .concat()
17187            .iter()
17188            .cloned()
17189            .map(|mut item| {
17190                if item.data.is_none() {
17191                    item.data = Some(default_data.clone());
17192                }
17193                item
17194            })
17195            .collect::<Vec<lsp::CompletionItem>>(),
17196        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17197    );
17198    resolved_items.lock().clear();
17199
17200    cx.update_editor(|editor, window, cx| {
17201        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17202    });
17203    cx.run_until_parked();
17204    // Completions that have already been resolved are skipped.
17205    assert_eq!(
17206        *resolved_items.lock(),
17207        items[items.len() - 17..items.len() - 4]
17208            .iter()
17209            .cloned()
17210            .map(|mut item| {
17211                if item.data.is_none() {
17212                    item.data = Some(default_data.clone());
17213                }
17214                item
17215            })
17216            .collect::<Vec<lsp::CompletionItem>>()
17217    );
17218    resolved_items.lock().clear();
17219}
17220
17221#[gpui::test]
17222async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17223    init_test(cx, |_| {});
17224
17225    let mut cx = EditorLspTestContext::new(
17226        Language::new(
17227            LanguageConfig {
17228                matcher: LanguageMatcher {
17229                    path_suffixes: vec!["jsx".into()],
17230                    ..Default::default()
17231                },
17232                overrides: [(
17233                    "element".into(),
17234                    LanguageConfigOverride {
17235                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17236                        ..Default::default()
17237                    },
17238                )]
17239                .into_iter()
17240                .collect(),
17241                ..Default::default()
17242            },
17243            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17244        )
17245        .with_override_query("(jsx_self_closing_element) @element")
17246        .unwrap(),
17247        lsp::ServerCapabilities {
17248            completion_provider: Some(lsp::CompletionOptions {
17249                trigger_characters: Some(vec![":".to_string()]),
17250                ..Default::default()
17251            }),
17252            ..Default::default()
17253        },
17254        cx,
17255    )
17256    .await;
17257
17258    cx.lsp
17259        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17260            Ok(Some(lsp::CompletionResponse::Array(vec![
17261                lsp::CompletionItem {
17262                    label: "bg-blue".into(),
17263                    ..Default::default()
17264                },
17265                lsp::CompletionItem {
17266                    label: "bg-red".into(),
17267                    ..Default::default()
17268                },
17269                lsp::CompletionItem {
17270                    label: "bg-yellow".into(),
17271                    ..Default::default()
17272                },
17273            ])))
17274        });
17275
17276    cx.set_state(r#"<p class="bgˇ" />"#);
17277
17278    // Trigger completion when typing a dash, because the dash is an extra
17279    // word character in the 'element' scope, which contains the cursor.
17280    cx.simulate_keystroke("-");
17281    cx.executor().run_until_parked();
17282    cx.update_editor(|editor, _, _| {
17283        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17284        {
17285            assert_eq!(
17286                completion_menu_entries(menu),
17287                &["bg-blue", "bg-red", "bg-yellow"]
17288            );
17289        } else {
17290            panic!("expected completion menu to be open");
17291        }
17292    });
17293
17294    cx.simulate_keystroke("l");
17295    cx.executor().run_until_parked();
17296    cx.update_editor(|editor, _, _| {
17297        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17298        {
17299            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17300        } else {
17301            panic!("expected completion menu to be open");
17302        }
17303    });
17304
17305    // When filtering completions, consider the character after the '-' to
17306    // be the start of a subword.
17307    cx.set_state(r#"<p class="yelˇ" />"#);
17308    cx.simulate_keystroke("l");
17309    cx.executor().run_until_parked();
17310    cx.update_editor(|editor, _, _| {
17311        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17312        {
17313            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
17314        } else {
17315            panic!("expected completion menu to be open");
17316        }
17317    });
17318}
17319
17320fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
17321    let entries = menu.entries.borrow();
17322    entries.iter().map(|mat| mat.string.clone()).collect()
17323}
17324
17325#[gpui::test]
17326async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
17327    init_test(cx, |settings| {
17328        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
17329            Formatter::Prettier,
17330        )))
17331    });
17332
17333    let fs = FakeFs::new(cx.executor());
17334    fs.insert_file(path!("/file.ts"), Default::default()).await;
17335
17336    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
17337    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17338
17339    language_registry.add(Arc::new(Language::new(
17340        LanguageConfig {
17341            name: "TypeScript".into(),
17342            matcher: LanguageMatcher {
17343                path_suffixes: vec!["ts".to_string()],
17344                ..Default::default()
17345            },
17346            ..Default::default()
17347        },
17348        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17349    )));
17350    update_test_language_settings(cx, |settings| {
17351        settings.defaults.prettier = Some(PrettierSettings {
17352            allowed: true,
17353            ..PrettierSettings::default()
17354        });
17355    });
17356
17357    let test_plugin = "test_plugin";
17358    let _ = language_registry.register_fake_lsp(
17359        "TypeScript",
17360        FakeLspAdapter {
17361            prettier_plugins: vec![test_plugin],
17362            ..Default::default()
17363        },
17364    );
17365
17366    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
17367    let buffer = project
17368        .update(cx, |project, cx| {
17369            project.open_local_buffer(path!("/file.ts"), cx)
17370        })
17371        .await
17372        .unwrap();
17373
17374    let buffer_text = "one\ntwo\nthree\n";
17375    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17376    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17377    editor.update_in(cx, |editor, window, cx| {
17378        editor.set_text(buffer_text, window, cx)
17379    });
17380
17381    editor
17382        .update_in(cx, |editor, window, cx| {
17383            editor.perform_format(
17384                project.clone(),
17385                FormatTrigger::Manual,
17386                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
17387                window,
17388                cx,
17389            )
17390        })
17391        .unwrap()
17392        .await;
17393    assert_eq!(
17394        editor.update(cx, |editor, cx| editor.text(cx)),
17395        buffer_text.to_string() + prettier_format_suffix,
17396        "Test prettier formatting was not applied to the original buffer text",
17397    );
17398
17399    update_test_language_settings(cx, |settings| {
17400        settings.defaults.formatter = Some(SelectedFormatter::Auto)
17401    });
17402    let format = editor.update_in(cx, |editor, window, cx| {
17403        editor.perform_format(
17404            project.clone(),
17405            FormatTrigger::Manual,
17406            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
17407            window,
17408            cx,
17409        )
17410    });
17411    format.await.unwrap();
17412    assert_eq!(
17413        editor.update(cx, |editor, cx| editor.text(cx)),
17414        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
17415        "Autoformatting (via test prettier) was not applied to the original buffer text",
17416    );
17417}
17418
17419#[gpui::test]
17420async fn test_addition_reverts(cx: &mut TestAppContext) {
17421    init_test(cx, |_| {});
17422    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17423    let base_text = indoc! {r#"
17424        struct Row;
17425        struct Row1;
17426        struct Row2;
17427
17428        struct Row4;
17429        struct Row5;
17430        struct Row6;
17431
17432        struct Row8;
17433        struct Row9;
17434        struct Row10;"#};
17435
17436    // When addition hunks are not adjacent to carets, no hunk revert is performed
17437    assert_hunk_revert(
17438        indoc! {r#"struct Row;
17439                   struct Row1;
17440                   struct Row1.1;
17441                   struct Row1.2;
17442                   struct Row2;ˇ
17443
17444                   struct Row4;
17445                   struct Row5;
17446                   struct Row6;
17447
17448                   struct Row8;
17449                   ˇstruct Row9;
17450                   struct Row9.1;
17451                   struct Row9.2;
17452                   struct Row9.3;
17453                   struct Row10;"#},
17454        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
17455        indoc! {r#"struct Row;
17456                   struct Row1;
17457                   struct Row1.1;
17458                   struct Row1.2;
17459                   struct Row2;ˇ
17460
17461                   struct Row4;
17462                   struct Row5;
17463                   struct Row6;
17464
17465                   struct Row8;
17466                   ˇstruct Row9;
17467                   struct Row9.1;
17468                   struct Row9.2;
17469                   struct Row9.3;
17470                   struct Row10;"#},
17471        base_text,
17472        &mut cx,
17473    );
17474    // Same for selections
17475    assert_hunk_revert(
17476        indoc! {r#"struct Row;
17477                   struct Row1;
17478                   struct Row2;
17479                   struct Row2.1;
17480                   struct Row2.2;
17481                   «ˇ
17482                   struct Row4;
17483                   struct» Row5;
17484                   «struct Row6;
17485                   ˇ»
17486                   struct Row9.1;
17487                   struct Row9.2;
17488                   struct Row9.3;
17489                   struct Row8;
17490                   struct Row9;
17491                   struct Row10;"#},
17492        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
17493        indoc! {r#"struct Row;
17494                   struct Row1;
17495                   struct Row2;
17496                   struct Row2.1;
17497                   struct Row2.2;
17498                   «ˇ
17499                   struct Row4;
17500                   struct» Row5;
17501                   «struct Row6;
17502                   ˇ»
17503                   struct Row9.1;
17504                   struct Row9.2;
17505                   struct Row9.3;
17506                   struct Row8;
17507                   struct Row9;
17508                   struct Row10;"#},
17509        base_text,
17510        &mut cx,
17511    );
17512
17513    // When carets and selections intersect the addition hunks, those are reverted.
17514    // Adjacent carets got merged.
17515    assert_hunk_revert(
17516        indoc! {r#"struct Row;
17517                   ˇ// something on the top
17518                   struct Row1;
17519                   struct Row2;
17520                   struct Roˇw3.1;
17521                   struct Row2.2;
17522                   struct Row2.3;ˇ
17523
17524                   struct Row4;
17525                   struct ˇRow5.1;
17526                   struct Row5.2;
17527                   struct «Rowˇ»5.3;
17528                   struct Row5;
17529                   struct Row6;
17530                   ˇ
17531                   struct Row9.1;
17532                   struct «Rowˇ»9.2;
17533                   struct «ˇRow»9.3;
17534                   struct Row8;
17535                   struct Row9;
17536                   «ˇ// something on bottom»
17537                   struct Row10;"#},
17538        vec![
17539            DiffHunkStatusKind::Added,
17540            DiffHunkStatusKind::Added,
17541            DiffHunkStatusKind::Added,
17542            DiffHunkStatusKind::Added,
17543            DiffHunkStatusKind::Added,
17544        ],
17545        indoc! {r#"struct Row;
17546                   ˇstruct Row1;
17547                   struct Row2;
17548                   ˇ
17549                   struct Row4;
17550                   ˇstruct Row5;
17551                   struct Row6;
17552                   ˇ
17553                   ˇstruct Row8;
17554                   struct Row9;
17555                   ˇstruct Row10;"#},
17556        base_text,
17557        &mut cx,
17558    );
17559}
17560
17561#[gpui::test]
17562async fn test_modification_reverts(cx: &mut TestAppContext) {
17563    init_test(cx, |_| {});
17564    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17565    let base_text = indoc! {r#"
17566        struct Row;
17567        struct Row1;
17568        struct Row2;
17569
17570        struct Row4;
17571        struct Row5;
17572        struct Row6;
17573
17574        struct Row8;
17575        struct Row9;
17576        struct Row10;"#};
17577
17578    // Modification hunks behave the same as the addition ones.
17579    assert_hunk_revert(
17580        indoc! {r#"struct Row;
17581                   struct Row1;
17582                   struct Row33;
17583                   ˇ
17584                   struct Row4;
17585                   struct Row5;
17586                   struct Row6;
17587                   ˇ
17588                   struct Row99;
17589                   struct Row9;
17590                   struct Row10;"#},
17591        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17592        indoc! {r#"struct Row;
17593                   struct Row1;
17594                   struct Row33;
17595                   ˇ
17596                   struct Row4;
17597                   struct Row5;
17598                   struct Row6;
17599                   ˇ
17600                   struct Row99;
17601                   struct Row9;
17602                   struct Row10;"#},
17603        base_text,
17604        &mut cx,
17605    );
17606    assert_hunk_revert(
17607        indoc! {r#"struct Row;
17608                   struct Row1;
17609                   struct Row33;
17610                   «ˇ
17611                   struct Row4;
17612                   struct» Row5;
17613                   «struct Row6;
17614                   ˇ»
17615                   struct Row99;
17616                   struct Row9;
17617                   struct Row10;"#},
17618        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
17619        indoc! {r#"struct Row;
17620                   struct Row1;
17621                   struct Row33;
17622                   «ˇ
17623                   struct Row4;
17624                   struct» Row5;
17625                   «struct Row6;
17626                   ˇ»
17627                   struct Row99;
17628                   struct Row9;
17629                   struct Row10;"#},
17630        base_text,
17631        &mut cx,
17632    );
17633
17634    assert_hunk_revert(
17635        indoc! {r#"ˇstruct Row1.1;
17636                   struct Row1;
17637                   «ˇstr»uct Row22;
17638
17639                   struct ˇRow44;
17640                   struct Row5;
17641                   struct «Rˇ»ow66;ˇ
17642
17643                   «struˇ»ct Row88;
17644                   struct Row9;
17645                   struct Row1011;ˇ"#},
17646        vec![
17647            DiffHunkStatusKind::Modified,
17648            DiffHunkStatusKind::Modified,
17649            DiffHunkStatusKind::Modified,
17650            DiffHunkStatusKind::Modified,
17651            DiffHunkStatusKind::Modified,
17652            DiffHunkStatusKind::Modified,
17653        ],
17654        indoc! {r#"struct Row;
17655                   ˇstruct Row1;
17656                   struct Row2;
17657                   ˇ
17658                   struct Row4;
17659                   ˇstruct Row5;
17660                   struct Row6;
17661                   ˇ
17662                   struct Row8;
17663                   ˇstruct Row9;
17664                   struct Row10;ˇ"#},
17665        base_text,
17666        &mut cx,
17667    );
17668}
17669
17670#[gpui::test]
17671async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
17672    init_test(cx, |_| {});
17673    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17674    let base_text = indoc! {r#"
17675        one
17676
17677        two
17678        three
17679        "#};
17680
17681    cx.set_head_text(base_text);
17682    cx.set_state("\nˇ\n");
17683    cx.executor().run_until_parked();
17684    cx.update_editor(|editor, _window, cx| {
17685        editor.expand_selected_diff_hunks(cx);
17686    });
17687    cx.executor().run_until_parked();
17688    cx.update_editor(|editor, window, cx| {
17689        editor.backspace(&Default::default(), window, cx);
17690    });
17691    cx.run_until_parked();
17692    cx.assert_state_with_diff(
17693        indoc! {r#"
17694
17695        - two
17696        - threeˇ
17697        +
17698        "#}
17699        .to_string(),
17700    );
17701}
17702
17703#[gpui::test]
17704async fn test_deletion_reverts(cx: &mut TestAppContext) {
17705    init_test(cx, |_| {});
17706    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
17707    let base_text = indoc! {r#"struct Row;
17708struct Row1;
17709struct Row2;
17710
17711struct Row4;
17712struct Row5;
17713struct Row6;
17714
17715struct Row8;
17716struct Row9;
17717struct Row10;"#};
17718
17719    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
17720    assert_hunk_revert(
17721        indoc! {r#"struct Row;
17722                   struct Row2;
17723
17724                   ˇstruct Row4;
17725                   struct Row5;
17726                   struct Row6;
17727                   ˇ
17728                   struct Row8;
17729                   struct Row10;"#},
17730        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17731        indoc! {r#"struct Row;
17732                   struct Row2;
17733
17734                   ˇstruct Row4;
17735                   struct Row5;
17736                   struct Row6;
17737                   ˇ
17738                   struct Row8;
17739                   struct Row10;"#},
17740        base_text,
17741        &mut cx,
17742    );
17743    assert_hunk_revert(
17744        indoc! {r#"struct Row;
17745                   struct Row2;
17746
17747                   «ˇstruct Row4;
17748                   struct» Row5;
17749                   «struct Row6;
17750                   ˇ»
17751                   struct Row8;
17752                   struct Row10;"#},
17753        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17754        indoc! {r#"struct Row;
17755                   struct Row2;
17756
17757                   «ˇstruct Row4;
17758                   struct» Row5;
17759                   «struct Row6;
17760                   ˇ»
17761                   struct Row8;
17762                   struct Row10;"#},
17763        base_text,
17764        &mut cx,
17765    );
17766
17767    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
17768    assert_hunk_revert(
17769        indoc! {r#"struct Row;
17770                   ˇstruct Row2;
17771
17772                   struct Row4;
17773                   struct Row5;
17774                   struct Row6;
17775
17776                   struct Row8;ˇ
17777                   struct Row10;"#},
17778        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
17779        indoc! {r#"struct Row;
17780                   struct Row1;
17781                   ˇstruct Row2;
17782
17783                   struct Row4;
17784                   struct Row5;
17785                   struct Row6;
17786
17787                   struct Row8;ˇ
17788                   struct Row9;
17789                   struct Row10;"#},
17790        base_text,
17791        &mut cx,
17792    );
17793    assert_hunk_revert(
17794        indoc! {r#"struct Row;
17795                   struct Row2«ˇ;
17796                   struct Row4;
17797                   struct» Row5;
17798                   «struct Row6;
17799
17800                   struct Row8;ˇ»
17801                   struct Row10;"#},
17802        vec![
17803            DiffHunkStatusKind::Deleted,
17804            DiffHunkStatusKind::Deleted,
17805            DiffHunkStatusKind::Deleted,
17806        ],
17807        indoc! {r#"struct Row;
17808                   struct Row1;
17809                   struct Row2«ˇ;
17810
17811                   struct Row4;
17812                   struct» Row5;
17813                   «struct Row6;
17814
17815                   struct Row8;ˇ»
17816                   struct Row9;
17817                   struct Row10;"#},
17818        base_text,
17819        &mut cx,
17820    );
17821}
17822
17823#[gpui::test]
17824async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
17825    init_test(cx, |_| {});
17826
17827    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
17828    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
17829    let base_text_3 =
17830        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
17831
17832    let text_1 = edit_first_char_of_every_line(base_text_1);
17833    let text_2 = edit_first_char_of_every_line(base_text_2);
17834    let text_3 = edit_first_char_of_every_line(base_text_3);
17835
17836    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
17837    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
17838    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
17839
17840    let multibuffer = cx.new(|cx| {
17841        let mut multibuffer = MultiBuffer::new(ReadWrite);
17842        multibuffer.push_excerpts(
17843            buffer_1.clone(),
17844            [
17845                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17846                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17847                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17848            ],
17849            cx,
17850        );
17851        multibuffer.push_excerpts(
17852            buffer_2.clone(),
17853            [
17854                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17855                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17856                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17857            ],
17858            cx,
17859        );
17860        multibuffer.push_excerpts(
17861            buffer_3.clone(),
17862            [
17863                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17864                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17865                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17866            ],
17867            cx,
17868        );
17869        multibuffer
17870    });
17871
17872    let fs = FakeFs::new(cx.executor());
17873    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
17874    let (editor, cx) = cx
17875        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
17876    editor.update_in(cx, |editor, _window, cx| {
17877        for (buffer, diff_base) in [
17878            (buffer_1.clone(), base_text_1),
17879            (buffer_2.clone(), base_text_2),
17880            (buffer_3.clone(), base_text_3),
17881        ] {
17882            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
17883            editor
17884                .buffer
17885                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
17886        }
17887    });
17888    cx.executor().run_until_parked();
17889
17890    editor.update_in(cx, |editor, window, cx| {
17891        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}");
17892        editor.select_all(&SelectAll, window, cx);
17893        editor.git_restore(&Default::default(), window, cx);
17894    });
17895    cx.executor().run_until_parked();
17896
17897    // When all ranges are selected, all buffer hunks are reverted.
17898    editor.update(cx, |editor, cx| {
17899        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");
17900    });
17901    buffer_1.update(cx, |buffer, _| {
17902        assert_eq!(buffer.text(), base_text_1);
17903    });
17904    buffer_2.update(cx, |buffer, _| {
17905        assert_eq!(buffer.text(), base_text_2);
17906    });
17907    buffer_3.update(cx, |buffer, _| {
17908        assert_eq!(buffer.text(), base_text_3);
17909    });
17910
17911    editor.update_in(cx, |editor, window, cx| {
17912        editor.undo(&Default::default(), window, cx);
17913    });
17914
17915    editor.update_in(cx, |editor, window, cx| {
17916        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17917            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
17918        });
17919        editor.git_restore(&Default::default(), window, cx);
17920    });
17921
17922    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
17923    // but not affect buffer_2 and its related excerpts.
17924    editor.update(cx, |editor, cx| {
17925        assert_eq!(
17926            editor.text(cx),
17927            "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}"
17928        );
17929    });
17930    buffer_1.update(cx, |buffer, _| {
17931        assert_eq!(buffer.text(), base_text_1);
17932    });
17933    buffer_2.update(cx, |buffer, _| {
17934        assert_eq!(
17935            buffer.text(),
17936            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
17937        );
17938    });
17939    buffer_3.update(cx, |buffer, _| {
17940        assert_eq!(
17941            buffer.text(),
17942            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
17943        );
17944    });
17945
17946    fn edit_first_char_of_every_line(text: &str) -> String {
17947        text.split('\n')
17948            .map(|line| format!("X{}", &line[1..]))
17949            .collect::<Vec<_>>()
17950            .join("\n")
17951    }
17952}
17953
17954#[gpui::test]
17955async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
17956    init_test(cx, |_| {});
17957
17958    let cols = 4;
17959    let rows = 10;
17960    let sample_text_1 = sample_text(rows, cols, 'a');
17961    assert_eq!(
17962        sample_text_1,
17963        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
17964    );
17965    let sample_text_2 = sample_text(rows, cols, 'l');
17966    assert_eq!(
17967        sample_text_2,
17968        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
17969    );
17970    let sample_text_3 = sample_text(rows, cols, 'v');
17971    assert_eq!(
17972        sample_text_3,
17973        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
17974    );
17975
17976    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
17977    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
17978    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
17979
17980    let multi_buffer = cx.new(|cx| {
17981        let mut multibuffer = MultiBuffer::new(ReadWrite);
17982        multibuffer.push_excerpts(
17983            buffer_1.clone(),
17984            [
17985                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17986                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17987                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17988            ],
17989            cx,
17990        );
17991        multibuffer.push_excerpts(
17992            buffer_2.clone(),
17993            [
17994                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
17995                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
17996                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
17997            ],
17998            cx,
17999        );
18000        multibuffer.push_excerpts(
18001            buffer_3.clone(),
18002            [
18003                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18004                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18005                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18006            ],
18007            cx,
18008        );
18009        multibuffer
18010    });
18011
18012    let fs = FakeFs::new(cx.executor());
18013    fs.insert_tree(
18014        "/a",
18015        json!({
18016            "main.rs": sample_text_1,
18017            "other.rs": sample_text_2,
18018            "lib.rs": sample_text_3,
18019        }),
18020    )
18021    .await;
18022    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18023    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18024    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18025    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18026        Editor::new(
18027            EditorMode::full(),
18028            multi_buffer,
18029            Some(project.clone()),
18030            window,
18031            cx,
18032        )
18033    });
18034    let multibuffer_item_id = workspace
18035        .update(cx, |workspace, window, cx| {
18036            assert!(
18037                workspace.active_item(cx).is_none(),
18038                "active item should be None before the first item is added"
18039            );
18040            workspace.add_item_to_active_pane(
18041                Box::new(multi_buffer_editor.clone()),
18042                None,
18043                true,
18044                window,
18045                cx,
18046            );
18047            let active_item = workspace
18048                .active_item(cx)
18049                .expect("should have an active item after adding the multi buffer");
18050            assert!(
18051                !active_item.is_singleton(cx),
18052                "A multi buffer was expected to active after adding"
18053            );
18054            active_item.item_id()
18055        })
18056        .unwrap();
18057    cx.executor().run_until_parked();
18058
18059    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18060        editor.change_selections(
18061            SelectionEffects::scroll(Autoscroll::Next),
18062            window,
18063            cx,
18064            |s| s.select_ranges(Some(1..2)),
18065        );
18066        editor.open_excerpts(&OpenExcerpts, window, cx);
18067    });
18068    cx.executor().run_until_parked();
18069    let first_item_id = workspace
18070        .update(cx, |workspace, window, cx| {
18071            let active_item = workspace
18072                .active_item(cx)
18073                .expect("should have an active item after navigating into the 1st buffer");
18074            let first_item_id = active_item.item_id();
18075            assert_ne!(
18076                first_item_id, multibuffer_item_id,
18077                "Should navigate into the 1st buffer and activate it"
18078            );
18079            assert!(
18080                active_item.is_singleton(cx),
18081                "New active item should be a singleton buffer"
18082            );
18083            assert_eq!(
18084                active_item
18085                    .act_as::<Editor>(cx)
18086                    .expect("should have navigated into an editor for the 1st buffer")
18087                    .read(cx)
18088                    .text(cx),
18089                sample_text_1
18090            );
18091
18092            workspace
18093                .go_back(workspace.active_pane().downgrade(), window, cx)
18094                .detach_and_log_err(cx);
18095
18096            first_item_id
18097        })
18098        .unwrap();
18099    cx.executor().run_until_parked();
18100    workspace
18101        .update(cx, |workspace, _, cx| {
18102            let active_item = workspace
18103                .active_item(cx)
18104                .expect("should have an active item after navigating back");
18105            assert_eq!(
18106                active_item.item_id(),
18107                multibuffer_item_id,
18108                "Should navigate back to the multi buffer"
18109            );
18110            assert!(!active_item.is_singleton(cx));
18111        })
18112        .unwrap();
18113
18114    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18115        editor.change_selections(
18116            SelectionEffects::scroll(Autoscroll::Next),
18117            window,
18118            cx,
18119            |s| s.select_ranges(Some(39..40)),
18120        );
18121        editor.open_excerpts(&OpenExcerpts, window, cx);
18122    });
18123    cx.executor().run_until_parked();
18124    let second_item_id = workspace
18125        .update(cx, |workspace, window, cx| {
18126            let active_item = workspace
18127                .active_item(cx)
18128                .expect("should have an active item after navigating into the 2nd buffer");
18129            let second_item_id = active_item.item_id();
18130            assert_ne!(
18131                second_item_id, multibuffer_item_id,
18132                "Should navigate away from the multibuffer"
18133            );
18134            assert_ne!(
18135                second_item_id, first_item_id,
18136                "Should navigate into the 2nd buffer and activate it"
18137            );
18138            assert!(
18139                active_item.is_singleton(cx),
18140                "New active item should be a singleton buffer"
18141            );
18142            assert_eq!(
18143                active_item
18144                    .act_as::<Editor>(cx)
18145                    .expect("should have navigated into an editor")
18146                    .read(cx)
18147                    .text(cx),
18148                sample_text_2
18149            );
18150
18151            workspace
18152                .go_back(workspace.active_pane().downgrade(), window, cx)
18153                .detach_and_log_err(cx);
18154
18155            second_item_id
18156        })
18157        .unwrap();
18158    cx.executor().run_until_parked();
18159    workspace
18160        .update(cx, |workspace, _, cx| {
18161            let active_item = workspace
18162                .active_item(cx)
18163                .expect("should have an active item after navigating back from the 2nd buffer");
18164            assert_eq!(
18165                active_item.item_id(),
18166                multibuffer_item_id,
18167                "Should navigate back from the 2nd buffer to the multi buffer"
18168            );
18169            assert!(!active_item.is_singleton(cx));
18170        })
18171        .unwrap();
18172
18173    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18174        editor.change_selections(
18175            SelectionEffects::scroll(Autoscroll::Next),
18176            window,
18177            cx,
18178            |s| s.select_ranges(Some(70..70)),
18179        );
18180        editor.open_excerpts(&OpenExcerpts, window, cx);
18181    });
18182    cx.executor().run_until_parked();
18183    workspace
18184        .update(cx, |workspace, window, cx| {
18185            let active_item = workspace
18186                .active_item(cx)
18187                .expect("should have an active item after navigating into the 3rd buffer");
18188            let third_item_id = active_item.item_id();
18189            assert_ne!(
18190                third_item_id, multibuffer_item_id,
18191                "Should navigate into the 3rd buffer and activate it"
18192            );
18193            assert_ne!(third_item_id, first_item_id);
18194            assert_ne!(third_item_id, second_item_id);
18195            assert!(
18196                active_item.is_singleton(cx),
18197                "New active item should be a singleton buffer"
18198            );
18199            assert_eq!(
18200                active_item
18201                    .act_as::<Editor>(cx)
18202                    .expect("should have navigated into an editor")
18203                    .read(cx)
18204                    .text(cx),
18205                sample_text_3
18206            );
18207
18208            workspace
18209                .go_back(workspace.active_pane().downgrade(), window, cx)
18210                .detach_and_log_err(cx);
18211        })
18212        .unwrap();
18213    cx.executor().run_until_parked();
18214    workspace
18215        .update(cx, |workspace, _, cx| {
18216            let active_item = workspace
18217                .active_item(cx)
18218                .expect("should have an active item after navigating back from the 3rd buffer");
18219            assert_eq!(
18220                active_item.item_id(),
18221                multibuffer_item_id,
18222                "Should navigate back from the 3rd buffer to the multi buffer"
18223            );
18224            assert!(!active_item.is_singleton(cx));
18225        })
18226        .unwrap();
18227}
18228
18229#[gpui::test]
18230async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18231    init_test(cx, |_| {});
18232
18233    let mut cx = EditorTestContext::new(cx).await;
18234
18235    let diff_base = r#"
18236        use some::mod;
18237
18238        const A: u32 = 42;
18239
18240        fn main() {
18241            println!("hello");
18242
18243            println!("world");
18244        }
18245        "#
18246    .unindent();
18247
18248    cx.set_state(
18249        &r#"
18250        use some::modified;
18251
18252        ˇ
18253        fn main() {
18254            println!("hello there");
18255
18256            println!("around the");
18257            println!("world");
18258        }
18259        "#
18260        .unindent(),
18261    );
18262
18263    cx.set_head_text(&diff_base);
18264    executor.run_until_parked();
18265
18266    cx.update_editor(|editor, window, cx| {
18267        editor.go_to_next_hunk(&GoToHunk, window, cx);
18268        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18269    });
18270    executor.run_until_parked();
18271    cx.assert_state_with_diff(
18272        r#"
18273          use some::modified;
18274
18275
18276          fn main() {
18277        -     println!("hello");
18278        + ˇ    println!("hello there");
18279
18280              println!("around the");
18281              println!("world");
18282          }
18283        "#
18284        .unindent(),
18285    );
18286
18287    cx.update_editor(|editor, window, cx| {
18288        for _ in 0..2 {
18289            editor.go_to_next_hunk(&GoToHunk, window, cx);
18290            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18291        }
18292    });
18293    executor.run_until_parked();
18294    cx.assert_state_with_diff(
18295        r#"
18296        - use some::mod;
18297        + ˇuse some::modified;
18298
18299
18300          fn main() {
18301        -     println!("hello");
18302        +     println!("hello there");
18303
18304        +     println!("around the");
18305              println!("world");
18306          }
18307        "#
18308        .unindent(),
18309    );
18310
18311    cx.update_editor(|editor, window, cx| {
18312        editor.go_to_next_hunk(&GoToHunk, window, cx);
18313        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18314    });
18315    executor.run_until_parked();
18316    cx.assert_state_with_diff(
18317        r#"
18318        - use some::mod;
18319        + use some::modified;
18320
18321        - const A: u32 = 42;
18322          ˇ
18323          fn main() {
18324        -     println!("hello");
18325        +     println!("hello there");
18326
18327        +     println!("around the");
18328              println!("world");
18329          }
18330        "#
18331        .unindent(),
18332    );
18333
18334    cx.update_editor(|editor, window, cx| {
18335        editor.cancel(&Cancel, window, cx);
18336    });
18337
18338    cx.assert_state_with_diff(
18339        r#"
18340          use some::modified;
18341
18342          ˇ
18343          fn main() {
18344              println!("hello there");
18345
18346              println!("around the");
18347              println!("world");
18348          }
18349        "#
18350        .unindent(),
18351    );
18352}
18353
18354#[gpui::test]
18355async fn test_diff_base_change_with_expanded_diff_hunks(
18356    executor: BackgroundExecutor,
18357    cx: &mut TestAppContext,
18358) {
18359    init_test(cx, |_| {});
18360
18361    let mut cx = EditorTestContext::new(cx).await;
18362
18363    let diff_base = r#"
18364        use some::mod1;
18365        use some::mod2;
18366
18367        const A: u32 = 42;
18368        const B: u32 = 42;
18369        const C: u32 = 42;
18370
18371        fn main() {
18372            println!("hello");
18373
18374            println!("world");
18375        }
18376        "#
18377    .unindent();
18378
18379    cx.set_state(
18380        &r#"
18381        use some::mod2;
18382
18383        const A: u32 = 42;
18384        const C: u32 = 42;
18385
18386        fn main(ˇ) {
18387            //println!("hello");
18388
18389            println!("world");
18390            //
18391            //
18392        }
18393        "#
18394        .unindent(),
18395    );
18396
18397    cx.set_head_text(&diff_base);
18398    executor.run_until_parked();
18399
18400    cx.update_editor(|editor, window, cx| {
18401        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18402    });
18403    executor.run_until_parked();
18404    cx.assert_state_with_diff(
18405        r#"
18406        - use some::mod1;
18407          use some::mod2;
18408
18409          const A: u32 = 42;
18410        - const B: u32 = 42;
18411          const C: u32 = 42;
18412
18413          fn main(ˇ) {
18414        -     println!("hello");
18415        +     //println!("hello");
18416
18417              println!("world");
18418        +     //
18419        +     //
18420          }
18421        "#
18422        .unindent(),
18423    );
18424
18425    cx.set_head_text("new diff base!");
18426    executor.run_until_parked();
18427    cx.assert_state_with_diff(
18428        r#"
18429        - new diff base!
18430        + use some::mod2;
18431        +
18432        + const A: u32 = 42;
18433        + const C: u32 = 42;
18434        +
18435        + fn main(ˇ) {
18436        +     //println!("hello");
18437        +
18438        +     println!("world");
18439        +     //
18440        +     //
18441        + }
18442        "#
18443        .unindent(),
18444    );
18445}
18446
18447#[gpui::test]
18448async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
18449    init_test(cx, |_| {});
18450
18451    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
18452    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
18453    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
18454    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
18455    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
18456    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
18457
18458    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
18459    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
18460    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
18461
18462    let multi_buffer = cx.new(|cx| {
18463        let mut multibuffer = MultiBuffer::new(ReadWrite);
18464        multibuffer.push_excerpts(
18465            buffer_1.clone(),
18466            [
18467                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18468                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18469                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18470            ],
18471            cx,
18472        );
18473        multibuffer.push_excerpts(
18474            buffer_2.clone(),
18475            [
18476                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18477                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18478                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18479            ],
18480            cx,
18481        );
18482        multibuffer.push_excerpts(
18483            buffer_3.clone(),
18484            [
18485                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18486                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18487                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
18488            ],
18489            cx,
18490        );
18491        multibuffer
18492    });
18493
18494    let editor =
18495        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18496    editor
18497        .update(cx, |editor, _window, cx| {
18498            for (buffer, diff_base) in [
18499                (buffer_1.clone(), file_1_old),
18500                (buffer_2.clone(), file_2_old),
18501                (buffer_3.clone(), file_3_old),
18502            ] {
18503                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18504                editor
18505                    .buffer
18506                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18507            }
18508        })
18509        .unwrap();
18510
18511    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18512    cx.run_until_parked();
18513
18514    cx.assert_editor_state(
18515        &"
18516            ˇaaa
18517            ccc
18518            ddd
18519
18520            ggg
18521            hhh
18522
18523
18524            lll
18525            mmm
18526            NNN
18527
18528            qqq
18529            rrr
18530
18531            uuu
18532            111
18533            222
18534            333
18535
18536            666
18537            777
18538
18539            000
18540            !!!"
18541        .unindent(),
18542    );
18543
18544    cx.update_editor(|editor, window, cx| {
18545        editor.select_all(&SelectAll, window, cx);
18546        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18547    });
18548    cx.executor().run_until_parked();
18549
18550    cx.assert_state_with_diff(
18551        "
18552            «aaa
18553          - bbb
18554            ccc
18555            ddd
18556
18557            ggg
18558            hhh
18559
18560
18561            lll
18562            mmm
18563          - nnn
18564          + NNN
18565
18566            qqq
18567            rrr
18568
18569            uuu
18570            111
18571            222
18572            333
18573
18574          + 666
18575            777
18576
18577            000
18578            !!!ˇ»"
18579            .unindent(),
18580    );
18581}
18582
18583#[gpui::test]
18584async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
18585    init_test(cx, |_| {});
18586
18587    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
18588    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
18589
18590    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
18591    let multi_buffer = cx.new(|cx| {
18592        let mut multibuffer = MultiBuffer::new(ReadWrite);
18593        multibuffer.push_excerpts(
18594            buffer.clone(),
18595            [
18596                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
18597                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
18598                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
18599            ],
18600            cx,
18601        );
18602        multibuffer
18603    });
18604
18605    let editor =
18606        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
18607    editor
18608        .update(cx, |editor, _window, cx| {
18609            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
18610            editor
18611                .buffer
18612                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
18613        })
18614        .unwrap();
18615
18616    let mut cx = EditorTestContext::for_editor(editor, cx).await;
18617    cx.run_until_parked();
18618
18619    cx.update_editor(|editor, window, cx| {
18620        editor.expand_all_diff_hunks(&Default::default(), window, cx)
18621    });
18622    cx.executor().run_until_parked();
18623
18624    // When the start of a hunk coincides with the start of its excerpt,
18625    // the hunk is expanded. When the start of a a hunk is earlier than
18626    // the start of its excerpt, the hunk is not expanded.
18627    cx.assert_state_with_diff(
18628        "
18629            ˇaaa
18630          - bbb
18631          + BBB
18632
18633          - ddd
18634          - eee
18635          + DDD
18636          + EEE
18637            fff
18638
18639            iii
18640        "
18641        .unindent(),
18642    );
18643}
18644
18645#[gpui::test]
18646async fn test_edits_around_expanded_insertion_hunks(
18647    executor: BackgroundExecutor,
18648    cx: &mut TestAppContext,
18649) {
18650    init_test(cx, |_| {});
18651
18652    let mut cx = EditorTestContext::new(cx).await;
18653
18654    let diff_base = r#"
18655        use some::mod1;
18656        use some::mod2;
18657
18658        const A: u32 = 42;
18659
18660        fn main() {
18661            println!("hello");
18662
18663            println!("world");
18664        }
18665        "#
18666    .unindent();
18667    executor.run_until_parked();
18668    cx.set_state(
18669        &r#"
18670        use some::mod1;
18671        use some::mod2;
18672
18673        const A: u32 = 42;
18674        const B: u32 = 42;
18675        const C: u32 = 42;
18676        ˇ
18677
18678        fn main() {
18679            println!("hello");
18680
18681            println!("world");
18682        }
18683        "#
18684        .unindent(),
18685    );
18686
18687    cx.set_head_text(&diff_base);
18688    executor.run_until_parked();
18689
18690    cx.update_editor(|editor, window, cx| {
18691        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18692    });
18693    executor.run_until_parked();
18694
18695    cx.assert_state_with_diff(
18696        r#"
18697        use some::mod1;
18698        use some::mod2;
18699
18700        const A: u32 = 42;
18701      + const B: u32 = 42;
18702      + const C: u32 = 42;
18703      + ˇ
18704
18705        fn main() {
18706            println!("hello");
18707
18708            println!("world");
18709        }
18710      "#
18711        .unindent(),
18712    );
18713
18714    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
18715    executor.run_until_parked();
18716
18717    cx.assert_state_with_diff(
18718        r#"
18719        use some::mod1;
18720        use some::mod2;
18721
18722        const A: u32 = 42;
18723      + const B: u32 = 42;
18724      + const C: u32 = 42;
18725      + const D: u32 = 42;
18726      + ˇ
18727
18728        fn main() {
18729            println!("hello");
18730
18731            println!("world");
18732        }
18733      "#
18734        .unindent(),
18735    );
18736
18737    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
18738    executor.run_until_parked();
18739
18740    cx.assert_state_with_diff(
18741        r#"
18742        use some::mod1;
18743        use some::mod2;
18744
18745        const A: u32 = 42;
18746      + const B: u32 = 42;
18747      + const C: u32 = 42;
18748      + const D: u32 = 42;
18749      + const E: u32 = 42;
18750      + ˇ
18751
18752        fn main() {
18753            println!("hello");
18754
18755            println!("world");
18756        }
18757      "#
18758        .unindent(),
18759    );
18760
18761    cx.update_editor(|editor, window, cx| {
18762        editor.delete_line(&DeleteLine, window, cx);
18763    });
18764    executor.run_until_parked();
18765
18766    cx.assert_state_with_diff(
18767        r#"
18768        use some::mod1;
18769        use some::mod2;
18770
18771        const A: u32 = 42;
18772      + const B: u32 = 42;
18773      + const C: u32 = 42;
18774      + const D: u32 = 42;
18775      + const E: u32 = 42;
18776        ˇ
18777        fn main() {
18778            println!("hello");
18779
18780            println!("world");
18781        }
18782      "#
18783        .unindent(),
18784    );
18785
18786    cx.update_editor(|editor, window, cx| {
18787        editor.move_up(&MoveUp, window, cx);
18788        editor.delete_line(&DeleteLine, window, cx);
18789        editor.move_up(&MoveUp, window, cx);
18790        editor.delete_line(&DeleteLine, window, cx);
18791        editor.move_up(&MoveUp, window, cx);
18792        editor.delete_line(&DeleteLine, window, cx);
18793    });
18794    executor.run_until_parked();
18795    cx.assert_state_with_diff(
18796        r#"
18797        use some::mod1;
18798        use some::mod2;
18799
18800        const A: u32 = 42;
18801      + const B: u32 = 42;
18802        ˇ
18803        fn main() {
18804            println!("hello");
18805
18806            println!("world");
18807        }
18808      "#
18809        .unindent(),
18810    );
18811
18812    cx.update_editor(|editor, window, cx| {
18813        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
18814        editor.delete_line(&DeleteLine, window, cx);
18815    });
18816    executor.run_until_parked();
18817    cx.assert_state_with_diff(
18818        r#"
18819        ˇ
18820        fn main() {
18821            println!("hello");
18822
18823            println!("world");
18824        }
18825      "#
18826        .unindent(),
18827    );
18828}
18829
18830#[gpui::test]
18831async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
18832    init_test(cx, |_| {});
18833
18834    let mut cx = EditorTestContext::new(cx).await;
18835    cx.set_head_text(indoc! { "
18836        one
18837        two
18838        three
18839        four
18840        five
18841        "
18842    });
18843    cx.set_state(indoc! { "
18844        one
18845        ˇthree
18846        five
18847    "});
18848    cx.run_until_parked();
18849    cx.update_editor(|editor, window, cx| {
18850        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18851    });
18852    cx.assert_state_with_diff(
18853        indoc! { "
18854        one
18855      - two
18856        ˇthree
18857      - four
18858        five
18859    "}
18860        .to_string(),
18861    );
18862    cx.update_editor(|editor, window, cx| {
18863        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18864    });
18865
18866    cx.assert_state_with_diff(
18867        indoc! { "
18868        one
18869        ˇthree
18870        five
18871    "}
18872        .to_string(),
18873    );
18874
18875    cx.set_state(indoc! { "
18876        one
18877        ˇTWO
18878        three
18879        four
18880        five
18881    "});
18882    cx.run_until_parked();
18883    cx.update_editor(|editor, window, cx| {
18884        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18885    });
18886
18887    cx.assert_state_with_diff(
18888        indoc! { "
18889            one
18890          - two
18891          + ˇTWO
18892            three
18893            four
18894            five
18895        "}
18896        .to_string(),
18897    );
18898    cx.update_editor(|editor, window, cx| {
18899        editor.move_up(&Default::default(), window, cx);
18900        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
18901    });
18902    cx.assert_state_with_diff(
18903        indoc! { "
18904            one
18905            ˇTWO
18906            three
18907            four
18908            five
18909        "}
18910        .to_string(),
18911    );
18912}
18913
18914#[gpui::test]
18915async fn test_edits_around_expanded_deletion_hunks(
18916    executor: BackgroundExecutor,
18917    cx: &mut TestAppContext,
18918) {
18919    init_test(cx, |_| {});
18920
18921    let mut cx = EditorTestContext::new(cx).await;
18922
18923    let diff_base = r#"
18924        use some::mod1;
18925        use some::mod2;
18926
18927        const A: u32 = 42;
18928        const B: u32 = 42;
18929        const C: u32 = 42;
18930
18931
18932        fn main() {
18933            println!("hello");
18934
18935            println!("world");
18936        }
18937    "#
18938    .unindent();
18939    executor.run_until_parked();
18940    cx.set_state(
18941        &r#"
18942        use some::mod1;
18943        use some::mod2;
18944
18945        ˇconst B: u32 = 42;
18946        const C: u32 = 42;
18947
18948
18949        fn main() {
18950            println!("hello");
18951
18952            println!("world");
18953        }
18954        "#
18955        .unindent(),
18956    );
18957
18958    cx.set_head_text(&diff_base);
18959    executor.run_until_parked();
18960
18961    cx.update_editor(|editor, window, cx| {
18962        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
18963    });
18964    executor.run_until_parked();
18965
18966    cx.assert_state_with_diff(
18967        r#"
18968        use some::mod1;
18969        use some::mod2;
18970
18971      - const A: u32 = 42;
18972        ˇconst B: u32 = 42;
18973        const C: u32 = 42;
18974
18975
18976        fn main() {
18977            println!("hello");
18978
18979            println!("world");
18980        }
18981      "#
18982        .unindent(),
18983    );
18984
18985    cx.update_editor(|editor, window, cx| {
18986        editor.delete_line(&DeleteLine, window, cx);
18987    });
18988    executor.run_until_parked();
18989    cx.assert_state_with_diff(
18990        r#"
18991        use some::mod1;
18992        use some::mod2;
18993
18994      - const A: u32 = 42;
18995      - const B: u32 = 42;
18996        ˇconst C: u32 = 42;
18997
18998
18999        fn main() {
19000            println!("hello");
19001
19002            println!("world");
19003        }
19004      "#
19005        .unindent(),
19006    );
19007
19008    cx.update_editor(|editor, window, cx| {
19009        editor.delete_line(&DeleteLine, window, cx);
19010    });
19011    executor.run_until_parked();
19012    cx.assert_state_with_diff(
19013        r#"
19014        use some::mod1;
19015        use some::mod2;
19016
19017      - const A: u32 = 42;
19018      - const B: u32 = 42;
19019      - const C: u32 = 42;
19020        ˇ
19021
19022        fn main() {
19023            println!("hello");
19024
19025            println!("world");
19026        }
19027      "#
19028        .unindent(),
19029    );
19030
19031    cx.update_editor(|editor, window, cx| {
19032        editor.handle_input("replacement", window, cx);
19033    });
19034    executor.run_until_parked();
19035    cx.assert_state_with_diff(
19036        r#"
19037        use some::mod1;
19038        use some::mod2;
19039
19040      - const A: u32 = 42;
19041      - const B: u32 = 42;
19042      - const C: u32 = 42;
19043      -
19044      + replacementˇ
19045
19046        fn main() {
19047            println!("hello");
19048
19049            println!("world");
19050        }
19051      "#
19052        .unindent(),
19053    );
19054}
19055
19056#[gpui::test]
19057async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19058    init_test(cx, |_| {});
19059
19060    let mut cx = EditorTestContext::new(cx).await;
19061
19062    let base_text = r#"
19063        one
19064        two
19065        three
19066        four
19067        five
19068    "#
19069    .unindent();
19070    executor.run_until_parked();
19071    cx.set_state(
19072        &r#"
19073        one
19074        two
19075        fˇour
19076        five
19077        "#
19078        .unindent(),
19079    );
19080
19081    cx.set_head_text(&base_text);
19082    executor.run_until_parked();
19083
19084    cx.update_editor(|editor, window, cx| {
19085        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19086    });
19087    executor.run_until_parked();
19088
19089    cx.assert_state_with_diff(
19090        r#"
19091          one
19092          two
19093        - three
19094          fˇour
19095          five
19096        "#
19097        .unindent(),
19098    );
19099
19100    cx.update_editor(|editor, window, cx| {
19101        editor.backspace(&Backspace, window, cx);
19102        editor.backspace(&Backspace, window, cx);
19103    });
19104    executor.run_until_parked();
19105    cx.assert_state_with_diff(
19106        r#"
19107          one
19108          two
19109        - threeˇ
19110        - four
19111        + our
19112          five
19113        "#
19114        .unindent(),
19115    );
19116}
19117
19118#[gpui::test]
19119async fn test_edit_after_expanded_modification_hunk(
19120    executor: BackgroundExecutor,
19121    cx: &mut TestAppContext,
19122) {
19123    init_test(cx, |_| {});
19124
19125    let mut cx = EditorTestContext::new(cx).await;
19126
19127    let diff_base = r#"
19128        use some::mod1;
19129        use some::mod2;
19130
19131        const A: u32 = 42;
19132        const B: u32 = 42;
19133        const C: u32 = 42;
19134        const D: u32 = 42;
19135
19136
19137        fn main() {
19138            println!("hello");
19139
19140            println!("world");
19141        }"#
19142    .unindent();
19143
19144    cx.set_state(
19145        &r#"
19146        use some::mod1;
19147        use some::mod2;
19148
19149        const A: u32 = 42;
19150        const B: u32 = 42;
19151        const C: u32 = 43ˇ
19152        const D: u32 = 42;
19153
19154
19155        fn main() {
19156            println!("hello");
19157
19158            println!("world");
19159        }"#
19160        .unindent(),
19161    );
19162
19163    cx.set_head_text(&diff_base);
19164    executor.run_until_parked();
19165    cx.update_editor(|editor, window, cx| {
19166        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19167    });
19168    executor.run_until_parked();
19169
19170    cx.assert_state_with_diff(
19171        r#"
19172        use some::mod1;
19173        use some::mod2;
19174
19175        const A: u32 = 42;
19176        const B: u32 = 42;
19177      - const C: u32 = 42;
19178      + const C: u32 = 43ˇ
19179        const D: u32 = 42;
19180
19181
19182        fn main() {
19183            println!("hello");
19184
19185            println!("world");
19186        }"#
19187        .unindent(),
19188    );
19189
19190    cx.update_editor(|editor, window, cx| {
19191        editor.handle_input("\nnew_line\n", window, cx);
19192    });
19193    executor.run_until_parked();
19194
19195    cx.assert_state_with_diff(
19196        r#"
19197        use some::mod1;
19198        use some::mod2;
19199
19200        const A: u32 = 42;
19201        const B: u32 = 42;
19202      - const C: u32 = 42;
19203      + const C: u32 = 43
19204      + new_line
19205      + ˇ
19206        const D: u32 = 42;
19207
19208
19209        fn main() {
19210            println!("hello");
19211
19212            println!("world");
19213        }"#
19214        .unindent(),
19215    );
19216}
19217
19218#[gpui::test]
19219async fn test_stage_and_unstage_added_file_hunk(
19220    executor: BackgroundExecutor,
19221    cx: &mut TestAppContext,
19222) {
19223    init_test(cx, |_| {});
19224
19225    let mut cx = EditorTestContext::new(cx).await;
19226    cx.update_editor(|editor, _, cx| {
19227        editor.set_expand_all_diff_hunks(cx);
19228    });
19229
19230    let working_copy = r#"
19231            ˇfn main() {
19232                println!("hello, world!");
19233            }
19234        "#
19235    .unindent();
19236
19237    cx.set_state(&working_copy);
19238    executor.run_until_parked();
19239
19240    cx.assert_state_with_diff(
19241        r#"
19242            + ˇfn main() {
19243            +     println!("hello, world!");
19244            + }
19245        "#
19246        .unindent(),
19247    );
19248    cx.assert_index_text(None);
19249
19250    cx.update_editor(|editor, window, cx| {
19251        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19252    });
19253    executor.run_until_parked();
19254    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19255    cx.assert_state_with_diff(
19256        r#"
19257            + ˇfn main() {
19258            +     println!("hello, world!");
19259            + }
19260        "#
19261        .unindent(),
19262    );
19263
19264    cx.update_editor(|editor, window, cx| {
19265        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19266    });
19267    executor.run_until_parked();
19268    cx.assert_index_text(None);
19269}
19270
19271async fn setup_indent_guides_editor(
19272    text: &str,
19273    cx: &mut TestAppContext,
19274) -> (BufferId, EditorTestContext) {
19275    init_test(cx, |_| {});
19276
19277    let mut cx = EditorTestContext::new(cx).await;
19278
19279    let buffer_id = cx.update_editor(|editor, window, cx| {
19280        editor.set_text(text, window, cx);
19281        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19282
19283        buffer_ids[0]
19284    });
19285
19286    (buffer_id, cx)
19287}
19288
19289fn assert_indent_guides(
19290    range: Range<u32>,
19291    expected: Vec<IndentGuide>,
19292    active_indices: Option<Vec<usize>>,
19293    cx: &mut EditorTestContext,
19294) {
19295    let indent_guides = cx.update_editor(|editor, window, cx| {
19296        let snapshot = editor.snapshot(window, cx).display_snapshot;
19297        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19298            editor,
19299            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19300            true,
19301            &snapshot,
19302            cx,
19303        );
19304
19305        indent_guides.sort_by(|a, b| {
19306            a.depth.cmp(&b.depth).then(
19307                a.start_row
19308                    .cmp(&b.start_row)
19309                    .then(a.end_row.cmp(&b.end_row)),
19310            )
19311        });
19312        indent_guides
19313    });
19314
19315    if let Some(expected) = active_indices {
19316        let active_indices = cx.update_editor(|editor, window, cx| {
19317            let snapshot = editor.snapshot(window, cx).display_snapshot;
19318            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
19319        });
19320
19321        assert_eq!(
19322            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
19323            expected,
19324            "Active indent guide indices do not match"
19325        );
19326    }
19327
19328    assert_eq!(indent_guides, expected, "Indent guides do not match");
19329}
19330
19331fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
19332    IndentGuide {
19333        buffer_id,
19334        start_row: MultiBufferRow(start_row),
19335        end_row: MultiBufferRow(end_row),
19336        depth,
19337        tab_size: 4,
19338        settings: IndentGuideSettings {
19339            enabled: true,
19340            line_width: 1,
19341            active_line_width: 1,
19342            ..Default::default()
19343        },
19344    }
19345}
19346
19347#[gpui::test]
19348async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
19349    let (buffer_id, mut cx) = setup_indent_guides_editor(
19350        &"
19351        fn main() {
19352            let a = 1;
19353        }"
19354        .unindent(),
19355        cx,
19356    )
19357    .await;
19358
19359    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19360}
19361
19362#[gpui::test]
19363async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
19364    let (buffer_id, mut cx) = setup_indent_guides_editor(
19365        &"
19366        fn main() {
19367            let a = 1;
19368            let b = 2;
19369        }"
19370        .unindent(),
19371        cx,
19372    )
19373    .await;
19374
19375    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
19376}
19377
19378#[gpui::test]
19379async fn test_indent_guide_nested(cx: &mut TestAppContext) {
19380    let (buffer_id, mut cx) = setup_indent_guides_editor(
19381        &"
19382        fn main() {
19383            let a = 1;
19384            if a == 3 {
19385                let b = 2;
19386            } else {
19387                let c = 3;
19388            }
19389        }"
19390        .unindent(),
19391        cx,
19392    )
19393    .await;
19394
19395    assert_indent_guides(
19396        0..8,
19397        vec![
19398            indent_guide(buffer_id, 1, 6, 0),
19399            indent_guide(buffer_id, 3, 3, 1),
19400            indent_guide(buffer_id, 5, 5, 1),
19401        ],
19402        None,
19403        &mut cx,
19404    );
19405}
19406
19407#[gpui::test]
19408async fn test_indent_guide_tab(cx: &mut TestAppContext) {
19409    let (buffer_id, mut cx) = setup_indent_guides_editor(
19410        &"
19411        fn main() {
19412            let a = 1;
19413                let b = 2;
19414            let c = 3;
19415        }"
19416        .unindent(),
19417        cx,
19418    )
19419    .await;
19420
19421    assert_indent_guides(
19422        0..5,
19423        vec![
19424            indent_guide(buffer_id, 1, 3, 0),
19425            indent_guide(buffer_id, 2, 2, 1),
19426        ],
19427        None,
19428        &mut cx,
19429    );
19430}
19431
19432#[gpui::test]
19433async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
19434    let (buffer_id, mut cx) = setup_indent_guides_editor(
19435        &"
19436        fn main() {
19437            let a = 1;
19438
19439            let c = 3;
19440        }"
19441        .unindent(),
19442        cx,
19443    )
19444    .await;
19445
19446    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
19447}
19448
19449#[gpui::test]
19450async fn test_indent_guide_complex(cx: &mut TestAppContext) {
19451    let (buffer_id, mut cx) = setup_indent_guides_editor(
19452        &"
19453        fn main() {
19454            let a = 1;
19455
19456            let c = 3;
19457
19458            if a == 3 {
19459                let b = 2;
19460            } else {
19461                let c = 3;
19462            }
19463        }"
19464        .unindent(),
19465        cx,
19466    )
19467    .await;
19468
19469    assert_indent_guides(
19470        0..11,
19471        vec![
19472            indent_guide(buffer_id, 1, 9, 0),
19473            indent_guide(buffer_id, 6, 6, 1),
19474            indent_guide(buffer_id, 8, 8, 1),
19475        ],
19476        None,
19477        &mut cx,
19478    );
19479}
19480
19481#[gpui::test]
19482async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
19483    let (buffer_id, mut cx) = setup_indent_guides_editor(
19484        &"
19485        fn main() {
19486            let a = 1;
19487
19488            let c = 3;
19489
19490            if a == 3 {
19491                let b = 2;
19492            } else {
19493                let c = 3;
19494            }
19495        }"
19496        .unindent(),
19497        cx,
19498    )
19499    .await;
19500
19501    assert_indent_guides(
19502        1..11,
19503        vec![
19504            indent_guide(buffer_id, 1, 9, 0),
19505            indent_guide(buffer_id, 6, 6, 1),
19506            indent_guide(buffer_id, 8, 8, 1),
19507        ],
19508        None,
19509        &mut cx,
19510    );
19511}
19512
19513#[gpui::test]
19514async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
19515    let (buffer_id, mut cx) = setup_indent_guides_editor(
19516        &"
19517        fn main() {
19518            let a = 1;
19519
19520            let c = 3;
19521
19522            if a == 3 {
19523                let b = 2;
19524            } else {
19525                let c = 3;
19526            }
19527        }"
19528        .unindent(),
19529        cx,
19530    )
19531    .await;
19532
19533    assert_indent_guides(
19534        1..10,
19535        vec![
19536            indent_guide(buffer_id, 1, 9, 0),
19537            indent_guide(buffer_id, 6, 6, 1),
19538            indent_guide(buffer_id, 8, 8, 1),
19539        ],
19540        None,
19541        &mut cx,
19542    );
19543}
19544
19545#[gpui::test]
19546async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
19547    let (buffer_id, mut cx) = setup_indent_guides_editor(
19548        &"
19549        fn main() {
19550            if a {
19551                b(
19552                    c,
19553                    d,
19554                )
19555            } else {
19556                e(
19557                    f
19558                )
19559            }
19560        }"
19561        .unindent(),
19562        cx,
19563    )
19564    .await;
19565
19566    assert_indent_guides(
19567        0..11,
19568        vec![
19569            indent_guide(buffer_id, 1, 10, 0),
19570            indent_guide(buffer_id, 2, 5, 1),
19571            indent_guide(buffer_id, 7, 9, 1),
19572            indent_guide(buffer_id, 3, 4, 2),
19573            indent_guide(buffer_id, 8, 8, 2),
19574        ],
19575        None,
19576        &mut cx,
19577    );
19578
19579    cx.update_editor(|editor, window, cx| {
19580        editor.fold_at(MultiBufferRow(2), window, cx);
19581        assert_eq!(
19582            editor.display_text(cx),
19583            "
19584            fn main() {
19585                if a {
19586                    b(⋯
19587                    )
19588                } else {
19589                    e(
19590                        f
19591                    )
19592                }
19593            }"
19594            .unindent()
19595        );
19596    });
19597
19598    assert_indent_guides(
19599        0..11,
19600        vec![
19601            indent_guide(buffer_id, 1, 10, 0),
19602            indent_guide(buffer_id, 2, 5, 1),
19603            indent_guide(buffer_id, 7, 9, 1),
19604            indent_guide(buffer_id, 8, 8, 2),
19605        ],
19606        None,
19607        &mut cx,
19608    );
19609}
19610
19611#[gpui::test]
19612async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
19613    let (buffer_id, mut cx) = setup_indent_guides_editor(
19614        &"
19615        block1
19616            block2
19617                block3
19618                    block4
19619            block2
19620        block1
19621        block1"
19622            .unindent(),
19623        cx,
19624    )
19625    .await;
19626
19627    assert_indent_guides(
19628        1..10,
19629        vec![
19630            indent_guide(buffer_id, 1, 4, 0),
19631            indent_guide(buffer_id, 2, 3, 1),
19632            indent_guide(buffer_id, 3, 3, 2),
19633        ],
19634        None,
19635        &mut cx,
19636    );
19637}
19638
19639#[gpui::test]
19640async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
19641    let (buffer_id, mut cx) = setup_indent_guides_editor(
19642        &"
19643        block1
19644            block2
19645                block3
19646
19647        block1
19648        block1"
19649            .unindent(),
19650        cx,
19651    )
19652    .await;
19653
19654    assert_indent_guides(
19655        0..6,
19656        vec![
19657            indent_guide(buffer_id, 1, 2, 0),
19658            indent_guide(buffer_id, 2, 2, 1),
19659        ],
19660        None,
19661        &mut cx,
19662    );
19663}
19664
19665#[gpui::test]
19666async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
19667    let (buffer_id, mut cx) = setup_indent_guides_editor(
19668        &"
19669        function component() {
19670        \treturn (
19671        \t\t\t
19672        \t\t<div>
19673        \t\t\t<abc></abc>
19674        \t\t</div>
19675        \t)
19676        }"
19677        .unindent(),
19678        cx,
19679    )
19680    .await;
19681
19682    assert_indent_guides(
19683        0..8,
19684        vec![
19685            indent_guide(buffer_id, 1, 6, 0),
19686            indent_guide(buffer_id, 2, 5, 1),
19687            indent_guide(buffer_id, 4, 4, 2),
19688        ],
19689        None,
19690        &mut cx,
19691    );
19692}
19693
19694#[gpui::test]
19695async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
19696    let (buffer_id, mut cx) = setup_indent_guides_editor(
19697        &"
19698        function component() {
19699        \treturn (
19700        \t
19701        \t\t<div>
19702        \t\t\t<abc></abc>
19703        \t\t</div>
19704        \t)
19705        }"
19706        .unindent(),
19707        cx,
19708    )
19709    .await;
19710
19711    assert_indent_guides(
19712        0..8,
19713        vec![
19714            indent_guide(buffer_id, 1, 6, 0),
19715            indent_guide(buffer_id, 2, 5, 1),
19716            indent_guide(buffer_id, 4, 4, 2),
19717        ],
19718        None,
19719        &mut cx,
19720    );
19721}
19722
19723#[gpui::test]
19724async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
19725    let (buffer_id, mut cx) = setup_indent_guides_editor(
19726        &"
19727        block1
19728
19729
19730
19731            block2
19732        "
19733        .unindent(),
19734        cx,
19735    )
19736    .await;
19737
19738    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19739}
19740
19741#[gpui::test]
19742async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
19743    let (buffer_id, mut cx) = setup_indent_guides_editor(
19744        &"
19745        def a:
19746        \tb = 3
19747        \tif True:
19748        \t\tc = 4
19749        \t\td = 5
19750        \tprint(b)
19751        "
19752        .unindent(),
19753        cx,
19754    )
19755    .await;
19756
19757    assert_indent_guides(
19758        0..6,
19759        vec![
19760            indent_guide(buffer_id, 1, 5, 0),
19761            indent_guide(buffer_id, 3, 4, 1),
19762        ],
19763        None,
19764        &mut cx,
19765    );
19766}
19767
19768#[gpui::test]
19769async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
19770    let (buffer_id, mut cx) = setup_indent_guides_editor(
19771        &"
19772    fn main() {
19773        let a = 1;
19774    }"
19775        .unindent(),
19776        cx,
19777    )
19778    .await;
19779
19780    cx.update_editor(|editor, window, cx| {
19781        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19782            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19783        });
19784    });
19785
19786    assert_indent_guides(
19787        0..3,
19788        vec![indent_guide(buffer_id, 1, 1, 0)],
19789        Some(vec![0]),
19790        &mut cx,
19791    );
19792}
19793
19794#[gpui::test]
19795async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
19796    let (buffer_id, mut cx) = setup_indent_guides_editor(
19797        &"
19798    fn main() {
19799        if 1 == 2 {
19800            let a = 1;
19801        }
19802    }"
19803        .unindent(),
19804        cx,
19805    )
19806    .await;
19807
19808    cx.update_editor(|editor, window, cx| {
19809        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19810            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19811        });
19812    });
19813
19814    assert_indent_guides(
19815        0..4,
19816        vec![
19817            indent_guide(buffer_id, 1, 3, 0),
19818            indent_guide(buffer_id, 2, 2, 1),
19819        ],
19820        Some(vec![1]),
19821        &mut cx,
19822    );
19823
19824    cx.update_editor(|editor, window, cx| {
19825        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19826            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19827        });
19828    });
19829
19830    assert_indent_guides(
19831        0..4,
19832        vec![
19833            indent_guide(buffer_id, 1, 3, 0),
19834            indent_guide(buffer_id, 2, 2, 1),
19835        ],
19836        Some(vec![1]),
19837        &mut cx,
19838    );
19839
19840    cx.update_editor(|editor, window, cx| {
19841        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19842            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
19843        });
19844    });
19845
19846    assert_indent_guides(
19847        0..4,
19848        vec![
19849            indent_guide(buffer_id, 1, 3, 0),
19850            indent_guide(buffer_id, 2, 2, 1),
19851        ],
19852        Some(vec![0]),
19853        &mut cx,
19854    );
19855}
19856
19857#[gpui::test]
19858async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
19859    let (buffer_id, mut cx) = setup_indent_guides_editor(
19860        &"
19861    fn main() {
19862        let a = 1;
19863
19864        let b = 2;
19865    }"
19866        .unindent(),
19867        cx,
19868    )
19869    .await;
19870
19871    cx.update_editor(|editor, window, cx| {
19872        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19873            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
19874        });
19875    });
19876
19877    assert_indent_guides(
19878        0..5,
19879        vec![indent_guide(buffer_id, 1, 3, 0)],
19880        Some(vec![0]),
19881        &mut cx,
19882    );
19883}
19884
19885#[gpui::test]
19886async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
19887    let (buffer_id, mut cx) = setup_indent_guides_editor(
19888        &"
19889    def m:
19890        a = 1
19891        pass"
19892            .unindent(),
19893        cx,
19894    )
19895    .await;
19896
19897    cx.update_editor(|editor, window, cx| {
19898        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19899            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
19900        });
19901    });
19902
19903    assert_indent_guides(
19904        0..3,
19905        vec![indent_guide(buffer_id, 1, 2, 0)],
19906        Some(vec![0]),
19907        &mut cx,
19908    );
19909}
19910
19911#[gpui::test]
19912async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
19913    init_test(cx, |_| {});
19914    let mut cx = EditorTestContext::new(cx).await;
19915    let text = indoc! {
19916        "
19917        impl A {
19918            fn b() {
19919                0;
19920                3;
19921                5;
19922                6;
19923                7;
19924            }
19925        }
19926        "
19927    };
19928    let base_text = indoc! {
19929        "
19930        impl A {
19931            fn b() {
19932                0;
19933                1;
19934                2;
19935                3;
19936                4;
19937            }
19938            fn c() {
19939                5;
19940                6;
19941                7;
19942            }
19943        }
19944        "
19945    };
19946
19947    cx.update_editor(|editor, window, cx| {
19948        editor.set_text(text, window, cx);
19949
19950        editor.buffer().update(cx, |multibuffer, cx| {
19951            let buffer = multibuffer.as_singleton().unwrap();
19952            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
19953
19954            multibuffer.set_all_diff_hunks_expanded(cx);
19955            multibuffer.add_diff(diff, cx);
19956
19957            buffer.read(cx).remote_id()
19958        })
19959    });
19960    cx.run_until_parked();
19961
19962    cx.assert_state_with_diff(
19963        indoc! { "
19964          impl A {
19965              fn b() {
19966                  0;
19967        -         1;
19968        -         2;
19969                  3;
19970        -         4;
19971        -     }
19972        -     fn c() {
19973                  5;
19974                  6;
19975                  7;
19976              }
19977          }
19978          ˇ"
19979        }
19980        .to_string(),
19981    );
19982
19983    let mut actual_guides = cx.update_editor(|editor, window, cx| {
19984        editor
19985            .snapshot(window, cx)
19986            .buffer_snapshot
19987            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
19988            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
19989            .collect::<Vec<_>>()
19990    });
19991    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
19992    assert_eq!(
19993        actual_guides,
19994        vec![
19995            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
19996            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
19997            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
19998        ]
19999    );
20000}
20001
20002#[gpui::test]
20003async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20004    init_test(cx, |_| {});
20005    let mut cx = EditorTestContext::new(cx).await;
20006
20007    let diff_base = r#"
20008        a
20009        b
20010        c
20011        "#
20012    .unindent();
20013
20014    cx.set_state(
20015        &r#"
20016        ˇA
20017        b
20018        C
20019        "#
20020        .unindent(),
20021    );
20022    cx.set_head_text(&diff_base);
20023    cx.update_editor(|editor, window, cx| {
20024        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20025    });
20026    executor.run_until_parked();
20027
20028    let both_hunks_expanded = r#"
20029        - a
20030        + ˇA
20031          b
20032        - c
20033        + C
20034        "#
20035    .unindent();
20036
20037    cx.assert_state_with_diff(both_hunks_expanded.clone());
20038
20039    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20040        let snapshot = editor.snapshot(window, cx);
20041        let hunks = editor
20042            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20043            .collect::<Vec<_>>();
20044        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20045        let buffer_id = hunks[0].buffer_id;
20046        hunks
20047            .into_iter()
20048            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20049            .collect::<Vec<_>>()
20050    });
20051    assert_eq!(hunk_ranges.len(), 2);
20052
20053    cx.update_editor(|editor, _, cx| {
20054        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20055    });
20056    executor.run_until_parked();
20057
20058    let second_hunk_expanded = r#"
20059          ˇA
20060          b
20061        - c
20062        + C
20063        "#
20064    .unindent();
20065
20066    cx.assert_state_with_diff(second_hunk_expanded);
20067
20068    cx.update_editor(|editor, _, cx| {
20069        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20070    });
20071    executor.run_until_parked();
20072
20073    cx.assert_state_with_diff(both_hunks_expanded.clone());
20074
20075    cx.update_editor(|editor, _, cx| {
20076        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20077    });
20078    executor.run_until_parked();
20079
20080    let first_hunk_expanded = r#"
20081        - a
20082        + ˇA
20083          b
20084          C
20085        "#
20086    .unindent();
20087
20088    cx.assert_state_with_diff(first_hunk_expanded);
20089
20090    cx.update_editor(|editor, _, cx| {
20091        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20092    });
20093    executor.run_until_parked();
20094
20095    cx.assert_state_with_diff(both_hunks_expanded);
20096
20097    cx.set_state(
20098        &r#"
20099        ˇA
20100        b
20101        "#
20102        .unindent(),
20103    );
20104    cx.run_until_parked();
20105
20106    // TODO this cursor position seems bad
20107    cx.assert_state_with_diff(
20108        r#"
20109        - ˇa
20110        + A
20111          b
20112        "#
20113        .unindent(),
20114    );
20115
20116    cx.update_editor(|editor, window, cx| {
20117        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20118    });
20119
20120    cx.assert_state_with_diff(
20121        r#"
20122            - ˇa
20123            + A
20124              b
20125            - c
20126            "#
20127        .unindent(),
20128    );
20129
20130    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20131        let snapshot = editor.snapshot(window, cx);
20132        let hunks = editor
20133            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20134            .collect::<Vec<_>>();
20135        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20136        let buffer_id = hunks[0].buffer_id;
20137        hunks
20138            .into_iter()
20139            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20140            .collect::<Vec<_>>()
20141    });
20142    assert_eq!(hunk_ranges.len(), 2);
20143
20144    cx.update_editor(|editor, _, cx| {
20145        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20146    });
20147    executor.run_until_parked();
20148
20149    cx.assert_state_with_diff(
20150        r#"
20151        - ˇa
20152        + A
20153          b
20154        "#
20155        .unindent(),
20156    );
20157}
20158
20159#[gpui::test]
20160async fn test_toggle_deletion_hunk_at_start_of_file(
20161    executor: BackgroundExecutor,
20162    cx: &mut TestAppContext,
20163) {
20164    init_test(cx, |_| {});
20165    let mut cx = EditorTestContext::new(cx).await;
20166
20167    let diff_base = r#"
20168        a
20169        b
20170        c
20171        "#
20172    .unindent();
20173
20174    cx.set_state(
20175        &r#"
20176        ˇb
20177        c
20178        "#
20179        .unindent(),
20180    );
20181    cx.set_head_text(&diff_base);
20182    cx.update_editor(|editor, window, cx| {
20183        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20184    });
20185    executor.run_until_parked();
20186
20187    let hunk_expanded = r#"
20188        - a
20189          ˇb
20190          c
20191        "#
20192    .unindent();
20193
20194    cx.assert_state_with_diff(hunk_expanded.clone());
20195
20196    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20197        let snapshot = editor.snapshot(window, cx);
20198        let hunks = editor
20199            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20200            .collect::<Vec<_>>();
20201        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20202        let buffer_id = hunks[0].buffer_id;
20203        hunks
20204            .into_iter()
20205            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20206            .collect::<Vec<_>>()
20207    });
20208    assert_eq!(hunk_ranges.len(), 1);
20209
20210    cx.update_editor(|editor, _, cx| {
20211        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20212    });
20213    executor.run_until_parked();
20214
20215    let hunk_collapsed = r#"
20216          ˇb
20217          c
20218        "#
20219    .unindent();
20220
20221    cx.assert_state_with_diff(hunk_collapsed);
20222
20223    cx.update_editor(|editor, _, cx| {
20224        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20225    });
20226    executor.run_until_parked();
20227
20228    cx.assert_state_with_diff(hunk_expanded);
20229}
20230
20231#[gpui::test]
20232async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20233    init_test(cx, |_| {});
20234
20235    let fs = FakeFs::new(cx.executor());
20236    fs.insert_tree(
20237        path!("/test"),
20238        json!({
20239            ".git": {},
20240            "file-1": "ONE\n",
20241            "file-2": "TWO\n",
20242            "file-3": "THREE\n",
20243        }),
20244    )
20245    .await;
20246
20247    fs.set_head_for_repo(
20248        path!("/test/.git").as_ref(),
20249        &[
20250            ("file-1".into(), "one\n".into()),
20251            ("file-2".into(), "two\n".into()),
20252            ("file-3".into(), "three\n".into()),
20253        ],
20254        "deadbeef",
20255    );
20256
20257    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20258    let mut buffers = vec![];
20259    for i in 1..=3 {
20260        let buffer = project
20261            .update(cx, |project, cx| {
20262                let path = format!(path!("/test/file-{}"), i);
20263                project.open_local_buffer(path, cx)
20264            })
20265            .await
20266            .unwrap();
20267        buffers.push(buffer);
20268    }
20269
20270    let multibuffer = cx.new(|cx| {
20271        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20272        multibuffer.set_all_diff_hunks_expanded(cx);
20273        for buffer in &buffers {
20274            let snapshot = buffer.read(cx).snapshot();
20275            multibuffer.set_excerpts_for_path(
20276                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20277                buffer.clone(),
20278                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20279                2,
20280                cx,
20281            );
20282        }
20283        multibuffer
20284    });
20285
20286    let editor = cx.add_window(|window, cx| {
20287        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20288    });
20289    cx.run_until_parked();
20290
20291    let snapshot = editor
20292        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20293        .unwrap();
20294    let hunks = snapshot
20295        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20296        .map(|hunk| match hunk {
20297            DisplayDiffHunk::Unfolded {
20298                display_row_range, ..
20299            } => display_row_range,
20300            DisplayDiffHunk::Folded { .. } => unreachable!(),
20301        })
20302        .collect::<Vec<_>>();
20303    assert_eq!(
20304        hunks,
20305        [
20306            DisplayRow(2)..DisplayRow(4),
20307            DisplayRow(7)..DisplayRow(9),
20308            DisplayRow(12)..DisplayRow(14),
20309        ]
20310    );
20311}
20312
20313#[gpui::test]
20314async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
20315    init_test(cx, |_| {});
20316
20317    let mut cx = EditorTestContext::new(cx).await;
20318    cx.set_head_text(indoc! { "
20319        one
20320        two
20321        three
20322        four
20323        five
20324        "
20325    });
20326    cx.set_index_text(indoc! { "
20327        one
20328        two
20329        three
20330        four
20331        five
20332        "
20333    });
20334    cx.set_state(indoc! {"
20335        one
20336        TWO
20337        ˇTHREE
20338        FOUR
20339        five
20340    "});
20341    cx.run_until_parked();
20342    cx.update_editor(|editor, window, cx| {
20343        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20344    });
20345    cx.run_until_parked();
20346    cx.assert_index_text(Some(indoc! {"
20347        one
20348        TWO
20349        THREE
20350        FOUR
20351        five
20352    "}));
20353    cx.set_state(indoc! { "
20354        one
20355        TWO
20356        ˇTHREE-HUNDRED
20357        FOUR
20358        five
20359    "});
20360    cx.run_until_parked();
20361    cx.update_editor(|editor, window, cx| {
20362        let snapshot = editor.snapshot(window, cx);
20363        let hunks = editor
20364            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20365            .collect::<Vec<_>>();
20366        assert_eq!(hunks.len(), 1);
20367        assert_eq!(
20368            hunks[0].status(),
20369            DiffHunkStatus {
20370                kind: DiffHunkStatusKind::Modified,
20371                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
20372            }
20373        );
20374
20375        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20376    });
20377    cx.run_until_parked();
20378    cx.assert_index_text(Some(indoc! {"
20379        one
20380        TWO
20381        THREE-HUNDRED
20382        FOUR
20383        five
20384    "}));
20385}
20386
20387#[gpui::test]
20388fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
20389    init_test(cx, |_| {});
20390
20391    let editor = cx.add_window(|window, cx| {
20392        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
20393        build_editor(buffer, window, cx)
20394    });
20395
20396    let render_args = Arc::new(Mutex::new(None));
20397    let snapshot = editor
20398        .update(cx, |editor, window, cx| {
20399            let snapshot = editor.buffer().read(cx).snapshot(cx);
20400            let range =
20401                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
20402
20403            struct RenderArgs {
20404                row: MultiBufferRow,
20405                folded: bool,
20406                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
20407            }
20408
20409            let crease = Crease::inline(
20410                range,
20411                FoldPlaceholder::test(),
20412                {
20413                    let toggle_callback = render_args.clone();
20414                    move |row, folded, callback, _window, _cx| {
20415                        *toggle_callback.lock() = Some(RenderArgs {
20416                            row,
20417                            folded,
20418                            callback,
20419                        });
20420                        div()
20421                    }
20422                },
20423                |_row, _folded, _window, _cx| div(),
20424            );
20425
20426            editor.insert_creases(Some(crease), cx);
20427            let snapshot = editor.snapshot(window, cx);
20428            let _div =
20429                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
20430            snapshot
20431        })
20432        .unwrap();
20433
20434    let render_args = render_args.lock().take().unwrap();
20435    assert_eq!(render_args.row, MultiBufferRow(1));
20436    assert!(!render_args.folded);
20437    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
20438
20439    cx.update_window(*editor, |_, window, cx| {
20440        (render_args.callback)(true, window, cx)
20441    })
20442    .unwrap();
20443    let snapshot = editor
20444        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20445        .unwrap();
20446    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
20447
20448    cx.update_window(*editor, |_, window, cx| {
20449        (render_args.callback)(false, window, cx)
20450    })
20451    .unwrap();
20452    let snapshot = editor
20453        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20454        .unwrap();
20455    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
20456}
20457
20458#[gpui::test]
20459async fn test_input_text(cx: &mut TestAppContext) {
20460    init_test(cx, |_| {});
20461    let mut cx = EditorTestContext::new(cx).await;
20462
20463    cx.set_state(
20464        &r#"ˇone
20465        two
20466
20467        three
20468        fourˇ
20469        five
20470
20471        siˇx"#
20472            .unindent(),
20473    );
20474
20475    cx.dispatch_action(HandleInput(String::new()));
20476    cx.assert_editor_state(
20477        &r#"ˇone
20478        two
20479
20480        three
20481        fourˇ
20482        five
20483
20484        siˇx"#
20485            .unindent(),
20486    );
20487
20488    cx.dispatch_action(HandleInput("AAAA".to_string()));
20489    cx.assert_editor_state(
20490        &r#"AAAAˇone
20491        two
20492
20493        three
20494        fourAAAAˇ
20495        five
20496
20497        siAAAAˇx"#
20498            .unindent(),
20499    );
20500}
20501
20502#[gpui::test]
20503async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
20504    init_test(cx, |_| {});
20505
20506    let mut cx = EditorTestContext::new(cx).await;
20507    cx.set_state(
20508        r#"let foo = 1;
20509let foo = 2;
20510let foo = 3;
20511let fooˇ = 4;
20512let foo = 5;
20513let foo = 6;
20514let foo = 7;
20515let foo = 8;
20516let foo = 9;
20517let foo = 10;
20518let foo = 11;
20519let foo = 12;
20520let foo = 13;
20521let foo = 14;
20522let foo = 15;"#,
20523    );
20524
20525    cx.update_editor(|e, window, cx| {
20526        assert_eq!(
20527            e.next_scroll_position,
20528            NextScrollCursorCenterTopBottom::Center,
20529            "Default next scroll direction is center",
20530        );
20531
20532        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20533        assert_eq!(
20534            e.next_scroll_position,
20535            NextScrollCursorCenterTopBottom::Top,
20536            "After center, next scroll direction should be top",
20537        );
20538
20539        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20540        assert_eq!(
20541            e.next_scroll_position,
20542            NextScrollCursorCenterTopBottom::Bottom,
20543            "After top, next scroll direction should be bottom",
20544        );
20545
20546        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20547        assert_eq!(
20548            e.next_scroll_position,
20549            NextScrollCursorCenterTopBottom::Center,
20550            "After bottom, scrolling should start over",
20551        );
20552
20553        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
20554        assert_eq!(
20555            e.next_scroll_position,
20556            NextScrollCursorCenterTopBottom::Top,
20557            "Scrolling continues if retriggered fast enough"
20558        );
20559    });
20560
20561    cx.executor()
20562        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
20563    cx.executor().run_until_parked();
20564    cx.update_editor(|e, _, _| {
20565        assert_eq!(
20566            e.next_scroll_position,
20567            NextScrollCursorCenterTopBottom::Center,
20568            "If scrolling is not triggered fast enough, it should reset"
20569        );
20570    });
20571}
20572
20573#[gpui::test]
20574async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
20575    init_test(cx, |_| {});
20576    let mut cx = EditorLspTestContext::new_rust(
20577        lsp::ServerCapabilities {
20578            definition_provider: Some(lsp::OneOf::Left(true)),
20579            references_provider: Some(lsp::OneOf::Left(true)),
20580            ..lsp::ServerCapabilities::default()
20581        },
20582        cx,
20583    )
20584    .await;
20585
20586    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
20587        let go_to_definition = cx
20588            .lsp
20589            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20590                move |params, _| async move {
20591                    if empty_go_to_definition {
20592                        Ok(None)
20593                    } else {
20594                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
20595                            uri: params.text_document_position_params.text_document.uri,
20596                            range: lsp::Range::new(
20597                                lsp::Position::new(4, 3),
20598                                lsp::Position::new(4, 6),
20599                            ),
20600                        })))
20601                    }
20602                },
20603            );
20604        let references = cx
20605            .lsp
20606            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
20607                Ok(Some(vec![lsp::Location {
20608                    uri: params.text_document_position.text_document.uri,
20609                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
20610                }]))
20611            });
20612        (go_to_definition, references)
20613    };
20614
20615    cx.set_state(
20616        &r#"fn one() {
20617            let mut a = ˇtwo();
20618        }
20619
20620        fn two() {}"#
20621            .unindent(),
20622    );
20623    set_up_lsp_handlers(false, &mut cx);
20624    let navigated = cx
20625        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20626        .await
20627        .expect("Failed to navigate to definition");
20628    assert_eq!(
20629        navigated,
20630        Navigated::Yes,
20631        "Should have navigated to definition from the GetDefinition response"
20632    );
20633    cx.assert_editor_state(
20634        &r#"fn one() {
20635            let mut a = two();
20636        }
20637
20638        fn «twoˇ»() {}"#
20639            .unindent(),
20640    );
20641
20642    let editors = cx.update_workspace(|workspace, _, cx| {
20643        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20644    });
20645    cx.update_editor(|_, _, test_editor_cx| {
20646        assert_eq!(
20647            editors.len(),
20648            1,
20649            "Initially, only one, test, editor should be open in the workspace"
20650        );
20651        assert_eq!(
20652            test_editor_cx.entity(),
20653            editors.last().expect("Asserted len is 1").clone()
20654        );
20655    });
20656
20657    set_up_lsp_handlers(true, &mut cx);
20658    let navigated = cx
20659        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20660        .await
20661        .expect("Failed to navigate to lookup references");
20662    assert_eq!(
20663        navigated,
20664        Navigated::Yes,
20665        "Should have navigated to references as a fallback after empty GoToDefinition response"
20666    );
20667    // We should not change the selections in the existing file,
20668    // if opening another milti buffer with the references
20669    cx.assert_editor_state(
20670        &r#"fn one() {
20671            let mut a = two();
20672        }
20673
20674        fn «twoˇ»() {}"#
20675            .unindent(),
20676    );
20677    let editors = cx.update_workspace(|workspace, _, cx| {
20678        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20679    });
20680    cx.update_editor(|_, _, test_editor_cx| {
20681        assert_eq!(
20682            editors.len(),
20683            2,
20684            "After falling back to references search, we open a new editor with the results"
20685        );
20686        let references_fallback_text = editors
20687            .into_iter()
20688            .find(|new_editor| *new_editor != test_editor_cx.entity())
20689            .expect("Should have one non-test editor now")
20690            .read(test_editor_cx)
20691            .text(test_editor_cx);
20692        assert_eq!(
20693            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
20694            "Should use the range from the references response and not the GoToDefinition one"
20695        );
20696    });
20697}
20698
20699#[gpui::test]
20700async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
20701    init_test(cx, |_| {});
20702    cx.update(|cx| {
20703        let mut editor_settings = EditorSettings::get_global(cx).clone();
20704        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
20705        EditorSettings::override_global(editor_settings, cx);
20706    });
20707    let mut cx = EditorLspTestContext::new_rust(
20708        lsp::ServerCapabilities {
20709            definition_provider: Some(lsp::OneOf::Left(true)),
20710            references_provider: Some(lsp::OneOf::Left(true)),
20711            ..lsp::ServerCapabilities::default()
20712        },
20713        cx,
20714    )
20715    .await;
20716    let original_state = r#"fn one() {
20717        let mut a = ˇtwo();
20718    }
20719
20720    fn two() {}"#
20721        .unindent();
20722    cx.set_state(&original_state);
20723
20724    let mut go_to_definition = cx
20725        .lsp
20726        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
20727            move |_, _| async move { Ok(None) },
20728        );
20729    let _references = cx
20730        .lsp
20731        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
20732            panic!("Should not call for references with no go to definition fallback")
20733        });
20734
20735    let navigated = cx
20736        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
20737        .await
20738        .expect("Failed to navigate to lookup references");
20739    go_to_definition
20740        .next()
20741        .await
20742        .expect("Should have called the go_to_definition handler");
20743
20744    assert_eq!(
20745        navigated,
20746        Navigated::No,
20747        "Should have navigated to references as a fallback after empty GoToDefinition response"
20748    );
20749    cx.assert_editor_state(&original_state);
20750    let editors = cx.update_workspace(|workspace, _, cx| {
20751        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
20752    });
20753    cx.update_editor(|_, _, _| {
20754        assert_eq!(
20755            editors.len(),
20756            1,
20757            "After unsuccessful fallback, no other editor should have been opened"
20758        );
20759    });
20760}
20761
20762#[gpui::test]
20763async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
20764    init_test(cx, |_| {});
20765
20766    let language = Arc::new(Language::new(
20767        LanguageConfig::default(),
20768        Some(tree_sitter_rust::LANGUAGE.into()),
20769    ));
20770
20771    let text = r#"
20772        #[cfg(test)]
20773        mod tests() {
20774            #[test]
20775            fn runnable_1() {
20776                let a = 1;
20777            }
20778
20779            #[test]
20780            fn runnable_2() {
20781                let a = 1;
20782                let b = 2;
20783            }
20784        }
20785    "#
20786    .unindent();
20787
20788    let fs = FakeFs::new(cx.executor());
20789    fs.insert_file("/file.rs", Default::default()).await;
20790
20791    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20792    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20793    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20794    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
20795    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
20796
20797    let editor = cx.new_window_entity(|window, cx| {
20798        Editor::new(
20799            EditorMode::full(),
20800            multi_buffer,
20801            Some(project.clone()),
20802            window,
20803            cx,
20804        )
20805    });
20806
20807    editor.update_in(cx, |editor, window, cx| {
20808        let snapshot = editor.buffer().read(cx).snapshot(cx);
20809        editor.tasks.insert(
20810            (buffer.read(cx).remote_id(), 3),
20811            RunnableTasks {
20812                templates: vec![],
20813                offset: snapshot.anchor_before(43),
20814                column: 0,
20815                extra_variables: HashMap::default(),
20816                context_range: BufferOffset(43)..BufferOffset(85),
20817            },
20818        );
20819        editor.tasks.insert(
20820            (buffer.read(cx).remote_id(), 8),
20821            RunnableTasks {
20822                templates: vec![],
20823                offset: snapshot.anchor_before(86),
20824                column: 0,
20825                extra_variables: HashMap::default(),
20826                context_range: BufferOffset(86)..BufferOffset(191),
20827            },
20828        );
20829
20830        // Test finding task when cursor is inside function body
20831        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20832            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
20833        });
20834        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20835        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
20836
20837        // Test finding task when cursor is on function name
20838        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20839            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
20840        });
20841        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
20842        assert_eq!(row, 8, "Should find task when cursor is on function name");
20843    });
20844}
20845
20846#[gpui::test]
20847async fn test_folding_buffers(cx: &mut TestAppContext) {
20848    init_test(cx, |_| {});
20849
20850    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
20851    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
20852    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
20853
20854    let fs = FakeFs::new(cx.executor());
20855    fs.insert_tree(
20856        path!("/a"),
20857        json!({
20858            "first.rs": sample_text_1,
20859            "second.rs": sample_text_2,
20860            "third.rs": sample_text_3,
20861        }),
20862    )
20863    .await;
20864    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
20865    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20866    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20867    let worktree = project.update(cx, |project, cx| {
20868        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
20869        assert_eq!(worktrees.len(), 1);
20870        worktrees.pop().unwrap()
20871    });
20872    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
20873
20874    let buffer_1 = project
20875        .update(cx, |project, cx| {
20876            project.open_buffer((worktree_id, "first.rs"), cx)
20877        })
20878        .await
20879        .unwrap();
20880    let buffer_2 = project
20881        .update(cx, |project, cx| {
20882            project.open_buffer((worktree_id, "second.rs"), cx)
20883        })
20884        .await
20885        .unwrap();
20886    let buffer_3 = project
20887        .update(cx, |project, cx| {
20888            project.open_buffer((worktree_id, "third.rs"), cx)
20889        })
20890        .await
20891        .unwrap();
20892
20893    let multi_buffer = cx.new(|cx| {
20894        let mut multi_buffer = MultiBuffer::new(ReadWrite);
20895        multi_buffer.push_excerpts(
20896            buffer_1.clone(),
20897            [
20898                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20899                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20900                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20901            ],
20902            cx,
20903        );
20904        multi_buffer.push_excerpts(
20905            buffer_2.clone(),
20906            [
20907                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20908                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20909                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20910            ],
20911            cx,
20912        );
20913        multi_buffer.push_excerpts(
20914            buffer_3.clone(),
20915            [
20916                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20917                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20918                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20919            ],
20920            cx,
20921        );
20922        multi_buffer
20923    });
20924    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20925        Editor::new(
20926            EditorMode::full(),
20927            multi_buffer.clone(),
20928            Some(project.clone()),
20929            window,
20930            cx,
20931        )
20932    });
20933
20934    assert_eq!(
20935        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20936        "\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",
20937    );
20938
20939    multi_buffer_editor.update(cx, |editor, cx| {
20940        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
20941    });
20942    assert_eq!(
20943        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20944        "\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",
20945        "After folding the first buffer, its text should not be displayed"
20946    );
20947
20948    multi_buffer_editor.update(cx, |editor, cx| {
20949        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
20950    });
20951    assert_eq!(
20952        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20953        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
20954        "After folding the second buffer, its text should not be displayed"
20955    );
20956
20957    multi_buffer_editor.update(cx, |editor, cx| {
20958        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
20959    });
20960    assert_eq!(
20961        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20962        "\n\n\n\n\n",
20963        "After folding the third buffer, its text should not be displayed"
20964    );
20965
20966    // Emulate selection inside the fold logic, that should work
20967    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20968        editor
20969            .snapshot(window, cx)
20970            .next_line_boundary(Point::new(0, 4));
20971    });
20972
20973    multi_buffer_editor.update(cx, |editor, cx| {
20974        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
20975    });
20976    assert_eq!(
20977        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
20978        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
20979        "After unfolding the second buffer, its text should be displayed"
20980    );
20981
20982    // Typing inside of buffer 1 causes that buffer to be unfolded.
20983    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20984        assert_eq!(
20985            multi_buffer
20986                .read(cx)
20987                .snapshot(cx)
20988                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
20989                .collect::<String>(),
20990            "bbbb"
20991        );
20992        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
20993            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
20994        });
20995        editor.handle_input("B", window, cx);
20996    });
20997
20998    assert_eq!(
20999        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21000        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21001        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21002    );
21003
21004    multi_buffer_editor.update(cx, |editor, cx| {
21005        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21006    });
21007    assert_eq!(
21008        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21009        "\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",
21010        "After unfolding the all buffers, all original text should be displayed"
21011    );
21012}
21013
21014#[gpui::test]
21015async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21016    init_test(cx, |_| {});
21017
21018    let sample_text_1 = "1111\n2222\n3333".to_string();
21019    let sample_text_2 = "4444\n5555\n6666".to_string();
21020    let sample_text_3 = "7777\n8888\n9999".to_string();
21021
21022    let fs = FakeFs::new(cx.executor());
21023    fs.insert_tree(
21024        path!("/a"),
21025        json!({
21026            "first.rs": sample_text_1,
21027            "second.rs": sample_text_2,
21028            "third.rs": sample_text_3,
21029        }),
21030    )
21031    .await;
21032    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21033    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21034    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21035    let worktree = project.update(cx, |project, cx| {
21036        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21037        assert_eq!(worktrees.len(), 1);
21038        worktrees.pop().unwrap()
21039    });
21040    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21041
21042    let buffer_1 = project
21043        .update(cx, |project, cx| {
21044            project.open_buffer((worktree_id, "first.rs"), cx)
21045        })
21046        .await
21047        .unwrap();
21048    let buffer_2 = project
21049        .update(cx, |project, cx| {
21050            project.open_buffer((worktree_id, "second.rs"), cx)
21051        })
21052        .await
21053        .unwrap();
21054    let buffer_3 = project
21055        .update(cx, |project, cx| {
21056            project.open_buffer((worktree_id, "third.rs"), cx)
21057        })
21058        .await
21059        .unwrap();
21060
21061    let multi_buffer = cx.new(|cx| {
21062        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21063        multi_buffer.push_excerpts(
21064            buffer_1.clone(),
21065            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21066            cx,
21067        );
21068        multi_buffer.push_excerpts(
21069            buffer_2.clone(),
21070            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21071            cx,
21072        );
21073        multi_buffer.push_excerpts(
21074            buffer_3.clone(),
21075            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21076            cx,
21077        );
21078        multi_buffer
21079    });
21080
21081    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21082        Editor::new(
21083            EditorMode::full(),
21084            multi_buffer,
21085            Some(project.clone()),
21086            window,
21087            cx,
21088        )
21089    });
21090
21091    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21092    assert_eq!(
21093        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21094        full_text,
21095    );
21096
21097    multi_buffer_editor.update(cx, |editor, cx| {
21098        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21099    });
21100    assert_eq!(
21101        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21102        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21103        "After folding the first buffer, its text should not be displayed"
21104    );
21105
21106    multi_buffer_editor.update(cx, |editor, cx| {
21107        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21108    });
21109
21110    assert_eq!(
21111        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21112        "\n\n\n\n\n\n7777\n8888\n9999",
21113        "After folding the second buffer, its text should not be displayed"
21114    );
21115
21116    multi_buffer_editor.update(cx, |editor, cx| {
21117        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21118    });
21119    assert_eq!(
21120        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21121        "\n\n\n\n\n",
21122        "After folding the third buffer, its text should not be displayed"
21123    );
21124
21125    multi_buffer_editor.update(cx, |editor, cx| {
21126        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21127    });
21128    assert_eq!(
21129        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21130        "\n\n\n\n4444\n5555\n6666\n\n",
21131        "After unfolding the second buffer, its text should be displayed"
21132    );
21133
21134    multi_buffer_editor.update(cx, |editor, cx| {
21135        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21136    });
21137    assert_eq!(
21138        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21139        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21140        "After unfolding the first buffer, its text should be displayed"
21141    );
21142
21143    multi_buffer_editor.update(cx, |editor, cx| {
21144        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21145    });
21146    assert_eq!(
21147        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21148        full_text,
21149        "After unfolding all buffers, all original text should be displayed"
21150    );
21151}
21152
21153#[gpui::test]
21154async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21155    init_test(cx, |_| {});
21156
21157    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21158
21159    let fs = FakeFs::new(cx.executor());
21160    fs.insert_tree(
21161        path!("/a"),
21162        json!({
21163            "main.rs": sample_text,
21164        }),
21165    )
21166    .await;
21167    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21168    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21169    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21170    let worktree = project.update(cx, |project, cx| {
21171        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21172        assert_eq!(worktrees.len(), 1);
21173        worktrees.pop().unwrap()
21174    });
21175    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21176
21177    let buffer_1 = project
21178        .update(cx, |project, cx| {
21179            project.open_buffer((worktree_id, "main.rs"), cx)
21180        })
21181        .await
21182        .unwrap();
21183
21184    let multi_buffer = cx.new(|cx| {
21185        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21186        multi_buffer.push_excerpts(
21187            buffer_1.clone(),
21188            [ExcerptRange::new(
21189                Point::new(0, 0)
21190                    ..Point::new(
21191                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
21192                        0,
21193                    ),
21194            )],
21195            cx,
21196        );
21197        multi_buffer
21198    });
21199    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21200        Editor::new(
21201            EditorMode::full(),
21202            multi_buffer,
21203            Some(project.clone()),
21204            window,
21205            cx,
21206        )
21207    });
21208
21209    let selection_range = Point::new(1, 0)..Point::new(2, 0);
21210    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21211        enum TestHighlight {}
21212        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
21213        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
21214        editor.highlight_text::<TestHighlight>(
21215            vec![highlight_range.clone()],
21216            HighlightStyle::color(Hsla::green()),
21217            cx,
21218        );
21219        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21220            s.select_ranges(Some(highlight_range))
21221        });
21222    });
21223
21224    let full_text = format!("\n\n{sample_text}");
21225    assert_eq!(
21226        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21227        full_text,
21228    );
21229}
21230
21231#[gpui::test]
21232async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
21233    init_test(cx, |_| {});
21234    cx.update(|cx| {
21235        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
21236            "keymaps/default-linux.json",
21237            cx,
21238        )
21239        .unwrap();
21240        cx.bind_keys(default_key_bindings);
21241    });
21242
21243    let (editor, cx) = cx.add_window_view(|window, cx| {
21244        let multi_buffer = MultiBuffer::build_multi(
21245            [
21246                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
21247                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
21248                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
21249                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
21250            ],
21251            cx,
21252        );
21253        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
21254
21255        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
21256        // fold all but the second buffer, so that we test navigating between two
21257        // adjacent folded buffers, as well as folded buffers at the start and
21258        // end the multibuffer
21259        editor.fold_buffer(buffer_ids[0], cx);
21260        editor.fold_buffer(buffer_ids[2], cx);
21261        editor.fold_buffer(buffer_ids[3], cx);
21262
21263        editor
21264    });
21265    cx.simulate_resize(size(px(1000.), px(1000.)));
21266
21267    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
21268    cx.assert_excerpts_with_selections(indoc! {"
21269        [EXCERPT]
21270        ˇ[FOLDED]
21271        [EXCERPT]
21272        a1
21273        b1
21274        [EXCERPT]
21275        [FOLDED]
21276        [EXCERPT]
21277        [FOLDED]
21278        "
21279    });
21280    cx.simulate_keystroke("down");
21281    cx.assert_excerpts_with_selections(indoc! {"
21282        [EXCERPT]
21283        [FOLDED]
21284        [EXCERPT]
21285        ˇa1
21286        b1
21287        [EXCERPT]
21288        [FOLDED]
21289        [EXCERPT]
21290        [FOLDED]
21291        "
21292    });
21293    cx.simulate_keystroke("down");
21294    cx.assert_excerpts_with_selections(indoc! {"
21295        [EXCERPT]
21296        [FOLDED]
21297        [EXCERPT]
21298        a1
21299        ˇb1
21300        [EXCERPT]
21301        [FOLDED]
21302        [EXCERPT]
21303        [FOLDED]
21304        "
21305    });
21306    cx.simulate_keystroke("down");
21307    cx.assert_excerpts_with_selections(indoc! {"
21308        [EXCERPT]
21309        [FOLDED]
21310        [EXCERPT]
21311        a1
21312        b1
21313        ˇ[EXCERPT]
21314        [FOLDED]
21315        [EXCERPT]
21316        [FOLDED]
21317        "
21318    });
21319    cx.simulate_keystroke("down");
21320    cx.assert_excerpts_with_selections(indoc! {"
21321        [EXCERPT]
21322        [FOLDED]
21323        [EXCERPT]
21324        a1
21325        b1
21326        [EXCERPT]
21327        ˇ[FOLDED]
21328        [EXCERPT]
21329        [FOLDED]
21330        "
21331    });
21332    for _ in 0..5 {
21333        cx.simulate_keystroke("down");
21334        cx.assert_excerpts_with_selections(indoc! {"
21335            [EXCERPT]
21336            [FOLDED]
21337            [EXCERPT]
21338            a1
21339            b1
21340            [EXCERPT]
21341            [FOLDED]
21342            [EXCERPT]
21343            ˇ[FOLDED]
21344            "
21345        });
21346    }
21347
21348    cx.simulate_keystroke("up");
21349    cx.assert_excerpts_with_selections(indoc! {"
21350        [EXCERPT]
21351        [FOLDED]
21352        [EXCERPT]
21353        a1
21354        b1
21355        [EXCERPT]
21356        ˇ[FOLDED]
21357        [EXCERPT]
21358        [FOLDED]
21359        "
21360    });
21361    cx.simulate_keystroke("up");
21362    cx.assert_excerpts_with_selections(indoc! {"
21363        [EXCERPT]
21364        [FOLDED]
21365        [EXCERPT]
21366        a1
21367        b1
21368        ˇ[EXCERPT]
21369        [FOLDED]
21370        [EXCERPT]
21371        [FOLDED]
21372        "
21373    });
21374    cx.simulate_keystroke("up");
21375    cx.assert_excerpts_with_selections(indoc! {"
21376        [EXCERPT]
21377        [FOLDED]
21378        [EXCERPT]
21379        a1
21380        ˇb1
21381        [EXCERPT]
21382        [FOLDED]
21383        [EXCERPT]
21384        [FOLDED]
21385        "
21386    });
21387    cx.simulate_keystroke("up");
21388    cx.assert_excerpts_with_selections(indoc! {"
21389        [EXCERPT]
21390        [FOLDED]
21391        [EXCERPT]
21392        ˇa1
21393        b1
21394        [EXCERPT]
21395        [FOLDED]
21396        [EXCERPT]
21397        [FOLDED]
21398        "
21399    });
21400    for _ in 0..5 {
21401        cx.simulate_keystroke("up");
21402        cx.assert_excerpts_with_selections(indoc! {"
21403            [EXCERPT]
21404            ˇ[FOLDED]
21405            [EXCERPT]
21406            a1
21407            b1
21408            [EXCERPT]
21409            [FOLDED]
21410            [EXCERPT]
21411            [FOLDED]
21412            "
21413        });
21414    }
21415}
21416
21417#[gpui::test]
21418async fn test_edit_prediction_text(cx: &mut TestAppContext) {
21419    init_test(cx, |_| {});
21420
21421    // Simple insertion
21422    assert_highlighted_edits(
21423        "Hello, world!",
21424        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
21425        true,
21426        cx,
21427        |highlighted_edits, cx| {
21428            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
21429            assert_eq!(highlighted_edits.highlights.len(), 1);
21430            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
21431            assert_eq!(
21432                highlighted_edits.highlights[0].1.background_color,
21433                Some(cx.theme().status().created_background)
21434            );
21435        },
21436    )
21437    .await;
21438
21439    // Replacement
21440    assert_highlighted_edits(
21441        "This is a test.",
21442        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
21443        false,
21444        cx,
21445        |highlighted_edits, cx| {
21446            assert_eq!(highlighted_edits.text, "That is a test.");
21447            assert_eq!(highlighted_edits.highlights.len(), 1);
21448            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
21449            assert_eq!(
21450                highlighted_edits.highlights[0].1.background_color,
21451                Some(cx.theme().status().created_background)
21452            );
21453        },
21454    )
21455    .await;
21456
21457    // Multiple edits
21458    assert_highlighted_edits(
21459        "Hello, world!",
21460        vec![
21461            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
21462            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
21463        ],
21464        false,
21465        cx,
21466        |highlighted_edits, cx| {
21467            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
21468            assert_eq!(highlighted_edits.highlights.len(), 2);
21469            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
21470            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
21471            assert_eq!(
21472                highlighted_edits.highlights[0].1.background_color,
21473                Some(cx.theme().status().created_background)
21474            );
21475            assert_eq!(
21476                highlighted_edits.highlights[1].1.background_color,
21477                Some(cx.theme().status().created_background)
21478            );
21479        },
21480    )
21481    .await;
21482
21483    // Multiple lines with edits
21484    assert_highlighted_edits(
21485        "First line\nSecond line\nThird line\nFourth line",
21486        vec![
21487            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
21488            (
21489                Point::new(2, 0)..Point::new(2, 10),
21490                "New third line".to_string(),
21491            ),
21492            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
21493        ],
21494        false,
21495        cx,
21496        |highlighted_edits, cx| {
21497            assert_eq!(
21498                highlighted_edits.text,
21499                "Second modified\nNew third line\nFourth updated line"
21500            );
21501            assert_eq!(highlighted_edits.highlights.len(), 3);
21502            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
21503            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
21504            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
21505            for highlight in &highlighted_edits.highlights {
21506                assert_eq!(
21507                    highlight.1.background_color,
21508                    Some(cx.theme().status().created_background)
21509                );
21510            }
21511        },
21512    )
21513    .await;
21514}
21515
21516#[gpui::test]
21517async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
21518    init_test(cx, |_| {});
21519
21520    // Deletion
21521    assert_highlighted_edits(
21522        "Hello, world!",
21523        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
21524        true,
21525        cx,
21526        |highlighted_edits, cx| {
21527            assert_eq!(highlighted_edits.text, "Hello, world!");
21528            assert_eq!(highlighted_edits.highlights.len(), 1);
21529            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
21530            assert_eq!(
21531                highlighted_edits.highlights[0].1.background_color,
21532                Some(cx.theme().status().deleted_background)
21533            );
21534        },
21535    )
21536    .await;
21537
21538    // Insertion
21539    assert_highlighted_edits(
21540        "Hello, world!",
21541        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
21542        true,
21543        cx,
21544        |highlighted_edits, cx| {
21545            assert_eq!(highlighted_edits.highlights.len(), 1);
21546            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
21547            assert_eq!(
21548                highlighted_edits.highlights[0].1.background_color,
21549                Some(cx.theme().status().created_background)
21550            );
21551        },
21552    )
21553    .await;
21554}
21555
21556async fn assert_highlighted_edits(
21557    text: &str,
21558    edits: Vec<(Range<Point>, String)>,
21559    include_deletions: bool,
21560    cx: &mut TestAppContext,
21561    assertion_fn: impl Fn(HighlightedText, &App),
21562) {
21563    let window = cx.add_window(|window, cx| {
21564        let buffer = MultiBuffer::build_simple(text, cx);
21565        Editor::new(EditorMode::full(), buffer, None, window, cx)
21566    });
21567    let cx = &mut VisualTestContext::from_window(*window, cx);
21568
21569    let (buffer, snapshot) = window
21570        .update(cx, |editor, _window, cx| {
21571            (
21572                editor.buffer().clone(),
21573                editor.buffer().read(cx).snapshot(cx),
21574            )
21575        })
21576        .unwrap();
21577
21578    let edits = edits
21579        .into_iter()
21580        .map(|(range, edit)| {
21581            (
21582                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
21583                edit,
21584            )
21585        })
21586        .collect::<Vec<_>>();
21587
21588    let text_anchor_edits = edits
21589        .clone()
21590        .into_iter()
21591        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
21592        .collect::<Vec<_>>();
21593
21594    let edit_preview = window
21595        .update(cx, |_, _window, cx| {
21596            buffer
21597                .read(cx)
21598                .as_singleton()
21599                .unwrap()
21600                .read(cx)
21601                .preview_edits(text_anchor_edits.into(), cx)
21602        })
21603        .unwrap()
21604        .await;
21605
21606    cx.update(|_window, cx| {
21607        let highlighted_edits = edit_prediction_edit_text(
21608            snapshot.as_singleton().unwrap().2,
21609            &edits,
21610            &edit_preview,
21611            include_deletions,
21612            cx,
21613        );
21614        assertion_fn(highlighted_edits, cx)
21615    });
21616}
21617
21618#[track_caller]
21619fn assert_breakpoint(
21620    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
21621    path: &Arc<Path>,
21622    expected: Vec<(u32, Breakpoint)>,
21623) {
21624    if expected.is_empty() {
21625        assert!(!breakpoints.contains_key(path), "{}", path.display());
21626    } else {
21627        let mut breakpoint = breakpoints
21628            .get(path)
21629            .unwrap()
21630            .iter()
21631            .map(|breakpoint| {
21632                (
21633                    breakpoint.row,
21634                    Breakpoint {
21635                        message: breakpoint.message.clone(),
21636                        state: breakpoint.state,
21637                        condition: breakpoint.condition.clone(),
21638                        hit_condition: breakpoint.hit_condition.clone(),
21639                    },
21640                )
21641            })
21642            .collect::<Vec<_>>();
21643
21644        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
21645
21646        assert_eq!(expected, breakpoint);
21647    }
21648}
21649
21650fn add_log_breakpoint_at_cursor(
21651    editor: &mut Editor,
21652    log_message: &str,
21653    window: &mut Window,
21654    cx: &mut Context<Editor>,
21655) {
21656    let (anchor, bp) = editor
21657        .breakpoints_at_cursors(window, cx)
21658        .first()
21659        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
21660        .unwrap_or_else(|| {
21661            let cursor_position: Point = editor.selections.newest(cx).head();
21662
21663            let breakpoint_position = editor
21664                .snapshot(window, cx)
21665                .display_snapshot
21666                .buffer_snapshot
21667                .anchor_before(Point::new(cursor_position.row, 0));
21668
21669            (breakpoint_position, Breakpoint::new_log(log_message))
21670        });
21671
21672    editor.edit_breakpoint_at_anchor(
21673        anchor,
21674        bp,
21675        BreakpointEditAction::EditLogMessage(log_message.into()),
21676        cx,
21677    );
21678}
21679
21680#[gpui::test]
21681async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
21682    init_test(cx, |_| {});
21683
21684    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21685    let fs = FakeFs::new(cx.executor());
21686    fs.insert_tree(
21687        path!("/a"),
21688        json!({
21689            "main.rs": sample_text,
21690        }),
21691    )
21692    .await;
21693    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21694    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21695    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21696
21697    let fs = FakeFs::new(cx.executor());
21698    fs.insert_tree(
21699        path!("/a"),
21700        json!({
21701            "main.rs": sample_text,
21702        }),
21703    )
21704    .await;
21705    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21706    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21707    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21708    let worktree_id = workspace
21709        .update(cx, |workspace, _window, cx| {
21710            workspace.project().update(cx, |project, cx| {
21711                project.worktrees(cx).next().unwrap().read(cx).id()
21712            })
21713        })
21714        .unwrap();
21715
21716    let buffer = project
21717        .update(cx, |project, cx| {
21718            project.open_buffer((worktree_id, "main.rs"), cx)
21719        })
21720        .await
21721        .unwrap();
21722
21723    let (editor, cx) = cx.add_window_view(|window, cx| {
21724        Editor::new(
21725            EditorMode::full(),
21726            MultiBuffer::build_from_buffer(buffer, cx),
21727            Some(project.clone()),
21728            window,
21729            cx,
21730        )
21731    });
21732
21733    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21734    let abs_path = project.read_with(cx, |project, cx| {
21735        project
21736            .absolute_path(&project_path, cx)
21737            .map(Arc::from)
21738            .unwrap()
21739    });
21740
21741    // assert we can add breakpoint on the first line
21742    editor.update_in(cx, |editor, window, cx| {
21743        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21744        editor.move_to_end(&MoveToEnd, window, cx);
21745        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21746    });
21747
21748    let breakpoints = editor.update(cx, |editor, cx| {
21749        editor
21750            .breakpoint_store()
21751            .as_ref()
21752            .unwrap()
21753            .read(cx)
21754            .all_source_breakpoints(cx)
21755    });
21756
21757    assert_eq!(1, breakpoints.len());
21758    assert_breakpoint(
21759        &breakpoints,
21760        &abs_path,
21761        vec![
21762            (0, Breakpoint::new_standard()),
21763            (3, Breakpoint::new_standard()),
21764        ],
21765    );
21766
21767    editor.update_in(cx, |editor, window, cx| {
21768        editor.move_to_beginning(&MoveToBeginning, window, cx);
21769        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21770    });
21771
21772    let breakpoints = editor.update(cx, |editor, cx| {
21773        editor
21774            .breakpoint_store()
21775            .as_ref()
21776            .unwrap()
21777            .read(cx)
21778            .all_source_breakpoints(cx)
21779    });
21780
21781    assert_eq!(1, breakpoints.len());
21782    assert_breakpoint(
21783        &breakpoints,
21784        &abs_path,
21785        vec![(3, Breakpoint::new_standard())],
21786    );
21787
21788    editor.update_in(cx, |editor, window, cx| {
21789        editor.move_to_end(&MoveToEnd, window, cx);
21790        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21791    });
21792
21793    let breakpoints = editor.update(cx, |editor, cx| {
21794        editor
21795            .breakpoint_store()
21796            .as_ref()
21797            .unwrap()
21798            .read(cx)
21799            .all_source_breakpoints(cx)
21800    });
21801
21802    assert_eq!(0, breakpoints.len());
21803    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21804}
21805
21806#[gpui::test]
21807async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
21808    init_test(cx, |_| {});
21809
21810    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21811
21812    let fs = FakeFs::new(cx.executor());
21813    fs.insert_tree(
21814        path!("/a"),
21815        json!({
21816            "main.rs": sample_text,
21817        }),
21818    )
21819    .await;
21820    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21821    let (workspace, cx) =
21822        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
21823
21824    let worktree_id = workspace.update(cx, |workspace, cx| {
21825        workspace.project().update(cx, |project, cx| {
21826            project.worktrees(cx).next().unwrap().read(cx).id()
21827        })
21828    });
21829
21830    let buffer = project
21831        .update(cx, |project, cx| {
21832            project.open_buffer((worktree_id, "main.rs"), cx)
21833        })
21834        .await
21835        .unwrap();
21836
21837    let (editor, cx) = cx.add_window_view(|window, cx| {
21838        Editor::new(
21839            EditorMode::full(),
21840            MultiBuffer::build_from_buffer(buffer, cx),
21841            Some(project.clone()),
21842            window,
21843            cx,
21844        )
21845    });
21846
21847    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
21848    let abs_path = project.read_with(cx, |project, cx| {
21849        project
21850            .absolute_path(&project_path, cx)
21851            .map(Arc::from)
21852            .unwrap()
21853    });
21854
21855    editor.update_in(cx, |editor, window, cx| {
21856        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21857    });
21858
21859    let breakpoints = editor.update(cx, |editor, cx| {
21860        editor
21861            .breakpoint_store()
21862            .as_ref()
21863            .unwrap()
21864            .read(cx)
21865            .all_source_breakpoints(cx)
21866    });
21867
21868    assert_breakpoint(
21869        &breakpoints,
21870        &abs_path,
21871        vec![(0, Breakpoint::new_log("hello world"))],
21872    );
21873
21874    // Removing a log message from a log breakpoint should remove it
21875    editor.update_in(cx, |editor, window, cx| {
21876        add_log_breakpoint_at_cursor(editor, "", window, cx);
21877    });
21878
21879    let breakpoints = editor.update(cx, |editor, cx| {
21880        editor
21881            .breakpoint_store()
21882            .as_ref()
21883            .unwrap()
21884            .read(cx)
21885            .all_source_breakpoints(cx)
21886    });
21887
21888    assert_breakpoint(&breakpoints, &abs_path, vec![]);
21889
21890    editor.update_in(cx, |editor, window, cx| {
21891        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21892        editor.move_to_end(&MoveToEnd, window, cx);
21893        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
21894        // Not adding a log message to a standard breakpoint shouldn't remove it
21895        add_log_breakpoint_at_cursor(editor, "", window, cx);
21896    });
21897
21898    let breakpoints = editor.update(cx, |editor, cx| {
21899        editor
21900            .breakpoint_store()
21901            .as_ref()
21902            .unwrap()
21903            .read(cx)
21904            .all_source_breakpoints(cx)
21905    });
21906
21907    assert_breakpoint(
21908        &breakpoints,
21909        &abs_path,
21910        vec![
21911            (0, Breakpoint::new_standard()),
21912            (3, Breakpoint::new_standard()),
21913        ],
21914    );
21915
21916    editor.update_in(cx, |editor, window, cx| {
21917        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
21918    });
21919
21920    let breakpoints = editor.update(cx, |editor, cx| {
21921        editor
21922            .breakpoint_store()
21923            .as_ref()
21924            .unwrap()
21925            .read(cx)
21926            .all_source_breakpoints(cx)
21927    });
21928
21929    assert_breakpoint(
21930        &breakpoints,
21931        &abs_path,
21932        vec![
21933            (0, Breakpoint::new_standard()),
21934            (3, Breakpoint::new_log("hello world")),
21935        ],
21936    );
21937
21938    editor.update_in(cx, |editor, window, cx| {
21939        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
21940    });
21941
21942    let breakpoints = editor.update(cx, |editor, cx| {
21943        editor
21944            .breakpoint_store()
21945            .as_ref()
21946            .unwrap()
21947            .read(cx)
21948            .all_source_breakpoints(cx)
21949    });
21950
21951    assert_breakpoint(
21952        &breakpoints,
21953        &abs_path,
21954        vec![
21955            (0, Breakpoint::new_standard()),
21956            (3, Breakpoint::new_log("hello Earth!!")),
21957        ],
21958    );
21959}
21960
21961/// This also tests that Editor::breakpoint_at_cursor_head is working properly
21962/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
21963/// or when breakpoints were placed out of order. This tests for a regression too
21964#[gpui::test]
21965async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
21966    init_test(cx, |_| {});
21967
21968    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
21969    let fs = FakeFs::new(cx.executor());
21970    fs.insert_tree(
21971        path!("/a"),
21972        json!({
21973            "main.rs": sample_text,
21974        }),
21975    )
21976    .await;
21977    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21978    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21979    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21980
21981    let fs = FakeFs::new(cx.executor());
21982    fs.insert_tree(
21983        path!("/a"),
21984        json!({
21985            "main.rs": sample_text,
21986        }),
21987    )
21988    .await;
21989    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21990    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21991    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21992    let worktree_id = workspace
21993        .update(cx, |workspace, _window, cx| {
21994            workspace.project().update(cx, |project, cx| {
21995                project.worktrees(cx).next().unwrap().read(cx).id()
21996            })
21997        })
21998        .unwrap();
21999
22000    let buffer = project
22001        .update(cx, |project, cx| {
22002            project.open_buffer((worktree_id, "main.rs"), cx)
22003        })
22004        .await
22005        .unwrap();
22006
22007    let (editor, cx) = cx.add_window_view(|window, cx| {
22008        Editor::new(
22009            EditorMode::full(),
22010            MultiBuffer::build_from_buffer(buffer, cx),
22011            Some(project.clone()),
22012            window,
22013            cx,
22014        )
22015    });
22016
22017    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22018    let abs_path = project.read_with(cx, |project, cx| {
22019        project
22020            .absolute_path(&project_path, cx)
22021            .map(Arc::from)
22022            .unwrap()
22023    });
22024
22025    // assert we can add breakpoint on the first line
22026    editor.update_in(cx, |editor, window, cx| {
22027        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22028        editor.move_to_end(&MoveToEnd, window, cx);
22029        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22030        editor.move_up(&MoveUp, window, cx);
22031        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22032    });
22033
22034    let breakpoints = editor.update(cx, |editor, cx| {
22035        editor
22036            .breakpoint_store()
22037            .as_ref()
22038            .unwrap()
22039            .read(cx)
22040            .all_source_breakpoints(cx)
22041    });
22042
22043    assert_eq!(1, breakpoints.len());
22044    assert_breakpoint(
22045        &breakpoints,
22046        &abs_path,
22047        vec![
22048            (0, Breakpoint::new_standard()),
22049            (2, Breakpoint::new_standard()),
22050            (3, Breakpoint::new_standard()),
22051        ],
22052    );
22053
22054    editor.update_in(cx, |editor, window, cx| {
22055        editor.move_to_beginning(&MoveToBeginning, window, cx);
22056        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22057        editor.move_to_end(&MoveToEnd, window, cx);
22058        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22059        // Disabling a breakpoint that doesn't exist should do nothing
22060        editor.move_up(&MoveUp, window, cx);
22061        editor.move_up(&MoveUp, window, cx);
22062        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22063    });
22064
22065    let breakpoints = editor.update(cx, |editor, cx| {
22066        editor
22067            .breakpoint_store()
22068            .as_ref()
22069            .unwrap()
22070            .read(cx)
22071            .all_source_breakpoints(cx)
22072    });
22073
22074    let disable_breakpoint = {
22075        let mut bp = Breakpoint::new_standard();
22076        bp.state = BreakpointState::Disabled;
22077        bp
22078    };
22079
22080    assert_eq!(1, breakpoints.len());
22081    assert_breakpoint(
22082        &breakpoints,
22083        &abs_path,
22084        vec![
22085            (0, disable_breakpoint.clone()),
22086            (2, Breakpoint::new_standard()),
22087            (3, disable_breakpoint.clone()),
22088        ],
22089    );
22090
22091    editor.update_in(cx, |editor, window, cx| {
22092        editor.move_to_beginning(&MoveToBeginning, window, cx);
22093        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22094        editor.move_to_end(&MoveToEnd, window, cx);
22095        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22096        editor.move_up(&MoveUp, window, cx);
22097        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22098    });
22099
22100    let breakpoints = editor.update(cx, |editor, cx| {
22101        editor
22102            .breakpoint_store()
22103            .as_ref()
22104            .unwrap()
22105            .read(cx)
22106            .all_source_breakpoints(cx)
22107    });
22108
22109    assert_eq!(1, breakpoints.len());
22110    assert_breakpoint(
22111        &breakpoints,
22112        &abs_path,
22113        vec![
22114            (0, Breakpoint::new_standard()),
22115            (2, disable_breakpoint),
22116            (3, Breakpoint::new_standard()),
22117        ],
22118    );
22119}
22120
22121#[gpui::test]
22122async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22123    init_test(cx, |_| {});
22124    let capabilities = lsp::ServerCapabilities {
22125        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22126            prepare_provider: Some(true),
22127            work_done_progress_options: Default::default(),
22128        })),
22129        ..Default::default()
22130    };
22131    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22132
22133    cx.set_state(indoc! {"
22134        struct Fˇoo {}
22135    "});
22136
22137    cx.update_editor(|editor, _, cx| {
22138        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22139        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22140        editor.highlight_background::<DocumentHighlightRead>(
22141            &[highlight_range],
22142            |theme| theme.colors().editor_document_highlight_read_background,
22143            cx,
22144        );
22145    });
22146
22147    let mut prepare_rename_handler = cx
22148        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22149            move |_, _, _| async move {
22150                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22151                    start: lsp::Position {
22152                        line: 0,
22153                        character: 7,
22154                    },
22155                    end: lsp::Position {
22156                        line: 0,
22157                        character: 10,
22158                    },
22159                })))
22160            },
22161        );
22162    let prepare_rename_task = cx
22163        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22164        .expect("Prepare rename was not started");
22165    prepare_rename_handler.next().await.unwrap();
22166    prepare_rename_task.await.expect("Prepare rename failed");
22167
22168    let mut rename_handler =
22169        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22170            let edit = lsp::TextEdit {
22171                range: lsp::Range {
22172                    start: lsp::Position {
22173                        line: 0,
22174                        character: 7,
22175                    },
22176                    end: lsp::Position {
22177                        line: 0,
22178                        character: 10,
22179                    },
22180                },
22181                new_text: "FooRenamed".to_string(),
22182            };
22183            Ok(Some(lsp::WorkspaceEdit::new(
22184                // Specify the same edit twice
22185                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
22186            )))
22187        });
22188    let rename_task = cx
22189        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22190        .expect("Confirm rename was not started");
22191    rename_handler.next().await.unwrap();
22192    rename_task.await.expect("Confirm rename failed");
22193    cx.run_until_parked();
22194
22195    // Despite two edits, only one is actually applied as those are identical
22196    cx.assert_editor_state(indoc! {"
22197        struct FooRenamedˇ {}
22198    "});
22199}
22200
22201#[gpui::test]
22202async fn test_rename_without_prepare(cx: &mut TestAppContext) {
22203    init_test(cx, |_| {});
22204    // These capabilities indicate that the server does not support prepare rename.
22205    let capabilities = lsp::ServerCapabilities {
22206        rename_provider: Some(lsp::OneOf::Left(true)),
22207        ..Default::default()
22208    };
22209    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22210
22211    cx.set_state(indoc! {"
22212        struct Fˇoo {}
22213    "});
22214
22215    cx.update_editor(|editor, _window, cx| {
22216        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22217        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22218        editor.highlight_background::<DocumentHighlightRead>(
22219            &[highlight_range],
22220            |theme| theme.colors().editor_document_highlight_read_background,
22221            cx,
22222        );
22223    });
22224
22225    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22226        .expect("Prepare rename was not started")
22227        .await
22228        .expect("Prepare rename failed");
22229
22230    let mut rename_handler =
22231        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22232            let edit = lsp::TextEdit {
22233                range: lsp::Range {
22234                    start: lsp::Position {
22235                        line: 0,
22236                        character: 7,
22237                    },
22238                    end: lsp::Position {
22239                        line: 0,
22240                        character: 10,
22241                    },
22242                },
22243                new_text: "FooRenamed".to_string(),
22244            };
22245            Ok(Some(lsp::WorkspaceEdit::new(
22246                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
22247            )))
22248        });
22249    let rename_task = cx
22250        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22251        .expect("Confirm rename was not started");
22252    rename_handler.next().await.unwrap();
22253    rename_task.await.expect("Confirm rename failed");
22254    cx.run_until_parked();
22255
22256    // Correct range is renamed, as `surrounding_word` is used to find it.
22257    cx.assert_editor_state(indoc! {"
22258        struct FooRenamedˇ {}
22259    "});
22260}
22261
22262#[gpui::test]
22263async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
22264    init_test(cx, |_| {});
22265    let mut cx = EditorTestContext::new(cx).await;
22266
22267    let language = Arc::new(
22268        Language::new(
22269            LanguageConfig::default(),
22270            Some(tree_sitter_html::LANGUAGE.into()),
22271        )
22272        .with_brackets_query(
22273            r#"
22274            ("<" @open "/>" @close)
22275            ("</" @open ">" @close)
22276            ("<" @open ">" @close)
22277            ("\"" @open "\"" @close)
22278            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
22279        "#,
22280        )
22281        .unwrap(),
22282    );
22283    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
22284
22285    cx.set_state(indoc! {"
22286        <span>ˇ</span>
22287    "});
22288    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22289    cx.assert_editor_state(indoc! {"
22290        <span>
22291        ˇ
22292        </span>
22293    "});
22294
22295    cx.set_state(indoc! {"
22296        <span><span></span>ˇ</span>
22297    "});
22298    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22299    cx.assert_editor_state(indoc! {"
22300        <span><span></span>
22301        ˇ</span>
22302    "});
22303
22304    cx.set_state(indoc! {"
22305        <span>ˇ
22306        </span>
22307    "});
22308    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
22309    cx.assert_editor_state(indoc! {"
22310        <span>
22311        ˇ
22312        </span>
22313    "});
22314}
22315
22316#[gpui::test(iterations = 10)]
22317async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
22318    init_test(cx, |_| {});
22319
22320    let fs = FakeFs::new(cx.executor());
22321    fs.insert_tree(
22322        path!("/dir"),
22323        json!({
22324            "a.ts": "a",
22325        }),
22326    )
22327    .await;
22328
22329    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
22330    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22331    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22332
22333    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
22334    language_registry.add(Arc::new(Language::new(
22335        LanguageConfig {
22336            name: "TypeScript".into(),
22337            matcher: LanguageMatcher {
22338                path_suffixes: vec!["ts".to_string()],
22339                ..Default::default()
22340            },
22341            ..Default::default()
22342        },
22343        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
22344    )));
22345    let mut fake_language_servers = language_registry.register_fake_lsp(
22346        "TypeScript",
22347        FakeLspAdapter {
22348            capabilities: lsp::ServerCapabilities {
22349                code_lens_provider: Some(lsp::CodeLensOptions {
22350                    resolve_provider: Some(true),
22351                }),
22352                execute_command_provider: Some(lsp::ExecuteCommandOptions {
22353                    commands: vec!["_the/command".to_string()],
22354                    ..lsp::ExecuteCommandOptions::default()
22355                }),
22356                ..lsp::ServerCapabilities::default()
22357            },
22358            ..FakeLspAdapter::default()
22359        },
22360    );
22361
22362    let editor = workspace
22363        .update(cx, |workspace, window, cx| {
22364            workspace.open_abs_path(
22365                PathBuf::from(path!("/dir/a.ts")),
22366                OpenOptions::default(),
22367                window,
22368                cx,
22369            )
22370        })
22371        .unwrap()
22372        .await
22373        .unwrap()
22374        .downcast::<Editor>()
22375        .unwrap();
22376    cx.executor().run_until_parked();
22377
22378    let fake_server = fake_language_servers.next().await.unwrap();
22379
22380    let buffer = editor.update(cx, |editor, cx| {
22381        editor
22382            .buffer()
22383            .read(cx)
22384            .as_singleton()
22385            .expect("have opened a single file by path")
22386    });
22387
22388    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
22389    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
22390    drop(buffer_snapshot);
22391    let actions = cx
22392        .update_window(*workspace, |_, window, cx| {
22393            project.code_actions(&buffer, anchor..anchor, window, cx)
22394        })
22395        .unwrap();
22396
22397    fake_server
22398        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
22399            Ok(Some(vec![
22400                lsp::CodeLens {
22401                    range: lsp::Range::default(),
22402                    command: Some(lsp::Command {
22403                        title: "Code lens command".to_owned(),
22404                        command: "_the/command".to_owned(),
22405                        arguments: None,
22406                    }),
22407                    data: None,
22408                },
22409                lsp::CodeLens {
22410                    range: lsp::Range::default(),
22411                    command: Some(lsp::Command {
22412                        title: "Command not in capabilities".to_owned(),
22413                        command: "not in capabilities".to_owned(),
22414                        arguments: None,
22415                    }),
22416                    data: None,
22417                },
22418                lsp::CodeLens {
22419                    range: lsp::Range {
22420                        start: lsp::Position {
22421                            line: 1,
22422                            character: 1,
22423                        },
22424                        end: lsp::Position {
22425                            line: 1,
22426                            character: 1,
22427                        },
22428                    },
22429                    command: Some(lsp::Command {
22430                        title: "Command not in range".to_owned(),
22431                        command: "_the/command".to_owned(),
22432                        arguments: None,
22433                    }),
22434                    data: None,
22435                },
22436            ]))
22437        })
22438        .next()
22439        .await;
22440
22441    let actions = actions.await.unwrap();
22442    assert_eq!(
22443        actions.len(),
22444        1,
22445        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
22446    );
22447    let action = actions[0].clone();
22448    let apply = project.update(cx, |project, cx| {
22449        project.apply_code_action(buffer.clone(), action, true, cx)
22450    });
22451
22452    // Resolving the code action does not populate its edits. In absence of
22453    // edits, we must execute the given command.
22454    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
22455        |mut lens, _| async move {
22456            let lens_command = lens.command.as_mut().expect("should have a command");
22457            assert_eq!(lens_command.title, "Code lens command");
22458            lens_command.arguments = Some(vec![json!("the-argument")]);
22459            Ok(lens)
22460        },
22461    );
22462
22463    // While executing the command, the language server sends the editor
22464    // a `workspaceEdit` request.
22465    fake_server
22466        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
22467            let fake = fake_server.clone();
22468            move |params, _| {
22469                assert_eq!(params.command, "_the/command");
22470                let fake = fake.clone();
22471                async move {
22472                    fake.server
22473                        .request::<lsp::request::ApplyWorkspaceEdit>(
22474                            lsp::ApplyWorkspaceEditParams {
22475                                label: None,
22476                                edit: lsp::WorkspaceEdit {
22477                                    changes: Some(
22478                                        [(
22479                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
22480                                            vec![lsp::TextEdit {
22481                                                range: lsp::Range::new(
22482                                                    lsp::Position::new(0, 0),
22483                                                    lsp::Position::new(0, 0),
22484                                                ),
22485                                                new_text: "X".into(),
22486                                            }],
22487                                        )]
22488                                        .into_iter()
22489                                        .collect(),
22490                                    ),
22491                                    ..lsp::WorkspaceEdit::default()
22492                                },
22493                            },
22494                        )
22495                        .await
22496                        .into_response()
22497                        .unwrap();
22498                    Ok(Some(json!(null)))
22499                }
22500            }
22501        })
22502        .next()
22503        .await;
22504
22505    // Applying the code lens command returns a project transaction containing the edits
22506    // sent by the language server in its `workspaceEdit` request.
22507    let transaction = apply.await.unwrap();
22508    assert!(transaction.0.contains_key(&buffer));
22509    buffer.update(cx, |buffer, cx| {
22510        assert_eq!(buffer.text(), "Xa");
22511        buffer.undo(cx);
22512        assert_eq!(buffer.text(), "a");
22513    });
22514
22515    let actions_after_edits = cx
22516        .update_window(*workspace, |_, window, cx| {
22517            project.code_actions(&buffer, anchor..anchor, window, cx)
22518        })
22519        .unwrap()
22520        .await
22521        .unwrap();
22522    assert_eq!(
22523        actions, actions_after_edits,
22524        "For the same selection, same code lens actions should be returned"
22525    );
22526
22527    let _responses =
22528        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
22529            panic!("No more code lens requests are expected");
22530        });
22531    editor.update_in(cx, |editor, window, cx| {
22532        editor.select_all(&SelectAll, window, cx);
22533    });
22534    cx.executor().run_until_parked();
22535    let new_actions = cx
22536        .update_window(*workspace, |_, window, cx| {
22537            project.code_actions(&buffer, anchor..anchor, window, cx)
22538        })
22539        .unwrap()
22540        .await
22541        .unwrap();
22542    assert_eq!(
22543        actions, new_actions,
22544        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
22545    );
22546}
22547
22548#[gpui::test]
22549async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
22550    init_test(cx, |_| {});
22551
22552    let fs = FakeFs::new(cx.executor());
22553    let main_text = r#"fn main() {
22554println!("1");
22555println!("2");
22556println!("3");
22557println!("4");
22558println!("5");
22559}"#;
22560    let lib_text = "mod foo {}";
22561    fs.insert_tree(
22562        path!("/a"),
22563        json!({
22564            "lib.rs": lib_text,
22565            "main.rs": main_text,
22566        }),
22567    )
22568    .await;
22569
22570    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22571    let (workspace, cx) =
22572        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22573    let worktree_id = workspace.update(cx, |workspace, cx| {
22574        workspace.project().update(cx, |project, cx| {
22575            project.worktrees(cx).next().unwrap().read(cx).id()
22576        })
22577    });
22578
22579    let expected_ranges = vec![
22580        Point::new(0, 0)..Point::new(0, 0),
22581        Point::new(1, 0)..Point::new(1, 1),
22582        Point::new(2, 0)..Point::new(2, 2),
22583        Point::new(3, 0)..Point::new(3, 3),
22584    ];
22585
22586    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22587    let editor_1 = workspace
22588        .update_in(cx, |workspace, window, cx| {
22589            workspace.open_path(
22590                (worktree_id, "main.rs"),
22591                Some(pane_1.downgrade()),
22592                true,
22593                window,
22594                cx,
22595            )
22596        })
22597        .unwrap()
22598        .await
22599        .downcast::<Editor>()
22600        .unwrap();
22601    pane_1.update(cx, |pane, cx| {
22602        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22603        open_editor.update(cx, |editor, cx| {
22604            assert_eq!(
22605                editor.display_text(cx),
22606                main_text,
22607                "Original main.rs text on initial open",
22608            );
22609            assert_eq!(
22610                editor
22611                    .selections
22612                    .all::<Point>(cx)
22613                    .into_iter()
22614                    .map(|s| s.range())
22615                    .collect::<Vec<_>>(),
22616                vec![Point::zero()..Point::zero()],
22617                "Default selections on initial open",
22618            );
22619        })
22620    });
22621    editor_1.update_in(cx, |editor, window, cx| {
22622        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22623            s.select_ranges(expected_ranges.clone());
22624        });
22625    });
22626
22627    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
22628        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
22629    });
22630    let editor_2 = workspace
22631        .update_in(cx, |workspace, window, cx| {
22632            workspace.open_path(
22633                (worktree_id, "main.rs"),
22634                Some(pane_2.downgrade()),
22635                true,
22636                window,
22637                cx,
22638            )
22639        })
22640        .unwrap()
22641        .await
22642        .downcast::<Editor>()
22643        .unwrap();
22644    pane_2.update(cx, |pane, cx| {
22645        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22646        open_editor.update(cx, |editor, cx| {
22647            assert_eq!(
22648                editor.display_text(cx),
22649                main_text,
22650                "Original main.rs text on initial open in another panel",
22651            );
22652            assert_eq!(
22653                editor
22654                    .selections
22655                    .all::<Point>(cx)
22656                    .into_iter()
22657                    .map(|s| s.range())
22658                    .collect::<Vec<_>>(),
22659                vec![Point::zero()..Point::zero()],
22660                "Default selections on initial open in another panel",
22661            );
22662        })
22663    });
22664
22665    editor_2.update_in(cx, |editor, window, cx| {
22666        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
22667    });
22668
22669    let _other_editor_1 = workspace
22670        .update_in(cx, |workspace, window, cx| {
22671            workspace.open_path(
22672                (worktree_id, "lib.rs"),
22673                Some(pane_1.downgrade()),
22674                true,
22675                window,
22676                cx,
22677            )
22678        })
22679        .unwrap()
22680        .await
22681        .downcast::<Editor>()
22682        .unwrap();
22683    pane_1
22684        .update_in(cx, |pane, window, cx| {
22685            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22686        })
22687        .await
22688        .unwrap();
22689    drop(editor_1);
22690    pane_1.update(cx, |pane, cx| {
22691        pane.active_item()
22692            .unwrap()
22693            .downcast::<Editor>()
22694            .unwrap()
22695            .update(cx, |editor, cx| {
22696                assert_eq!(
22697                    editor.display_text(cx),
22698                    lib_text,
22699                    "Other file should be open and active",
22700                );
22701            });
22702        assert_eq!(pane.items().count(), 1, "No other editors should be open");
22703    });
22704
22705    let _other_editor_2 = workspace
22706        .update_in(cx, |workspace, window, cx| {
22707            workspace.open_path(
22708                (worktree_id, "lib.rs"),
22709                Some(pane_2.downgrade()),
22710                true,
22711                window,
22712                cx,
22713            )
22714        })
22715        .unwrap()
22716        .await
22717        .downcast::<Editor>()
22718        .unwrap();
22719    pane_2
22720        .update_in(cx, |pane, window, cx| {
22721            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
22722        })
22723        .await
22724        .unwrap();
22725    drop(editor_2);
22726    pane_2.update(cx, |pane, cx| {
22727        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22728        open_editor.update(cx, |editor, cx| {
22729            assert_eq!(
22730                editor.display_text(cx),
22731                lib_text,
22732                "Other file should be open and active in another panel too",
22733            );
22734        });
22735        assert_eq!(
22736            pane.items().count(),
22737            1,
22738            "No other editors should be open in another pane",
22739        );
22740    });
22741
22742    let _editor_1_reopened = workspace
22743        .update_in(cx, |workspace, window, cx| {
22744            workspace.open_path(
22745                (worktree_id, "main.rs"),
22746                Some(pane_1.downgrade()),
22747                true,
22748                window,
22749                cx,
22750            )
22751        })
22752        .unwrap()
22753        .await
22754        .downcast::<Editor>()
22755        .unwrap();
22756    let _editor_2_reopened = workspace
22757        .update_in(cx, |workspace, window, cx| {
22758            workspace.open_path(
22759                (worktree_id, "main.rs"),
22760                Some(pane_2.downgrade()),
22761                true,
22762                window,
22763                cx,
22764            )
22765        })
22766        .unwrap()
22767        .await
22768        .downcast::<Editor>()
22769        .unwrap();
22770    pane_1.update(cx, |pane, cx| {
22771        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22772        open_editor.update(cx, |editor, cx| {
22773            assert_eq!(
22774                editor.display_text(cx),
22775                main_text,
22776                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
22777            );
22778            assert_eq!(
22779                editor
22780                    .selections
22781                    .all::<Point>(cx)
22782                    .into_iter()
22783                    .map(|s| s.range())
22784                    .collect::<Vec<_>>(),
22785                expected_ranges,
22786                "Previous editor in the 1st panel had selections and should get them restored on reopen",
22787            );
22788        })
22789    });
22790    pane_2.update(cx, |pane, cx| {
22791        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22792        open_editor.update(cx, |editor, cx| {
22793            assert_eq!(
22794                editor.display_text(cx),
22795                r#"fn main() {
22796⋯rintln!("1");
22797⋯intln!("2");
22798⋯ntln!("3");
22799println!("4");
22800println!("5");
22801}"#,
22802                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
22803            );
22804            assert_eq!(
22805                editor
22806                    .selections
22807                    .all::<Point>(cx)
22808                    .into_iter()
22809                    .map(|s| s.range())
22810                    .collect::<Vec<_>>(),
22811                vec![Point::zero()..Point::zero()],
22812                "Previous editor in the 2nd pane had no selections changed hence should restore none",
22813            );
22814        })
22815    });
22816}
22817
22818#[gpui::test]
22819async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
22820    init_test(cx, |_| {});
22821
22822    let fs = FakeFs::new(cx.executor());
22823    let main_text = r#"fn main() {
22824println!("1");
22825println!("2");
22826println!("3");
22827println!("4");
22828println!("5");
22829}"#;
22830    let lib_text = "mod foo {}";
22831    fs.insert_tree(
22832        path!("/a"),
22833        json!({
22834            "lib.rs": lib_text,
22835            "main.rs": main_text,
22836        }),
22837    )
22838    .await;
22839
22840    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22841    let (workspace, cx) =
22842        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22843    let worktree_id = workspace.update(cx, |workspace, cx| {
22844        workspace.project().update(cx, |project, cx| {
22845            project.worktrees(cx).next().unwrap().read(cx).id()
22846        })
22847    });
22848
22849    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
22850    let editor = workspace
22851        .update_in(cx, |workspace, window, cx| {
22852            workspace.open_path(
22853                (worktree_id, "main.rs"),
22854                Some(pane.downgrade()),
22855                true,
22856                window,
22857                cx,
22858            )
22859        })
22860        .unwrap()
22861        .await
22862        .downcast::<Editor>()
22863        .unwrap();
22864    pane.update(cx, |pane, cx| {
22865        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22866        open_editor.update(cx, |editor, cx| {
22867            assert_eq!(
22868                editor.display_text(cx),
22869                main_text,
22870                "Original main.rs text on initial open",
22871            );
22872        })
22873    });
22874    editor.update_in(cx, |editor, window, cx| {
22875        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
22876    });
22877
22878    cx.update_global(|store: &mut SettingsStore, cx| {
22879        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22880            s.restore_on_file_reopen = Some(false);
22881        });
22882    });
22883    editor.update_in(cx, |editor, window, cx| {
22884        editor.fold_ranges(
22885            vec![
22886                Point::new(1, 0)..Point::new(1, 1),
22887                Point::new(2, 0)..Point::new(2, 2),
22888                Point::new(3, 0)..Point::new(3, 3),
22889            ],
22890            false,
22891            window,
22892            cx,
22893        );
22894    });
22895    pane.update_in(cx, |pane, window, cx| {
22896        pane.close_all_items(&CloseAllItems::default(), window, cx)
22897    })
22898    .await
22899    .unwrap();
22900    pane.update(cx, |pane, _| {
22901        assert!(pane.active_item().is_none());
22902    });
22903    cx.update_global(|store: &mut SettingsStore, cx| {
22904        store.update_user_settings::<WorkspaceSettings>(cx, |s| {
22905            s.restore_on_file_reopen = Some(true);
22906        });
22907    });
22908
22909    let _editor_reopened = workspace
22910        .update_in(cx, |workspace, window, cx| {
22911            workspace.open_path(
22912                (worktree_id, "main.rs"),
22913                Some(pane.downgrade()),
22914                true,
22915                window,
22916                cx,
22917            )
22918        })
22919        .unwrap()
22920        .await
22921        .downcast::<Editor>()
22922        .unwrap();
22923    pane.update(cx, |pane, cx| {
22924        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
22925        open_editor.update(cx, |editor, cx| {
22926            assert_eq!(
22927                editor.display_text(cx),
22928                main_text,
22929                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
22930            );
22931        })
22932    });
22933}
22934
22935#[gpui::test]
22936async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
22937    struct EmptyModalView {
22938        focus_handle: gpui::FocusHandle,
22939    }
22940    impl EventEmitter<DismissEvent> for EmptyModalView {}
22941    impl Render for EmptyModalView {
22942        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
22943            div()
22944        }
22945    }
22946    impl Focusable for EmptyModalView {
22947        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
22948            self.focus_handle.clone()
22949        }
22950    }
22951    impl workspace::ModalView for EmptyModalView {}
22952    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
22953        EmptyModalView {
22954            focus_handle: cx.focus_handle(),
22955        }
22956    }
22957
22958    init_test(cx, |_| {});
22959
22960    let fs = FakeFs::new(cx.executor());
22961    let project = Project::test(fs, [], cx).await;
22962    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22963    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
22964    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22965    let editor = cx.new_window_entity(|window, cx| {
22966        Editor::new(
22967            EditorMode::full(),
22968            buffer,
22969            Some(project.clone()),
22970            window,
22971            cx,
22972        )
22973    });
22974    workspace
22975        .update(cx, |workspace, window, cx| {
22976            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
22977        })
22978        .unwrap();
22979    editor.update_in(cx, |editor, window, cx| {
22980        editor.open_context_menu(&OpenContextMenu, window, cx);
22981        assert!(editor.mouse_context_menu.is_some());
22982    });
22983    workspace
22984        .update(cx, |workspace, window, cx| {
22985            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
22986        })
22987        .unwrap();
22988    cx.read(|cx| {
22989        assert!(editor.read(cx).mouse_context_menu.is_none());
22990    });
22991}
22992
22993#[gpui::test]
22994async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
22995    init_test(cx, |_| {});
22996
22997    let fs = FakeFs::new(cx.executor());
22998    fs.insert_file(path!("/file.html"), Default::default())
22999        .await;
23000
23001    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23002
23003    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23004    let html_language = Arc::new(Language::new(
23005        LanguageConfig {
23006            name: "HTML".into(),
23007            matcher: LanguageMatcher {
23008                path_suffixes: vec!["html".to_string()],
23009                ..LanguageMatcher::default()
23010            },
23011            brackets: BracketPairConfig {
23012                pairs: vec![BracketPair {
23013                    start: "<".into(),
23014                    end: ">".into(),
23015                    close: true,
23016                    ..Default::default()
23017                }],
23018                ..Default::default()
23019            },
23020            ..Default::default()
23021        },
23022        Some(tree_sitter_html::LANGUAGE.into()),
23023    ));
23024    language_registry.add(html_language);
23025    let mut fake_servers = language_registry.register_fake_lsp(
23026        "HTML",
23027        FakeLspAdapter {
23028            capabilities: lsp::ServerCapabilities {
23029                completion_provider: Some(lsp::CompletionOptions {
23030                    resolve_provider: Some(true),
23031                    ..Default::default()
23032                }),
23033                ..Default::default()
23034            },
23035            ..Default::default()
23036        },
23037    );
23038
23039    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23040    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23041
23042    let worktree_id = workspace
23043        .update(cx, |workspace, _window, cx| {
23044            workspace.project().update(cx, |project, cx| {
23045                project.worktrees(cx).next().unwrap().read(cx).id()
23046            })
23047        })
23048        .unwrap();
23049    project
23050        .update(cx, |project, cx| {
23051            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23052        })
23053        .await
23054        .unwrap();
23055    let editor = workspace
23056        .update(cx, |workspace, window, cx| {
23057            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23058        })
23059        .unwrap()
23060        .await
23061        .unwrap()
23062        .downcast::<Editor>()
23063        .unwrap();
23064
23065    let fake_server = fake_servers.next().await.unwrap();
23066    editor.update_in(cx, |editor, window, cx| {
23067        editor.set_text("<ad></ad>", window, cx);
23068        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23069            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23070        });
23071        let Some((buffer, _)) = editor
23072            .buffer
23073            .read(cx)
23074            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23075        else {
23076            panic!("Failed to get buffer for selection position");
23077        };
23078        let buffer = buffer.read(cx);
23079        let buffer_id = buffer.remote_id();
23080        let opening_range =
23081            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
23082        let closing_range =
23083            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
23084        let mut linked_ranges = HashMap::default();
23085        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23086        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23087    });
23088    let mut completion_handle =
23089        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23090            Ok(Some(lsp::CompletionResponse::Array(vec![
23091                lsp::CompletionItem {
23092                    label: "head".to_string(),
23093                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23094                        lsp::InsertReplaceEdit {
23095                            new_text: "head".to_string(),
23096                            insert: lsp::Range::new(
23097                                lsp::Position::new(0, 1),
23098                                lsp::Position::new(0, 3),
23099                            ),
23100                            replace: lsp::Range::new(
23101                                lsp::Position::new(0, 1),
23102                                lsp::Position::new(0, 3),
23103                            ),
23104                        },
23105                    )),
23106                    ..Default::default()
23107                },
23108            ])))
23109        });
23110    editor.update_in(cx, |editor, window, cx| {
23111        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23112    });
23113    cx.run_until_parked();
23114    completion_handle.next().await.unwrap();
23115    editor.update(cx, |editor, _| {
23116        assert!(
23117            editor.context_menu_visible(),
23118            "Completion menu should be visible"
23119        );
23120    });
23121    editor.update_in(cx, |editor, window, cx| {
23122        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23123    });
23124    cx.executor().run_until_parked();
23125    editor.update(cx, |editor, cx| {
23126        assert_eq!(editor.text(cx), "<head></head>");
23127    });
23128}
23129
23130#[gpui::test]
23131async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
23132    init_test(cx, |_| {});
23133
23134    let fs = FakeFs::new(cx.executor());
23135    fs.insert_tree(
23136        path!("/root"),
23137        json!({
23138            "a": {
23139                "main.rs": "fn main() {}",
23140            },
23141            "foo": {
23142                "bar": {
23143                    "external_file.rs": "pub mod external {}",
23144                }
23145            }
23146        }),
23147    )
23148    .await;
23149
23150    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
23151    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23152    language_registry.add(rust_lang());
23153    let _fake_servers = language_registry.register_fake_lsp(
23154        "Rust",
23155        FakeLspAdapter {
23156            ..FakeLspAdapter::default()
23157        },
23158    );
23159    let (workspace, cx) =
23160        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23161    let worktree_id = workspace.update(cx, |workspace, cx| {
23162        workspace.project().update(cx, |project, cx| {
23163            project.worktrees(cx).next().unwrap().read(cx).id()
23164        })
23165    });
23166
23167    let assert_language_servers_count =
23168        |expected: usize, context: &str, cx: &mut VisualTestContext| {
23169            project.update(cx, |project, cx| {
23170                let current = project
23171                    .lsp_store()
23172                    .read(cx)
23173                    .as_local()
23174                    .unwrap()
23175                    .language_servers
23176                    .len();
23177                assert_eq!(expected, current, "{context}");
23178            });
23179        };
23180
23181    assert_language_servers_count(
23182        0,
23183        "No servers should be running before any file is open",
23184        cx,
23185    );
23186    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23187    let main_editor = workspace
23188        .update_in(cx, |workspace, window, cx| {
23189            workspace.open_path(
23190                (worktree_id, "main.rs"),
23191                Some(pane.downgrade()),
23192                true,
23193                window,
23194                cx,
23195            )
23196        })
23197        .unwrap()
23198        .await
23199        .downcast::<Editor>()
23200        .unwrap();
23201    pane.update(cx, |pane, cx| {
23202        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23203        open_editor.update(cx, |editor, cx| {
23204            assert_eq!(
23205                editor.display_text(cx),
23206                "fn main() {}",
23207                "Original main.rs text on initial open",
23208            );
23209        });
23210        assert_eq!(open_editor, main_editor);
23211    });
23212    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
23213
23214    let external_editor = workspace
23215        .update_in(cx, |workspace, window, cx| {
23216            workspace.open_abs_path(
23217                PathBuf::from("/root/foo/bar/external_file.rs"),
23218                OpenOptions::default(),
23219                window,
23220                cx,
23221            )
23222        })
23223        .await
23224        .expect("opening external file")
23225        .downcast::<Editor>()
23226        .expect("downcasted external file's open element to editor");
23227    pane.update(cx, |pane, cx| {
23228        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23229        open_editor.update(cx, |editor, cx| {
23230            assert_eq!(
23231                editor.display_text(cx),
23232                "pub mod external {}",
23233                "External file is open now",
23234            );
23235        });
23236        assert_eq!(open_editor, external_editor);
23237    });
23238    assert_language_servers_count(
23239        1,
23240        "Second, external, *.rs file should join the existing server",
23241        cx,
23242    );
23243
23244    pane.update_in(cx, |pane, window, cx| {
23245        pane.close_active_item(&CloseActiveItem::default(), window, cx)
23246    })
23247    .await
23248    .unwrap();
23249    pane.update_in(cx, |pane, window, cx| {
23250        pane.navigate_backward(&Default::default(), window, cx);
23251    });
23252    cx.run_until_parked();
23253    pane.update(cx, |pane, cx| {
23254        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23255        open_editor.update(cx, |editor, cx| {
23256            assert_eq!(
23257                editor.display_text(cx),
23258                "pub mod external {}",
23259                "External file is open now",
23260            );
23261        });
23262    });
23263    assert_language_servers_count(
23264        1,
23265        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
23266        cx,
23267    );
23268
23269    cx.update(|_, cx| {
23270        workspace::reload(cx);
23271    });
23272    assert_language_servers_count(
23273        1,
23274        "After reloading the worktree with local and external files opened, only one project should be started",
23275        cx,
23276    );
23277}
23278
23279#[gpui::test]
23280async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
23281    init_test(cx, |_| {});
23282
23283    let mut cx = EditorTestContext::new(cx).await;
23284    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23285    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23286
23287    // test cursor move to start of each line on tab
23288    // for `if`, `elif`, `else`, `while`, `with` and `for`
23289    cx.set_state(indoc! {"
23290        def main():
23291        ˇ    for item in items:
23292        ˇ        while item.active:
23293        ˇ            if item.value > 10:
23294        ˇ                continue
23295        ˇ            elif item.value < 0:
23296        ˇ                break
23297        ˇ            else:
23298        ˇ                with item.context() as ctx:
23299        ˇ                    yield count
23300        ˇ        else:
23301        ˇ            log('while else')
23302        ˇ    else:
23303        ˇ        log('for else')
23304    "});
23305    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23306    cx.assert_editor_state(indoc! {"
23307        def main():
23308            ˇfor item in items:
23309                ˇwhile item.active:
23310                    ˇif item.value > 10:
23311                        ˇcontinue
23312                    ˇelif item.value < 0:
23313                        ˇbreak
23314                    ˇelse:
23315                        ˇwith item.context() as ctx:
23316                            ˇyield count
23317                ˇelse:
23318                    ˇlog('while else')
23319            ˇelse:
23320                ˇlog('for else')
23321    "});
23322    // test relative indent is preserved when tab
23323    // for `if`, `elif`, `else`, `while`, `with` and `for`
23324    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23325    cx.assert_editor_state(indoc! {"
23326        def main():
23327                ˇfor item in items:
23328                    ˇwhile item.active:
23329                        ˇif item.value > 10:
23330                            ˇcontinue
23331                        ˇelif item.value < 0:
23332                            ˇbreak
23333                        ˇelse:
23334                            ˇwith item.context() as ctx:
23335                                ˇyield count
23336                    ˇelse:
23337                        ˇlog('while else')
23338                ˇelse:
23339                    ˇlog('for else')
23340    "});
23341
23342    // test cursor move to start of each line on tab
23343    // for `try`, `except`, `else`, `finally`, `match` and `def`
23344    cx.set_state(indoc! {"
23345        def main():
23346        ˇ    try:
23347        ˇ        fetch()
23348        ˇ    except ValueError:
23349        ˇ        handle_error()
23350        ˇ    else:
23351        ˇ        match value:
23352        ˇ            case _:
23353        ˇ    finally:
23354        ˇ        def status():
23355        ˇ            return 0
23356    "});
23357    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23358    cx.assert_editor_state(indoc! {"
23359        def main():
23360            ˇtry:
23361                ˇfetch()
23362            ˇexcept ValueError:
23363                ˇhandle_error()
23364            ˇelse:
23365                ˇmatch value:
23366                    ˇcase _:
23367            ˇfinally:
23368                ˇdef status():
23369                    ˇreturn 0
23370    "});
23371    // test relative indent is preserved when tab
23372    // for `try`, `except`, `else`, `finally`, `match` and `def`
23373    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23374    cx.assert_editor_state(indoc! {"
23375        def main():
23376                ˇtry:
23377                    ˇfetch()
23378                ˇexcept ValueError:
23379                    ˇhandle_error()
23380                ˇelse:
23381                    ˇmatch value:
23382                        ˇcase _:
23383                ˇfinally:
23384                    ˇdef status():
23385                        ˇreturn 0
23386    "});
23387}
23388
23389#[gpui::test]
23390async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
23391    init_test(cx, |_| {});
23392
23393    let mut cx = EditorTestContext::new(cx).await;
23394    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23395    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23396
23397    // test `else` auto outdents when typed inside `if` block
23398    cx.set_state(indoc! {"
23399        def main():
23400            if i == 2:
23401                return
23402                ˇ
23403    "});
23404    cx.update_editor(|editor, window, cx| {
23405        editor.handle_input("else:", window, cx);
23406    });
23407    cx.assert_editor_state(indoc! {"
23408        def main():
23409            if i == 2:
23410                return
23411            else:ˇ
23412    "});
23413
23414    // test `except` auto outdents when typed inside `try` block
23415    cx.set_state(indoc! {"
23416        def main():
23417            try:
23418                i = 2
23419                ˇ
23420    "});
23421    cx.update_editor(|editor, window, cx| {
23422        editor.handle_input("except:", window, cx);
23423    });
23424    cx.assert_editor_state(indoc! {"
23425        def main():
23426            try:
23427                i = 2
23428            except:ˇ
23429    "});
23430
23431    // test `else` auto outdents when typed inside `except` block
23432    cx.set_state(indoc! {"
23433        def main():
23434            try:
23435                i = 2
23436            except:
23437                j = 2
23438                ˇ
23439    "});
23440    cx.update_editor(|editor, window, cx| {
23441        editor.handle_input("else:", window, cx);
23442    });
23443    cx.assert_editor_state(indoc! {"
23444        def main():
23445            try:
23446                i = 2
23447            except:
23448                j = 2
23449            else:ˇ
23450    "});
23451
23452    // test `finally` auto outdents when typed inside `else` block
23453    cx.set_state(indoc! {"
23454        def main():
23455            try:
23456                i = 2
23457            except:
23458                j = 2
23459            else:
23460                k = 2
23461                ˇ
23462    "});
23463    cx.update_editor(|editor, window, cx| {
23464        editor.handle_input("finally:", window, cx);
23465    });
23466    cx.assert_editor_state(indoc! {"
23467        def main():
23468            try:
23469                i = 2
23470            except:
23471                j = 2
23472            else:
23473                k = 2
23474            finally:ˇ
23475    "});
23476
23477    // test `else` does not outdents when typed inside `except` block right after for block
23478    cx.set_state(indoc! {"
23479        def main():
23480            try:
23481                i = 2
23482            except:
23483                for i in range(n):
23484                    pass
23485                ˇ
23486    "});
23487    cx.update_editor(|editor, window, cx| {
23488        editor.handle_input("else:", window, cx);
23489    });
23490    cx.assert_editor_state(indoc! {"
23491        def main():
23492            try:
23493                i = 2
23494            except:
23495                for i in range(n):
23496                    pass
23497                else:ˇ
23498    "});
23499
23500    // test `finally` auto outdents when typed inside `else` block right after for block
23501    cx.set_state(indoc! {"
23502        def main():
23503            try:
23504                i = 2
23505            except:
23506                j = 2
23507            else:
23508                for i in range(n):
23509                    pass
23510                ˇ
23511    "});
23512    cx.update_editor(|editor, window, cx| {
23513        editor.handle_input("finally:", window, cx);
23514    });
23515    cx.assert_editor_state(indoc! {"
23516        def main():
23517            try:
23518                i = 2
23519            except:
23520                j = 2
23521            else:
23522                for i in range(n):
23523                    pass
23524            finally:ˇ
23525    "});
23526
23527    // test `except` outdents to inner "try" block
23528    cx.set_state(indoc! {"
23529        def main():
23530            try:
23531                i = 2
23532                if i == 2:
23533                    try:
23534                        i = 3
23535                        ˇ
23536    "});
23537    cx.update_editor(|editor, window, cx| {
23538        editor.handle_input("except:", window, cx);
23539    });
23540    cx.assert_editor_state(indoc! {"
23541        def main():
23542            try:
23543                i = 2
23544                if i == 2:
23545                    try:
23546                        i = 3
23547                    except:ˇ
23548    "});
23549
23550    // test `except` outdents to outer "try" block
23551    cx.set_state(indoc! {"
23552        def main():
23553            try:
23554                i = 2
23555                if i == 2:
23556                    try:
23557                        i = 3
23558                ˇ
23559    "});
23560    cx.update_editor(|editor, window, cx| {
23561        editor.handle_input("except:", window, cx);
23562    });
23563    cx.assert_editor_state(indoc! {"
23564        def main():
23565            try:
23566                i = 2
23567                if i == 2:
23568                    try:
23569                        i = 3
23570            except:ˇ
23571    "});
23572
23573    // test `else` stays at correct indent when typed after `for` block
23574    cx.set_state(indoc! {"
23575        def main():
23576            for i in range(10):
23577                if i == 3:
23578                    break
23579            ˇ
23580    "});
23581    cx.update_editor(|editor, window, cx| {
23582        editor.handle_input("else:", window, cx);
23583    });
23584    cx.assert_editor_state(indoc! {"
23585        def main():
23586            for i in range(10):
23587                if i == 3:
23588                    break
23589            else:ˇ
23590    "});
23591
23592    // test does not outdent on typing after line with square brackets
23593    cx.set_state(indoc! {"
23594        def f() -> list[str]:
23595            ˇ
23596    "});
23597    cx.update_editor(|editor, window, cx| {
23598        editor.handle_input("a", window, cx);
23599    });
23600    cx.assert_editor_state(indoc! {"
23601        def f() -> list[str]:
2360223603    "});
23604
23605    // test does not outdent on typing : after case keyword
23606    cx.set_state(indoc! {"
23607        match 1:
23608            caseˇ
23609    "});
23610    cx.update_editor(|editor, window, cx| {
23611        editor.handle_input(":", window, cx);
23612    });
23613    cx.assert_editor_state(indoc! {"
23614        match 1:
23615            case:ˇ
23616    "});
23617}
23618
23619#[gpui::test]
23620async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
23621    init_test(cx, |_| {});
23622    update_test_language_settings(cx, |settings| {
23623        settings.defaults.extend_comment_on_newline = Some(false);
23624    });
23625    let mut cx = EditorTestContext::new(cx).await;
23626    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
23627    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23628
23629    // test correct indent after newline on comment
23630    cx.set_state(indoc! {"
23631        # COMMENT:ˇ
23632    "});
23633    cx.update_editor(|editor, window, cx| {
23634        editor.newline(&Newline, window, cx);
23635    });
23636    cx.assert_editor_state(indoc! {"
23637        # COMMENT:
23638        ˇ
23639    "});
23640
23641    // test correct indent after newline in brackets
23642    cx.set_state(indoc! {"
23643        {ˇ}
23644    "});
23645    cx.update_editor(|editor, window, cx| {
23646        editor.newline(&Newline, window, cx);
23647    });
23648    cx.run_until_parked();
23649    cx.assert_editor_state(indoc! {"
23650        {
23651            ˇ
23652        }
23653    "});
23654
23655    cx.set_state(indoc! {"
23656        (ˇ)
23657    "});
23658    cx.update_editor(|editor, window, cx| {
23659        editor.newline(&Newline, window, cx);
23660    });
23661    cx.run_until_parked();
23662    cx.assert_editor_state(indoc! {"
23663        (
23664            ˇ
23665        )
23666    "});
23667
23668    // do not indent after empty lists or dictionaries
23669    cx.set_state(indoc! {"
23670        a = []ˇ
23671    "});
23672    cx.update_editor(|editor, window, cx| {
23673        editor.newline(&Newline, window, cx);
23674    });
23675    cx.run_until_parked();
23676    cx.assert_editor_state(indoc! {"
23677        a = []
23678        ˇ
23679    "});
23680}
23681
23682#[gpui::test]
23683async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
23684    init_test(cx, |_| {});
23685
23686    let mut cx = EditorTestContext::new(cx).await;
23687    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23688    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23689
23690    // test cursor move to start of each line on tab
23691    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
23692    cx.set_state(indoc! {"
23693        function main() {
23694        ˇ    for item in $items; do
23695        ˇ        while [ -n \"$item\" ]; do
23696        ˇ            if [ \"$value\" -gt 10 ]; then
23697        ˇ                continue
23698        ˇ            elif [ \"$value\" -lt 0 ]; then
23699        ˇ                break
23700        ˇ            else
23701        ˇ                echo \"$item\"
23702        ˇ            fi
23703        ˇ        done
23704        ˇ    done
23705        ˇ}
23706    "});
23707    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23708    cx.assert_editor_state(indoc! {"
23709        function main() {
23710            ˇfor item in $items; do
23711                ˇwhile [ -n \"$item\" ]; do
23712                    ˇif [ \"$value\" -gt 10 ]; then
23713                        ˇcontinue
23714                    ˇelif [ \"$value\" -lt 0 ]; then
23715                        ˇbreak
23716                    ˇelse
23717                        ˇecho \"$item\"
23718                    ˇfi
23719                ˇdone
23720            ˇdone
23721        ˇ}
23722    "});
23723    // test relative indent is preserved when tab
23724    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23725    cx.assert_editor_state(indoc! {"
23726        function main() {
23727                ˇfor item in $items; do
23728                    ˇwhile [ -n \"$item\" ]; do
23729                        ˇif [ \"$value\" -gt 10 ]; then
23730                            ˇcontinue
23731                        ˇelif [ \"$value\" -lt 0 ]; then
23732                            ˇbreak
23733                        ˇelse
23734                            ˇecho \"$item\"
23735                        ˇfi
23736                    ˇdone
23737                ˇdone
23738            ˇ}
23739    "});
23740
23741    // test cursor move to start of each line on tab
23742    // for `case` statement with patterns
23743    cx.set_state(indoc! {"
23744        function handle() {
23745        ˇ    case \"$1\" in
23746        ˇ        start)
23747        ˇ            echo \"a\"
23748        ˇ            ;;
23749        ˇ        stop)
23750        ˇ            echo \"b\"
23751        ˇ            ;;
23752        ˇ        *)
23753        ˇ            echo \"c\"
23754        ˇ            ;;
23755        ˇ    esac
23756        ˇ}
23757    "});
23758    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
23759    cx.assert_editor_state(indoc! {"
23760        function handle() {
23761            ˇcase \"$1\" in
23762                ˇstart)
23763                    ˇecho \"a\"
23764                    ˇ;;
23765                ˇstop)
23766                    ˇecho \"b\"
23767                    ˇ;;
23768                ˇ*)
23769                    ˇecho \"c\"
23770                    ˇ;;
23771            ˇesac
23772        ˇ}
23773    "});
23774}
23775
23776#[gpui::test]
23777async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
23778    init_test(cx, |_| {});
23779
23780    let mut cx = EditorTestContext::new(cx).await;
23781    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23782    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23783
23784    // test indents on comment insert
23785    cx.set_state(indoc! {"
23786        function main() {
23787        ˇ    for item in $items; do
23788        ˇ        while [ -n \"$item\" ]; do
23789        ˇ            if [ \"$value\" -gt 10 ]; then
23790        ˇ                continue
23791        ˇ            elif [ \"$value\" -lt 0 ]; then
23792        ˇ                break
23793        ˇ            else
23794        ˇ                echo \"$item\"
23795        ˇ            fi
23796        ˇ        done
23797        ˇ    done
23798        ˇ}
23799    "});
23800    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
23801    cx.assert_editor_state(indoc! {"
23802        function main() {
23803        #ˇ    for item in $items; do
23804        #ˇ        while [ -n \"$item\" ]; do
23805        #ˇ            if [ \"$value\" -gt 10 ]; then
23806        #ˇ                continue
23807        #ˇ            elif [ \"$value\" -lt 0 ]; then
23808        #ˇ                break
23809        #ˇ            else
23810        #ˇ                echo \"$item\"
23811        #ˇ            fi
23812        #ˇ        done
23813        #ˇ    done
23814        #ˇ}
23815    "});
23816}
23817
23818#[gpui::test]
23819async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
23820    init_test(cx, |_| {});
23821
23822    let mut cx = EditorTestContext::new(cx).await;
23823    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23824    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23825
23826    // test `else` auto outdents when typed inside `if` block
23827    cx.set_state(indoc! {"
23828        if [ \"$1\" = \"test\" ]; then
23829            echo \"foo bar\"
23830            ˇ
23831    "});
23832    cx.update_editor(|editor, window, cx| {
23833        editor.handle_input("else", window, cx);
23834    });
23835    cx.assert_editor_state(indoc! {"
23836        if [ \"$1\" = \"test\" ]; then
23837            echo \"foo bar\"
23838        elseˇ
23839    "});
23840
23841    // test `elif` auto outdents when typed inside `if` block
23842    cx.set_state(indoc! {"
23843        if [ \"$1\" = \"test\" ]; then
23844            echo \"foo bar\"
23845            ˇ
23846    "});
23847    cx.update_editor(|editor, window, cx| {
23848        editor.handle_input("elif", window, cx);
23849    });
23850    cx.assert_editor_state(indoc! {"
23851        if [ \"$1\" = \"test\" ]; then
23852            echo \"foo bar\"
23853        elifˇ
23854    "});
23855
23856    // test `fi` auto outdents when typed inside `else` block
23857    cx.set_state(indoc! {"
23858        if [ \"$1\" = \"test\" ]; then
23859            echo \"foo bar\"
23860        else
23861            echo \"bar baz\"
23862            ˇ
23863    "});
23864    cx.update_editor(|editor, window, cx| {
23865        editor.handle_input("fi", window, cx);
23866    });
23867    cx.assert_editor_state(indoc! {"
23868        if [ \"$1\" = \"test\" ]; then
23869            echo \"foo bar\"
23870        else
23871            echo \"bar baz\"
23872        fiˇ
23873    "});
23874
23875    // test `done` auto outdents when typed inside `while` block
23876    cx.set_state(indoc! {"
23877        while read line; do
23878            echo \"$line\"
23879            ˇ
23880    "});
23881    cx.update_editor(|editor, window, cx| {
23882        editor.handle_input("done", window, cx);
23883    });
23884    cx.assert_editor_state(indoc! {"
23885        while read line; do
23886            echo \"$line\"
23887        doneˇ
23888    "});
23889
23890    // test `done` auto outdents when typed inside `for` block
23891    cx.set_state(indoc! {"
23892        for file in *.txt; do
23893            cat \"$file\"
23894            ˇ
23895    "});
23896    cx.update_editor(|editor, window, cx| {
23897        editor.handle_input("done", window, cx);
23898    });
23899    cx.assert_editor_state(indoc! {"
23900        for file in *.txt; do
23901            cat \"$file\"
23902        doneˇ
23903    "});
23904
23905    // test `esac` auto outdents when typed inside `case` block
23906    cx.set_state(indoc! {"
23907        case \"$1\" in
23908            start)
23909                echo \"foo bar\"
23910                ;;
23911            stop)
23912                echo \"bar baz\"
23913                ;;
23914            ˇ
23915    "});
23916    cx.update_editor(|editor, window, cx| {
23917        editor.handle_input("esac", window, cx);
23918    });
23919    cx.assert_editor_state(indoc! {"
23920        case \"$1\" in
23921            start)
23922                echo \"foo bar\"
23923                ;;
23924            stop)
23925                echo \"bar baz\"
23926                ;;
23927        esacˇ
23928    "});
23929
23930    // test `*)` auto outdents when typed inside `case` block
23931    cx.set_state(indoc! {"
23932        case \"$1\" in
23933            start)
23934                echo \"foo bar\"
23935                ;;
23936                ˇ
23937    "});
23938    cx.update_editor(|editor, window, cx| {
23939        editor.handle_input("*)", window, cx);
23940    });
23941    cx.assert_editor_state(indoc! {"
23942        case \"$1\" in
23943            start)
23944                echo \"foo bar\"
23945                ;;
23946            *)ˇ
23947    "});
23948
23949    // test `fi` outdents to correct level with nested if blocks
23950    cx.set_state(indoc! {"
23951        if [ \"$1\" = \"test\" ]; then
23952            echo \"outer if\"
23953            if [ \"$2\" = \"debug\" ]; then
23954                echo \"inner if\"
23955                ˇ
23956    "});
23957    cx.update_editor(|editor, window, cx| {
23958        editor.handle_input("fi", window, cx);
23959    });
23960    cx.assert_editor_state(indoc! {"
23961        if [ \"$1\" = \"test\" ]; then
23962            echo \"outer if\"
23963            if [ \"$2\" = \"debug\" ]; then
23964                echo \"inner if\"
23965            fiˇ
23966    "});
23967}
23968
23969#[gpui::test]
23970async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
23971    init_test(cx, |_| {});
23972    update_test_language_settings(cx, |settings| {
23973        settings.defaults.extend_comment_on_newline = Some(false);
23974    });
23975    let mut cx = EditorTestContext::new(cx).await;
23976    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
23977    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23978
23979    // test correct indent after newline on comment
23980    cx.set_state(indoc! {"
23981        # COMMENT:ˇ
23982    "});
23983    cx.update_editor(|editor, window, cx| {
23984        editor.newline(&Newline, window, cx);
23985    });
23986    cx.assert_editor_state(indoc! {"
23987        # COMMENT:
23988        ˇ
23989    "});
23990
23991    // test correct indent after newline after `then`
23992    cx.set_state(indoc! {"
23993
23994        if [ \"$1\" = \"test\" ]; thenˇ
23995    "});
23996    cx.update_editor(|editor, window, cx| {
23997        editor.newline(&Newline, window, cx);
23998    });
23999    cx.run_until_parked();
24000    cx.assert_editor_state(indoc! {"
24001
24002        if [ \"$1\" = \"test\" ]; then
24003            ˇ
24004    "});
24005
24006    // test correct indent after newline after `else`
24007    cx.set_state(indoc! {"
24008        if [ \"$1\" = \"test\" ]; then
24009        elseˇ
24010    "});
24011    cx.update_editor(|editor, window, cx| {
24012        editor.newline(&Newline, window, cx);
24013    });
24014    cx.run_until_parked();
24015    cx.assert_editor_state(indoc! {"
24016        if [ \"$1\" = \"test\" ]; then
24017        else
24018            ˇ
24019    "});
24020
24021    // test correct indent after newline after `elif`
24022    cx.set_state(indoc! {"
24023        if [ \"$1\" = \"test\" ]; then
24024        elifˇ
24025    "});
24026    cx.update_editor(|editor, window, cx| {
24027        editor.newline(&Newline, window, cx);
24028    });
24029    cx.run_until_parked();
24030    cx.assert_editor_state(indoc! {"
24031        if [ \"$1\" = \"test\" ]; then
24032        elif
24033            ˇ
24034    "});
24035
24036    // test correct indent after newline after `do`
24037    cx.set_state(indoc! {"
24038        for file in *.txt; doˇ
24039    "});
24040    cx.update_editor(|editor, window, cx| {
24041        editor.newline(&Newline, window, cx);
24042    });
24043    cx.run_until_parked();
24044    cx.assert_editor_state(indoc! {"
24045        for file in *.txt; do
24046            ˇ
24047    "});
24048
24049    // test correct indent after newline after case pattern
24050    cx.set_state(indoc! {"
24051        case \"$1\" in
24052            start)ˇ
24053    "});
24054    cx.update_editor(|editor, window, cx| {
24055        editor.newline(&Newline, window, cx);
24056    });
24057    cx.run_until_parked();
24058    cx.assert_editor_state(indoc! {"
24059        case \"$1\" in
24060            start)
24061                ˇ
24062    "});
24063
24064    // test correct indent after newline after case pattern
24065    cx.set_state(indoc! {"
24066        case \"$1\" in
24067            start)
24068                ;;
24069            *)ˇ
24070    "});
24071    cx.update_editor(|editor, window, cx| {
24072        editor.newline(&Newline, window, cx);
24073    });
24074    cx.run_until_parked();
24075    cx.assert_editor_state(indoc! {"
24076        case \"$1\" in
24077            start)
24078                ;;
24079            *)
24080                ˇ
24081    "});
24082
24083    // test correct indent after newline after function opening brace
24084    cx.set_state(indoc! {"
24085        function test() {ˇ}
24086    "});
24087    cx.update_editor(|editor, window, cx| {
24088        editor.newline(&Newline, window, cx);
24089    });
24090    cx.run_until_parked();
24091    cx.assert_editor_state(indoc! {"
24092        function test() {
24093            ˇ
24094        }
24095    "});
24096
24097    // test no extra indent after semicolon on same line
24098    cx.set_state(indoc! {"
24099        echo \"test\"24100    "});
24101    cx.update_editor(|editor, window, cx| {
24102        editor.newline(&Newline, window, cx);
24103    });
24104    cx.run_until_parked();
24105    cx.assert_editor_state(indoc! {"
24106        echo \"test\";
24107        ˇ
24108    "});
24109}
24110
24111fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24112    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24113    point..point
24114}
24115
24116#[track_caller]
24117fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24118    let (text, ranges) = marked_text_ranges(marked_text, true);
24119    assert_eq!(editor.text(cx), text);
24120    assert_eq!(
24121        editor.selections.ranges(cx),
24122        ranges,
24123        "Assert selections are {}",
24124        marked_text
24125    );
24126}
24127
24128pub fn handle_signature_help_request(
24129    cx: &mut EditorLspTestContext,
24130    mocked_response: lsp::SignatureHelp,
24131) -> impl Future<Output = ()> + use<> {
24132    let mut request =
24133        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
24134            let mocked_response = mocked_response.clone();
24135            async move { Ok(Some(mocked_response)) }
24136        });
24137
24138    async move {
24139        request.next().await;
24140    }
24141}
24142
24143#[track_caller]
24144pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
24145    cx.update_editor(|editor, _, _| {
24146        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
24147            let entries = menu.entries.borrow();
24148            let entries = entries
24149                .iter()
24150                .map(|entry| entry.string.as_str())
24151                .collect::<Vec<_>>();
24152            assert_eq!(entries, expected);
24153        } else {
24154            panic!("Expected completions menu");
24155        }
24156    });
24157}
24158
24159/// Handle completion request passing a marked string specifying where the completion
24160/// should be triggered from using '|' character, what range should be replaced, and what completions
24161/// should be returned using '<' and '>' to delimit the range.
24162///
24163/// Also see `handle_completion_request_with_insert_and_replace`.
24164#[track_caller]
24165pub fn handle_completion_request(
24166    marked_string: &str,
24167    completions: Vec<&'static str>,
24168    is_incomplete: bool,
24169    counter: Arc<AtomicUsize>,
24170    cx: &mut EditorLspTestContext,
24171) -> impl Future<Output = ()> {
24172    let complete_from_marker: TextRangeMarker = '|'.into();
24173    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24174    let (_, mut marked_ranges) = marked_text_ranges_by(
24175        marked_string,
24176        vec![complete_from_marker.clone(), replace_range_marker.clone()],
24177    );
24178
24179    let complete_from_position =
24180        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24181    let replace_range =
24182        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24183
24184    let mut request =
24185        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24186            let completions = completions.clone();
24187            counter.fetch_add(1, atomic::Ordering::Release);
24188            async move {
24189                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24190                assert_eq!(
24191                    params.text_document_position.position,
24192                    complete_from_position
24193                );
24194                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
24195                    is_incomplete,
24196                    item_defaults: None,
24197                    items: completions
24198                        .iter()
24199                        .map(|completion_text| lsp::CompletionItem {
24200                            label: completion_text.to_string(),
24201                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
24202                                range: replace_range,
24203                                new_text: completion_text.to_string(),
24204                            })),
24205                            ..Default::default()
24206                        })
24207                        .collect(),
24208                })))
24209            }
24210        });
24211
24212    async move {
24213        request.next().await;
24214    }
24215}
24216
24217/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
24218/// given instead, which also contains an `insert` range.
24219///
24220/// This function uses markers to define ranges:
24221/// - `|` marks the cursor position
24222/// - `<>` marks the replace range
24223/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
24224pub fn handle_completion_request_with_insert_and_replace(
24225    cx: &mut EditorLspTestContext,
24226    marked_string: &str,
24227    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
24228    counter: Arc<AtomicUsize>,
24229) -> impl Future<Output = ()> {
24230    let complete_from_marker: TextRangeMarker = '|'.into();
24231    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24232    let insert_range_marker: TextRangeMarker = ('{', '}').into();
24233
24234    let (_, mut marked_ranges) = marked_text_ranges_by(
24235        marked_string,
24236        vec![
24237            complete_from_marker.clone(),
24238            replace_range_marker.clone(),
24239            insert_range_marker.clone(),
24240        ],
24241    );
24242
24243    let complete_from_position =
24244        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24245    let replace_range =
24246        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24247
24248    let insert_range = match marked_ranges.remove(&insert_range_marker) {
24249        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
24250        _ => lsp::Range {
24251            start: replace_range.start,
24252            end: complete_from_position,
24253        },
24254    };
24255
24256    let mut request =
24257        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24258            let completions = completions.clone();
24259            counter.fetch_add(1, atomic::Ordering::Release);
24260            async move {
24261                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24262                assert_eq!(
24263                    params.text_document_position.position, complete_from_position,
24264                    "marker `|` position doesn't match",
24265                );
24266                Ok(Some(lsp::CompletionResponse::Array(
24267                    completions
24268                        .iter()
24269                        .map(|(label, new_text)| lsp::CompletionItem {
24270                            label: label.to_string(),
24271                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24272                                lsp::InsertReplaceEdit {
24273                                    insert: insert_range,
24274                                    replace: replace_range,
24275                                    new_text: new_text.to_string(),
24276                                },
24277                            )),
24278                            ..Default::default()
24279                        })
24280                        .collect(),
24281                )))
24282            }
24283        });
24284
24285    async move {
24286        request.next().await;
24287    }
24288}
24289
24290fn handle_resolve_completion_request(
24291    cx: &mut EditorLspTestContext,
24292    edits: Option<Vec<(&'static str, &'static str)>>,
24293) -> impl Future<Output = ()> {
24294    let edits = edits.map(|edits| {
24295        edits
24296            .iter()
24297            .map(|(marked_string, new_text)| {
24298                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
24299                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
24300                lsp::TextEdit::new(replace_range, new_text.to_string())
24301            })
24302            .collect::<Vec<_>>()
24303    });
24304
24305    let mut request =
24306        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
24307            let edits = edits.clone();
24308            async move {
24309                Ok(lsp::CompletionItem {
24310                    additional_text_edits: edits,
24311                    ..Default::default()
24312                })
24313            }
24314        });
24315
24316    async move {
24317        request.next().await;
24318    }
24319}
24320
24321pub(crate) fn update_test_language_settings(
24322    cx: &mut TestAppContext,
24323    f: impl Fn(&mut AllLanguageSettingsContent),
24324) {
24325    cx.update(|cx| {
24326        SettingsStore::update_global(cx, |store, cx| {
24327            store.update_user_settings::<AllLanguageSettings>(cx, f);
24328        });
24329    });
24330}
24331
24332pub(crate) fn update_test_project_settings(
24333    cx: &mut TestAppContext,
24334    f: impl Fn(&mut ProjectSettings),
24335) {
24336    cx.update(|cx| {
24337        SettingsStore::update_global(cx, |store, cx| {
24338            store.update_user_settings::<ProjectSettings>(cx, f);
24339        });
24340    });
24341}
24342
24343pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
24344    cx.update(|cx| {
24345        assets::Assets.load_test_fonts(cx);
24346        let store = SettingsStore::test(cx);
24347        cx.set_global(store);
24348        theme::init(theme::LoadThemes::JustBase, cx);
24349        release_channel::init(SemanticVersion::default(), cx);
24350        client::init_settings(cx);
24351        language::init(cx);
24352        Project::init_settings(cx);
24353        workspace::init_settings(cx);
24354        crate::init(cx);
24355    });
24356    zlog::init_test();
24357    update_test_language_settings(cx, f);
24358}
24359
24360#[track_caller]
24361fn assert_hunk_revert(
24362    not_reverted_text_with_selections: &str,
24363    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
24364    expected_reverted_text_with_selections: &str,
24365    base_text: &str,
24366    cx: &mut EditorLspTestContext,
24367) {
24368    cx.set_state(not_reverted_text_with_selections);
24369    cx.set_head_text(base_text);
24370    cx.executor().run_until_parked();
24371
24372    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
24373        let snapshot = editor.snapshot(window, cx);
24374        let reverted_hunk_statuses = snapshot
24375            .buffer_snapshot
24376            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
24377            .map(|hunk| hunk.status().kind)
24378            .collect::<Vec<_>>();
24379
24380        editor.git_restore(&Default::default(), window, cx);
24381        reverted_hunk_statuses
24382    });
24383    cx.executor().run_until_parked();
24384    cx.assert_editor_state(expected_reverted_text_with_selections);
24385    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
24386}
24387
24388#[gpui::test(iterations = 10)]
24389async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
24390    init_test(cx, |_| {});
24391
24392    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
24393    let counter = diagnostic_requests.clone();
24394
24395    let fs = FakeFs::new(cx.executor());
24396    fs.insert_tree(
24397        path!("/a"),
24398        json!({
24399            "first.rs": "fn main() { let a = 5; }",
24400            "second.rs": "// Test file",
24401        }),
24402    )
24403    .await;
24404
24405    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24406    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24407    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24408
24409    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24410    language_registry.add(rust_lang());
24411    let mut fake_servers = language_registry.register_fake_lsp(
24412        "Rust",
24413        FakeLspAdapter {
24414            capabilities: lsp::ServerCapabilities {
24415                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
24416                    lsp::DiagnosticOptions {
24417                        identifier: None,
24418                        inter_file_dependencies: true,
24419                        workspace_diagnostics: true,
24420                        work_done_progress_options: Default::default(),
24421                    },
24422                )),
24423                ..Default::default()
24424            },
24425            ..Default::default()
24426        },
24427    );
24428
24429    let editor = workspace
24430        .update(cx, |workspace, window, cx| {
24431            workspace.open_abs_path(
24432                PathBuf::from(path!("/a/first.rs")),
24433                OpenOptions::default(),
24434                window,
24435                cx,
24436            )
24437        })
24438        .unwrap()
24439        .await
24440        .unwrap()
24441        .downcast::<Editor>()
24442        .unwrap();
24443    let fake_server = fake_servers.next().await.unwrap();
24444    let server_id = fake_server.server.server_id();
24445    let mut first_request = fake_server
24446        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
24447            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
24448            let result_id = Some(new_result_id.to_string());
24449            assert_eq!(
24450                params.text_document.uri,
24451                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
24452            );
24453            async move {
24454                Ok(lsp::DocumentDiagnosticReportResult::Report(
24455                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
24456                        related_documents: None,
24457                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
24458                            items: Vec::new(),
24459                            result_id,
24460                        },
24461                    }),
24462                ))
24463            }
24464        });
24465
24466    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
24467        project.update(cx, |project, cx| {
24468            let buffer_id = editor
24469                .read(cx)
24470                .buffer()
24471                .read(cx)
24472                .as_singleton()
24473                .expect("created a singleton buffer")
24474                .read(cx)
24475                .remote_id();
24476            let buffer_result_id = project
24477                .lsp_store()
24478                .read(cx)
24479                .result_id(server_id, buffer_id, cx);
24480            assert_eq!(expected, buffer_result_id);
24481        });
24482    };
24483
24484    ensure_result_id(None, cx);
24485    cx.executor().advance_clock(Duration::from_millis(60));
24486    cx.executor().run_until_parked();
24487    assert_eq!(
24488        diagnostic_requests.load(atomic::Ordering::Acquire),
24489        1,
24490        "Opening file should trigger diagnostic request"
24491    );
24492    first_request
24493        .next()
24494        .await
24495        .expect("should have sent the first diagnostics pull request");
24496    ensure_result_id(Some("1".to_string()), cx);
24497
24498    // Editing should trigger diagnostics
24499    editor.update_in(cx, |editor, window, cx| {
24500        editor.handle_input("2", window, cx)
24501    });
24502    cx.executor().advance_clock(Duration::from_millis(60));
24503    cx.executor().run_until_parked();
24504    assert_eq!(
24505        diagnostic_requests.load(atomic::Ordering::Acquire),
24506        2,
24507        "Editing should trigger diagnostic request"
24508    );
24509    ensure_result_id(Some("2".to_string()), cx);
24510
24511    // Moving cursor should not trigger diagnostic request
24512    editor.update_in(cx, |editor, window, cx| {
24513        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24514            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
24515        });
24516    });
24517    cx.executor().advance_clock(Duration::from_millis(60));
24518    cx.executor().run_until_parked();
24519    assert_eq!(
24520        diagnostic_requests.load(atomic::Ordering::Acquire),
24521        2,
24522        "Cursor movement should not trigger diagnostic request"
24523    );
24524    ensure_result_id(Some("2".to_string()), cx);
24525    // Multiple rapid edits should be debounced
24526    for _ in 0..5 {
24527        editor.update_in(cx, |editor, window, cx| {
24528            editor.handle_input("x", window, cx)
24529        });
24530    }
24531    cx.executor().advance_clock(Duration::from_millis(60));
24532    cx.executor().run_until_parked();
24533
24534    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
24535    assert!(
24536        final_requests <= 4,
24537        "Multiple rapid edits should be debounced (got {final_requests} requests)",
24538    );
24539    ensure_result_id(Some(final_requests.to_string()), cx);
24540}
24541
24542#[gpui::test]
24543async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
24544    // Regression test for issue #11671
24545    // Previously, adding a cursor after moving multiple cursors would reset
24546    // the cursor count instead of adding to the existing cursors.
24547    init_test(cx, |_| {});
24548    let mut cx = EditorTestContext::new(cx).await;
24549
24550    // Create a simple buffer with cursor at start
24551    cx.set_state(indoc! {"
24552        ˇaaaa
24553        bbbb
24554        cccc
24555        dddd
24556        eeee
24557        ffff
24558        gggg
24559        hhhh"});
24560
24561    // Add 2 cursors below (so we have 3 total)
24562    cx.update_editor(|editor, window, cx| {
24563        editor.add_selection_below(&Default::default(), window, cx);
24564        editor.add_selection_below(&Default::default(), window, cx);
24565    });
24566
24567    // Verify we have 3 cursors
24568    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
24569    assert_eq!(
24570        initial_count, 3,
24571        "Should have 3 cursors after adding 2 below"
24572    );
24573
24574    // Move down one line
24575    cx.update_editor(|editor, window, cx| {
24576        editor.move_down(&MoveDown, window, cx);
24577    });
24578
24579    // Add another cursor below
24580    cx.update_editor(|editor, window, cx| {
24581        editor.add_selection_below(&Default::default(), window, cx);
24582    });
24583
24584    // Should now have 4 cursors (3 original + 1 new)
24585    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
24586    assert_eq!(
24587        final_count, 4,
24588        "Should have 4 cursors after moving and adding another"
24589    );
24590}
24591
24592#[gpui::test(iterations = 10)]
24593async fn test_document_colors(cx: &mut TestAppContext) {
24594    let expected_color = Rgba {
24595        r: 0.33,
24596        g: 0.33,
24597        b: 0.33,
24598        a: 0.33,
24599    };
24600
24601    init_test(cx, |_| {});
24602
24603    let fs = FakeFs::new(cx.executor());
24604    fs.insert_tree(
24605        path!("/a"),
24606        json!({
24607            "first.rs": "fn main() { let a = 5; }",
24608        }),
24609    )
24610    .await;
24611
24612    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24613    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24614    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24615
24616    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24617    language_registry.add(rust_lang());
24618    let mut fake_servers = language_registry.register_fake_lsp(
24619        "Rust",
24620        FakeLspAdapter {
24621            capabilities: lsp::ServerCapabilities {
24622                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
24623                ..lsp::ServerCapabilities::default()
24624            },
24625            name: "rust-analyzer",
24626            ..FakeLspAdapter::default()
24627        },
24628    );
24629    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
24630        "Rust",
24631        FakeLspAdapter {
24632            capabilities: lsp::ServerCapabilities {
24633                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
24634                ..lsp::ServerCapabilities::default()
24635            },
24636            name: "not-rust-analyzer",
24637            ..FakeLspAdapter::default()
24638        },
24639    );
24640
24641    let editor = workspace
24642        .update(cx, |workspace, window, cx| {
24643            workspace.open_abs_path(
24644                PathBuf::from(path!("/a/first.rs")),
24645                OpenOptions::default(),
24646                window,
24647                cx,
24648            )
24649        })
24650        .unwrap()
24651        .await
24652        .unwrap()
24653        .downcast::<Editor>()
24654        .unwrap();
24655    let fake_language_server = fake_servers.next().await.unwrap();
24656    let fake_language_server_without_capabilities =
24657        fake_servers_without_capabilities.next().await.unwrap();
24658    let requests_made = Arc::new(AtomicUsize::new(0));
24659    let closure_requests_made = Arc::clone(&requests_made);
24660    let mut color_request_handle = fake_language_server
24661        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
24662            let requests_made = Arc::clone(&closure_requests_made);
24663            async move {
24664                assert_eq!(
24665                    params.text_document.uri,
24666                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
24667                );
24668                requests_made.fetch_add(1, atomic::Ordering::Release);
24669                Ok(vec![
24670                    lsp::ColorInformation {
24671                        range: lsp::Range {
24672                            start: lsp::Position {
24673                                line: 0,
24674                                character: 0,
24675                            },
24676                            end: lsp::Position {
24677                                line: 0,
24678                                character: 1,
24679                            },
24680                        },
24681                        color: lsp::Color {
24682                            red: 0.33,
24683                            green: 0.33,
24684                            blue: 0.33,
24685                            alpha: 0.33,
24686                        },
24687                    },
24688                    lsp::ColorInformation {
24689                        range: lsp::Range {
24690                            start: lsp::Position {
24691                                line: 0,
24692                                character: 0,
24693                            },
24694                            end: lsp::Position {
24695                                line: 0,
24696                                character: 1,
24697                            },
24698                        },
24699                        color: lsp::Color {
24700                            red: 0.33,
24701                            green: 0.33,
24702                            blue: 0.33,
24703                            alpha: 0.33,
24704                        },
24705                    },
24706                ])
24707            }
24708        });
24709
24710    let _handle = fake_language_server_without_capabilities
24711        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
24712            panic!("Should not be called");
24713        });
24714    cx.executor().advance_clock(Duration::from_millis(100));
24715    color_request_handle.next().await.unwrap();
24716    cx.run_until_parked();
24717    assert_eq!(
24718        1,
24719        requests_made.load(atomic::Ordering::Acquire),
24720        "Should query for colors once per editor open"
24721    );
24722    editor.update_in(cx, |editor, _, cx| {
24723        assert_eq!(
24724            vec![expected_color],
24725            extract_color_inlays(editor, cx),
24726            "Should have an initial inlay"
24727        );
24728    });
24729
24730    // opening another file in a split should not influence the LSP query counter
24731    workspace
24732        .update(cx, |workspace, window, cx| {
24733            assert_eq!(
24734                workspace.panes().len(),
24735                1,
24736                "Should have one pane with one editor"
24737            );
24738            workspace.move_item_to_pane_in_direction(
24739                &MoveItemToPaneInDirection {
24740                    direction: SplitDirection::Right,
24741                    focus: false,
24742                    clone: true,
24743                },
24744                window,
24745                cx,
24746            );
24747        })
24748        .unwrap();
24749    cx.run_until_parked();
24750    workspace
24751        .update(cx, |workspace, _, cx| {
24752            let panes = workspace.panes();
24753            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
24754            for pane in panes {
24755                let editor = pane
24756                    .read(cx)
24757                    .active_item()
24758                    .and_then(|item| item.downcast::<Editor>())
24759                    .expect("Should have opened an editor in each split");
24760                let editor_file = editor
24761                    .read(cx)
24762                    .buffer()
24763                    .read(cx)
24764                    .as_singleton()
24765                    .expect("test deals with singleton buffers")
24766                    .read(cx)
24767                    .file()
24768                    .expect("test buffese should have a file")
24769                    .path();
24770                assert_eq!(
24771                    editor_file.as_ref(),
24772                    Path::new("first.rs"),
24773                    "Both editors should be opened for the same file"
24774                )
24775            }
24776        })
24777        .unwrap();
24778
24779    cx.executor().advance_clock(Duration::from_millis(500));
24780    let save = editor.update_in(cx, |editor, window, cx| {
24781        editor.move_to_end(&MoveToEnd, window, cx);
24782        editor.handle_input("dirty", window, cx);
24783        editor.save(
24784            SaveOptions {
24785                format: true,
24786                autosave: true,
24787            },
24788            project.clone(),
24789            window,
24790            cx,
24791        )
24792    });
24793    save.await.unwrap();
24794
24795    color_request_handle.next().await.unwrap();
24796    cx.run_until_parked();
24797    assert_eq!(
24798        3,
24799        requests_made.load(atomic::Ordering::Acquire),
24800        "Should query for colors once per save and once per formatting after save"
24801    );
24802
24803    drop(editor);
24804    let close = workspace
24805        .update(cx, |workspace, window, cx| {
24806            workspace.active_pane().update(cx, |pane, cx| {
24807                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24808            })
24809        })
24810        .unwrap();
24811    close.await.unwrap();
24812    let close = workspace
24813        .update(cx, |workspace, window, cx| {
24814            workspace.active_pane().update(cx, |pane, cx| {
24815                pane.close_active_item(&CloseActiveItem::default(), window, cx)
24816            })
24817        })
24818        .unwrap();
24819    close.await.unwrap();
24820    assert_eq!(
24821        3,
24822        requests_made.load(atomic::Ordering::Acquire),
24823        "After saving and closing all editors, no extra requests should be made"
24824    );
24825    workspace
24826        .update(cx, |workspace, _, cx| {
24827            assert!(
24828                workspace.active_item(cx).is_none(),
24829                "Should close all editors"
24830            )
24831        })
24832        .unwrap();
24833
24834    workspace
24835        .update(cx, |workspace, window, cx| {
24836            workspace.active_pane().update(cx, |pane, cx| {
24837                pane.navigate_backward(&Default::default(), window, cx);
24838            })
24839        })
24840        .unwrap();
24841    cx.executor().advance_clock(Duration::from_millis(100));
24842    cx.run_until_parked();
24843    let editor = workspace
24844        .update(cx, |workspace, _, cx| {
24845            workspace
24846                .active_item(cx)
24847                .expect("Should have reopened the editor again after navigating back")
24848                .downcast::<Editor>()
24849                .expect("Should be an editor")
24850        })
24851        .unwrap();
24852    color_request_handle.next().await.unwrap();
24853    assert_eq!(
24854        3,
24855        requests_made.load(atomic::Ordering::Acquire),
24856        "Cache should be reused on buffer close and reopen"
24857    );
24858    editor.update(cx, |editor, cx| {
24859        assert_eq!(
24860            vec![expected_color],
24861            extract_color_inlays(editor, cx),
24862            "Should have an initial inlay"
24863        );
24864    });
24865}
24866
24867#[gpui::test]
24868async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
24869    init_test(cx, |_| {});
24870    let (editor, cx) = cx.add_window_view(Editor::single_line);
24871    editor.update_in(cx, |editor, window, cx| {
24872        editor.set_text("oops\n\nwow\n", window, cx)
24873    });
24874    cx.run_until_parked();
24875    editor.update(cx, |editor, cx| {
24876        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
24877    });
24878    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
24879    cx.run_until_parked();
24880    editor.update(cx, |editor, cx| {
24881        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
24882    });
24883}
24884
24885#[gpui::test]
24886async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
24887    init_test(cx, |_| {});
24888
24889    cx.update(|cx| {
24890        register_project_item::<Editor>(cx);
24891    });
24892
24893    let fs = FakeFs::new(cx.executor());
24894    fs.insert_tree("/root1", json!({})).await;
24895    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
24896        .await;
24897
24898    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
24899    let (workspace, cx) =
24900        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24901
24902    let worktree_id = project.update(cx, |project, cx| {
24903        project.worktrees(cx).next().unwrap().read(cx).id()
24904    });
24905
24906    let handle = workspace
24907        .update_in(cx, |workspace, window, cx| {
24908            let project_path = (worktree_id, "one.pdf");
24909            workspace.open_path(project_path, None, true, window, cx)
24910        })
24911        .await
24912        .unwrap();
24913
24914    assert_eq!(
24915        handle.to_any().entity_type(),
24916        TypeId::of::<InvalidBufferView>()
24917    );
24918}
24919
24920#[track_caller]
24921fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
24922    editor
24923        .all_inlays(cx)
24924        .into_iter()
24925        .filter_map(|inlay| inlay.get_color())
24926        .map(Rgba::from)
24927        .collect()
24928}