editor_tests.rs

    1use super::*;
    2use crate::{
    3    scroll::scroll_amount::ScrollAmount,
    4    test::{
    5        assert_text_with_selections, build_editor,
    6        editor_lsp_test_context::{git_commit_lang, EditorLspTestContext},
    7        editor_test_context::EditorTestContext,
    8        select_ranges,
    9    },
   10    JoinLines,
   11};
   12use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   13use futures::StreamExt;
   14use gpui::{
   15    div, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal, VisualTestContext,
   16    WindowBounds, WindowOptions,
   17};
   18use indoc::indoc;
   19use language::{
   20    language_settings::{
   21        AllLanguageSettings, AllLanguageSettingsContent, CompletionSettings,
   22        LanguageSettingsContent, PrettierSettings,
   23    },
   24    BracketPairConfig,
   25    Capability::ReadWrite,
   26    FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher, LanguageName,
   27    Override, Point,
   28};
   29use language_settings::{Formatter, FormatterList, IndentGuideSettings};
   30use multi_buffer::{IndentGuide, PathKey};
   31use parking_lot::Mutex;
   32use pretty_assertions::{assert_eq, assert_ne};
   33use project::{
   34    debugger::breakpoint_store::{BreakpointKind, SerializedBreakpoint},
   35    project_settings::{LspSettings, ProjectSettings},
   36    FakeFs,
   37};
   38use serde_json::{self, json};
   39use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   40use std::{
   41    iter,
   42    sync::atomic::{self, AtomicUsize},
   43};
   44use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   45use text::ToPoint as _;
   46use unindent::Unindent;
   47use util::{
   48    assert_set_eq, path,
   49    test::{marked_text_ranges, marked_text_ranges_by, sample_text, TextRangeMarker},
   50    uri,
   51};
   52use workspace::{
   53    item::{FollowEvent, FollowableItem, Item, ItemHandle},
   54    NavigationEntry, ViewId,
   55};
   56
   57#[gpui::test]
   58fn test_edit_events(cx: &mut TestAppContext) {
   59    init_test(cx, |_| {});
   60
   61    let buffer = cx.new(|cx| {
   62        let mut buffer = language::Buffer::local("123456", cx);
   63        buffer.set_group_interval(Duration::from_secs(1));
   64        buffer
   65    });
   66
   67    let events = Rc::new(RefCell::new(Vec::new()));
   68    let editor1 = cx.add_window({
   69        let events = events.clone();
   70        |window, cx| {
   71            let entity = cx.entity().clone();
   72            cx.subscribe_in(
   73                &entity,
   74                window,
   75                move |_, _, event: &EditorEvent, _, _| match event {
   76                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   77                    EditorEvent::BufferEdited => {
   78                        events.borrow_mut().push(("editor1", "buffer edited"))
   79                    }
   80                    _ => {}
   81                },
   82            )
   83            .detach();
   84            Editor::for_buffer(buffer.clone(), None, window, cx)
   85        }
   86    });
   87
   88    let editor2 = cx.add_window({
   89        let events = events.clone();
   90        |window, cx| {
   91            cx.subscribe_in(
   92                &cx.entity().clone(),
   93                window,
   94                move |_, _, event: &EditorEvent, _, _| match event {
   95                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
   96                    EditorEvent::BufferEdited => {
   97                        events.borrow_mut().push(("editor2", "buffer edited"))
   98                    }
   99                    _ => {}
  100                },
  101            )
  102            .detach();
  103            Editor::for_buffer(buffer.clone(), None, window, cx)
  104        }
  105    });
  106
  107    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  108
  109    // Mutating editor 1 will emit an `Edited` event only for that editor.
  110    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  111    assert_eq!(
  112        mem::take(&mut *events.borrow_mut()),
  113        [
  114            ("editor1", "edited"),
  115            ("editor1", "buffer edited"),
  116            ("editor2", "buffer edited"),
  117        ]
  118    );
  119
  120    // Mutating editor 2 will emit an `Edited` event only for that editor.
  121    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  122    assert_eq!(
  123        mem::take(&mut *events.borrow_mut()),
  124        [
  125            ("editor2", "edited"),
  126            ("editor1", "buffer edited"),
  127            ("editor2", "buffer edited"),
  128        ]
  129    );
  130
  131    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  132    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  133    assert_eq!(
  134        mem::take(&mut *events.borrow_mut()),
  135        [
  136            ("editor1", "edited"),
  137            ("editor1", "buffer edited"),
  138            ("editor2", "buffer edited"),
  139        ]
  140    );
  141
  142    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  143    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  144    assert_eq!(
  145        mem::take(&mut *events.borrow_mut()),
  146        [
  147            ("editor1", "edited"),
  148            ("editor1", "buffer edited"),
  149            ("editor2", "buffer edited"),
  150        ]
  151    );
  152
  153    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  154    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  155    assert_eq!(
  156        mem::take(&mut *events.borrow_mut()),
  157        [
  158            ("editor2", "edited"),
  159            ("editor1", "buffer edited"),
  160            ("editor2", "buffer edited"),
  161        ]
  162    );
  163
  164    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  165    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  166    assert_eq!(
  167        mem::take(&mut *events.borrow_mut()),
  168        [
  169            ("editor2", "edited"),
  170            ("editor1", "buffer edited"),
  171            ("editor2", "buffer edited"),
  172        ]
  173    );
  174
  175    // No event is emitted when the mutation is a no-op.
  176    _ = editor2.update(cx, |editor, window, cx| {
  177        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
  178
  179        editor.backspace(&Backspace, window, cx);
  180    });
  181    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  182}
  183
  184#[gpui::test]
  185fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  186    init_test(cx, |_| {});
  187
  188    let mut now = Instant::now();
  189    let group_interval = Duration::from_millis(1);
  190    let buffer = cx.new(|cx| {
  191        let mut buf = language::Buffer::local("123456", cx);
  192        buf.set_group_interval(group_interval);
  193        buf
  194    });
  195    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  196    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  197
  198    _ = editor.update(cx, |editor, window, cx| {
  199        editor.start_transaction_at(now, window, cx);
  200        editor.change_selections(None, window, cx, |s| s.select_ranges([2..4]));
  201
  202        editor.insert("cd", window, cx);
  203        editor.end_transaction_at(now, cx);
  204        assert_eq!(editor.text(cx), "12cd56");
  205        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  206
  207        editor.start_transaction_at(now, window, cx);
  208        editor.change_selections(None, window, cx, |s| s.select_ranges([4..5]));
  209        editor.insert("e", window, cx);
  210        editor.end_transaction_at(now, cx);
  211        assert_eq!(editor.text(cx), "12cde6");
  212        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  213
  214        now += group_interval + Duration::from_millis(1);
  215        editor.change_selections(None, window, cx, |s| s.select_ranges([2..2]));
  216
  217        // Simulate an edit in another editor
  218        buffer.update(cx, |buffer, cx| {
  219            buffer.start_transaction_at(now, cx);
  220            buffer.edit([(0..1, "a")], None, cx);
  221            buffer.edit([(1..1, "b")], None, cx);
  222            buffer.end_transaction_at(now, cx);
  223        });
  224
  225        assert_eq!(editor.text(cx), "ab2cde6");
  226        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  227
  228        // Last transaction happened past the group interval in a different editor.
  229        // Undo it individually and don't restore selections.
  230        editor.undo(&Undo, window, cx);
  231        assert_eq!(editor.text(cx), "12cde6");
  232        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  233
  234        // First two transactions happened within the group interval in this editor.
  235        // Undo them together and restore selections.
  236        editor.undo(&Undo, window, cx);
  237        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  238        assert_eq!(editor.text(cx), "123456");
  239        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  240
  241        // Redo the first two transactions together.
  242        editor.redo(&Redo, window, cx);
  243        assert_eq!(editor.text(cx), "12cde6");
  244        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  245
  246        // Redo the last transaction on its own.
  247        editor.redo(&Redo, window, cx);
  248        assert_eq!(editor.text(cx), "ab2cde6");
  249        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  250
  251        // Test empty transactions.
  252        editor.start_transaction_at(now, window, cx);
  253        editor.end_transaction_at(now, cx);
  254        editor.undo(&Undo, window, cx);
  255        assert_eq!(editor.text(cx), "12cde6");
  256    });
  257}
  258
  259#[gpui::test]
  260fn test_ime_composition(cx: &mut TestAppContext) {
  261    init_test(cx, |_| {});
  262
  263    let buffer = cx.new(|cx| {
  264        let mut buffer = language::Buffer::local("abcde", cx);
  265        // Ensure automatic grouping doesn't occur.
  266        buffer.set_group_interval(Duration::ZERO);
  267        buffer
  268    });
  269
  270    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  271    cx.add_window(|window, cx| {
  272        let mut editor = build_editor(buffer.clone(), window, cx);
  273
  274        // Start a new IME composition.
  275        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  276        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  277        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  278        assert_eq!(editor.text(cx), "äbcde");
  279        assert_eq!(
  280            editor.marked_text_ranges(cx),
  281            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  282        );
  283
  284        // Finalize IME composition.
  285        editor.replace_text_in_range(None, "ā", window, cx);
  286        assert_eq!(editor.text(cx), "ābcde");
  287        assert_eq!(editor.marked_text_ranges(cx), None);
  288
  289        // IME composition edits are grouped and are undone/redone at once.
  290        editor.undo(&Default::default(), window, cx);
  291        assert_eq!(editor.text(cx), "abcde");
  292        assert_eq!(editor.marked_text_ranges(cx), None);
  293        editor.redo(&Default::default(), window, cx);
  294        assert_eq!(editor.text(cx), "ābcde");
  295        assert_eq!(editor.marked_text_ranges(cx), None);
  296
  297        // Start a new IME composition.
  298        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  299        assert_eq!(
  300            editor.marked_text_ranges(cx),
  301            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  302        );
  303
  304        // Undoing during an IME composition cancels it.
  305        editor.undo(&Default::default(), window, cx);
  306        assert_eq!(editor.text(cx), "ābcde");
  307        assert_eq!(editor.marked_text_ranges(cx), None);
  308
  309        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  310        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  311        assert_eq!(editor.text(cx), "ābcdè");
  312        assert_eq!(
  313            editor.marked_text_ranges(cx),
  314            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  315        );
  316
  317        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  318        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  319        assert_eq!(editor.text(cx), "ābcdę");
  320        assert_eq!(editor.marked_text_ranges(cx), None);
  321
  322        // Start a new IME composition with multiple cursors.
  323        editor.change_selections(None, window, cx, |s| {
  324            s.select_ranges([
  325                OffsetUtf16(1)..OffsetUtf16(1),
  326                OffsetUtf16(3)..OffsetUtf16(3),
  327                OffsetUtf16(5)..OffsetUtf16(5),
  328            ])
  329        });
  330        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  331        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  332        assert_eq!(
  333            editor.marked_text_ranges(cx),
  334            Some(vec![
  335                OffsetUtf16(0)..OffsetUtf16(3),
  336                OffsetUtf16(4)..OffsetUtf16(7),
  337                OffsetUtf16(8)..OffsetUtf16(11)
  338            ])
  339        );
  340
  341        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  342        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  343        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  344        assert_eq!(
  345            editor.marked_text_ranges(cx),
  346            Some(vec![
  347                OffsetUtf16(1)..OffsetUtf16(2),
  348                OffsetUtf16(5)..OffsetUtf16(6),
  349                OffsetUtf16(9)..OffsetUtf16(10)
  350            ])
  351        );
  352
  353        // Finalize IME composition with multiple cursors.
  354        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  355        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  356        assert_eq!(editor.marked_text_ranges(cx), None);
  357
  358        editor
  359    });
  360}
  361
  362#[gpui::test]
  363fn test_selection_with_mouse(cx: &mut TestAppContext) {
  364    init_test(cx, |_| {});
  365
  366    let editor = cx.add_window(|window, cx| {
  367        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  368        build_editor(buffer, window, cx)
  369    });
  370
  371    _ = editor.update(cx, |editor, window, cx| {
  372        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  373    });
  374    assert_eq!(
  375        editor
  376            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  377            .unwrap(),
  378        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  379    );
  380
  381    _ = editor.update(cx, |editor, window, cx| {
  382        editor.update_selection(
  383            DisplayPoint::new(DisplayRow(3), 3),
  384            0,
  385            gpui::Point::<f32>::default(),
  386            window,
  387            cx,
  388        );
  389    });
  390
  391    assert_eq!(
  392        editor
  393            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  394            .unwrap(),
  395        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  396    );
  397
  398    _ = editor.update(cx, |editor, window, cx| {
  399        editor.update_selection(
  400            DisplayPoint::new(DisplayRow(1), 1),
  401            0,
  402            gpui::Point::<f32>::default(),
  403            window,
  404            cx,
  405        );
  406    });
  407
  408    assert_eq!(
  409        editor
  410            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  411            .unwrap(),
  412        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  413    );
  414
  415    _ = editor.update(cx, |editor, window, cx| {
  416        editor.end_selection(window, cx);
  417        editor.update_selection(
  418            DisplayPoint::new(DisplayRow(3), 3),
  419            0,
  420            gpui::Point::<f32>::default(),
  421            window,
  422            cx,
  423        );
  424    });
  425
  426    assert_eq!(
  427        editor
  428            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  429            .unwrap(),
  430        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  431    );
  432
  433    _ = editor.update(cx, |editor, window, cx| {
  434        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  435        editor.update_selection(
  436            DisplayPoint::new(DisplayRow(0), 0),
  437            0,
  438            gpui::Point::<f32>::default(),
  439            window,
  440            cx,
  441        );
  442    });
  443
  444    assert_eq!(
  445        editor
  446            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  447            .unwrap(),
  448        [
  449            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  450            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  451        ]
  452    );
  453
  454    _ = editor.update(cx, |editor, window, cx| {
  455        editor.end_selection(window, cx);
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  461            .unwrap(),
  462        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  463    );
  464}
  465
  466#[gpui::test]
  467fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  468    init_test(cx, |_| {});
  469
  470    let editor = cx.add_window(|window, cx| {
  471        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  472        build_editor(buffer, window, cx)
  473    });
  474
  475    _ = editor.update(cx, |editor, window, cx| {
  476        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  477    });
  478
  479    _ = editor.update(cx, |editor, window, cx| {
  480        editor.end_selection(window, cx);
  481    });
  482
  483    _ = editor.update(cx, |editor, window, cx| {
  484        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  485    });
  486
  487    _ = editor.update(cx, |editor, window, cx| {
  488        editor.end_selection(window, cx);
  489    });
  490
  491    assert_eq!(
  492        editor
  493            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  494            .unwrap(),
  495        [
  496            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  497            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  498        ]
  499    );
  500
  501    _ = editor.update(cx, |editor, window, cx| {
  502        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  503    });
  504
  505    _ = editor.update(cx, |editor, window, cx| {
  506        editor.end_selection(window, cx);
  507    });
  508
  509    assert_eq!(
  510        editor
  511            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  512            .unwrap(),
  513        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  514    );
  515}
  516
  517#[gpui::test]
  518fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  519    init_test(cx, |_| {});
  520
  521    let editor = cx.add_window(|window, cx| {
  522        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  523        build_editor(buffer, window, cx)
  524    });
  525
  526    _ = editor.update(cx, |editor, window, cx| {
  527        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  528        assert_eq!(
  529            editor.selections.display_ranges(cx),
  530            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  531        );
  532    });
  533
  534    _ = editor.update(cx, |editor, window, cx| {
  535        editor.update_selection(
  536            DisplayPoint::new(DisplayRow(3), 3),
  537            0,
  538            gpui::Point::<f32>::default(),
  539            window,
  540            cx,
  541        );
  542        assert_eq!(
  543            editor.selections.display_ranges(cx),
  544            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  545        );
  546    });
  547
  548    _ = editor.update(cx, |editor, window, cx| {
  549        editor.cancel(&Cancel, window, cx);
  550        editor.update_selection(
  551            DisplayPoint::new(DisplayRow(1), 1),
  552            0,
  553            gpui::Point::<f32>::default(),
  554            window,
  555            cx,
  556        );
  557        assert_eq!(
  558            editor.selections.display_ranges(cx),
  559            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  560        );
  561    });
  562}
  563
  564#[gpui::test]
  565fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  566    init_test(cx, |_| {});
  567
  568    let editor = cx.add_window(|window, cx| {
  569        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  570        build_editor(buffer, window, cx)
  571    });
  572
  573    _ = editor.update(cx, |editor, window, cx| {
  574        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  575        assert_eq!(
  576            editor.selections.display_ranges(cx),
  577            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  578        );
  579
  580        editor.move_down(&Default::default(), window, cx);
  581        assert_eq!(
  582            editor.selections.display_ranges(cx),
  583            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  584        );
  585
  586        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  587        assert_eq!(
  588            editor.selections.display_ranges(cx),
  589            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  590        );
  591
  592        editor.move_up(&Default::default(), window, cx);
  593        assert_eq!(
  594            editor.selections.display_ranges(cx),
  595            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  596        );
  597    });
  598}
  599
  600#[gpui::test]
  601fn test_clone(cx: &mut TestAppContext) {
  602    init_test(cx, |_| {});
  603
  604    let (text, selection_ranges) = marked_text_ranges(
  605        indoc! {"
  606            one
  607            two
  608            threeˇ
  609            four
  610            fiveˇ
  611        "},
  612        true,
  613    );
  614
  615    let editor = cx.add_window(|window, cx| {
  616        let buffer = MultiBuffer::build_simple(&text, cx);
  617        build_editor(buffer, window, cx)
  618    });
  619
  620    _ = editor.update(cx, |editor, window, cx| {
  621        editor.change_selections(None, window, cx, |s| {
  622            s.select_ranges(selection_ranges.clone())
  623        });
  624        editor.fold_creases(
  625            vec![
  626                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  627                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  628            ],
  629            true,
  630            window,
  631            cx,
  632        );
  633    });
  634
  635    let cloned_editor = editor
  636        .update(cx, |editor, _, cx| {
  637            cx.open_window(Default::default(), |window, cx| {
  638                cx.new(|cx| editor.clone(window, cx))
  639            })
  640        })
  641        .unwrap()
  642        .unwrap();
  643
  644    let snapshot = editor
  645        .update(cx, |e, window, cx| e.snapshot(window, cx))
  646        .unwrap();
  647    let cloned_snapshot = cloned_editor
  648        .update(cx, |e, window, cx| e.snapshot(window, cx))
  649        .unwrap();
  650
  651    assert_eq!(
  652        cloned_editor
  653            .update(cx, |e, _, cx| e.display_text(cx))
  654            .unwrap(),
  655        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  656    );
  657    assert_eq!(
  658        cloned_snapshot
  659            .folds_in_range(0..text.len())
  660            .collect::<Vec<_>>(),
  661        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  662    );
  663    assert_set_eq!(
  664        cloned_editor
  665            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  666            .unwrap(),
  667        editor
  668            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  669            .unwrap()
  670    );
  671    assert_set_eq!(
  672        cloned_editor
  673            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  674            .unwrap(),
  675        editor
  676            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  677            .unwrap()
  678    );
  679}
  680
  681#[gpui::test]
  682async fn test_navigation_history(cx: &mut TestAppContext) {
  683    init_test(cx, |_| {});
  684
  685    use workspace::item::Item;
  686
  687    let fs = FakeFs::new(cx.executor());
  688    let project = Project::test(fs, [], cx).await;
  689    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  690    let pane = workspace
  691        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  692        .unwrap();
  693
  694    _ = workspace.update(cx, |_v, window, cx| {
  695        cx.new(|cx| {
  696            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  697            let mut editor = build_editor(buffer.clone(), window, cx);
  698            let handle = cx.entity();
  699            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  700
  701            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  702                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  703            }
  704
  705            // Move the cursor a small distance.
  706            // Nothing is added to the navigation history.
  707            editor.change_selections(None, window, cx, |s| {
  708                s.select_display_ranges([
  709                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  710                ])
  711            });
  712            editor.change_selections(None, window, cx, |s| {
  713                s.select_display_ranges([
  714                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  715                ])
  716            });
  717            assert!(pop_history(&mut editor, cx).is_none());
  718
  719            // Move the cursor a large distance.
  720            // The history can jump back to the previous position.
  721            editor.change_selections(None, window, cx, |s| {
  722                s.select_display_ranges([
  723                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  724                ])
  725            });
  726            let nav_entry = pop_history(&mut editor, cx).unwrap();
  727            editor.navigate(nav_entry.data.unwrap(), window, cx);
  728            assert_eq!(nav_entry.item.id(), cx.entity_id());
  729            assert_eq!(
  730                editor.selections.display_ranges(cx),
  731                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  732            );
  733            assert!(pop_history(&mut editor, cx).is_none());
  734
  735            // Move the cursor a small distance via the mouse.
  736            // Nothing is added to the navigation history.
  737            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  738            editor.end_selection(window, cx);
  739            assert_eq!(
  740                editor.selections.display_ranges(cx),
  741                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  742            );
  743            assert!(pop_history(&mut editor, cx).is_none());
  744
  745            // Move the cursor a large distance via the mouse.
  746            // The history can jump back to the previous position.
  747            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  748            editor.end_selection(window, cx);
  749            assert_eq!(
  750                editor.selections.display_ranges(cx),
  751                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  752            );
  753            let nav_entry = pop_history(&mut editor, cx).unwrap();
  754            editor.navigate(nav_entry.data.unwrap(), window, cx);
  755            assert_eq!(nav_entry.item.id(), cx.entity_id());
  756            assert_eq!(
  757                editor.selections.display_ranges(cx),
  758                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  759            );
  760            assert!(pop_history(&mut editor, cx).is_none());
  761
  762            // Set scroll position to check later
  763            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  764            let original_scroll_position = editor.scroll_manager.anchor();
  765
  766            // Jump to the end of the document and adjust scroll
  767            editor.move_to_end(&MoveToEnd, window, cx);
  768            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  769            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  770
  771            let nav_entry = pop_history(&mut editor, cx).unwrap();
  772            editor.navigate(nav_entry.data.unwrap(), window, cx);
  773            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  774
  775            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  776            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  777            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  778            let invalid_point = Point::new(9999, 0);
  779            editor.navigate(
  780                Box::new(NavigationData {
  781                    cursor_anchor: invalid_anchor,
  782                    cursor_position: invalid_point,
  783                    scroll_anchor: ScrollAnchor {
  784                        anchor: invalid_anchor,
  785                        offset: Default::default(),
  786                    },
  787                    scroll_top_row: invalid_point.row,
  788                }),
  789                window,
  790                cx,
  791            );
  792            assert_eq!(
  793                editor.selections.display_ranges(cx),
  794                &[editor.max_point(cx)..editor.max_point(cx)]
  795            );
  796            assert_eq!(
  797                editor.scroll_position(cx),
  798                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  799            );
  800
  801            editor
  802        })
  803    });
  804}
  805
  806#[gpui::test]
  807fn test_cancel(cx: &mut TestAppContext) {
  808    init_test(cx, |_| {});
  809
  810    let editor = cx.add_window(|window, cx| {
  811        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  812        build_editor(buffer, window, cx)
  813    });
  814
  815    _ = editor.update(cx, |editor, window, cx| {
  816        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  817        editor.update_selection(
  818            DisplayPoint::new(DisplayRow(1), 1),
  819            0,
  820            gpui::Point::<f32>::default(),
  821            window,
  822            cx,
  823        );
  824        editor.end_selection(window, cx);
  825
  826        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  827        editor.update_selection(
  828            DisplayPoint::new(DisplayRow(0), 3),
  829            0,
  830            gpui::Point::<f32>::default(),
  831            window,
  832            cx,
  833        );
  834        editor.end_selection(window, cx);
  835        assert_eq!(
  836            editor.selections.display_ranges(cx),
  837            [
  838                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  839                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  840            ]
  841        );
  842    });
  843
  844    _ = editor.update(cx, |editor, window, cx| {
  845        editor.cancel(&Cancel, window, cx);
  846        assert_eq!(
  847            editor.selections.display_ranges(cx),
  848            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  849        );
  850    });
  851
  852    _ = editor.update(cx, |editor, window, cx| {
  853        editor.cancel(&Cancel, window, cx);
  854        assert_eq!(
  855            editor.selections.display_ranges(cx),
  856            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  857        );
  858    });
  859}
  860
  861#[gpui::test]
  862fn test_fold_action(cx: &mut TestAppContext) {
  863    init_test(cx, |_| {});
  864
  865    let editor = cx.add_window(|window, cx| {
  866        let buffer = MultiBuffer::build_simple(
  867            &"
  868                impl Foo {
  869                    // Hello!
  870
  871                    fn a() {
  872                        1
  873                    }
  874
  875                    fn b() {
  876                        2
  877                    }
  878
  879                    fn c() {
  880                        3
  881                    }
  882                }
  883            "
  884            .unindent(),
  885            cx,
  886        );
  887        build_editor(buffer.clone(), window, cx)
  888    });
  889
  890    _ = editor.update(cx, |editor, window, cx| {
  891        editor.change_selections(None, window, cx, |s| {
  892            s.select_display_ranges([
  893                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  894            ]);
  895        });
  896        editor.fold(&Fold, window, cx);
  897        assert_eq!(
  898            editor.display_text(cx),
  899            "
  900                impl Foo {
  901                    // Hello!
  902
  903                    fn a() {
  904                        1
  905                    }
  906
  907                    fn b() {⋯
  908                    }
  909
  910                    fn c() {⋯
  911                    }
  912                }
  913            "
  914            .unindent(),
  915        );
  916
  917        editor.fold(&Fold, window, cx);
  918        assert_eq!(
  919            editor.display_text(cx),
  920            "
  921                impl Foo {⋯
  922                }
  923            "
  924            .unindent(),
  925        );
  926
  927        editor.unfold_lines(&UnfoldLines, window, cx);
  928        assert_eq!(
  929            editor.display_text(cx),
  930            "
  931                impl Foo {
  932                    // Hello!
  933
  934                    fn a() {
  935                        1
  936                    }
  937
  938                    fn b() {⋯
  939                    }
  940
  941                    fn c() {⋯
  942                    }
  943                }
  944            "
  945            .unindent(),
  946        );
  947
  948        editor.unfold_lines(&UnfoldLines, window, cx);
  949        assert_eq!(
  950            editor.display_text(cx),
  951            editor.buffer.read(cx).read(cx).text()
  952        );
  953    });
  954}
  955
  956#[gpui::test]
  957fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  958    init_test(cx, |_| {});
  959
  960    let editor = cx.add_window(|window, cx| {
  961        let buffer = MultiBuffer::build_simple(
  962            &"
  963                class Foo:
  964                    # Hello!
  965
  966                    def a():
  967                        print(1)
  968
  969                    def b():
  970                        print(2)
  971
  972                    def c():
  973                        print(3)
  974            "
  975            .unindent(),
  976            cx,
  977        );
  978        build_editor(buffer.clone(), window, cx)
  979    });
  980
  981    _ = editor.update(cx, |editor, window, cx| {
  982        editor.change_selections(None, window, cx, |s| {
  983            s.select_display_ranges([
  984                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
  985            ]);
  986        });
  987        editor.fold(&Fold, window, cx);
  988        assert_eq!(
  989            editor.display_text(cx),
  990            "
  991                class Foo:
  992                    # Hello!
  993
  994                    def a():
  995                        print(1)
  996
  997                    def b():⋯
  998
  999                    def c():⋯
 1000            "
 1001            .unindent(),
 1002        );
 1003
 1004        editor.fold(&Fold, window, cx);
 1005        assert_eq!(
 1006            editor.display_text(cx),
 1007            "
 1008                class Foo:⋯
 1009            "
 1010            .unindent(),
 1011        );
 1012
 1013        editor.unfold_lines(&UnfoldLines, window, cx);
 1014        assert_eq!(
 1015            editor.display_text(cx),
 1016            "
 1017                class Foo:
 1018                    # Hello!
 1019
 1020                    def a():
 1021                        print(1)
 1022
 1023                    def b():⋯
 1024
 1025                    def c():⋯
 1026            "
 1027            .unindent(),
 1028        );
 1029
 1030        editor.unfold_lines(&UnfoldLines, window, cx);
 1031        assert_eq!(
 1032            editor.display_text(cx),
 1033            editor.buffer.read(cx).read(cx).text()
 1034        );
 1035    });
 1036}
 1037
 1038#[gpui::test]
 1039fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1040    init_test(cx, |_| {});
 1041
 1042    let editor = cx.add_window(|window, cx| {
 1043        let buffer = MultiBuffer::build_simple(
 1044            &"
 1045                class Foo:
 1046                    # Hello!
 1047
 1048                    def a():
 1049                        print(1)
 1050
 1051                    def b():
 1052                        print(2)
 1053
 1054
 1055                    def c():
 1056                        print(3)
 1057
 1058
 1059            "
 1060            .unindent(),
 1061            cx,
 1062        );
 1063        build_editor(buffer.clone(), window, cx)
 1064    });
 1065
 1066    _ = editor.update(cx, |editor, window, cx| {
 1067        editor.change_selections(None, window, cx, |s| {
 1068            s.select_display_ranges([
 1069                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1070            ]);
 1071        });
 1072        editor.fold(&Fold, window, cx);
 1073        assert_eq!(
 1074            editor.display_text(cx),
 1075            "
 1076                class Foo:
 1077                    # Hello!
 1078
 1079                    def a():
 1080                        print(1)
 1081
 1082                    def b():⋯
 1083
 1084
 1085                    def c():⋯
 1086
 1087
 1088            "
 1089            .unindent(),
 1090        );
 1091
 1092        editor.fold(&Fold, window, cx);
 1093        assert_eq!(
 1094            editor.display_text(cx),
 1095            "
 1096                class Foo:⋯
 1097
 1098
 1099            "
 1100            .unindent(),
 1101        );
 1102
 1103        editor.unfold_lines(&UnfoldLines, window, cx);
 1104        assert_eq!(
 1105            editor.display_text(cx),
 1106            "
 1107                class Foo:
 1108                    # Hello!
 1109
 1110                    def a():
 1111                        print(1)
 1112
 1113                    def b():⋯
 1114
 1115
 1116                    def c():⋯
 1117
 1118
 1119            "
 1120            .unindent(),
 1121        );
 1122
 1123        editor.unfold_lines(&UnfoldLines, window, cx);
 1124        assert_eq!(
 1125            editor.display_text(cx),
 1126            editor.buffer.read(cx).read(cx).text()
 1127        );
 1128    });
 1129}
 1130
 1131#[gpui::test]
 1132fn test_fold_at_level(cx: &mut TestAppContext) {
 1133    init_test(cx, |_| {});
 1134
 1135    let editor = cx.add_window(|window, cx| {
 1136        let buffer = MultiBuffer::build_simple(
 1137            &"
 1138                class Foo:
 1139                    # Hello!
 1140
 1141                    def a():
 1142                        print(1)
 1143
 1144                    def b():
 1145                        print(2)
 1146
 1147
 1148                class Bar:
 1149                    # World!
 1150
 1151                    def a():
 1152                        print(1)
 1153
 1154                    def b():
 1155                        print(2)
 1156
 1157
 1158            "
 1159            .unindent(),
 1160            cx,
 1161        );
 1162        build_editor(buffer.clone(), window, cx)
 1163    });
 1164
 1165    _ = editor.update(cx, |editor, window, cx| {
 1166        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1167        assert_eq!(
 1168            editor.display_text(cx),
 1169            "
 1170                class Foo:
 1171                    # Hello!
 1172
 1173                    def a():⋯
 1174
 1175                    def b():⋯
 1176
 1177
 1178                class Bar:
 1179                    # World!
 1180
 1181                    def a():⋯
 1182
 1183                    def b():⋯
 1184
 1185
 1186            "
 1187            .unindent(),
 1188        );
 1189
 1190        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1191        assert_eq!(
 1192            editor.display_text(cx),
 1193            "
 1194                class Foo:⋯
 1195
 1196
 1197                class Bar:⋯
 1198
 1199
 1200            "
 1201            .unindent(),
 1202        );
 1203
 1204        editor.unfold_all(&UnfoldAll, window, cx);
 1205        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1206        assert_eq!(
 1207            editor.display_text(cx),
 1208            "
 1209                class Foo:
 1210                    # Hello!
 1211
 1212                    def a():
 1213                        print(1)
 1214
 1215                    def b():
 1216                        print(2)
 1217
 1218
 1219                class Bar:
 1220                    # World!
 1221
 1222                    def a():
 1223                        print(1)
 1224
 1225                    def b():
 1226                        print(2)
 1227
 1228
 1229            "
 1230            .unindent(),
 1231        );
 1232
 1233        assert_eq!(
 1234            editor.display_text(cx),
 1235            editor.buffer.read(cx).read(cx).text()
 1236        );
 1237    });
 1238}
 1239
 1240#[gpui::test]
 1241fn test_move_cursor(cx: &mut TestAppContext) {
 1242    init_test(cx, |_| {});
 1243
 1244    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1245    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1246
 1247    buffer.update(cx, |buffer, cx| {
 1248        buffer.edit(
 1249            vec![
 1250                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1251                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1252            ],
 1253            None,
 1254            cx,
 1255        );
 1256    });
 1257    _ = editor.update(cx, |editor, window, cx| {
 1258        assert_eq!(
 1259            editor.selections.display_ranges(cx),
 1260            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1261        );
 1262
 1263        editor.move_down(&MoveDown, window, cx);
 1264        assert_eq!(
 1265            editor.selections.display_ranges(cx),
 1266            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1267        );
 1268
 1269        editor.move_right(&MoveRight, window, cx);
 1270        assert_eq!(
 1271            editor.selections.display_ranges(cx),
 1272            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1273        );
 1274
 1275        editor.move_left(&MoveLeft, window, cx);
 1276        assert_eq!(
 1277            editor.selections.display_ranges(cx),
 1278            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1279        );
 1280
 1281        editor.move_up(&MoveUp, window, cx);
 1282        assert_eq!(
 1283            editor.selections.display_ranges(cx),
 1284            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1285        );
 1286
 1287        editor.move_to_end(&MoveToEnd, window, cx);
 1288        assert_eq!(
 1289            editor.selections.display_ranges(cx),
 1290            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1291        );
 1292
 1293        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1294        assert_eq!(
 1295            editor.selections.display_ranges(cx),
 1296            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1297        );
 1298
 1299        editor.change_selections(None, window, cx, |s| {
 1300            s.select_display_ranges([
 1301                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1302            ]);
 1303        });
 1304        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1305        assert_eq!(
 1306            editor.selections.display_ranges(cx),
 1307            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1308        );
 1309
 1310        editor.select_to_end(&SelectToEnd, window, cx);
 1311        assert_eq!(
 1312            editor.selections.display_ranges(cx),
 1313            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1314        );
 1315    });
 1316}
 1317
 1318// TODO: Re-enable this test
 1319#[cfg(target_os = "macos")]
 1320#[gpui::test]
 1321fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1322    init_test(cx, |_| {});
 1323
 1324    let editor = cx.add_window(|window, cx| {
 1325        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1326        build_editor(buffer.clone(), window, cx)
 1327    });
 1328
 1329    assert_eq!('🟥'.len_utf8(), 4);
 1330    assert_eq!('α'.len_utf8(), 2);
 1331
 1332    _ = editor.update(cx, |editor, window, cx| {
 1333        editor.fold_creases(
 1334            vec![
 1335                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1336                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1337                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1338            ],
 1339            true,
 1340            window,
 1341            cx,
 1342        );
 1343        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1344
 1345        editor.move_right(&MoveRight, window, cx);
 1346        assert_eq!(
 1347            editor.selections.display_ranges(cx),
 1348            &[empty_range(0, "🟥".len())]
 1349        );
 1350        editor.move_right(&MoveRight, window, cx);
 1351        assert_eq!(
 1352            editor.selections.display_ranges(cx),
 1353            &[empty_range(0, "🟥🟧".len())]
 1354        );
 1355        editor.move_right(&MoveRight, window, cx);
 1356        assert_eq!(
 1357            editor.selections.display_ranges(cx),
 1358            &[empty_range(0, "🟥🟧⋯".len())]
 1359        );
 1360
 1361        editor.move_down(&MoveDown, window, cx);
 1362        assert_eq!(
 1363            editor.selections.display_ranges(cx),
 1364            &[empty_range(1, "ab⋯e".len())]
 1365        );
 1366        editor.move_left(&MoveLeft, window, cx);
 1367        assert_eq!(
 1368            editor.selections.display_ranges(cx),
 1369            &[empty_range(1, "ab⋯".len())]
 1370        );
 1371        editor.move_left(&MoveLeft, window, cx);
 1372        assert_eq!(
 1373            editor.selections.display_ranges(cx),
 1374            &[empty_range(1, "ab".len())]
 1375        );
 1376        editor.move_left(&MoveLeft, window, cx);
 1377        assert_eq!(
 1378            editor.selections.display_ranges(cx),
 1379            &[empty_range(1, "a".len())]
 1380        );
 1381
 1382        editor.move_down(&MoveDown, window, cx);
 1383        assert_eq!(
 1384            editor.selections.display_ranges(cx),
 1385            &[empty_range(2, "α".len())]
 1386        );
 1387        editor.move_right(&MoveRight, window, cx);
 1388        assert_eq!(
 1389            editor.selections.display_ranges(cx),
 1390            &[empty_range(2, "αβ".len())]
 1391        );
 1392        editor.move_right(&MoveRight, window, cx);
 1393        assert_eq!(
 1394            editor.selections.display_ranges(cx),
 1395            &[empty_range(2, "αβ⋯".len())]
 1396        );
 1397        editor.move_right(&MoveRight, window, cx);
 1398        assert_eq!(
 1399            editor.selections.display_ranges(cx),
 1400            &[empty_range(2, "αβ⋯ε".len())]
 1401        );
 1402
 1403        editor.move_up(&MoveUp, window, cx);
 1404        assert_eq!(
 1405            editor.selections.display_ranges(cx),
 1406            &[empty_range(1, "ab⋯e".len())]
 1407        );
 1408        editor.move_down(&MoveDown, window, cx);
 1409        assert_eq!(
 1410            editor.selections.display_ranges(cx),
 1411            &[empty_range(2, "αβ⋯ε".len())]
 1412        );
 1413        editor.move_up(&MoveUp, window, cx);
 1414        assert_eq!(
 1415            editor.selections.display_ranges(cx),
 1416            &[empty_range(1, "ab⋯e".len())]
 1417        );
 1418
 1419        editor.move_up(&MoveUp, window, cx);
 1420        assert_eq!(
 1421            editor.selections.display_ranges(cx),
 1422            &[empty_range(0, "🟥🟧".len())]
 1423        );
 1424        editor.move_left(&MoveLeft, window, cx);
 1425        assert_eq!(
 1426            editor.selections.display_ranges(cx),
 1427            &[empty_range(0, "🟥".len())]
 1428        );
 1429        editor.move_left(&MoveLeft, window, cx);
 1430        assert_eq!(
 1431            editor.selections.display_ranges(cx),
 1432            &[empty_range(0, "".len())]
 1433        );
 1434    });
 1435}
 1436
 1437#[gpui::test]
 1438fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1439    init_test(cx, |_| {});
 1440
 1441    let editor = cx.add_window(|window, cx| {
 1442        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1443        build_editor(buffer.clone(), window, cx)
 1444    });
 1445    _ = editor.update(cx, |editor, window, cx| {
 1446        editor.change_selections(None, window, cx, |s| {
 1447            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1448        });
 1449
 1450        // moving above start of document should move selection to start of document,
 1451        // but the next move down should still be at the original goal_x
 1452        editor.move_up(&MoveUp, window, cx);
 1453        assert_eq!(
 1454            editor.selections.display_ranges(cx),
 1455            &[empty_range(0, "".len())]
 1456        );
 1457
 1458        editor.move_down(&MoveDown, window, cx);
 1459        assert_eq!(
 1460            editor.selections.display_ranges(cx),
 1461            &[empty_range(1, "abcd".len())]
 1462        );
 1463
 1464        editor.move_down(&MoveDown, window, cx);
 1465        assert_eq!(
 1466            editor.selections.display_ranges(cx),
 1467            &[empty_range(2, "αβγ".len())]
 1468        );
 1469
 1470        editor.move_down(&MoveDown, window, cx);
 1471        assert_eq!(
 1472            editor.selections.display_ranges(cx),
 1473            &[empty_range(3, "abcd".len())]
 1474        );
 1475
 1476        editor.move_down(&MoveDown, window, cx);
 1477        assert_eq!(
 1478            editor.selections.display_ranges(cx),
 1479            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1480        );
 1481
 1482        // moving past end of document should not change goal_x
 1483        editor.move_down(&MoveDown, window, cx);
 1484        assert_eq!(
 1485            editor.selections.display_ranges(cx),
 1486            &[empty_range(5, "".len())]
 1487        );
 1488
 1489        editor.move_down(&MoveDown, window, cx);
 1490        assert_eq!(
 1491            editor.selections.display_ranges(cx),
 1492            &[empty_range(5, "".len())]
 1493        );
 1494
 1495        editor.move_up(&MoveUp, window, cx);
 1496        assert_eq!(
 1497            editor.selections.display_ranges(cx),
 1498            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1499        );
 1500
 1501        editor.move_up(&MoveUp, window, cx);
 1502        assert_eq!(
 1503            editor.selections.display_ranges(cx),
 1504            &[empty_range(3, "abcd".len())]
 1505        );
 1506
 1507        editor.move_up(&MoveUp, window, cx);
 1508        assert_eq!(
 1509            editor.selections.display_ranges(cx),
 1510            &[empty_range(2, "αβγ".len())]
 1511        );
 1512    });
 1513}
 1514
 1515#[gpui::test]
 1516fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1517    init_test(cx, |_| {});
 1518    let move_to_beg = MoveToBeginningOfLine {
 1519        stop_at_soft_wraps: true,
 1520        stop_at_indent: true,
 1521    };
 1522
 1523    let delete_to_beg = DeleteToBeginningOfLine {
 1524        stop_at_indent: false,
 1525    };
 1526
 1527    let move_to_end = MoveToEndOfLine {
 1528        stop_at_soft_wraps: true,
 1529    };
 1530
 1531    let editor = cx.add_window(|window, cx| {
 1532        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1533        build_editor(buffer, window, cx)
 1534    });
 1535    _ = editor.update(cx, |editor, window, cx| {
 1536        editor.change_selections(None, window, cx, |s| {
 1537            s.select_display_ranges([
 1538                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1539                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1540            ]);
 1541        });
 1542    });
 1543
 1544    _ = editor.update(cx, |editor, window, cx| {
 1545        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1546        assert_eq!(
 1547            editor.selections.display_ranges(cx),
 1548            &[
 1549                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1550                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1551            ]
 1552        );
 1553    });
 1554
 1555    _ = editor.update(cx, |editor, window, cx| {
 1556        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1557        assert_eq!(
 1558            editor.selections.display_ranges(cx),
 1559            &[
 1560                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1561                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1562            ]
 1563        );
 1564    });
 1565
 1566    _ = editor.update(cx, |editor, window, cx| {
 1567        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1568        assert_eq!(
 1569            editor.selections.display_ranges(cx),
 1570            &[
 1571                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1572                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1573            ]
 1574        );
 1575    });
 1576
 1577    _ = editor.update(cx, |editor, window, cx| {
 1578        editor.move_to_end_of_line(&move_to_end, window, cx);
 1579        assert_eq!(
 1580            editor.selections.display_ranges(cx),
 1581            &[
 1582                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1583                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1584            ]
 1585        );
 1586    });
 1587
 1588    // Moving to the end of line again is a no-op.
 1589    _ = editor.update(cx, |editor, window, cx| {
 1590        editor.move_to_end_of_line(&move_to_end, window, cx);
 1591        assert_eq!(
 1592            editor.selections.display_ranges(cx),
 1593            &[
 1594                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1595                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1596            ]
 1597        );
 1598    });
 1599
 1600    _ = editor.update(cx, |editor, window, cx| {
 1601        editor.move_left(&MoveLeft, window, cx);
 1602        editor.select_to_beginning_of_line(
 1603            &SelectToBeginningOfLine {
 1604                stop_at_soft_wraps: true,
 1605                stop_at_indent: true,
 1606            },
 1607            window,
 1608            cx,
 1609        );
 1610        assert_eq!(
 1611            editor.selections.display_ranges(cx),
 1612            &[
 1613                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1614                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1615            ]
 1616        );
 1617    });
 1618
 1619    _ = editor.update(cx, |editor, window, cx| {
 1620        editor.select_to_beginning_of_line(
 1621            &SelectToBeginningOfLine {
 1622                stop_at_soft_wraps: true,
 1623                stop_at_indent: true,
 1624            },
 1625            window,
 1626            cx,
 1627        );
 1628        assert_eq!(
 1629            editor.selections.display_ranges(cx),
 1630            &[
 1631                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1632                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1633            ]
 1634        );
 1635    });
 1636
 1637    _ = editor.update(cx, |editor, window, cx| {
 1638        editor.select_to_beginning_of_line(
 1639            &SelectToBeginningOfLine {
 1640                stop_at_soft_wraps: true,
 1641                stop_at_indent: true,
 1642            },
 1643            window,
 1644            cx,
 1645        );
 1646        assert_eq!(
 1647            editor.selections.display_ranges(cx),
 1648            &[
 1649                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1650                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1651            ]
 1652        );
 1653    });
 1654
 1655    _ = editor.update(cx, |editor, window, cx| {
 1656        editor.select_to_end_of_line(
 1657            &SelectToEndOfLine {
 1658                stop_at_soft_wraps: true,
 1659            },
 1660            window,
 1661            cx,
 1662        );
 1663        assert_eq!(
 1664            editor.selections.display_ranges(cx),
 1665            &[
 1666                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1667                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1668            ]
 1669        );
 1670    });
 1671
 1672    _ = editor.update(cx, |editor, window, cx| {
 1673        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1674        assert_eq!(editor.display_text(cx), "ab\n  de");
 1675        assert_eq!(
 1676            editor.selections.display_ranges(cx),
 1677            &[
 1678                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1679                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1680            ]
 1681        );
 1682    });
 1683
 1684    _ = editor.update(cx, |editor, window, cx| {
 1685        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1686        assert_eq!(editor.display_text(cx), "\n");
 1687        assert_eq!(
 1688            editor.selections.display_ranges(cx),
 1689            &[
 1690                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1691                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1692            ]
 1693        );
 1694    });
 1695}
 1696
 1697#[gpui::test]
 1698fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1699    init_test(cx, |_| {});
 1700    let move_to_beg = MoveToBeginningOfLine {
 1701        stop_at_soft_wraps: false,
 1702        stop_at_indent: false,
 1703    };
 1704
 1705    let move_to_end = MoveToEndOfLine {
 1706        stop_at_soft_wraps: false,
 1707    };
 1708
 1709    let editor = cx.add_window(|window, cx| {
 1710        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1711        build_editor(buffer, window, cx)
 1712    });
 1713
 1714    _ = editor.update(cx, |editor, window, cx| {
 1715        editor.set_wrap_width(Some(140.0.into()), cx);
 1716
 1717        // We expect the following lines after wrapping
 1718        // ```
 1719        // thequickbrownfox
 1720        // jumpedoverthelazydo
 1721        // gs
 1722        // ```
 1723        // The final `gs` was soft-wrapped onto a new line.
 1724        assert_eq!(
 1725            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1726            editor.display_text(cx),
 1727        );
 1728
 1729        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1730        // Start the cursor at the `k` on the first line
 1731        editor.change_selections(None, window, cx, |s| {
 1732            s.select_display_ranges([
 1733                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1734            ]);
 1735        });
 1736
 1737        // Moving to the beginning of the line should put us at the beginning of the line.
 1738        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1739        assert_eq!(
 1740            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1741            editor.selections.display_ranges(cx)
 1742        );
 1743
 1744        // Moving to the end of the line should put us at the end of the line.
 1745        editor.move_to_end_of_line(&move_to_end, window, cx);
 1746        assert_eq!(
 1747            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1748            editor.selections.display_ranges(cx)
 1749        );
 1750
 1751        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1752        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1753        editor.change_selections(None, window, cx, |s| {
 1754            s.select_display_ranges([
 1755                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1756            ]);
 1757        });
 1758
 1759        // Moving to the beginning of the line should put us at the start of the second line of
 1760        // display text, i.e., the `j`.
 1761        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1762        assert_eq!(
 1763            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1764            editor.selections.display_ranges(cx)
 1765        );
 1766
 1767        // Moving to the beginning of the line again should be a no-op.
 1768        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1769        assert_eq!(
 1770            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1771            editor.selections.display_ranges(cx)
 1772        );
 1773
 1774        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1775        // next display line.
 1776        editor.move_to_end_of_line(&move_to_end, window, cx);
 1777        assert_eq!(
 1778            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1779            editor.selections.display_ranges(cx)
 1780        );
 1781
 1782        // Moving to the end of the line again should be a no-op.
 1783        editor.move_to_end_of_line(&move_to_end, window, cx);
 1784        assert_eq!(
 1785            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1786            editor.selections.display_ranges(cx)
 1787        );
 1788    });
 1789}
 1790
 1791#[gpui::test]
 1792fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1793    init_test(cx, |_| {});
 1794
 1795    let move_to_beg = MoveToBeginningOfLine {
 1796        stop_at_soft_wraps: true,
 1797        stop_at_indent: true,
 1798    };
 1799
 1800    let select_to_beg = SelectToBeginningOfLine {
 1801        stop_at_soft_wraps: true,
 1802        stop_at_indent: true,
 1803    };
 1804
 1805    let delete_to_beg = DeleteToBeginningOfLine {
 1806        stop_at_indent: true,
 1807    };
 1808
 1809    let move_to_end = MoveToEndOfLine {
 1810        stop_at_soft_wraps: false,
 1811    };
 1812
 1813    let editor = cx.add_window(|window, cx| {
 1814        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1815        build_editor(buffer, window, cx)
 1816    });
 1817
 1818    _ = editor.update(cx, |editor, window, cx| {
 1819        editor.change_selections(None, window, cx, |s| {
 1820            s.select_display_ranges([
 1821                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1822                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1823            ]);
 1824        });
 1825
 1826        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1827        // and the second cursor at the first non-whitespace character in the line.
 1828        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1829        assert_eq!(
 1830            editor.selections.display_ranges(cx),
 1831            &[
 1832                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1833                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1834            ]
 1835        );
 1836
 1837        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1838        // and should move the second cursor to the beginning of the line.
 1839        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1840        assert_eq!(
 1841            editor.selections.display_ranges(cx),
 1842            &[
 1843                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1844                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1845            ]
 1846        );
 1847
 1848        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1849        // and should move the second cursor back to the first non-whitespace character in the line.
 1850        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1851        assert_eq!(
 1852            editor.selections.display_ranges(cx),
 1853            &[
 1854                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1855                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1856            ]
 1857        );
 1858
 1859        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1860        // and to the first non-whitespace character in the line for the second cursor.
 1861        editor.move_to_end_of_line(&move_to_end, window, cx);
 1862        editor.move_left(&MoveLeft, window, cx);
 1863        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1864        assert_eq!(
 1865            editor.selections.display_ranges(cx),
 1866            &[
 1867                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1868                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1869            ]
 1870        );
 1871
 1872        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1873        // and should select to the beginning of the line for the second cursor.
 1874        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1875        assert_eq!(
 1876            editor.selections.display_ranges(cx),
 1877            &[
 1878                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1879                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1880            ]
 1881        );
 1882
 1883        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1884        // and should delete to the first non-whitespace character in the line for the second cursor.
 1885        editor.move_to_end_of_line(&move_to_end, window, cx);
 1886        editor.move_left(&MoveLeft, window, cx);
 1887        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1888        assert_eq!(editor.text(cx), "c\n  f");
 1889    });
 1890}
 1891
 1892#[gpui::test]
 1893fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1894    init_test(cx, |_| {});
 1895
 1896    let editor = cx.add_window(|window, cx| {
 1897        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1898        build_editor(buffer, window, cx)
 1899    });
 1900    _ = editor.update(cx, |editor, window, cx| {
 1901        editor.change_selections(None, window, cx, |s| {
 1902            s.select_display_ranges([
 1903                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1904                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1905            ])
 1906        });
 1907
 1908        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1909        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1910
 1911        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1912        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\n  ˇ{baz.qux()}", editor, cx);
 1913
 1914        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1915        assert_selection_ranges("use ˇstd::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1916
 1917        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1918        assert_selection_ranges("ˇuse std::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1919
 1920        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1921        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1922
 1923        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1924        assert_selection_ranges("useˇ std::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1925
 1926        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1927        assert_selection_ranges("use stdˇ::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1928
 1929        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1930        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1931
 1932        editor.move_right(&MoveRight, window, cx);
 1933        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1934        assert_selection_ranges(
 1935            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1936            editor,
 1937            cx,
 1938        );
 1939
 1940        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1941        assert_selection_ranges(
 1942            "use std«ˇ::s»tr::{foo, bar}\n\n  «ˇ{b»az.qux()}",
 1943            editor,
 1944            cx,
 1945        );
 1946
 1947        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 1948        assert_selection_ranges(
 1949            "use std::«ˇs»tr::{foo, bar}\n\n  {«ˇb»az.qux()}",
 1950            editor,
 1951            cx,
 1952        );
 1953    });
 1954}
 1955
 1956#[gpui::test]
 1957fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 1958    init_test(cx, |_| {});
 1959
 1960    let editor = cx.add_window(|window, cx| {
 1961        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 1962        build_editor(buffer, window, cx)
 1963    });
 1964
 1965    _ = editor.update(cx, |editor, window, cx| {
 1966        editor.set_wrap_width(Some(140.0.into()), cx);
 1967        assert_eq!(
 1968            editor.display_text(cx),
 1969            "use one::{\n    two::three::\n    four::five\n};"
 1970        );
 1971
 1972        editor.change_selections(None, window, cx, |s| {
 1973            s.select_display_ranges([
 1974                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 1975            ]);
 1976        });
 1977
 1978        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1979        assert_eq!(
 1980            editor.selections.display_ranges(cx),
 1981            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 1982        );
 1983
 1984        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1985        assert_eq!(
 1986            editor.selections.display_ranges(cx),
 1987            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 1988        );
 1989
 1990        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1991        assert_eq!(
 1992            editor.selections.display_ranges(cx),
 1993            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 1994        );
 1995
 1996        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1997        assert_eq!(
 1998            editor.selections.display_ranges(cx),
 1999            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2000        );
 2001
 2002        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2003        assert_eq!(
 2004            editor.selections.display_ranges(cx),
 2005            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2006        );
 2007
 2008        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2009        assert_eq!(
 2010            editor.selections.display_ranges(cx),
 2011            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2012        );
 2013    });
 2014}
 2015
 2016#[gpui::test]
 2017async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2018    init_test(cx, |_| {});
 2019    let mut cx = EditorTestContext::new(cx).await;
 2020
 2021    let line_height = cx.editor(|editor, window, _| {
 2022        editor
 2023            .style()
 2024            .unwrap()
 2025            .text
 2026            .line_height_in_pixels(window.rem_size())
 2027    });
 2028    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2029
 2030    cx.set_state(
 2031        &r#"ˇone
 2032        two
 2033
 2034        three
 2035        fourˇ
 2036        five
 2037
 2038        six"#
 2039            .unindent(),
 2040    );
 2041
 2042    cx.update_editor(|editor, window, cx| {
 2043        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2044    });
 2045    cx.assert_editor_state(
 2046        &r#"one
 2047        two
 2048        ˇ
 2049        three
 2050        four
 2051        five
 2052        ˇ
 2053        six"#
 2054            .unindent(),
 2055    );
 2056
 2057    cx.update_editor(|editor, window, cx| {
 2058        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2059    });
 2060    cx.assert_editor_state(
 2061        &r#"one
 2062        two
 2063
 2064        three
 2065        four
 2066        five
 2067        ˇ
 2068        sixˇ"#
 2069            .unindent(),
 2070    );
 2071
 2072    cx.update_editor(|editor, window, cx| {
 2073        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2074    });
 2075    cx.assert_editor_state(
 2076        &r#"one
 2077        two
 2078
 2079        three
 2080        four
 2081        five
 2082
 2083        sixˇ"#
 2084            .unindent(),
 2085    );
 2086
 2087    cx.update_editor(|editor, window, cx| {
 2088        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2089    });
 2090    cx.assert_editor_state(
 2091        &r#"one
 2092        two
 2093
 2094        three
 2095        four
 2096        five
 2097        ˇ
 2098        six"#
 2099            .unindent(),
 2100    );
 2101
 2102    cx.update_editor(|editor, window, cx| {
 2103        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2104    });
 2105    cx.assert_editor_state(
 2106        &r#"one
 2107        two
 2108        ˇ
 2109        three
 2110        four
 2111        five
 2112
 2113        six"#
 2114            .unindent(),
 2115    );
 2116
 2117    cx.update_editor(|editor, window, cx| {
 2118        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2119    });
 2120    cx.assert_editor_state(
 2121        &r#"ˇone
 2122        two
 2123
 2124        three
 2125        four
 2126        five
 2127
 2128        six"#
 2129            .unindent(),
 2130    );
 2131}
 2132
 2133#[gpui::test]
 2134async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2135    init_test(cx, |_| {});
 2136    let mut cx = EditorTestContext::new(cx).await;
 2137    let line_height = cx.editor(|editor, window, _| {
 2138        editor
 2139            .style()
 2140            .unwrap()
 2141            .text
 2142            .line_height_in_pixels(window.rem_size())
 2143    });
 2144    let window = cx.window;
 2145    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2146
 2147    cx.set_state(
 2148        r#"ˇone
 2149        two
 2150        three
 2151        four
 2152        five
 2153        six
 2154        seven
 2155        eight
 2156        nine
 2157        ten
 2158        "#,
 2159    );
 2160
 2161    cx.update_editor(|editor, window, cx| {
 2162        assert_eq!(
 2163            editor.snapshot(window, cx).scroll_position(),
 2164            gpui::Point::new(0., 0.)
 2165        );
 2166        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2167        assert_eq!(
 2168            editor.snapshot(window, cx).scroll_position(),
 2169            gpui::Point::new(0., 3.)
 2170        );
 2171        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2172        assert_eq!(
 2173            editor.snapshot(window, cx).scroll_position(),
 2174            gpui::Point::new(0., 6.)
 2175        );
 2176        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2177        assert_eq!(
 2178            editor.snapshot(window, cx).scroll_position(),
 2179            gpui::Point::new(0., 3.)
 2180        );
 2181
 2182        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2183        assert_eq!(
 2184            editor.snapshot(window, cx).scroll_position(),
 2185            gpui::Point::new(0., 1.)
 2186        );
 2187        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2188        assert_eq!(
 2189            editor.snapshot(window, cx).scroll_position(),
 2190            gpui::Point::new(0., 3.)
 2191        );
 2192    });
 2193}
 2194
 2195#[gpui::test]
 2196async fn test_autoscroll(cx: &mut TestAppContext) {
 2197    init_test(cx, |_| {});
 2198    let mut cx = EditorTestContext::new(cx).await;
 2199
 2200    let line_height = cx.update_editor(|editor, window, cx| {
 2201        editor.set_vertical_scroll_margin(2, cx);
 2202        editor
 2203            .style()
 2204            .unwrap()
 2205            .text
 2206            .line_height_in_pixels(window.rem_size())
 2207    });
 2208    let window = cx.window;
 2209    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2210
 2211    cx.set_state(
 2212        r#"ˇone
 2213            two
 2214            three
 2215            four
 2216            five
 2217            six
 2218            seven
 2219            eight
 2220            nine
 2221            ten
 2222        "#,
 2223    );
 2224    cx.update_editor(|editor, window, cx| {
 2225        assert_eq!(
 2226            editor.snapshot(window, cx).scroll_position(),
 2227            gpui::Point::new(0., 0.0)
 2228        );
 2229    });
 2230
 2231    // Add a cursor below the visible area. Since both cursors cannot fit
 2232    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2233    // allows the vertical scroll margin below that cursor.
 2234    cx.update_editor(|editor, window, cx| {
 2235        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2236            selections.select_ranges([
 2237                Point::new(0, 0)..Point::new(0, 0),
 2238                Point::new(6, 0)..Point::new(6, 0),
 2239            ]);
 2240        })
 2241    });
 2242    cx.update_editor(|editor, window, cx| {
 2243        assert_eq!(
 2244            editor.snapshot(window, cx).scroll_position(),
 2245            gpui::Point::new(0., 3.0)
 2246        );
 2247    });
 2248
 2249    // Move down. The editor cursor scrolls down to track the newest cursor.
 2250    cx.update_editor(|editor, window, cx| {
 2251        editor.move_down(&Default::default(), window, cx);
 2252    });
 2253    cx.update_editor(|editor, window, cx| {
 2254        assert_eq!(
 2255            editor.snapshot(window, cx).scroll_position(),
 2256            gpui::Point::new(0., 4.0)
 2257        );
 2258    });
 2259
 2260    // Add a cursor above the visible area. Since both cursors fit on screen,
 2261    // the editor scrolls to show both.
 2262    cx.update_editor(|editor, window, cx| {
 2263        editor.change_selections(Some(Autoscroll::fit()), window, cx, |selections| {
 2264            selections.select_ranges([
 2265                Point::new(1, 0)..Point::new(1, 0),
 2266                Point::new(6, 0)..Point::new(6, 0),
 2267            ]);
 2268        })
 2269    });
 2270    cx.update_editor(|editor, window, cx| {
 2271        assert_eq!(
 2272            editor.snapshot(window, cx).scroll_position(),
 2273            gpui::Point::new(0., 1.0)
 2274        );
 2275    });
 2276}
 2277
 2278#[gpui::test]
 2279async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2280    init_test(cx, |_| {});
 2281    let mut cx = EditorTestContext::new(cx).await;
 2282
 2283    let line_height = cx.editor(|editor, window, _cx| {
 2284        editor
 2285            .style()
 2286            .unwrap()
 2287            .text
 2288            .line_height_in_pixels(window.rem_size())
 2289    });
 2290    let window = cx.window;
 2291    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2292    cx.set_state(
 2293        &r#"
 2294        ˇone
 2295        two
 2296        threeˇ
 2297        four
 2298        five
 2299        six
 2300        seven
 2301        eight
 2302        nine
 2303        ten
 2304        "#
 2305        .unindent(),
 2306    );
 2307
 2308    cx.update_editor(|editor, window, cx| {
 2309        editor.move_page_down(&MovePageDown::default(), window, cx)
 2310    });
 2311    cx.assert_editor_state(
 2312        &r#"
 2313        one
 2314        two
 2315        three
 2316        ˇfour
 2317        five
 2318        sixˇ
 2319        seven
 2320        eight
 2321        nine
 2322        ten
 2323        "#
 2324        .unindent(),
 2325    );
 2326
 2327    cx.update_editor(|editor, window, cx| {
 2328        editor.move_page_down(&MovePageDown::default(), window, cx)
 2329    });
 2330    cx.assert_editor_state(
 2331        &r#"
 2332        one
 2333        two
 2334        three
 2335        four
 2336        five
 2337        six
 2338        ˇseven
 2339        eight
 2340        nineˇ
 2341        ten
 2342        "#
 2343        .unindent(),
 2344    );
 2345
 2346    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2347    cx.assert_editor_state(
 2348        &r#"
 2349        one
 2350        two
 2351        three
 2352        ˇfour
 2353        five
 2354        sixˇ
 2355        seven
 2356        eight
 2357        nine
 2358        ten
 2359        "#
 2360        .unindent(),
 2361    );
 2362
 2363    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2364    cx.assert_editor_state(
 2365        &r#"
 2366        ˇone
 2367        two
 2368        threeˇ
 2369        four
 2370        five
 2371        six
 2372        seven
 2373        eight
 2374        nine
 2375        ten
 2376        "#
 2377        .unindent(),
 2378    );
 2379
 2380    // Test select collapsing
 2381    cx.update_editor(|editor, window, cx| {
 2382        editor.move_page_down(&MovePageDown::default(), window, cx);
 2383        editor.move_page_down(&MovePageDown::default(), window, cx);
 2384        editor.move_page_down(&MovePageDown::default(), window, cx);
 2385    });
 2386    cx.assert_editor_state(
 2387        &r#"
 2388        one
 2389        two
 2390        three
 2391        four
 2392        five
 2393        six
 2394        seven
 2395        eight
 2396        nine
 2397        ˇten
 2398        ˇ"#
 2399        .unindent(),
 2400    );
 2401}
 2402
 2403#[gpui::test]
 2404async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2405    init_test(cx, |_| {});
 2406    let mut cx = EditorTestContext::new(cx).await;
 2407    cx.set_state("one «two threeˇ» four");
 2408    cx.update_editor(|editor, window, cx| {
 2409        editor.delete_to_beginning_of_line(
 2410            &DeleteToBeginningOfLine {
 2411                stop_at_indent: false,
 2412            },
 2413            window,
 2414            cx,
 2415        );
 2416        assert_eq!(editor.text(cx), " four");
 2417    });
 2418}
 2419
 2420#[gpui::test]
 2421fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2422    init_test(cx, |_| {});
 2423
 2424    let editor = cx.add_window(|window, cx| {
 2425        let buffer = MultiBuffer::build_simple("one two three four", cx);
 2426        build_editor(buffer.clone(), window, cx)
 2427    });
 2428
 2429    _ = editor.update(cx, |editor, window, cx| {
 2430        editor.change_selections(None, window, cx, |s| {
 2431            s.select_display_ranges([
 2432                // an empty selection - the preceding word fragment is deleted
 2433                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2434                // characters selected - they are deleted
 2435                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 12),
 2436            ])
 2437        });
 2438        editor.delete_to_previous_word_start(
 2439            &DeleteToPreviousWordStart {
 2440                ignore_newlines: false,
 2441            },
 2442            window,
 2443            cx,
 2444        );
 2445        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e two te four");
 2446    });
 2447
 2448    _ = editor.update(cx, |editor, window, cx| {
 2449        editor.change_selections(None, window, cx, |s| {
 2450            s.select_display_ranges([
 2451                // an empty selection - the following word fragment is deleted
 2452                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 2453                // characters selected - they are deleted
 2454                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 10),
 2455            ])
 2456        });
 2457        editor.delete_to_next_word_end(
 2458            &DeleteToNextWordEnd {
 2459                ignore_newlines: false,
 2460            },
 2461            window,
 2462            cx,
 2463        );
 2464        assert_eq!(editor.buffer.read(cx).read(cx).text(), "e t te our");
 2465    });
 2466}
 2467
 2468#[gpui::test]
 2469fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2470    init_test(cx, |_| {});
 2471
 2472    let editor = cx.add_window(|window, cx| {
 2473        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2474        build_editor(buffer.clone(), window, cx)
 2475    });
 2476    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2477        ignore_newlines: false,
 2478    };
 2479    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2480        ignore_newlines: true,
 2481    };
 2482
 2483    _ = editor.update(cx, |editor, window, cx| {
 2484        editor.change_selections(None, window, cx, |s| {
 2485            s.select_display_ranges([
 2486                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2487            ])
 2488        });
 2489        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2490        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2491        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2492        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2493        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2494        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2495        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2496        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2497        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2498        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2499        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2500        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2501    });
 2502}
 2503
 2504#[gpui::test]
 2505fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2506    init_test(cx, |_| {});
 2507
 2508    let editor = cx.add_window(|window, cx| {
 2509        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2510        build_editor(buffer.clone(), window, cx)
 2511    });
 2512    let del_to_next_word_end = DeleteToNextWordEnd {
 2513        ignore_newlines: false,
 2514    };
 2515    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2516        ignore_newlines: true,
 2517    };
 2518
 2519    _ = editor.update(cx, |editor, window, cx| {
 2520        editor.change_selections(None, window, cx, |s| {
 2521            s.select_display_ranges([
 2522                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2523            ])
 2524        });
 2525        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2526        assert_eq!(
 2527            editor.buffer.read(cx).read(cx).text(),
 2528            "one\n   two\nthree\n   four"
 2529        );
 2530        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2531        assert_eq!(
 2532            editor.buffer.read(cx).read(cx).text(),
 2533            "\n   two\nthree\n   four"
 2534        );
 2535        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2536        assert_eq!(
 2537            editor.buffer.read(cx).read(cx).text(),
 2538            "two\nthree\n   four"
 2539        );
 2540        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2541        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2542        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2543        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2544        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2545        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2546    });
 2547}
 2548
 2549#[gpui::test]
 2550fn test_newline(cx: &mut TestAppContext) {
 2551    init_test(cx, |_| {});
 2552
 2553    let editor = cx.add_window(|window, cx| {
 2554        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2555        build_editor(buffer.clone(), window, cx)
 2556    });
 2557
 2558    _ = editor.update(cx, |editor, window, cx| {
 2559        editor.change_selections(None, window, cx, |s| {
 2560            s.select_display_ranges([
 2561                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2562                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2563                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2564            ])
 2565        });
 2566
 2567        editor.newline(&Newline, window, cx);
 2568        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2569    });
 2570}
 2571
 2572#[gpui::test]
 2573fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2574    init_test(cx, |_| {});
 2575
 2576    let editor = cx.add_window(|window, cx| {
 2577        let buffer = MultiBuffer::build_simple(
 2578            "
 2579                a
 2580                b(
 2581                    X
 2582                )
 2583                c(
 2584                    X
 2585                )
 2586            "
 2587            .unindent()
 2588            .as_str(),
 2589            cx,
 2590        );
 2591        let mut editor = build_editor(buffer.clone(), window, cx);
 2592        editor.change_selections(None, window, cx, |s| {
 2593            s.select_ranges([
 2594                Point::new(2, 4)..Point::new(2, 5),
 2595                Point::new(5, 4)..Point::new(5, 5),
 2596            ])
 2597        });
 2598        editor
 2599    });
 2600
 2601    _ = editor.update(cx, |editor, window, cx| {
 2602        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2603        editor.buffer.update(cx, |buffer, cx| {
 2604            buffer.edit(
 2605                [
 2606                    (Point::new(1, 2)..Point::new(3, 0), ""),
 2607                    (Point::new(4, 2)..Point::new(6, 0), ""),
 2608                ],
 2609                None,
 2610                cx,
 2611            );
 2612            assert_eq!(
 2613                buffer.read(cx).text(),
 2614                "
 2615                    a
 2616                    b()
 2617                    c()
 2618                "
 2619                .unindent()
 2620            );
 2621        });
 2622        assert_eq!(
 2623            editor.selections.ranges(cx),
 2624            &[
 2625                Point::new(1, 2)..Point::new(1, 2),
 2626                Point::new(2, 2)..Point::new(2, 2),
 2627            ],
 2628        );
 2629
 2630        editor.newline(&Newline, window, cx);
 2631        assert_eq!(
 2632            editor.text(cx),
 2633            "
 2634                a
 2635                b(
 2636                )
 2637                c(
 2638                )
 2639            "
 2640            .unindent()
 2641        );
 2642
 2643        // The selections are moved after the inserted newlines
 2644        assert_eq!(
 2645            editor.selections.ranges(cx),
 2646            &[
 2647                Point::new(2, 0)..Point::new(2, 0),
 2648                Point::new(4, 0)..Point::new(4, 0),
 2649            ],
 2650        );
 2651    });
 2652}
 2653
 2654#[gpui::test]
 2655async fn test_newline_above(cx: &mut TestAppContext) {
 2656    init_test(cx, |settings| {
 2657        settings.defaults.tab_size = NonZeroU32::new(4)
 2658    });
 2659
 2660    let language = Arc::new(
 2661        Language::new(
 2662            LanguageConfig::default(),
 2663            Some(tree_sitter_rust::LANGUAGE.into()),
 2664        )
 2665        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2666        .unwrap(),
 2667    );
 2668
 2669    let mut cx = EditorTestContext::new(cx).await;
 2670    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2671    cx.set_state(indoc! {"
 2672        const a: ˇA = (
 2673 2674                «const_functionˇ»(ˇ),
 2675                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2676 2677        ˇ);ˇ
 2678    "});
 2679
 2680    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 2681    cx.assert_editor_state(indoc! {"
 2682        ˇ
 2683        const a: A = (
 2684            ˇ
 2685            (
 2686                ˇ
 2687                ˇ
 2688                const_function(),
 2689                ˇ
 2690                ˇ
 2691                ˇ
 2692                ˇ
 2693                something_else,
 2694                ˇ
 2695            )
 2696            ˇ
 2697            ˇ
 2698        );
 2699    "});
 2700}
 2701
 2702#[gpui::test]
 2703async fn test_newline_below(cx: &mut TestAppContext) {
 2704    init_test(cx, |settings| {
 2705        settings.defaults.tab_size = NonZeroU32::new(4)
 2706    });
 2707
 2708    let language = Arc::new(
 2709        Language::new(
 2710            LanguageConfig::default(),
 2711            Some(tree_sitter_rust::LANGUAGE.into()),
 2712        )
 2713        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2714        .unwrap(),
 2715    );
 2716
 2717    let mut cx = EditorTestContext::new(cx).await;
 2718    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2719    cx.set_state(indoc! {"
 2720        const a: ˇA = (
 2721 2722                «const_functionˇ»(ˇ),
 2723                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 2724 2725        ˇ);ˇ
 2726    "});
 2727
 2728    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 2729    cx.assert_editor_state(indoc! {"
 2730        const a: A = (
 2731            ˇ
 2732            (
 2733                ˇ
 2734                const_function(),
 2735                ˇ
 2736                ˇ
 2737                something_else,
 2738                ˇ
 2739                ˇ
 2740                ˇ
 2741                ˇ
 2742            )
 2743            ˇ
 2744        );
 2745        ˇ
 2746        ˇ
 2747    "});
 2748}
 2749
 2750#[gpui::test]
 2751async fn test_newline_comments(cx: &mut TestAppContext) {
 2752    init_test(cx, |settings| {
 2753        settings.defaults.tab_size = NonZeroU32::new(4)
 2754    });
 2755
 2756    let language = Arc::new(Language::new(
 2757        LanguageConfig {
 2758            line_comments: vec!["//".into()],
 2759            ..LanguageConfig::default()
 2760        },
 2761        None,
 2762    ));
 2763    {
 2764        let mut cx = EditorTestContext::new(cx).await;
 2765        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2766        cx.set_state(indoc! {"
 2767        // Fooˇ
 2768    "});
 2769
 2770        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2771        cx.assert_editor_state(indoc! {"
 2772        // Foo
 2773        //ˇ
 2774    "});
 2775        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 2776        cx.set_state(indoc! {"
 2777        ˇ// Foo
 2778    "});
 2779        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2780        cx.assert_editor_state(indoc! {"
 2781
 2782        ˇ// Foo
 2783    "});
 2784    }
 2785    // Ensure that comment continuations can be disabled.
 2786    update_test_language_settings(cx, |settings| {
 2787        settings.defaults.extend_comment_on_newline = Some(false);
 2788    });
 2789    let mut cx = EditorTestContext::new(cx).await;
 2790    cx.set_state(indoc! {"
 2791        // Fooˇ
 2792    "});
 2793    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 2794    cx.assert_editor_state(indoc! {"
 2795        // Foo
 2796        ˇ
 2797    "});
 2798}
 2799
 2800#[gpui::test]
 2801fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 2802    init_test(cx, |_| {});
 2803
 2804    let editor = cx.add_window(|window, cx| {
 2805        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 2806        let mut editor = build_editor(buffer.clone(), window, cx);
 2807        editor.change_selections(None, window, cx, |s| {
 2808            s.select_ranges([3..4, 11..12, 19..20])
 2809        });
 2810        editor
 2811    });
 2812
 2813    _ = editor.update(cx, |editor, window, cx| {
 2814        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2815        editor.buffer.update(cx, |buffer, cx| {
 2816            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 2817            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 2818        });
 2819        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 2820
 2821        editor.insert("Z", window, cx);
 2822        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 2823
 2824        // The selections are moved after the inserted characters
 2825        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 2826    });
 2827}
 2828
 2829#[gpui::test]
 2830async fn test_tab(cx: &mut TestAppContext) {
 2831    init_test(cx, |settings| {
 2832        settings.defaults.tab_size = NonZeroU32::new(3)
 2833    });
 2834
 2835    let mut cx = EditorTestContext::new(cx).await;
 2836    cx.set_state(indoc! {"
 2837        ˇabˇc
 2838        ˇ🏀ˇ🏀ˇefg
 2839 2840    "});
 2841    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2842    cx.assert_editor_state(indoc! {"
 2843           ˇab ˇc
 2844           ˇ🏀  ˇ🏀  ˇefg
 2845        d  ˇ
 2846    "});
 2847
 2848    cx.set_state(indoc! {"
 2849        a
 2850        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 2851    "});
 2852    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2853    cx.assert_editor_state(indoc! {"
 2854        a
 2855           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 2856    "});
 2857}
 2858
 2859#[gpui::test]
 2860async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 2861    init_test(cx, |_| {});
 2862
 2863    let mut cx = EditorTestContext::new(cx).await;
 2864    let language = Arc::new(
 2865        Language::new(
 2866            LanguageConfig::default(),
 2867            Some(tree_sitter_rust::LANGUAGE.into()),
 2868        )
 2869        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 2870        .unwrap(),
 2871    );
 2872    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2873
 2874    // cursors that are already at the suggested indent level insert
 2875    // a soft tab. cursors that are to the left of the suggested indent
 2876    // auto-indent their line.
 2877    cx.set_state(indoc! {"
 2878        ˇ
 2879        const a: B = (
 2880            c(
 2881                d(
 2882        ˇ
 2883                )
 2884        ˇ
 2885        ˇ    )
 2886        );
 2887    "});
 2888    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2889    cx.assert_editor_state(indoc! {"
 2890            ˇ
 2891        const a: B = (
 2892            c(
 2893                d(
 2894                    ˇ
 2895                )
 2896                ˇ
 2897            ˇ)
 2898        );
 2899    "});
 2900
 2901    // handle auto-indent when there are multiple cursors on the same line
 2902    cx.set_state(indoc! {"
 2903        const a: B = (
 2904            c(
 2905        ˇ    ˇ
 2906        ˇ    )
 2907        );
 2908    "});
 2909    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2910    cx.assert_editor_state(indoc! {"
 2911        const a: B = (
 2912            c(
 2913                ˇ
 2914            ˇ)
 2915        );
 2916    "});
 2917}
 2918
 2919#[gpui::test]
 2920async fn test_tab_with_mixed_whitespace(cx: &mut TestAppContext) {
 2921    init_test(cx, |settings| {
 2922        settings.defaults.tab_size = NonZeroU32::new(4)
 2923    });
 2924
 2925    let language = Arc::new(
 2926        Language::new(
 2927            LanguageConfig::default(),
 2928            Some(tree_sitter_rust::LANGUAGE.into()),
 2929        )
 2930        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 2931        .unwrap(),
 2932    );
 2933
 2934    let mut cx = EditorTestContext::new(cx).await;
 2935    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2936    cx.set_state(indoc! {"
 2937        fn a() {
 2938            if b {
 2939        \t ˇc
 2940            }
 2941        }
 2942    "});
 2943
 2944    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2945    cx.assert_editor_state(indoc! {"
 2946        fn a() {
 2947            if b {
 2948                ˇc
 2949            }
 2950        }
 2951    "});
 2952}
 2953
 2954#[gpui::test]
 2955async fn test_indent_outdent(cx: &mut TestAppContext) {
 2956    init_test(cx, |settings| {
 2957        settings.defaults.tab_size = NonZeroU32::new(4);
 2958    });
 2959
 2960    let mut cx = EditorTestContext::new(cx).await;
 2961
 2962    cx.set_state(indoc! {"
 2963          «oneˇ» «twoˇ»
 2964        three
 2965         four
 2966    "});
 2967    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2968    cx.assert_editor_state(indoc! {"
 2969            «oneˇ» «twoˇ»
 2970        three
 2971         four
 2972    "});
 2973
 2974    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 2975    cx.assert_editor_state(indoc! {"
 2976        «oneˇ» «twoˇ»
 2977        three
 2978         four
 2979    "});
 2980
 2981    // select across line ending
 2982    cx.set_state(indoc! {"
 2983        one two
 2984        t«hree
 2985        ˇ» four
 2986    "});
 2987    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 2988    cx.assert_editor_state(indoc! {"
 2989        one two
 2990            t«hree
 2991        ˇ» four
 2992    "});
 2993
 2994    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 2995    cx.assert_editor_state(indoc! {"
 2996        one two
 2997        t«hree
 2998        ˇ» four
 2999    "});
 3000
 3001    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3002    cx.set_state(indoc! {"
 3003        one two
 3004        ˇthree
 3005            four
 3006    "});
 3007    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3008    cx.assert_editor_state(indoc! {"
 3009        one two
 3010            ˇthree
 3011            four
 3012    "});
 3013
 3014    cx.set_state(indoc! {"
 3015        one two
 3016        ˇ    three
 3017            four
 3018    "});
 3019    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3020    cx.assert_editor_state(indoc! {"
 3021        one two
 3022        ˇthree
 3023            four
 3024    "});
 3025}
 3026
 3027#[gpui::test]
 3028async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3029    init_test(cx, |settings| {
 3030        settings.defaults.hard_tabs = Some(true);
 3031    });
 3032
 3033    let mut cx = EditorTestContext::new(cx).await;
 3034
 3035    // select two ranges on one line
 3036    cx.set_state(indoc! {"
 3037        «oneˇ» «twoˇ»
 3038        three
 3039        four
 3040    "});
 3041    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3042    cx.assert_editor_state(indoc! {"
 3043        \t«oneˇ» «twoˇ»
 3044        three
 3045        four
 3046    "});
 3047    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3048    cx.assert_editor_state(indoc! {"
 3049        \t\t«oneˇ» «twoˇ»
 3050        three
 3051        four
 3052    "});
 3053    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3054    cx.assert_editor_state(indoc! {"
 3055        \t«oneˇ» «twoˇ»
 3056        three
 3057        four
 3058    "});
 3059    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3060    cx.assert_editor_state(indoc! {"
 3061        «oneˇ» «twoˇ»
 3062        three
 3063        four
 3064    "});
 3065
 3066    // select across a line ending
 3067    cx.set_state(indoc! {"
 3068        one two
 3069        t«hree
 3070        ˇ»four
 3071    "});
 3072    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3073    cx.assert_editor_state(indoc! {"
 3074        one two
 3075        \tt«hree
 3076        ˇ»four
 3077    "});
 3078    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3079    cx.assert_editor_state(indoc! {"
 3080        one two
 3081        \t\tt«hree
 3082        ˇ»four
 3083    "});
 3084    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3085    cx.assert_editor_state(indoc! {"
 3086        one two
 3087        \tt«hree
 3088        ˇ»four
 3089    "});
 3090    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3091    cx.assert_editor_state(indoc! {"
 3092        one two
 3093        t«hree
 3094        ˇ»four
 3095    "});
 3096
 3097    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3098    cx.set_state(indoc! {"
 3099        one two
 3100        ˇthree
 3101        four
 3102    "});
 3103    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3104    cx.assert_editor_state(indoc! {"
 3105        one two
 3106        ˇthree
 3107        four
 3108    "});
 3109    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3110    cx.assert_editor_state(indoc! {"
 3111        one two
 3112        \tˇthree
 3113        four
 3114    "});
 3115    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3116    cx.assert_editor_state(indoc! {"
 3117        one two
 3118        ˇthree
 3119        four
 3120    "});
 3121}
 3122
 3123#[gpui::test]
 3124fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 3125    init_test(cx, |settings| {
 3126        settings.languages.extend([
 3127            (
 3128                "TOML".into(),
 3129                LanguageSettingsContent {
 3130                    tab_size: NonZeroU32::new(2),
 3131                    ..Default::default()
 3132                },
 3133            ),
 3134            (
 3135                "Rust".into(),
 3136                LanguageSettingsContent {
 3137                    tab_size: NonZeroU32::new(4),
 3138                    ..Default::default()
 3139                },
 3140            ),
 3141        ]);
 3142    });
 3143
 3144    let toml_language = Arc::new(Language::new(
 3145        LanguageConfig {
 3146            name: "TOML".into(),
 3147            ..Default::default()
 3148        },
 3149        None,
 3150    ));
 3151    let rust_language = Arc::new(Language::new(
 3152        LanguageConfig {
 3153            name: "Rust".into(),
 3154            ..Default::default()
 3155        },
 3156        None,
 3157    ));
 3158
 3159    let toml_buffer =
 3160        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 3161    let rust_buffer =
 3162        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 3163    let multibuffer = cx.new(|cx| {
 3164        let mut multibuffer = MultiBuffer::new(ReadWrite);
 3165        multibuffer.push_excerpts(
 3166            toml_buffer.clone(),
 3167            [ExcerptRange {
 3168                context: Point::new(0, 0)..Point::new(2, 0),
 3169                primary: None,
 3170            }],
 3171            cx,
 3172        );
 3173        multibuffer.push_excerpts(
 3174            rust_buffer.clone(),
 3175            [ExcerptRange {
 3176                context: Point::new(0, 0)..Point::new(1, 0),
 3177                primary: None,
 3178            }],
 3179            cx,
 3180        );
 3181        multibuffer
 3182    });
 3183
 3184    cx.add_window(|window, cx| {
 3185        let mut editor = build_editor(multibuffer, window, cx);
 3186
 3187        assert_eq!(
 3188            editor.text(cx),
 3189            indoc! {"
 3190                a = 1
 3191                b = 2
 3192
 3193                const c: usize = 3;
 3194            "}
 3195        );
 3196
 3197        select_ranges(
 3198            &mut editor,
 3199            indoc! {"
 3200                «aˇ» = 1
 3201                b = 2
 3202
 3203                «const c:ˇ» usize = 3;
 3204            "},
 3205            window,
 3206            cx,
 3207        );
 3208
 3209        editor.tab(&Tab, window, cx);
 3210        assert_text_with_selections(
 3211            &mut editor,
 3212            indoc! {"
 3213                  «aˇ» = 1
 3214                b = 2
 3215
 3216                    «const c:ˇ» usize = 3;
 3217            "},
 3218            cx,
 3219        );
 3220        editor.backtab(&Backtab, window, cx);
 3221        assert_text_with_selections(
 3222            &mut editor,
 3223            indoc! {"
 3224                «aˇ» = 1
 3225                b = 2
 3226
 3227                «const c:ˇ» usize = 3;
 3228            "},
 3229            cx,
 3230        );
 3231
 3232        editor
 3233    });
 3234}
 3235
 3236#[gpui::test]
 3237async fn test_backspace(cx: &mut TestAppContext) {
 3238    init_test(cx, |_| {});
 3239
 3240    let mut cx = EditorTestContext::new(cx).await;
 3241
 3242    // Basic backspace
 3243    cx.set_state(indoc! {"
 3244        onˇe two three
 3245        fou«rˇ» five six
 3246        seven «ˇeight nine
 3247        »ten
 3248    "});
 3249    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3250    cx.assert_editor_state(indoc! {"
 3251        oˇe two three
 3252        fouˇ five six
 3253        seven ˇten
 3254    "});
 3255
 3256    // Test backspace inside and around indents
 3257    cx.set_state(indoc! {"
 3258        zero
 3259            ˇone
 3260                ˇtwo
 3261            ˇ ˇ ˇ  three
 3262        ˇ  ˇ  four
 3263    "});
 3264    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3265    cx.assert_editor_state(indoc! {"
 3266        zero
 3267        ˇone
 3268            ˇtwo
 3269        ˇ  threeˇ  four
 3270    "});
 3271
 3272    // Test backspace with line_mode set to true
 3273    cx.update_editor(|e, _, _| e.selections.line_mode = true);
 3274    cx.set_state(indoc! {"
 3275        The ˇquick ˇbrown
 3276        fox jumps over
 3277        the lazy dog
 3278        ˇThe qu«ick bˇ»rown"});
 3279    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3280    cx.assert_editor_state(indoc! {"
 3281        ˇfox jumps over
 3282        the lazy dogˇ"});
 3283}
 3284
 3285#[gpui::test]
 3286async fn test_delete(cx: &mut TestAppContext) {
 3287    init_test(cx, |_| {});
 3288
 3289    let mut cx = EditorTestContext::new(cx).await;
 3290    cx.set_state(indoc! {"
 3291        onˇe two three
 3292        fou«rˇ» five six
 3293        seven «ˇeight nine
 3294        »ten
 3295    "});
 3296    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 3297    cx.assert_editor_state(indoc! {"
 3298        onˇ two three
 3299        fouˇ five six
 3300        seven ˇten
 3301    "});
 3302
 3303    // Test backspace with line_mode set to true
 3304    cx.update_editor(|e, _, _| e.selections.line_mode = true);
 3305    cx.set_state(indoc! {"
 3306        The ˇquick ˇbrown
 3307        fox «ˇjum»ps over
 3308        the lazy dog
 3309        ˇThe qu«ick bˇ»rown"});
 3310    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 3311    cx.assert_editor_state("ˇthe lazy dogˇ");
 3312}
 3313
 3314#[gpui::test]
 3315fn test_delete_line(cx: &mut TestAppContext) {
 3316    init_test(cx, |_| {});
 3317
 3318    let editor = cx.add_window(|window, cx| {
 3319        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3320        build_editor(buffer, window, cx)
 3321    });
 3322    _ = editor.update(cx, |editor, window, cx| {
 3323        editor.change_selections(None, window, cx, |s| {
 3324            s.select_display_ranges([
 3325                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 3326                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 3327                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 3328            ])
 3329        });
 3330        editor.delete_line(&DeleteLine, window, cx);
 3331        assert_eq!(editor.display_text(cx), "ghi");
 3332        assert_eq!(
 3333            editor.selections.display_ranges(cx),
 3334            vec![
 3335                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 3336                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 3337            ]
 3338        );
 3339    });
 3340
 3341    let editor = cx.add_window(|window, cx| {
 3342        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 3343        build_editor(buffer, window, cx)
 3344    });
 3345    _ = editor.update(cx, |editor, window, cx| {
 3346        editor.change_selections(None, window, cx, |s| {
 3347            s.select_display_ranges([
 3348                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 3349            ])
 3350        });
 3351        editor.delete_line(&DeleteLine, window, cx);
 3352        assert_eq!(editor.display_text(cx), "ghi\n");
 3353        assert_eq!(
 3354            editor.selections.display_ranges(cx),
 3355            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 3356        );
 3357    });
 3358}
 3359
 3360#[gpui::test]
 3361fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 3362    init_test(cx, |_| {});
 3363
 3364    cx.add_window(|window, cx| {
 3365        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3366        let mut editor = build_editor(buffer.clone(), window, cx);
 3367        let buffer = buffer.read(cx).as_singleton().unwrap();
 3368
 3369        assert_eq!(
 3370            editor.selections.ranges::<Point>(cx),
 3371            &[Point::new(0, 0)..Point::new(0, 0)]
 3372        );
 3373
 3374        // When on single line, replace newline at end by space
 3375        editor.join_lines(&JoinLines, window, cx);
 3376        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3377        assert_eq!(
 3378            editor.selections.ranges::<Point>(cx),
 3379            &[Point::new(0, 3)..Point::new(0, 3)]
 3380        );
 3381
 3382        // When multiple lines are selected, remove newlines that are spanned by the selection
 3383        editor.change_selections(None, window, cx, |s| {
 3384            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 3385        });
 3386        editor.join_lines(&JoinLines, window, cx);
 3387        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 3388        assert_eq!(
 3389            editor.selections.ranges::<Point>(cx),
 3390            &[Point::new(0, 11)..Point::new(0, 11)]
 3391        );
 3392
 3393        // Undo should be transactional
 3394        editor.undo(&Undo, window, cx);
 3395        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 3396        assert_eq!(
 3397            editor.selections.ranges::<Point>(cx),
 3398            &[Point::new(0, 5)..Point::new(2, 2)]
 3399        );
 3400
 3401        // When joining an empty line don't insert a space
 3402        editor.change_selections(None, window, cx, |s| {
 3403            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 3404        });
 3405        editor.join_lines(&JoinLines, window, cx);
 3406        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 3407        assert_eq!(
 3408            editor.selections.ranges::<Point>(cx),
 3409            [Point::new(2, 3)..Point::new(2, 3)]
 3410        );
 3411
 3412        // We can remove trailing newlines
 3413        editor.join_lines(&JoinLines, window, cx);
 3414        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3415        assert_eq!(
 3416            editor.selections.ranges::<Point>(cx),
 3417            [Point::new(2, 3)..Point::new(2, 3)]
 3418        );
 3419
 3420        // We don't blow up on the last line
 3421        editor.join_lines(&JoinLines, window, cx);
 3422        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 3423        assert_eq!(
 3424            editor.selections.ranges::<Point>(cx),
 3425            [Point::new(2, 3)..Point::new(2, 3)]
 3426        );
 3427
 3428        // reset to test indentation
 3429        editor.buffer.update(cx, |buffer, cx| {
 3430            buffer.edit(
 3431                [
 3432                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 3433                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 3434                ],
 3435                None,
 3436                cx,
 3437            )
 3438        });
 3439
 3440        // We remove any leading spaces
 3441        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 3442        editor.change_selections(None, window, cx, |s| {
 3443            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 3444        });
 3445        editor.join_lines(&JoinLines, window, cx);
 3446        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 3447
 3448        // We don't insert a space for a line containing only spaces
 3449        editor.join_lines(&JoinLines, window, cx);
 3450        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 3451
 3452        // We ignore any leading tabs
 3453        editor.join_lines(&JoinLines, window, cx);
 3454        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 3455
 3456        editor
 3457    });
 3458}
 3459
 3460#[gpui::test]
 3461fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 3462    init_test(cx, |_| {});
 3463
 3464    cx.add_window(|window, cx| {
 3465        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 3466        let mut editor = build_editor(buffer.clone(), window, cx);
 3467        let buffer = buffer.read(cx).as_singleton().unwrap();
 3468
 3469        editor.change_selections(None, window, cx, |s| {
 3470            s.select_ranges([
 3471                Point::new(0, 2)..Point::new(1, 1),
 3472                Point::new(1, 2)..Point::new(1, 2),
 3473                Point::new(3, 1)..Point::new(3, 2),
 3474            ])
 3475        });
 3476
 3477        editor.join_lines(&JoinLines, window, cx);
 3478        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 3479
 3480        assert_eq!(
 3481            editor.selections.ranges::<Point>(cx),
 3482            [
 3483                Point::new(0, 7)..Point::new(0, 7),
 3484                Point::new(1, 3)..Point::new(1, 3)
 3485            ]
 3486        );
 3487        editor
 3488    });
 3489}
 3490
 3491#[gpui::test]
 3492async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 3493    init_test(cx, |_| {});
 3494
 3495    let mut cx = EditorTestContext::new(cx).await;
 3496
 3497    let diff_base = r#"
 3498        Line 0
 3499        Line 1
 3500        Line 2
 3501        Line 3
 3502        "#
 3503    .unindent();
 3504
 3505    cx.set_state(
 3506        &r#"
 3507        ˇLine 0
 3508        Line 1
 3509        Line 2
 3510        Line 3
 3511        "#
 3512        .unindent(),
 3513    );
 3514
 3515    cx.set_head_text(&diff_base);
 3516    executor.run_until_parked();
 3517
 3518    // Join lines
 3519    cx.update_editor(|editor, window, cx| {
 3520        editor.join_lines(&JoinLines, window, cx);
 3521    });
 3522    executor.run_until_parked();
 3523
 3524    cx.assert_editor_state(
 3525        &r#"
 3526        Line 0ˇ Line 1
 3527        Line 2
 3528        Line 3
 3529        "#
 3530        .unindent(),
 3531    );
 3532    // Join again
 3533    cx.update_editor(|editor, window, cx| {
 3534        editor.join_lines(&JoinLines, window, cx);
 3535    });
 3536    executor.run_until_parked();
 3537
 3538    cx.assert_editor_state(
 3539        &r#"
 3540        Line 0 Line 1ˇ Line 2
 3541        Line 3
 3542        "#
 3543        .unindent(),
 3544    );
 3545}
 3546
 3547#[gpui::test]
 3548async fn test_custom_newlines_cause_no_false_positive_diffs(
 3549    executor: BackgroundExecutor,
 3550    cx: &mut TestAppContext,
 3551) {
 3552    init_test(cx, |_| {});
 3553    let mut cx = EditorTestContext::new(cx).await;
 3554    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 3555    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 3556    executor.run_until_parked();
 3557
 3558    cx.update_editor(|editor, window, cx| {
 3559        let snapshot = editor.snapshot(window, cx);
 3560        assert_eq!(
 3561            snapshot
 3562                .buffer_snapshot
 3563                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 3564                .collect::<Vec<_>>(),
 3565            Vec::new(),
 3566            "Should not have any diffs for files with custom newlines"
 3567        );
 3568    });
 3569}
 3570
 3571#[gpui::test]
 3572async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
 3573    init_test(cx, |_| {});
 3574
 3575    let mut cx = EditorTestContext::new(cx).await;
 3576
 3577    // Test sort_lines_case_insensitive()
 3578    cx.set_state(indoc! {"
 3579        «z
 3580        y
 3581        x
 3582        Z
 3583        Y
 3584        Xˇ»
 3585    "});
 3586    cx.update_editor(|e, window, cx| {
 3587        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 3588    });
 3589    cx.assert_editor_state(indoc! {"
 3590        «x
 3591        X
 3592        y
 3593        Y
 3594        z
 3595        Zˇ»
 3596    "});
 3597
 3598    // Test reverse_lines()
 3599    cx.set_state(indoc! {"
 3600        «5
 3601        4
 3602        3
 3603        2
 3604        1ˇ»
 3605    "});
 3606    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 3607    cx.assert_editor_state(indoc! {"
 3608        «1
 3609        2
 3610        3
 3611        4
 3612        5ˇ»
 3613    "});
 3614
 3615    // Skip testing shuffle_line()
 3616
 3617    // From here on out, test more complex cases of manipulate_lines() with a single driver method: sort_lines_case_sensitive()
 3618    // Since all methods calling manipulate_lines() are doing the exact same general thing (reordering lines)
 3619
 3620    // Don't manipulate when cursor is on single line, but expand the selection
 3621    cx.set_state(indoc! {"
 3622        ddˇdd
 3623        ccc
 3624        bb
 3625        a
 3626    "});
 3627    cx.update_editor(|e, window, cx| {
 3628        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3629    });
 3630    cx.assert_editor_state(indoc! {"
 3631        «ddddˇ»
 3632        ccc
 3633        bb
 3634        a
 3635    "});
 3636
 3637    // Basic manipulate case
 3638    // Start selection moves to column 0
 3639    // End of selection shrinks to fit shorter line
 3640    cx.set_state(indoc! {"
 3641        dd«d
 3642        ccc
 3643        bb
 3644        aaaaaˇ»
 3645    "});
 3646    cx.update_editor(|e, window, cx| {
 3647        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3648    });
 3649    cx.assert_editor_state(indoc! {"
 3650        «aaaaa
 3651        bb
 3652        ccc
 3653        dddˇ»
 3654    "});
 3655
 3656    // Manipulate case with newlines
 3657    cx.set_state(indoc! {"
 3658        dd«d
 3659        ccc
 3660
 3661        bb
 3662        aaaaa
 3663
 3664        ˇ»
 3665    "});
 3666    cx.update_editor(|e, window, cx| {
 3667        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3668    });
 3669    cx.assert_editor_state(indoc! {"
 3670        «
 3671
 3672        aaaaa
 3673        bb
 3674        ccc
 3675        dddˇ»
 3676
 3677    "});
 3678
 3679    // Adding new line
 3680    cx.set_state(indoc! {"
 3681        aa«a
 3682        bbˇ»b
 3683    "});
 3684    cx.update_editor(|e, window, cx| {
 3685        e.manipulate_lines(window, cx, |lines| lines.push("added_line"))
 3686    });
 3687    cx.assert_editor_state(indoc! {"
 3688        «aaa
 3689        bbb
 3690        added_lineˇ»
 3691    "});
 3692
 3693    // Removing line
 3694    cx.set_state(indoc! {"
 3695        aa«a
 3696        bbbˇ»
 3697    "});
 3698    cx.update_editor(|e, window, cx| {
 3699        e.manipulate_lines(window, cx, |lines| {
 3700            lines.pop();
 3701        })
 3702    });
 3703    cx.assert_editor_state(indoc! {"
 3704        «aaaˇ»
 3705    "});
 3706
 3707    // Removing all lines
 3708    cx.set_state(indoc! {"
 3709        aa«a
 3710        bbbˇ»
 3711    "});
 3712    cx.update_editor(|e, window, cx| {
 3713        e.manipulate_lines(window, cx, |lines| {
 3714            lines.drain(..);
 3715        })
 3716    });
 3717    cx.assert_editor_state(indoc! {"
 3718        ˇ
 3719    "});
 3720}
 3721
 3722#[gpui::test]
 3723async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 3724    init_test(cx, |_| {});
 3725
 3726    let mut cx = EditorTestContext::new(cx).await;
 3727
 3728    // Consider continuous selection as single selection
 3729    cx.set_state(indoc! {"
 3730        Aaa«aa
 3731        cˇ»c«c
 3732        bb
 3733        aaaˇ»aa
 3734    "});
 3735    cx.update_editor(|e, window, cx| {
 3736        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 3737    });
 3738    cx.assert_editor_state(indoc! {"
 3739        «Aaaaa
 3740        ccc
 3741        bb
 3742        aaaaaˇ»
 3743    "});
 3744
 3745    cx.set_state(indoc! {"
 3746        Aaa«aa
 3747        cˇ»c«c
 3748        bb
 3749        aaaˇ»aa
 3750    "});
 3751    cx.update_editor(|e, window, cx| {
 3752        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 3753    });
 3754    cx.assert_editor_state(indoc! {"
 3755        «Aaaaa
 3756        ccc
 3757        bbˇ»
 3758    "});
 3759
 3760    // Consider non continuous selection as distinct dedup operations
 3761    cx.set_state(indoc! {"
 3762        «aaaaa
 3763        bb
 3764        aaaaa
 3765        aaaaaˇ»
 3766
 3767        aaa«aaˇ»
 3768    "});
 3769    cx.update_editor(|e, window, cx| {
 3770        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 3771    });
 3772    cx.assert_editor_state(indoc! {"
 3773        «aaaaa
 3774        bbˇ»
 3775
 3776        «aaaaaˇ»
 3777    "});
 3778}
 3779
 3780#[gpui::test]
 3781async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 3782    init_test(cx, |_| {});
 3783
 3784    let mut cx = EditorTestContext::new(cx).await;
 3785
 3786    cx.set_state(indoc! {"
 3787        «Aaa
 3788        aAa
 3789        Aaaˇ»
 3790    "});
 3791    cx.update_editor(|e, window, cx| {
 3792        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 3793    });
 3794    cx.assert_editor_state(indoc! {"
 3795        «Aaa
 3796        aAaˇ»
 3797    "});
 3798
 3799    cx.set_state(indoc! {"
 3800        «Aaa
 3801        aAa
 3802        aaAˇ»
 3803    "});
 3804    cx.update_editor(|e, window, cx| {
 3805        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 3806    });
 3807    cx.assert_editor_state(indoc! {"
 3808        «Aaaˇ»
 3809    "});
 3810}
 3811
 3812#[gpui::test]
 3813async fn test_manipulate_lines_with_multi_selection(cx: &mut TestAppContext) {
 3814    init_test(cx, |_| {});
 3815
 3816    let mut cx = EditorTestContext::new(cx).await;
 3817
 3818    // Manipulate with multiple selections on a single line
 3819    cx.set_state(indoc! {"
 3820        dd«dd
 3821        cˇ»c«c
 3822        bb
 3823        aaaˇ»aa
 3824    "});
 3825    cx.update_editor(|e, window, cx| {
 3826        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3827    });
 3828    cx.assert_editor_state(indoc! {"
 3829        «aaaaa
 3830        bb
 3831        ccc
 3832        ddddˇ»
 3833    "});
 3834
 3835    // Manipulate with multiple disjoin selections
 3836    cx.set_state(indoc! {"
 3837 3838        4
 3839        3
 3840        2
 3841        1ˇ»
 3842
 3843        dd«dd
 3844        ccc
 3845        bb
 3846        aaaˇ»aa
 3847    "});
 3848    cx.update_editor(|e, window, cx| {
 3849        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 3850    });
 3851    cx.assert_editor_state(indoc! {"
 3852        «1
 3853        2
 3854        3
 3855        4
 3856        5ˇ»
 3857
 3858        «aaaaa
 3859        bb
 3860        ccc
 3861        ddddˇ»
 3862    "});
 3863
 3864    // Adding lines on each selection
 3865    cx.set_state(indoc! {"
 3866 3867        1ˇ»
 3868
 3869        bb«bb
 3870        aaaˇ»aa
 3871    "});
 3872    cx.update_editor(|e, window, cx| {
 3873        e.manipulate_lines(window, cx, |lines| lines.push("added line"))
 3874    });
 3875    cx.assert_editor_state(indoc! {"
 3876        «2
 3877        1
 3878        added lineˇ»
 3879
 3880        «bbbb
 3881        aaaaa
 3882        added lineˇ»
 3883    "});
 3884
 3885    // Removing lines on each selection
 3886    cx.set_state(indoc! {"
 3887 3888        1ˇ»
 3889
 3890        bb«bb
 3891        aaaˇ»aa
 3892    "});
 3893    cx.update_editor(|e, window, cx| {
 3894        e.manipulate_lines(window, cx, |lines| {
 3895            lines.pop();
 3896        })
 3897    });
 3898    cx.assert_editor_state(indoc! {"
 3899        «2ˇ»
 3900
 3901        «bbbbˇ»
 3902    "});
 3903}
 3904
 3905#[gpui::test]
 3906async fn test_manipulate_text(cx: &mut TestAppContext) {
 3907    init_test(cx, |_| {});
 3908
 3909    let mut cx = EditorTestContext::new(cx).await;
 3910
 3911    // Test convert_to_upper_case()
 3912    cx.set_state(indoc! {"
 3913        «hello worldˇ»
 3914    "});
 3915    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 3916    cx.assert_editor_state(indoc! {"
 3917        «HELLO WORLDˇ»
 3918    "});
 3919
 3920    // Test convert_to_lower_case()
 3921    cx.set_state(indoc! {"
 3922        «HELLO WORLDˇ»
 3923    "});
 3924    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 3925    cx.assert_editor_state(indoc! {"
 3926        «hello worldˇ»
 3927    "});
 3928
 3929    // Test multiple line, single selection case
 3930    cx.set_state(indoc! {"
 3931        «The quick brown
 3932        fox jumps over
 3933        the lazy dogˇ»
 3934    "});
 3935    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 3936    cx.assert_editor_state(indoc! {"
 3937        «The Quick Brown
 3938        Fox Jumps Over
 3939        The Lazy Dogˇ»
 3940    "});
 3941
 3942    // Test multiple line, single selection case
 3943    cx.set_state(indoc! {"
 3944        «The quick brown
 3945        fox jumps over
 3946        the lazy dogˇ»
 3947    "});
 3948    cx.update_editor(|e, window, cx| {
 3949        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 3950    });
 3951    cx.assert_editor_state(indoc! {"
 3952        «TheQuickBrown
 3953        FoxJumpsOver
 3954        TheLazyDogˇ»
 3955    "});
 3956
 3957    // From here on out, test more complex cases of manipulate_text()
 3958
 3959    // Test no selection case - should affect words cursors are in
 3960    // Cursor at beginning, middle, and end of word
 3961    cx.set_state(indoc! {"
 3962        ˇhello big beauˇtiful worldˇ
 3963    "});
 3964    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 3965    cx.assert_editor_state(indoc! {"
 3966        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 3967    "});
 3968
 3969    // Test multiple selections on a single line and across multiple lines
 3970    cx.set_state(indoc! {"
 3971        «Theˇ» quick «brown
 3972        foxˇ» jumps «overˇ»
 3973        the «lazyˇ» dog
 3974    "});
 3975    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 3976    cx.assert_editor_state(indoc! {"
 3977        «THEˇ» quick «BROWN
 3978        FOXˇ» jumps «OVERˇ»
 3979        the «LAZYˇ» dog
 3980    "});
 3981
 3982    // Test case where text length grows
 3983    cx.set_state(indoc! {"
 3984        «tschüߡ»
 3985    "});
 3986    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 3987    cx.assert_editor_state(indoc! {"
 3988        «TSCHÜSSˇ»
 3989    "});
 3990
 3991    // Test to make sure we don't crash when text shrinks
 3992    cx.set_state(indoc! {"
 3993        aaa_bbbˇ
 3994    "});
 3995    cx.update_editor(|e, window, cx| {
 3996        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 3997    });
 3998    cx.assert_editor_state(indoc! {"
 3999        «aaaBbbˇ»
 4000    "});
 4001
 4002    // Test to make sure we all aware of the fact that each word can grow and shrink
 4003    // Final selections should be aware of this fact
 4004    cx.set_state(indoc! {"
 4005        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 4006    "});
 4007    cx.update_editor(|e, window, cx| {
 4008        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 4009    });
 4010    cx.assert_editor_state(indoc! {"
 4011        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 4012    "});
 4013
 4014    cx.set_state(indoc! {"
 4015        «hElLo, WoRld!ˇ»
 4016    "});
 4017    cx.update_editor(|e, window, cx| {
 4018        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 4019    });
 4020    cx.assert_editor_state(indoc! {"
 4021        «HeLlO, wOrLD!ˇ»
 4022    "});
 4023}
 4024
 4025#[gpui::test]
 4026fn test_duplicate_line(cx: &mut TestAppContext) {
 4027    init_test(cx, |_| {});
 4028
 4029    let editor = cx.add_window(|window, cx| {
 4030        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4031        build_editor(buffer, window, cx)
 4032    });
 4033    _ = editor.update(cx, |editor, window, cx| {
 4034        editor.change_selections(None, window, cx, |s| {
 4035            s.select_display_ranges([
 4036                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4037                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4038                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4039                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4040            ])
 4041        });
 4042        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4043        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4044        assert_eq!(
 4045            editor.selections.display_ranges(cx),
 4046            vec![
 4047                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4048                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 4049                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4050                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4051            ]
 4052        );
 4053    });
 4054
 4055    let editor = cx.add_window(|window, cx| {
 4056        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4057        build_editor(buffer, window, cx)
 4058    });
 4059    _ = editor.update(cx, |editor, window, cx| {
 4060        editor.change_selections(None, window, cx, |s| {
 4061            s.select_display_ranges([
 4062                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4063                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4064            ])
 4065        });
 4066        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 4067        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4068        assert_eq!(
 4069            editor.selections.display_ranges(cx),
 4070            vec![
 4071                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 4072                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 4073            ]
 4074        );
 4075    });
 4076
 4077    // With `move_upwards` the selections stay in place, except for
 4078    // the lines inserted above them
 4079    let editor = cx.add_window(|window, cx| {
 4080        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4081        build_editor(buffer, window, cx)
 4082    });
 4083    _ = editor.update(cx, |editor, window, cx| {
 4084        editor.change_selections(None, window, cx, |s| {
 4085            s.select_display_ranges([
 4086                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4087                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4088                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 4089                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4090            ])
 4091        });
 4092        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4093        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 4094        assert_eq!(
 4095            editor.selections.display_ranges(cx),
 4096            vec![
 4097                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 4098                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 4099                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 4100                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 4101            ]
 4102        );
 4103    });
 4104
 4105    let editor = cx.add_window(|window, cx| {
 4106        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4107        build_editor(buffer, window, cx)
 4108    });
 4109    _ = editor.update(cx, |editor, window, cx| {
 4110        editor.change_selections(None, window, cx, |s| {
 4111            s.select_display_ranges([
 4112                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4113                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4114            ])
 4115        });
 4116        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 4117        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 4118        assert_eq!(
 4119            editor.selections.display_ranges(cx),
 4120            vec![
 4121                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4122                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4123            ]
 4124        );
 4125    });
 4126
 4127    let editor = cx.add_window(|window, cx| {
 4128        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4129        build_editor(buffer, window, cx)
 4130    });
 4131    _ = editor.update(cx, |editor, window, cx| {
 4132        editor.change_selections(None, window, cx, |s| {
 4133            s.select_display_ranges([
 4134                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4135                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 4136            ])
 4137        });
 4138        editor.duplicate_selection(&DuplicateSelection, window, cx);
 4139        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 4140        assert_eq!(
 4141            editor.selections.display_ranges(cx),
 4142            vec![
 4143                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4144                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 4145            ]
 4146        );
 4147    });
 4148}
 4149
 4150#[gpui::test]
 4151fn test_move_line_up_down(cx: &mut TestAppContext) {
 4152    init_test(cx, |_| {});
 4153
 4154    let editor = cx.add_window(|window, cx| {
 4155        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4156        build_editor(buffer, window, cx)
 4157    });
 4158    _ = editor.update(cx, |editor, window, cx| {
 4159        editor.fold_creases(
 4160            vec![
 4161                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 4162                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 4163                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 4164            ],
 4165            true,
 4166            window,
 4167            cx,
 4168        );
 4169        editor.change_selections(None, window, cx, |s| {
 4170            s.select_display_ranges([
 4171                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4172                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4173                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4174                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 4175            ])
 4176        });
 4177        assert_eq!(
 4178            editor.display_text(cx),
 4179            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 4180        );
 4181
 4182        editor.move_line_up(&MoveLineUp, window, cx);
 4183        assert_eq!(
 4184            editor.display_text(cx),
 4185            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 4186        );
 4187        assert_eq!(
 4188            editor.selections.display_ranges(cx),
 4189            vec![
 4190                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4191                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4192                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4193                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4194            ]
 4195        );
 4196    });
 4197
 4198    _ = editor.update(cx, |editor, window, cx| {
 4199        editor.move_line_down(&MoveLineDown, window, cx);
 4200        assert_eq!(
 4201            editor.display_text(cx),
 4202            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 4203        );
 4204        assert_eq!(
 4205            editor.selections.display_ranges(cx),
 4206            vec![
 4207                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4208                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4209                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4210                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4211            ]
 4212        );
 4213    });
 4214
 4215    _ = editor.update(cx, |editor, window, cx| {
 4216        editor.move_line_down(&MoveLineDown, window, cx);
 4217        assert_eq!(
 4218            editor.display_text(cx),
 4219            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 4220        );
 4221        assert_eq!(
 4222            editor.selections.display_ranges(cx),
 4223            vec![
 4224                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4225                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 4226                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 4227                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 4228            ]
 4229        );
 4230    });
 4231
 4232    _ = editor.update(cx, |editor, window, cx| {
 4233        editor.move_line_up(&MoveLineUp, window, cx);
 4234        assert_eq!(
 4235            editor.display_text(cx),
 4236            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 4237        );
 4238        assert_eq!(
 4239            editor.selections.display_ranges(cx),
 4240            vec![
 4241                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 4242                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 4243                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 4244                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 4245            ]
 4246        );
 4247    });
 4248}
 4249
 4250#[gpui::test]
 4251fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 4252    init_test(cx, |_| {});
 4253
 4254    let editor = cx.add_window(|window, cx| {
 4255        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 4256        build_editor(buffer, window, cx)
 4257    });
 4258    _ = editor.update(cx, |editor, window, cx| {
 4259        let snapshot = editor.buffer.read(cx).snapshot(cx);
 4260        editor.insert_blocks(
 4261            [BlockProperties {
 4262                style: BlockStyle::Fixed,
 4263                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 4264                height: 1,
 4265                render: Arc::new(|_| div().into_any()),
 4266                priority: 0,
 4267            }],
 4268            Some(Autoscroll::fit()),
 4269            cx,
 4270        );
 4271        editor.change_selections(None, window, cx, |s| {
 4272            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 4273        });
 4274        editor.move_line_down(&MoveLineDown, window, cx);
 4275    });
 4276}
 4277
 4278#[gpui::test]
 4279async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 4280    init_test(cx, |_| {});
 4281
 4282    let mut cx = EditorTestContext::new(cx).await;
 4283    cx.set_state(
 4284        &"
 4285            ˇzero
 4286            one
 4287            two
 4288            three
 4289            four
 4290            five
 4291        "
 4292        .unindent(),
 4293    );
 4294
 4295    // Create a four-line block that replaces three lines of text.
 4296    cx.update_editor(|editor, window, cx| {
 4297        let snapshot = editor.snapshot(window, cx);
 4298        let snapshot = &snapshot.buffer_snapshot;
 4299        let placement = BlockPlacement::Replace(
 4300            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 4301        );
 4302        editor.insert_blocks(
 4303            [BlockProperties {
 4304                placement,
 4305                height: 4,
 4306                style: BlockStyle::Sticky,
 4307                render: Arc::new(|_| gpui::div().into_any_element()),
 4308                priority: 0,
 4309            }],
 4310            None,
 4311            cx,
 4312        );
 4313    });
 4314
 4315    // Move down so that the cursor touches the block.
 4316    cx.update_editor(|editor, window, cx| {
 4317        editor.move_down(&Default::default(), window, cx);
 4318    });
 4319    cx.assert_editor_state(
 4320        &"
 4321            zero
 4322            «one
 4323            two
 4324            threeˇ»
 4325            four
 4326            five
 4327        "
 4328        .unindent(),
 4329    );
 4330
 4331    // Move down past the block.
 4332    cx.update_editor(|editor, window, cx| {
 4333        editor.move_down(&Default::default(), window, cx);
 4334    });
 4335    cx.assert_editor_state(
 4336        &"
 4337            zero
 4338            one
 4339            two
 4340            three
 4341            ˇfour
 4342            five
 4343        "
 4344        .unindent(),
 4345    );
 4346}
 4347
 4348#[gpui::test]
 4349fn test_transpose(cx: &mut TestAppContext) {
 4350    init_test(cx, |_| {});
 4351
 4352    _ = cx.add_window(|window, cx| {
 4353        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 4354        editor.set_style(EditorStyle::default(), window, cx);
 4355        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
 4356        editor.transpose(&Default::default(), window, cx);
 4357        assert_eq!(editor.text(cx), "bac");
 4358        assert_eq!(editor.selections.ranges(cx), [2..2]);
 4359
 4360        editor.transpose(&Default::default(), window, cx);
 4361        assert_eq!(editor.text(cx), "bca");
 4362        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4363
 4364        editor.transpose(&Default::default(), window, cx);
 4365        assert_eq!(editor.text(cx), "bac");
 4366        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4367
 4368        editor
 4369    });
 4370
 4371    _ = cx.add_window(|window, cx| {
 4372        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4373        editor.set_style(EditorStyle::default(), window, cx);
 4374        editor.change_selections(None, window, cx, |s| s.select_ranges([3..3]));
 4375        editor.transpose(&Default::default(), window, cx);
 4376        assert_eq!(editor.text(cx), "acb\nde");
 4377        assert_eq!(editor.selections.ranges(cx), [3..3]);
 4378
 4379        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4380        editor.transpose(&Default::default(), window, cx);
 4381        assert_eq!(editor.text(cx), "acbd\ne");
 4382        assert_eq!(editor.selections.ranges(cx), [5..5]);
 4383
 4384        editor.transpose(&Default::default(), window, cx);
 4385        assert_eq!(editor.text(cx), "acbde\n");
 4386        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4387
 4388        editor.transpose(&Default::default(), window, cx);
 4389        assert_eq!(editor.text(cx), "acbd\ne");
 4390        assert_eq!(editor.selections.ranges(cx), [6..6]);
 4391
 4392        editor
 4393    });
 4394
 4395    _ = cx.add_window(|window, cx| {
 4396        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 4397        editor.set_style(EditorStyle::default(), window, cx);
 4398        editor.change_selections(None, window, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
 4399        editor.transpose(&Default::default(), window, cx);
 4400        assert_eq!(editor.text(cx), "bacd\ne");
 4401        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 4402
 4403        editor.transpose(&Default::default(), window, cx);
 4404        assert_eq!(editor.text(cx), "bcade\n");
 4405        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 4406
 4407        editor.transpose(&Default::default(), window, cx);
 4408        assert_eq!(editor.text(cx), "bcda\ne");
 4409        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4410
 4411        editor.transpose(&Default::default(), window, cx);
 4412        assert_eq!(editor.text(cx), "bcade\n");
 4413        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 4414
 4415        editor.transpose(&Default::default(), window, cx);
 4416        assert_eq!(editor.text(cx), "bcaed\n");
 4417        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 4418
 4419        editor
 4420    });
 4421
 4422    _ = cx.add_window(|window, cx| {
 4423        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 4424        editor.set_style(EditorStyle::default(), window, cx);
 4425        editor.change_selections(None, window, cx, |s| s.select_ranges([4..4]));
 4426        editor.transpose(&Default::default(), window, cx);
 4427        assert_eq!(editor.text(cx), "🏀🍐✋");
 4428        assert_eq!(editor.selections.ranges(cx), [8..8]);
 4429
 4430        editor.transpose(&Default::default(), window, cx);
 4431        assert_eq!(editor.text(cx), "🏀✋🍐");
 4432        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4433
 4434        editor.transpose(&Default::default(), window, cx);
 4435        assert_eq!(editor.text(cx), "🏀🍐✋");
 4436        assert_eq!(editor.selections.ranges(cx), [11..11]);
 4437
 4438        editor
 4439    });
 4440}
 4441
 4442#[gpui::test]
 4443async fn test_rewrap(cx: &mut TestAppContext) {
 4444    init_test(cx, |settings| {
 4445        settings.languages.extend([
 4446            (
 4447                "Markdown".into(),
 4448                LanguageSettingsContent {
 4449                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4450                    ..Default::default()
 4451                },
 4452            ),
 4453            (
 4454                "Plain Text".into(),
 4455                LanguageSettingsContent {
 4456                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 4457                    ..Default::default()
 4458                },
 4459            ),
 4460        ])
 4461    });
 4462
 4463    let mut cx = EditorTestContext::new(cx).await;
 4464
 4465    let language_with_c_comments = Arc::new(Language::new(
 4466        LanguageConfig {
 4467            line_comments: vec!["// ".into()],
 4468            ..LanguageConfig::default()
 4469        },
 4470        None,
 4471    ));
 4472    let language_with_pound_comments = Arc::new(Language::new(
 4473        LanguageConfig {
 4474            line_comments: vec!["# ".into()],
 4475            ..LanguageConfig::default()
 4476        },
 4477        None,
 4478    ));
 4479    let markdown_language = Arc::new(Language::new(
 4480        LanguageConfig {
 4481            name: "Markdown".into(),
 4482            ..LanguageConfig::default()
 4483        },
 4484        None,
 4485    ));
 4486    let language_with_doc_comments = Arc::new(Language::new(
 4487        LanguageConfig {
 4488            line_comments: vec!["// ".into(), "/// ".into()],
 4489            ..LanguageConfig::default()
 4490        },
 4491        Some(tree_sitter_rust::LANGUAGE.into()),
 4492    ));
 4493
 4494    let plaintext_language = Arc::new(Language::new(
 4495        LanguageConfig {
 4496            name: "Plain Text".into(),
 4497            ..LanguageConfig::default()
 4498        },
 4499        None,
 4500    ));
 4501
 4502    assert_rewrap(
 4503        indoc! {"
 4504            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 4505        "},
 4506        indoc! {"
 4507            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4508            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4509            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4510            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4511            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4512            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4513            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4514            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4515            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4516            // porttitor id. Aliquam id accumsan eros.
 4517        "},
 4518        language_with_c_comments.clone(),
 4519        &mut cx,
 4520    );
 4521
 4522    // Test that rewrapping works inside of a selection
 4523    assert_rewrap(
 4524        indoc! {"
 4525            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.ˇ»
 4526        "},
 4527        indoc! {"
 4528            «// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4529            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4530            // auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam
 4531            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4532            // Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed
 4533            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4534            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4535            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4536            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4537            // porttitor id. Aliquam id accumsan eros.ˇ»
 4538        "},
 4539        language_with_c_comments.clone(),
 4540        &mut cx,
 4541    );
 4542
 4543    // Test that cursors that expand to the same region are collapsed.
 4544    assert_rewrap(
 4545        indoc! {"
 4546            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4547            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4548            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4549            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 4550        "},
 4551        indoc! {"
 4552            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4553            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4554            // auctor, eu lacinia sapien scelerisque. ˇVivamus sit amet neque et quam
 4555            // tincidunt hendrerit. Praesent semper egestas tellus id dignissim.
 4556            // Pellentesque odio lectus, iaculis ac volutpat et, ˇblandit quis urna. Sed
 4557            // vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam,
 4558            // et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum
 4559            // dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu
 4560            // viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis
 4561            // porttitor id. Aliquam id accumsan eros.
 4562        "},
 4563        language_with_c_comments.clone(),
 4564        &mut cx,
 4565    );
 4566
 4567    // Test that non-contiguous selections are treated separately.
 4568    assert_rewrap(
 4569        indoc! {"
 4570            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit.
 4571            // ˇVivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque.
 4572            //
 4573            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4574            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 4575        "},
 4576        indoc! {"
 4577            // ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. ˇVivamus mollis elit
 4578            // purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus
 4579            // auctor, eu lacinia sapien scelerisque.
 4580            //
 4581            // ˇVivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas
 4582            // tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et,
 4583            // ˇblandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec
 4584            // molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque
 4585            // nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas
 4586            // porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id
 4587            // vulputate turpis porttitor id. Aliquam id accumsan eros.
 4588        "},
 4589        language_with_c_comments.clone(),
 4590        &mut cx,
 4591    );
 4592
 4593    // Test that different comment prefixes are supported.
 4594    assert_rewrap(
 4595        indoc! {"
 4596            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis. Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id accumsan eros.
 4597        "},
 4598        indoc! {"
 4599            # ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit
 4600            # purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 4601            # eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 4602            # hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 4603            # lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit
 4604            # amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet
 4605            # in. Integer sit amet scelerisque nisi. Lorem ipsum dolor sit amet, consectetur
 4606            # adipiscing elit. Cras egestas porta metus, eu viverra ipsum efficitur quis.
 4607            # Donec luctus eros turpis, id vulputate turpis porttitor id. Aliquam id
 4608            # accumsan eros.
 4609        "},
 4610        language_with_pound_comments.clone(),
 4611        &mut cx,
 4612    );
 4613
 4614    // Test that rewrapping is ignored outside of comments in most languages.
 4615    assert_rewrap(
 4616        indoc! {"
 4617            /// Adds two numbers.
 4618            /// Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 4619            fn add(a: u32, b: u32) -> u32 {
 4620                a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
 4621            }
 4622        "},
 4623        indoc! {"
 4624            /// Adds two numbers. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 4625            /// Vivamus mollis elit purus, a ornare lacus gravida vitae.ˇ
 4626            fn add(a: u32, b: u32) -> u32 {
 4627                a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + b + a + bˇ
 4628            }
 4629        "},
 4630        language_with_doc_comments.clone(),
 4631        &mut cx,
 4632    );
 4633
 4634    // Test that rewrapping works in Markdown and Plain Text languages.
 4635    assert_rewrap(
 4636        indoc! {"
 4637            # Hello
 4638
 4639            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
 4640        "},
 4641        indoc! {"
 4642            # Hello
 4643
 4644            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 4645            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 4646            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 4647            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 4648            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 4649            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 4650            Integer sit amet scelerisque nisi.
 4651        "},
 4652        markdown_language,
 4653        &mut cx,
 4654    );
 4655
 4656    assert_rewrap(
 4657        indoc! {"
 4658            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor, eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in. Integer sit amet scelerisque nisi.
 4659        "},
 4660        indoc! {"
 4661            Lorem ipsum dolor sit amet, ˇconsectetur adipiscing elit. Vivamus mollis elit
 4662            purus, a ornare lacus gravida vitae. Proin consectetur felis vel purus auctor,
 4663            eu lacinia sapien scelerisque. Vivamus sit amet neque et quam tincidunt
 4664            hendrerit. Praesent semper egestas tellus id dignissim. Pellentesque odio
 4665            lectus, iaculis ac volutpat et, blandit quis urna. Sed vestibulum nisi sit amet
 4666            nisl venenatis tempus. Donec molestie blandit quam, et porta nunc laoreet in.
 4667            Integer sit amet scelerisque nisi.
 4668        "},
 4669        plaintext_language,
 4670        &mut cx,
 4671    );
 4672
 4673    // Test rewrapping unaligned comments in a selection.
 4674    assert_rewrap(
 4675        indoc! {"
 4676            fn foo() {
 4677                if true {
 4678            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 4679            // Praesent semper egestas tellus id dignissim.ˇ»
 4680                    do_something();
 4681                } else {
 4682                    //
 4683                }
 4684            }
 4685        "},
 4686        indoc! {"
 4687            fn foo() {
 4688                if true {
 4689            «        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 4690                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 4691                    // egestas tellus id dignissim.ˇ»
 4692                    do_something();
 4693                } else {
 4694                    //
 4695                }
 4696            }
 4697        "},
 4698        language_with_doc_comments.clone(),
 4699        &mut cx,
 4700    );
 4701
 4702    assert_rewrap(
 4703        indoc! {"
 4704            fn foo() {
 4705                if true {
 4706            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus mollis elit purus, a ornare lacus gravida vitae.
 4707            // Praesent semper egestas tellus id dignissim.»
 4708                    do_something();
 4709                } else {
 4710                    //
 4711                }
 4712
 4713            }
 4714        "},
 4715        indoc! {"
 4716            fn foo() {
 4717                if true {
 4718            «ˇ        // Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus
 4719                    // mollis elit purus, a ornare lacus gravida vitae. Praesent semper
 4720                    // egestas tellus id dignissim.»
 4721                    do_something();
 4722                } else {
 4723                    //
 4724                }
 4725
 4726            }
 4727        "},
 4728        language_with_doc_comments.clone(),
 4729        &mut cx,
 4730    );
 4731
 4732    #[track_caller]
 4733    fn assert_rewrap(
 4734        unwrapped_text: &str,
 4735        wrapped_text: &str,
 4736        language: Arc<Language>,
 4737        cx: &mut EditorTestContext,
 4738    ) {
 4739        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4740        cx.set_state(unwrapped_text);
 4741        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 4742        cx.assert_editor_state(wrapped_text);
 4743    }
 4744}
 4745
 4746#[gpui::test]
 4747async fn test_hard_wrap(cx: &mut TestAppContext) {
 4748    init_test(cx, |_| {});
 4749    let mut cx = EditorTestContext::new(cx).await;
 4750
 4751    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 4752    cx.update_editor(|editor, _, cx| {
 4753        editor.set_hard_wrap(Some(14), cx);
 4754    });
 4755
 4756    cx.set_state(indoc!(
 4757        "
 4758        one two three ˇ
 4759        "
 4760    ));
 4761    cx.simulate_input("four");
 4762    cx.run_until_parked();
 4763
 4764    cx.assert_editor_state(indoc!(
 4765        "
 4766        one two three
 4767        fourˇ
 4768        "
 4769    ));
 4770
 4771    cx.update_editor(|editor, window, cx| {
 4772        editor.newline(&Default::default(), window, cx);
 4773    });
 4774    cx.run_until_parked();
 4775    cx.assert_editor_state(indoc!(
 4776        "
 4777        one two three
 4778        four
 4779        ˇ
 4780        "
 4781    ));
 4782
 4783    cx.simulate_input("five");
 4784    cx.run_until_parked();
 4785    cx.assert_editor_state(indoc!(
 4786        "
 4787        one two three
 4788        four
 4789        fiveˇ
 4790        "
 4791    ));
 4792
 4793    cx.update_editor(|editor, window, cx| {
 4794        editor.newline(&Default::default(), window, cx);
 4795    });
 4796    cx.run_until_parked();
 4797    cx.simulate_input("# ");
 4798    cx.run_until_parked();
 4799    cx.assert_editor_state(indoc!(
 4800        "
 4801        one two three
 4802        four
 4803        five
 4804        # ˇ
 4805        "
 4806    ));
 4807
 4808    cx.update_editor(|editor, window, cx| {
 4809        editor.newline(&Default::default(), window, cx);
 4810    });
 4811    cx.run_until_parked();
 4812    cx.assert_editor_state(indoc!(
 4813        "
 4814        one two three
 4815        four
 4816        five
 4817        #\x20
 4818 4819        "
 4820    ));
 4821
 4822    cx.simulate_input(" 6");
 4823    cx.run_until_parked();
 4824    cx.assert_editor_state(indoc!(
 4825        "
 4826        one two three
 4827        four
 4828        five
 4829        #
 4830        # 6ˇ
 4831        "
 4832    ));
 4833}
 4834
 4835#[gpui::test]
 4836async fn test_clipboard(cx: &mut TestAppContext) {
 4837    init_test(cx, |_| {});
 4838
 4839    let mut cx = EditorTestContext::new(cx).await;
 4840
 4841    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 4842    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 4843    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 4844
 4845    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 4846    cx.set_state("two ˇfour ˇsix ˇ");
 4847    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 4848    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 4849
 4850    // Paste again but with only two cursors. Since the number of cursors doesn't
 4851    // match the number of slices in the clipboard, the entire clipboard text
 4852    // is pasted at each cursor.
 4853    cx.set_state("ˇtwo one✅ four three six five ˇ");
 4854    cx.update_editor(|e, window, cx| {
 4855        e.handle_input("( ", window, cx);
 4856        e.paste(&Paste, window, cx);
 4857        e.handle_input(") ", window, cx);
 4858    });
 4859    cx.assert_editor_state(
 4860        &([
 4861            "( one✅ ",
 4862            "three ",
 4863            "five ) ˇtwo one✅ four three six five ( one✅ ",
 4864            "three ",
 4865            "five ) ˇ",
 4866        ]
 4867        .join("\n")),
 4868    );
 4869
 4870    // Cut with three selections, one of which is full-line.
 4871    cx.set_state(indoc! {"
 4872        1«2ˇ»3
 4873        4ˇ567
 4874        «8ˇ»9"});
 4875    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 4876    cx.assert_editor_state(indoc! {"
 4877        1ˇ3
 4878        ˇ9"});
 4879
 4880    // Paste with three selections, noticing how the copied selection that was full-line
 4881    // gets inserted before the second cursor.
 4882    cx.set_state(indoc! {"
 4883        1ˇ3
 4884 4885        «oˇ»ne"});
 4886    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 4887    cx.assert_editor_state(indoc! {"
 4888        12ˇ3
 4889        4567
 4890 4891        8ˇne"});
 4892
 4893    // Copy with a single cursor only, which writes the whole line into the clipboard.
 4894    cx.set_state(indoc! {"
 4895        The quick brown
 4896        fox juˇmps over
 4897        the lazy dog"});
 4898    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 4899    assert_eq!(
 4900        cx.read_from_clipboard()
 4901            .and_then(|item| item.text().as_deref().map(str::to_string)),
 4902        Some("fox jumps over\n".to_string())
 4903    );
 4904
 4905    // Paste with three selections, noticing how the copied full-line selection is inserted
 4906    // before the empty selections but replaces the selection that is non-empty.
 4907    cx.set_state(indoc! {"
 4908        Tˇhe quick brown
 4909        «foˇ»x jumps over
 4910        tˇhe lazy dog"});
 4911    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 4912    cx.assert_editor_state(indoc! {"
 4913        fox jumps over
 4914        Tˇhe quick brown
 4915        fox jumps over
 4916        ˇx jumps over
 4917        fox jumps over
 4918        tˇhe lazy dog"});
 4919}
 4920
 4921#[gpui::test]
 4922async fn test_copy_trim(cx: &mut TestAppContext) {
 4923    init_test(cx, |_| {});
 4924
 4925    let mut cx = EditorTestContext::new(cx).await;
 4926    cx.set_state(
 4927        r#"            «for selection in selections.iter() {
 4928            let mut start = selection.start;
 4929            let mut end = selection.end;
 4930            let is_entire_line = selection.is_empty() || self.selections.line_mode;
 4931            if is_entire_line {
 4932                start = Point::new(start.row, 0);ˇ»
 4933                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 4934            }
 4935        "#,
 4936    );
 4937    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 4938    assert_eq!(
 4939        cx.read_from_clipboard()
 4940            .and_then(|item| item.text().as_deref().map(str::to_string)),
 4941        Some(
 4942            "for selection in selections.iter() {
 4943            let mut start = selection.start;
 4944            let mut end = selection.end;
 4945            let is_entire_line = selection.is_empty() || self.selections.line_mode;
 4946            if is_entire_line {
 4947                start = Point::new(start.row, 0);"
 4948                .to_string()
 4949        ),
 4950        "Regular copying preserves all indentation selected",
 4951    );
 4952    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 4953    assert_eq!(
 4954        cx.read_from_clipboard()
 4955            .and_then(|item| item.text().as_deref().map(str::to_string)),
 4956        Some(
 4957            "for selection in selections.iter() {
 4958let mut start = selection.start;
 4959let mut end = selection.end;
 4960let is_entire_line = selection.is_empty() || self.selections.line_mode;
 4961if is_entire_line {
 4962    start = Point::new(start.row, 0);"
 4963                .to_string()
 4964        ),
 4965        "Copying with stripping should strip all leading whitespaces"
 4966    );
 4967
 4968    cx.set_state(
 4969        r#"       «     for selection in selections.iter() {
 4970            let mut start = selection.start;
 4971            let mut end = selection.end;
 4972            let is_entire_line = selection.is_empty() || self.selections.line_mode;
 4973            if is_entire_line {
 4974                start = Point::new(start.row, 0);ˇ»
 4975                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 4976            }
 4977        "#,
 4978    );
 4979    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 4980    assert_eq!(
 4981        cx.read_from_clipboard()
 4982            .and_then(|item| item.text().as_deref().map(str::to_string)),
 4983        Some(
 4984            "     for selection in selections.iter() {
 4985            let mut start = selection.start;
 4986            let mut end = selection.end;
 4987            let is_entire_line = selection.is_empty() || self.selections.line_mode;
 4988            if is_entire_line {
 4989                start = Point::new(start.row, 0);"
 4990                .to_string()
 4991        ),
 4992        "Regular copying preserves all indentation selected",
 4993    );
 4994    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 4995    assert_eq!(
 4996        cx.read_from_clipboard()
 4997            .and_then(|item| item.text().as_deref().map(str::to_string)),
 4998        Some(
 4999            "for selection in selections.iter() {
 5000let mut start = selection.start;
 5001let mut end = selection.end;
 5002let is_entire_line = selection.is_empty() || self.selections.line_mode;
 5003if is_entire_line {
 5004    start = Point::new(start.row, 0);"
 5005                .to_string()
 5006        ),
 5007        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 5008    );
 5009
 5010    cx.set_state(
 5011        r#"       «ˇ     for selection in selections.iter() {
 5012            let mut start = selection.start;
 5013            let mut end = selection.end;
 5014            let is_entire_line = selection.is_empty() || self.selections.line_mode;
 5015            if is_entire_line {
 5016                start = Point::new(start.row, 0);»
 5017                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5018            }
 5019        "#,
 5020    );
 5021    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5022    assert_eq!(
 5023        cx.read_from_clipboard()
 5024            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5025        Some(
 5026            "     for selection in selections.iter() {
 5027            let mut start = selection.start;
 5028            let mut end = selection.end;
 5029            let is_entire_line = selection.is_empty() || self.selections.line_mode;
 5030            if is_entire_line {
 5031                start = Point::new(start.row, 0);"
 5032                .to_string()
 5033        ),
 5034        "Regular copying for reverse selection works the same",
 5035    );
 5036    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5037    assert_eq!(
 5038        cx.read_from_clipboard()
 5039            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5040        Some(
 5041            "for selection in selections.iter() {
 5042let mut start = selection.start;
 5043let mut end = selection.end;
 5044let is_entire_line = selection.is_empty() || self.selections.line_mode;
 5045if is_entire_line {
 5046    start = Point::new(start.row, 0);"
 5047                .to_string()
 5048        ),
 5049        "Copying with stripping for reverse selection works the same"
 5050    );
 5051
 5052    cx.set_state(
 5053        r#"            for selection «in selections.iter() {
 5054            let mut start = selection.start;
 5055            let mut end = selection.end;
 5056            let is_entire_line = selection.is_empty() || self.selections.line_mode;
 5057            if is_entire_line {
 5058                start = Point::new(start.row, 0);ˇ»
 5059                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 5060            }
 5061        "#,
 5062    );
 5063    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5064    assert_eq!(
 5065        cx.read_from_clipboard()
 5066            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5067        Some(
 5068            "in selections.iter() {
 5069            let mut start = selection.start;
 5070            let mut end = selection.end;
 5071            let is_entire_line = selection.is_empty() || self.selections.line_mode;
 5072            if is_entire_line {
 5073                start = Point::new(start.row, 0);"
 5074                .to_string()
 5075        ),
 5076        "When selecting past the indent, the copying works as usual",
 5077    );
 5078    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 5079    assert_eq!(
 5080        cx.read_from_clipboard()
 5081            .and_then(|item| item.text().as_deref().map(str::to_string)),
 5082        Some(
 5083            "in selections.iter() {
 5084            let mut start = selection.start;
 5085            let mut end = selection.end;
 5086            let is_entire_line = selection.is_empty() || self.selections.line_mode;
 5087            if is_entire_line {
 5088                start = Point::new(start.row, 0);"
 5089                .to_string()
 5090        ),
 5091        "When selecting past the indent, nothing is trimmed"
 5092    );
 5093}
 5094
 5095#[gpui::test]
 5096async fn test_paste_multiline(cx: &mut TestAppContext) {
 5097    init_test(cx, |_| {});
 5098
 5099    let mut cx = EditorTestContext::new(cx).await;
 5100    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5101
 5102    // Cut an indented block, without the leading whitespace.
 5103    cx.set_state(indoc! {"
 5104        const a: B = (
 5105            c(),
 5106            «d(
 5107                e,
 5108                f
 5109            )ˇ»
 5110        );
 5111    "});
 5112    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5113    cx.assert_editor_state(indoc! {"
 5114        const a: B = (
 5115            c(),
 5116            ˇ
 5117        );
 5118    "});
 5119
 5120    // Paste it at the same position.
 5121    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5122    cx.assert_editor_state(indoc! {"
 5123        const a: B = (
 5124            c(),
 5125            d(
 5126                e,
 5127                f
 5128 5129        );
 5130    "});
 5131
 5132    // Paste it at a line with a lower indent level.
 5133    cx.set_state(indoc! {"
 5134        ˇ
 5135        const a: B = (
 5136            c(),
 5137        );
 5138    "});
 5139    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5140    cx.assert_editor_state(indoc! {"
 5141        d(
 5142            e,
 5143            f
 5144 5145        const a: B = (
 5146            c(),
 5147        );
 5148    "});
 5149
 5150    // Cut an indented block, with the leading whitespace.
 5151    cx.set_state(indoc! {"
 5152        const a: B = (
 5153            c(),
 5154        «    d(
 5155                e,
 5156                f
 5157            )
 5158        ˇ»);
 5159    "});
 5160    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 5161    cx.assert_editor_state(indoc! {"
 5162        const a: B = (
 5163            c(),
 5164        ˇ);
 5165    "});
 5166
 5167    // Paste it at the same position.
 5168    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5169    cx.assert_editor_state(indoc! {"
 5170        const a: B = (
 5171            c(),
 5172            d(
 5173                e,
 5174                f
 5175            )
 5176        ˇ);
 5177    "});
 5178
 5179    // Paste it at a line with a higher indent level.
 5180    cx.set_state(indoc! {"
 5181        const a: B = (
 5182            c(),
 5183            d(
 5184                e,
 5185 5186            )
 5187        );
 5188    "});
 5189    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5190    cx.assert_editor_state(indoc! {"
 5191        const a: B = (
 5192            c(),
 5193            d(
 5194                e,
 5195                f    d(
 5196                    e,
 5197                    f
 5198                )
 5199        ˇ
 5200            )
 5201        );
 5202    "});
 5203
 5204    // Copy an indented block, starting mid-line
 5205    cx.set_state(indoc! {"
 5206        const a: B = (
 5207            c(),
 5208            somethin«g(
 5209                e,
 5210                f
 5211            )ˇ»
 5212        );
 5213    "});
 5214    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 5215
 5216    // Paste it on a line with a lower indent level
 5217    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 5218    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5219    cx.assert_editor_state(indoc! {"
 5220        const a: B = (
 5221            c(),
 5222            something(
 5223                e,
 5224                f
 5225            )
 5226        );
 5227        g(
 5228            e,
 5229            f
 5230"});
 5231}
 5232
 5233#[gpui::test]
 5234async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 5235    init_test(cx, |_| {});
 5236
 5237    cx.write_to_clipboard(ClipboardItem::new_string(
 5238        "    d(\n        e\n    );\n".into(),
 5239    ));
 5240
 5241    let mut cx = EditorTestContext::new(cx).await;
 5242    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 5243
 5244    cx.set_state(indoc! {"
 5245        fn a() {
 5246            b();
 5247            if c() {
 5248                ˇ
 5249            }
 5250        }
 5251    "});
 5252
 5253    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5254    cx.assert_editor_state(indoc! {"
 5255        fn a() {
 5256            b();
 5257            if c() {
 5258                d(
 5259                    e
 5260                );
 5261        ˇ
 5262            }
 5263        }
 5264    "});
 5265
 5266    cx.set_state(indoc! {"
 5267        fn a() {
 5268            b();
 5269            ˇ
 5270        }
 5271    "});
 5272
 5273    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 5274    cx.assert_editor_state(indoc! {"
 5275        fn a() {
 5276            b();
 5277            d(
 5278                e
 5279            );
 5280        ˇ
 5281        }
 5282    "});
 5283}
 5284
 5285#[gpui::test]
 5286fn test_select_all(cx: &mut TestAppContext) {
 5287    init_test(cx, |_| {});
 5288
 5289    let editor = cx.add_window(|window, cx| {
 5290        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 5291        build_editor(buffer, window, cx)
 5292    });
 5293    _ = editor.update(cx, |editor, window, cx| {
 5294        editor.select_all(&SelectAll, window, cx);
 5295        assert_eq!(
 5296            editor.selections.display_ranges(cx),
 5297            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 5298        );
 5299    });
 5300}
 5301
 5302#[gpui::test]
 5303fn test_select_line(cx: &mut TestAppContext) {
 5304    init_test(cx, |_| {});
 5305
 5306    let editor = cx.add_window(|window, cx| {
 5307        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 5308        build_editor(buffer, window, cx)
 5309    });
 5310    _ = editor.update(cx, |editor, window, cx| {
 5311        editor.change_selections(None, window, cx, |s| {
 5312            s.select_display_ranges([
 5313                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5314                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5315                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5316                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 5317            ])
 5318        });
 5319        editor.select_line(&SelectLine, window, cx);
 5320        assert_eq!(
 5321            editor.selections.display_ranges(cx),
 5322            vec![
 5323                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5324                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5325            ]
 5326        );
 5327    });
 5328
 5329    _ = editor.update(cx, |editor, window, cx| {
 5330        editor.select_line(&SelectLine, window, cx);
 5331        assert_eq!(
 5332            editor.selections.display_ranges(cx),
 5333            vec![
 5334                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5335                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 5336            ]
 5337        );
 5338    });
 5339
 5340    _ = editor.update(cx, |editor, window, cx| {
 5341        editor.select_line(&SelectLine, window, cx);
 5342        assert_eq!(
 5343            editor.selections.display_ranges(cx),
 5344            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 5345        );
 5346    });
 5347}
 5348
 5349#[gpui::test]
 5350async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 5351    init_test(cx, |_| {});
 5352    let mut cx = EditorTestContext::new(cx).await;
 5353
 5354    #[track_caller]
 5355    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 5356        cx.set_state(initial_state);
 5357        cx.update_editor(|e, window, cx| {
 5358            e.split_selection_into_lines(&SplitSelectionIntoLines, window, cx)
 5359        });
 5360        cx.assert_editor_state(expected_state);
 5361    }
 5362
 5363    // Selection starts and ends at the middle of lines, left-to-right
 5364    test(
 5365        &mut cx,
 5366        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 5367        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5368    );
 5369    // Same thing, right-to-left
 5370    test(
 5371        &mut cx,
 5372        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 5373        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 5374    );
 5375
 5376    // Whole buffer, left-to-right, last line *doesn't* end with newline
 5377    test(
 5378        &mut cx,
 5379        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 5380        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5381    );
 5382    // Same thing, right-to-left
 5383    test(
 5384        &mut cx,
 5385        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 5386        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 5387    );
 5388
 5389    // Whole buffer, left-to-right, last line ends with newline
 5390    test(
 5391        &mut cx,
 5392        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 5393        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5394    );
 5395    // Same thing, right-to-left
 5396    test(
 5397        &mut cx,
 5398        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 5399        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 5400    );
 5401
 5402    // Starts at the end of a line, ends at the start of another
 5403    test(
 5404        &mut cx,
 5405        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 5406        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 5407    );
 5408}
 5409
 5410#[gpui::test]
 5411async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 5412    init_test(cx, |_| {});
 5413
 5414    let editor = cx.add_window(|window, cx| {
 5415        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 5416        build_editor(buffer, window, cx)
 5417    });
 5418
 5419    // setup
 5420    _ = editor.update(cx, |editor, window, cx| {
 5421        editor.fold_creases(
 5422            vec![
 5423                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5424                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5425                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5426            ],
 5427            true,
 5428            window,
 5429            cx,
 5430        );
 5431        assert_eq!(
 5432            editor.display_text(cx),
 5433            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5434        );
 5435    });
 5436
 5437    _ = editor.update(cx, |editor, window, cx| {
 5438        editor.change_selections(None, window, cx, |s| {
 5439            s.select_display_ranges([
 5440                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5441                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5442                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5443                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 5444            ])
 5445        });
 5446        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5447        assert_eq!(
 5448            editor.display_text(cx),
 5449            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 5450        );
 5451    });
 5452    EditorTestContext::for_editor(editor, cx)
 5453        .await
 5454        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 5455
 5456    _ = editor.update(cx, |editor, window, cx| {
 5457        editor.change_selections(None, window, cx, |s| {
 5458            s.select_display_ranges([
 5459                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 5460            ])
 5461        });
 5462        editor.split_selection_into_lines(&SplitSelectionIntoLines, window, cx);
 5463        assert_eq!(
 5464            editor.display_text(cx),
 5465            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 5466        );
 5467        assert_eq!(
 5468            editor.selections.display_ranges(cx),
 5469            [
 5470                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 5471                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 5472                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 5473                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 5474                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 5475                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 5476                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 5477            ]
 5478        );
 5479    });
 5480    EditorTestContext::for_editor(editor, cx)
 5481        .await
 5482        .assert_editor_state(
 5483            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 5484        );
 5485}
 5486
 5487#[gpui::test]
 5488async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 5489    init_test(cx, |_| {});
 5490
 5491    let mut cx = EditorTestContext::new(cx).await;
 5492
 5493    cx.set_state(indoc!(
 5494        r#"abc
 5495           defˇghi
 5496
 5497           jk
 5498           nlmo
 5499           "#
 5500    ));
 5501
 5502    cx.update_editor(|editor, window, cx| {
 5503        editor.add_selection_above(&Default::default(), window, cx);
 5504    });
 5505
 5506    cx.assert_editor_state(indoc!(
 5507        r#"abcˇ
 5508           defˇghi
 5509
 5510           jk
 5511           nlmo
 5512           "#
 5513    ));
 5514
 5515    cx.update_editor(|editor, window, cx| {
 5516        editor.add_selection_above(&Default::default(), window, cx);
 5517    });
 5518
 5519    cx.assert_editor_state(indoc!(
 5520        r#"abcˇ
 5521            defˇghi
 5522
 5523            jk
 5524            nlmo
 5525            "#
 5526    ));
 5527
 5528    cx.update_editor(|editor, window, cx| {
 5529        editor.add_selection_below(&Default::default(), window, cx);
 5530    });
 5531
 5532    cx.assert_editor_state(indoc!(
 5533        r#"abc
 5534           defˇghi
 5535
 5536           jk
 5537           nlmo
 5538           "#
 5539    ));
 5540
 5541    cx.update_editor(|editor, window, cx| {
 5542        editor.undo_selection(&Default::default(), window, cx);
 5543    });
 5544
 5545    cx.assert_editor_state(indoc!(
 5546        r#"abcˇ
 5547           defˇghi
 5548
 5549           jk
 5550           nlmo
 5551           "#
 5552    ));
 5553
 5554    cx.update_editor(|editor, window, cx| {
 5555        editor.redo_selection(&Default::default(), window, cx);
 5556    });
 5557
 5558    cx.assert_editor_state(indoc!(
 5559        r#"abc
 5560           defˇghi
 5561
 5562           jk
 5563           nlmo
 5564           "#
 5565    ));
 5566
 5567    cx.update_editor(|editor, window, cx| {
 5568        editor.add_selection_below(&Default::default(), window, cx);
 5569    });
 5570
 5571    cx.assert_editor_state(indoc!(
 5572        r#"abc
 5573           defˇghi
 5574
 5575           jk
 5576           nlmˇo
 5577           "#
 5578    ));
 5579
 5580    cx.update_editor(|editor, window, cx| {
 5581        editor.add_selection_below(&Default::default(), window, cx);
 5582    });
 5583
 5584    cx.assert_editor_state(indoc!(
 5585        r#"abc
 5586           defˇghi
 5587
 5588           jk
 5589           nlmˇo
 5590           "#
 5591    ));
 5592
 5593    // change selections
 5594    cx.set_state(indoc!(
 5595        r#"abc
 5596           def«ˇg»hi
 5597
 5598           jk
 5599           nlmo
 5600           "#
 5601    ));
 5602
 5603    cx.update_editor(|editor, window, cx| {
 5604        editor.add_selection_below(&Default::default(), window, cx);
 5605    });
 5606
 5607    cx.assert_editor_state(indoc!(
 5608        r#"abc
 5609           def«ˇg»hi
 5610
 5611           jk
 5612           nlm«ˇo»
 5613           "#
 5614    ));
 5615
 5616    cx.update_editor(|editor, window, cx| {
 5617        editor.add_selection_below(&Default::default(), window, cx);
 5618    });
 5619
 5620    cx.assert_editor_state(indoc!(
 5621        r#"abc
 5622           def«ˇg»hi
 5623
 5624           jk
 5625           nlm«ˇo»
 5626           "#
 5627    ));
 5628
 5629    cx.update_editor(|editor, window, cx| {
 5630        editor.add_selection_above(&Default::default(), window, cx);
 5631    });
 5632
 5633    cx.assert_editor_state(indoc!(
 5634        r#"abc
 5635           def«ˇg»hi
 5636
 5637           jk
 5638           nlmo
 5639           "#
 5640    ));
 5641
 5642    cx.update_editor(|editor, window, cx| {
 5643        editor.add_selection_above(&Default::default(), window, cx);
 5644    });
 5645
 5646    cx.assert_editor_state(indoc!(
 5647        r#"abc
 5648           def«ˇg»hi
 5649
 5650           jk
 5651           nlmo
 5652           "#
 5653    ));
 5654
 5655    // Change selections again
 5656    cx.set_state(indoc!(
 5657        r#"a«bc
 5658           defgˇ»hi
 5659
 5660           jk
 5661           nlmo
 5662           "#
 5663    ));
 5664
 5665    cx.update_editor(|editor, window, cx| {
 5666        editor.add_selection_below(&Default::default(), window, cx);
 5667    });
 5668
 5669    cx.assert_editor_state(indoc!(
 5670        r#"a«bcˇ»
 5671           d«efgˇ»hi
 5672
 5673           j«kˇ»
 5674           nlmo
 5675           "#
 5676    ));
 5677
 5678    cx.update_editor(|editor, window, cx| {
 5679        editor.add_selection_below(&Default::default(), window, cx);
 5680    });
 5681    cx.assert_editor_state(indoc!(
 5682        r#"a«bcˇ»
 5683           d«efgˇ»hi
 5684
 5685           j«kˇ»
 5686           n«lmoˇ»
 5687           "#
 5688    ));
 5689    cx.update_editor(|editor, window, cx| {
 5690        editor.add_selection_above(&Default::default(), window, cx);
 5691    });
 5692
 5693    cx.assert_editor_state(indoc!(
 5694        r#"a«bcˇ»
 5695           d«efgˇ»hi
 5696
 5697           j«kˇ»
 5698           nlmo
 5699           "#
 5700    ));
 5701
 5702    // Change selections again
 5703    cx.set_state(indoc!(
 5704        r#"abc
 5705           d«ˇefghi
 5706
 5707           jk
 5708           nlm»o
 5709           "#
 5710    ));
 5711
 5712    cx.update_editor(|editor, window, cx| {
 5713        editor.add_selection_above(&Default::default(), window, cx);
 5714    });
 5715
 5716    cx.assert_editor_state(indoc!(
 5717        r#"a«ˇbc»
 5718           d«ˇef»ghi
 5719
 5720           j«ˇk»
 5721           n«ˇlm»o
 5722           "#
 5723    ));
 5724
 5725    cx.update_editor(|editor, window, cx| {
 5726        editor.add_selection_below(&Default::default(), window, cx);
 5727    });
 5728
 5729    cx.assert_editor_state(indoc!(
 5730        r#"abc
 5731           d«ˇef»ghi
 5732
 5733           j«ˇk»
 5734           n«ˇlm»o
 5735           "#
 5736    ));
 5737}
 5738
 5739#[gpui::test]
 5740async fn test_select_next(cx: &mut TestAppContext) {
 5741    init_test(cx, |_| {});
 5742
 5743    let mut cx = EditorTestContext::new(cx).await;
 5744    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 5745
 5746    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5747        .unwrap();
 5748    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 5749
 5750    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5751        .unwrap();
 5752    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 5753
 5754    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 5755    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 5756
 5757    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 5758    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 5759
 5760    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5761        .unwrap();
 5762    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 5763
 5764    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5765        .unwrap();
 5766    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 5767}
 5768
 5769#[gpui::test]
 5770async fn test_select_all_matches(cx: &mut TestAppContext) {
 5771    init_test(cx, |_| {});
 5772
 5773    let mut cx = EditorTestContext::new(cx).await;
 5774
 5775    // Test caret-only selections
 5776    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 5777    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5778        .unwrap();
 5779    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 5780
 5781    // Test left-to-right selections
 5782    cx.set_state("abc\n«abcˇ»\nabc");
 5783    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5784        .unwrap();
 5785    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 5786
 5787    // Test right-to-left selections
 5788    cx.set_state("abc\n«ˇabc»\nabc");
 5789    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5790        .unwrap();
 5791    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 5792
 5793    // Test selecting whitespace with caret selection
 5794    cx.set_state("abc\nˇ   abc\nabc");
 5795    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5796        .unwrap();
 5797    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 5798
 5799    // Test selecting whitespace with left-to-right selection
 5800    cx.set_state("abc\n«ˇ  »abc\nabc");
 5801    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5802        .unwrap();
 5803    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 5804
 5805    // Test no matches with right-to-left selection
 5806    cx.set_state("abc\n«  ˇ»abc\nabc");
 5807    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 5808        .unwrap();
 5809    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 5810}
 5811
 5812#[gpui::test]
 5813async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 5814    init_test(cx, |_| {});
 5815
 5816    let mut cx = EditorTestContext::new(cx).await;
 5817    cx.set_state(
 5818        r#"let foo = 2;
 5819lˇet foo = 2;
 5820let fooˇ = 2;
 5821let foo = 2;
 5822let foo = ˇ2;"#,
 5823    );
 5824
 5825    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5826        .unwrap();
 5827    cx.assert_editor_state(
 5828        r#"let foo = 2;
 5829«letˇ» foo = 2;
 5830let «fooˇ» = 2;
 5831let foo = 2;
 5832let foo = «2ˇ»;"#,
 5833    );
 5834
 5835    // noop for multiple selections with different contents
 5836    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 5837        .unwrap();
 5838    cx.assert_editor_state(
 5839        r#"let foo = 2;
 5840«letˇ» foo = 2;
 5841let «fooˇ» = 2;
 5842let foo = 2;
 5843let foo = «2ˇ»;"#,
 5844    );
 5845}
 5846
 5847#[gpui::test]
 5848async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 5849    init_test(cx, |_| {});
 5850
 5851    let mut cx =
 5852        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 5853
 5854    cx.assert_editor_state(indoc! {"
 5855        ˇbbb
 5856        ccc
 5857
 5858        bbb
 5859        ccc
 5860        "});
 5861    cx.dispatch_action(SelectPrevious::default());
 5862    cx.assert_editor_state(indoc! {"
 5863                «bbbˇ»
 5864                ccc
 5865
 5866                bbb
 5867                ccc
 5868                "});
 5869    cx.dispatch_action(SelectPrevious::default());
 5870    cx.assert_editor_state(indoc! {"
 5871                «bbbˇ»
 5872                ccc
 5873
 5874                «bbbˇ»
 5875                ccc
 5876                "});
 5877}
 5878
 5879#[gpui::test]
 5880async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 5881    init_test(cx, |_| {});
 5882
 5883    let mut cx = EditorTestContext::new(cx).await;
 5884    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 5885
 5886    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5887        .unwrap();
 5888    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 5889
 5890    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5891        .unwrap();
 5892    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 5893
 5894    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 5895    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 5896
 5897    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 5898    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 5899
 5900    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5901        .unwrap();
 5902    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 5903
 5904    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5905        .unwrap();
 5906    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndef«abcˇ»\n«abcˇ»");
 5907
 5908    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5909        .unwrap();
 5910    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 5911}
 5912
 5913#[gpui::test]
 5914async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 5915    init_test(cx, |_| {});
 5916
 5917    let mut cx = EditorTestContext::new(cx).await;
 5918    cx.set_state("");
 5919
 5920    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5921        .unwrap();
 5922    cx.assert_editor_state("«aˇ»");
 5923    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5924        .unwrap();
 5925    cx.assert_editor_state("«aˇ»");
 5926}
 5927
 5928#[gpui::test]
 5929async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 5930    init_test(cx, |_| {});
 5931
 5932    let mut cx = EditorTestContext::new(cx).await;
 5933    cx.set_state(
 5934        r#"let foo = 2;
 5935lˇet foo = 2;
 5936let fooˇ = 2;
 5937let foo = 2;
 5938let foo = ˇ2;"#,
 5939    );
 5940
 5941    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5942        .unwrap();
 5943    cx.assert_editor_state(
 5944        r#"let foo = 2;
 5945«letˇ» foo = 2;
 5946let «fooˇ» = 2;
 5947let foo = 2;
 5948let foo = «2ˇ»;"#,
 5949    );
 5950
 5951    // noop for multiple selections with different contents
 5952    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5953        .unwrap();
 5954    cx.assert_editor_state(
 5955        r#"let foo = 2;
 5956«letˇ» foo = 2;
 5957let «fooˇ» = 2;
 5958let foo = 2;
 5959let foo = «2ˇ»;"#,
 5960    );
 5961}
 5962
 5963#[gpui::test]
 5964async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 5965    init_test(cx, |_| {});
 5966
 5967    let mut cx = EditorTestContext::new(cx).await;
 5968    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 5969
 5970    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5971        .unwrap();
 5972    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 5973
 5974    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5975        .unwrap();
 5976    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 5977
 5978    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 5979    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
 5980
 5981    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 5982    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
 5983
 5984    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5985        .unwrap();
 5986    cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
 5987
 5988    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 5989        .unwrap();
 5990    cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
 5991}
 5992
 5993#[gpui::test]
 5994async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 5995    init_test(cx, |_| {});
 5996
 5997    let language = Arc::new(Language::new(
 5998        LanguageConfig::default(),
 5999        Some(tree_sitter_rust::LANGUAGE.into()),
 6000    ));
 6001
 6002    let text = r#"
 6003        use mod1::mod2::{mod3, mod4};
 6004
 6005        fn fn_1(param1: bool, param2: &str) {
 6006            let var1 = "text";
 6007        }
 6008    "#
 6009    .unindent();
 6010
 6011    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6012    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6013    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6014
 6015    editor
 6016        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6017        .await;
 6018
 6019    editor.update_in(cx, |editor, window, cx| {
 6020        editor.change_selections(None, window, cx, |s| {
 6021            s.select_display_ranges([
 6022                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 6023                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 6024                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 6025            ]);
 6026        });
 6027        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6028    });
 6029    editor.update(cx, |editor, cx| {
 6030        assert_text_with_selections(
 6031            editor,
 6032            indoc! {r#"
 6033                use mod1::mod2::{mod3, «mod4ˇ»};
 6034
 6035                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6036                    let var1 = "«textˇ»";
 6037                }
 6038            "#},
 6039            cx,
 6040        );
 6041    });
 6042
 6043    editor.update_in(cx, |editor, window, cx| {
 6044        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6045    });
 6046    editor.update(cx, |editor, cx| {
 6047        assert_text_with_selections(
 6048            editor,
 6049            indoc! {r#"
 6050                use mod1::mod2::«{mod3, mod4}ˇ»;
 6051
 6052                «ˇfn fn_1(param1: bool, param2: &str) {
 6053                    let var1 = "text";
 6054 6055            "#},
 6056            cx,
 6057        );
 6058    });
 6059
 6060    editor.update_in(cx, |editor, window, cx| {
 6061        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6062    });
 6063    assert_eq!(
 6064        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6065        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6066    );
 6067
 6068    // Trying to expand the selected syntax node one more time has no effect.
 6069    editor.update_in(cx, |editor, window, cx| {
 6070        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6071    });
 6072    assert_eq!(
 6073        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 6074        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 6075    );
 6076
 6077    editor.update_in(cx, |editor, window, cx| {
 6078        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6079    });
 6080    editor.update(cx, |editor, cx| {
 6081        assert_text_with_selections(
 6082            editor,
 6083            indoc! {r#"
 6084                use mod1::mod2::«{mod3, mod4}ˇ»;
 6085
 6086                «ˇfn fn_1(param1: bool, param2: &str) {
 6087                    let var1 = "text";
 6088 6089            "#},
 6090            cx,
 6091        );
 6092    });
 6093
 6094    editor.update_in(cx, |editor, window, cx| {
 6095        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6096    });
 6097    editor.update(cx, |editor, cx| {
 6098        assert_text_with_selections(
 6099            editor,
 6100            indoc! {r#"
 6101                use mod1::mod2::{mod3, «mod4ˇ»};
 6102
 6103                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6104                    let var1 = "«textˇ»";
 6105                }
 6106            "#},
 6107            cx,
 6108        );
 6109    });
 6110
 6111    editor.update_in(cx, |editor, window, cx| {
 6112        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6113    });
 6114    editor.update(cx, |editor, cx| {
 6115        assert_text_with_selections(
 6116            editor,
 6117            indoc! {r#"
 6118                use mod1::mod2::{mod3, mo«ˇ»d4};
 6119
 6120                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6121                    let var1 = "te«ˇ»xt";
 6122                }
 6123            "#},
 6124            cx,
 6125        );
 6126    });
 6127
 6128    // Trying to shrink the selected syntax node one more time has no effect.
 6129    editor.update_in(cx, |editor, window, cx| {
 6130        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 6131    });
 6132    editor.update_in(cx, |editor, _, cx| {
 6133        assert_text_with_selections(
 6134            editor,
 6135            indoc! {r#"
 6136                use mod1::mod2::{mod3, mo«ˇ»d4};
 6137
 6138                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 6139                    let var1 = "te«ˇ»xt";
 6140                }
 6141            "#},
 6142            cx,
 6143        );
 6144    });
 6145
 6146    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 6147    // a fold.
 6148    editor.update_in(cx, |editor, window, cx| {
 6149        editor.fold_creases(
 6150            vec![
 6151                Crease::simple(
 6152                    Point::new(0, 21)..Point::new(0, 24),
 6153                    FoldPlaceholder::test(),
 6154                ),
 6155                Crease::simple(
 6156                    Point::new(3, 20)..Point::new(3, 22),
 6157                    FoldPlaceholder::test(),
 6158                ),
 6159            ],
 6160            true,
 6161            window,
 6162            cx,
 6163        );
 6164        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 6165    });
 6166    editor.update(cx, |editor, cx| {
 6167        assert_text_with_selections(
 6168            editor,
 6169            indoc! {r#"
 6170                use mod1::mod2::«{mod3, mod4}ˇ»;
 6171
 6172                fn fn_1«ˇ(param1: bool, param2: &str)» {
 6173                    «let var1 = "text";ˇ»
 6174                }
 6175            "#},
 6176            cx,
 6177        );
 6178    });
 6179}
 6180
 6181#[gpui::test]
 6182async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 6183    init_test(cx, |_| {});
 6184
 6185    let base_text = r#"
 6186        impl A {
 6187            // this is an uncommitted comment
 6188
 6189            fn b() {
 6190                c();
 6191            }
 6192
 6193            // this is another uncommitted comment
 6194
 6195            fn d() {
 6196                // e
 6197                // f
 6198            }
 6199        }
 6200
 6201        fn g() {
 6202            // h
 6203        }
 6204    "#
 6205    .unindent();
 6206
 6207    let text = r#"
 6208        ˇimpl A {
 6209
 6210            fn b() {
 6211                c();
 6212            }
 6213
 6214            fn d() {
 6215                // e
 6216                // f
 6217            }
 6218        }
 6219
 6220        fn g() {
 6221            // h
 6222        }
 6223    "#
 6224    .unindent();
 6225
 6226    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 6227    cx.set_state(&text);
 6228    cx.set_head_text(&base_text);
 6229    cx.update_editor(|editor, window, cx| {
 6230        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 6231    });
 6232
 6233    cx.assert_state_with_diff(
 6234        "
 6235        ˇimpl A {
 6236      -     // this is an uncommitted comment
 6237
 6238            fn b() {
 6239                c();
 6240            }
 6241
 6242      -     // this is another uncommitted comment
 6243      -
 6244            fn d() {
 6245                // e
 6246                // f
 6247            }
 6248        }
 6249
 6250        fn g() {
 6251            // h
 6252        }
 6253    "
 6254        .unindent(),
 6255    );
 6256
 6257    let expected_display_text = "
 6258        impl A {
 6259            // this is an uncommitted comment
 6260
 6261            fn b() {
 6262 6263            }
 6264
 6265            // this is another uncommitted comment
 6266
 6267            fn d() {
 6268 6269            }
 6270        }
 6271
 6272        fn g() {
 6273 6274        }
 6275        "
 6276    .unindent();
 6277
 6278    cx.update_editor(|editor, window, cx| {
 6279        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 6280        assert_eq!(editor.display_text(cx), expected_display_text);
 6281    });
 6282}
 6283
 6284#[gpui::test]
 6285async fn test_autoindent(cx: &mut TestAppContext) {
 6286    init_test(cx, |_| {});
 6287
 6288    let language = Arc::new(
 6289        Language::new(
 6290            LanguageConfig {
 6291                brackets: BracketPairConfig {
 6292                    pairs: vec![
 6293                        BracketPair {
 6294                            start: "{".to_string(),
 6295                            end: "}".to_string(),
 6296                            close: false,
 6297                            surround: false,
 6298                            newline: true,
 6299                        },
 6300                        BracketPair {
 6301                            start: "(".to_string(),
 6302                            end: ")".to_string(),
 6303                            close: false,
 6304                            surround: false,
 6305                            newline: true,
 6306                        },
 6307                    ],
 6308                    ..Default::default()
 6309                },
 6310                ..Default::default()
 6311            },
 6312            Some(tree_sitter_rust::LANGUAGE.into()),
 6313        )
 6314        .with_indents_query(
 6315            r#"
 6316                (_ "(" ")" @end) @indent
 6317                (_ "{" "}" @end) @indent
 6318            "#,
 6319        )
 6320        .unwrap(),
 6321    );
 6322
 6323    let text = "fn a() {}";
 6324
 6325    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 6326    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 6327    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 6328    editor
 6329        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 6330        .await;
 6331
 6332    editor.update_in(cx, |editor, window, cx| {
 6333        editor.change_selections(None, window, cx, |s| s.select_ranges([5..5, 8..8, 9..9]));
 6334        editor.newline(&Newline, window, cx);
 6335        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 6336        assert_eq!(
 6337            editor.selections.ranges(cx),
 6338            &[
 6339                Point::new(1, 4)..Point::new(1, 4),
 6340                Point::new(3, 4)..Point::new(3, 4),
 6341                Point::new(5, 0)..Point::new(5, 0)
 6342            ]
 6343        );
 6344    });
 6345}
 6346
 6347#[gpui::test]
 6348async fn test_autoindent_selections(cx: &mut TestAppContext) {
 6349    init_test(cx, |_| {});
 6350
 6351    {
 6352        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 6353        cx.set_state(indoc! {"
 6354            impl A {
 6355
 6356                fn b() {}
 6357
 6358            «fn c() {
 6359
 6360            }ˇ»
 6361            }
 6362        "});
 6363
 6364        cx.update_editor(|editor, window, cx| {
 6365            editor.autoindent(&Default::default(), window, cx);
 6366        });
 6367
 6368        cx.assert_editor_state(indoc! {"
 6369            impl A {
 6370
 6371                fn b() {}
 6372
 6373                «fn c() {
 6374
 6375                }ˇ»
 6376            }
 6377        "});
 6378    }
 6379
 6380    {
 6381        let mut cx = EditorTestContext::new_multibuffer(
 6382            cx,
 6383            [indoc! { "
 6384                impl A {
 6385                «
 6386                // a
 6387                fn b(){}
 6388                »
 6389                «
 6390                    }
 6391                    fn c(){}
 6392                »
 6393            "}],
 6394        );
 6395
 6396        let buffer = cx.update_editor(|editor, _, cx| {
 6397            let buffer = editor.buffer().update(cx, |buffer, _| {
 6398                buffer.all_buffers().iter().next().unwrap().clone()
 6399            });
 6400            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 6401            buffer
 6402        });
 6403
 6404        cx.run_until_parked();
 6405        cx.update_editor(|editor, window, cx| {
 6406            editor.select_all(&Default::default(), window, cx);
 6407            editor.autoindent(&Default::default(), window, cx)
 6408        });
 6409        cx.run_until_parked();
 6410
 6411        cx.update(|_, cx| {
 6412            pretty_assertions::assert_eq!(
 6413                buffer.read(cx).text(),
 6414                indoc! { "
 6415                    impl A {
 6416
 6417                        // a
 6418                        fn b(){}
 6419
 6420
 6421                    }
 6422                    fn c(){}
 6423
 6424                " }
 6425            )
 6426        });
 6427    }
 6428}
 6429
 6430#[gpui::test]
 6431async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 6432    init_test(cx, |_| {});
 6433
 6434    let mut cx = EditorTestContext::new(cx).await;
 6435
 6436    let language = Arc::new(Language::new(
 6437        LanguageConfig {
 6438            brackets: BracketPairConfig {
 6439                pairs: vec![
 6440                    BracketPair {
 6441                        start: "{".to_string(),
 6442                        end: "}".to_string(),
 6443                        close: true,
 6444                        surround: true,
 6445                        newline: true,
 6446                    },
 6447                    BracketPair {
 6448                        start: "(".to_string(),
 6449                        end: ")".to_string(),
 6450                        close: true,
 6451                        surround: true,
 6452                        newline: true,
 6453                    },
 6454                    BracketPair {
 6455                        start: "/*".to_string(),
 6456                        end: " */".to_string(),
 6457                        close: true,
 6458                        surround: true,
 6459                        newline: true,
 6460                    },
 6461                    BracketPair {
 6462                        start: "[".to_string(),
 6463                        end: "]".to_string(),
 6464                        close: false,
 6465                        surround: false,
 6466                        newline: true,
 6467                    },
 6468                    BracketPair {
 6469                        start: "\"".to_string(),
 6470                        end: "\"".to_string(),
 6471                        close: true,
 6472                        surround: true,
 6473                        newline: false,
 6474                    },
 6475                    BracketPair {
 6476                        start: "<".to_string(),
 6477                        end: ">".to_string(),
 6478                        close: false,
 6479                        surround: true,
 6480                        newline: true,
 6481                    },
 6482                ],
 6483                ..Default::default()
 6484            },
 6485            autoclose_before: "})]".to_string(),
 6486            ..Default::default()
 6487        },
 6488        Some(tree_sitter_rust::LANGUAGE.into()),
 6489    ));
 6490
 6491    cx.language_registry().add(language.clone());
 6492    cx.update_buffer(|buffer, cx| {
 6493        buffer.set_language(Some(language), cx);
 6494    });
 6495
 6496    cx.set_state(
 6497        &r#"
 6498            🏀ˇ
 6499            εˇ
 6500            ❤️ˇ
 6501        "#
 6502        .unindent(),
 6503    );
 6504
 6505    // autoclose multiple nested brackets at multiple cursors
 6506    cx.update_editor(|editor, window, cx| {
 6507        editor.handle_input("{", window, cx);
 6508        editor.handle_input("{", window, cx);
 6509        editor.handle_input("{", window, cx);
 6510    });
 6511    cx.assert_editor_state(
 6512        &"
 6513            🏀{{{ˇ}}}
 6514            ε{{{ˇ}}}
 6515            ❤️{{{ˇ}}}
 6516        "
 6517        .unindent(),
 6518    );
 6519
 6520    // insert a different closing bracket
 6521    cx.update_editor(|editor, window, cx| {
 6522        editor.handle_input(")", window, cx);
 6523    });
 6524    cx.assert_editor_state(
 6525        &"
 6526            🏀{{{)ˇ}}}
 6527            ε{{{)ˇ}}}
 6528            ❤️{{{)ˇ}}}
 6529        "
 6530        .unindent(),
 6531    );
 6532
 6533    // skip over the auto-closed brackets when typing a closing bracket
 6534    cx.update_editor(|editor, window, cx| {
 6535        editor.move_right(&MoveRight, window, cx);
 6536        editor.handle_input("}", window, cx);
 6537        editor.handle_input("}", window, cx);
 6538        editor.handle_input("}", window, cx);
 6539    });
 6540    cx.assert_editor_state(
 6541        &"
 6542            🏀{{{)}}}}ˇ
 6543            ε{{{)}}}}ˇ
 6544            ❤️{{{)}}}}ˇ
 6545        "
 6546        .unindent(),
 6547    );
 6548
 6549    // autoclose multi-character pairs
 6550    cx.set_state(
 6551        &"
 6552            ˇ
 6553            ˇ
 6554        "
 6555        .unindent(),
 6556    );
 6557    cx.update_editor(|editor, window, cx| {
 6558        editor.handle_input("/", window, cx);
 6559        editor.handle_input("*", window, cx);
 6560    });
 6561    cx.assert_editor_state(
 6562        &"
 6563            /*ˇ */
 6564            /*ˇ */
 6565        "
 6566        .unindent(),
 6567    );
 6568
 6569    // one cursor autocloses a multi-character pair, one cursor
 6570    // does not autoclose.
 6571    cx.set_state(
 6572        &"
 6573 6574            ˇ
 6575        "
 6576        .unindent(),
 6577    );
 6578    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 6579    cx.assert_editor_state(
 6580        &"
 6581            /*ˇ */
 6582 6583        "
 6584        .unindent(),
 6585    );
 6586
 6587    // Don't autoclose if the next character isn't whitespace and isn't
 6588    // listed in the language's "autoclose_before" section.
 6589    cx.set_state("ˇa b");
 6590    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 6591    cx.assert_editor_state("{ˇa b");
 6592
 6593    // Don't autoclose if `close` is false for the bracket pair
 6594    cx.set_state("ˇ");
 6595    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 6596    cx.assert_editor_state("");
 6597
 6598    // Surround with brackets if text is selected
 6599    cx.set_state("«aˇ» b");
 6600    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 6601    cx.assert_editor_state("{«aˇ»} b");
 6602
 6603    // Autoclose when not immediately after a word character
 6604    cx.set_state("a ˇ");
 6605    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6606    cx.assert_editor_state("a \"ˇ\"");
 6607
 6608    // Autoclose pair where the start and end characters are the same
 6609    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6610    cx.assert_editor_state("a \"\"ˇ");
 6611
 6612    // Don't autoclose when immediately after a word character
 6613    cx.set_state("");
 6614    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6615    cx.assert_editor_state("a\"ˇ");
 6616
 6617    // Do autoclose when after a non-word character
 6618    cx.set_state("");
 6619    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 6620    cx.assert_editor_state("{\"ˇ\"");
 6621
 6622    // Non identical pairs autoclose regardless of preceding character
 6623    cx.set_state("");
 6624    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 6625    cx.assert_editor_state("a{ˇ}");
 6626
 6627    // Don't autoclose pair if autoclose is disabled
 6628    cx.set_state("ˇ");
 6629    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 6630    cx.assert_editor_state("");
 6631
 6632    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 6633    cx.set_state("«aˇ» b");
 6634    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 6635    cx.assert_editor_state("<«aˇ»> b");
 6636}
 6637
 6638#[gpui::test]
 6639async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 6640    init_test(cx, |settings| {
 6641        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 6642    });
 6643
 6644    let mut cx = EditorTestContext::new(cx).await;
 6645
 6646    let language = Arc::new(Language::new(
 6647        LanguageConfig {
 6648            brackets: BracketPairConfig {
 6649                pairs: vec![
 6650                    BracketPair {
 6651                        start: "{".to_string(),
 6652                        end: "}".to_string(),
 6653                        close: true,
 6654                        surround: true,
 6655                        newline: true,
 6656                    },
 6657                    BracketPair {
 6658                        start: "(".to_string(),
 6659                        end: ")".to_string(),
 6660                        close: true,
 6661                        surround: true,
 6662                        newline: true,
 6663                    },
 6664                    BracketPair {
 6665                        start: "[".to_string(),
 6666                        end: "]".to_string(),
 6667                        close: false,
 6668                        surround: false,
 6669                        newline: true,
 6670                    },
 6671                ],
 6672                ..Default::default()
 6673            },
 6674            autoclose_before: "})]".to_string(),
 6675            ..Default::default()
 6676        },
 6677        Some(tree_sitter_rust::LANGUAGE.into()),
 6678    ));
 6679
 6680    cx.language_registry().add(language.clone());
 6681    cx.update_buffer(|buffer, cx| {
 6682        buffer.set_language(Some(language), cx);
 6683    });
 6684
 6685    cx.set_state(
 6686        &"
 6687            ˇ
 6688            ˇ
 6689            ˇ
 6690        "
 6691        .unindent(),
 6692    );
 6693
 6694    // ensure only matching closing brackets are skipped over
 6695    cx.update_editor(|editor, window, cx| {
 6696        editor.handle_input("}", window, cx);
 6697        editor.move_left(&MoveLeft, window, cx);
 6698        editor.handle_input(")", window, cx);
 6699        editor.move_left(&MoveLeft, window, cx);
 6700    });
 6701    cx.assert_editor_state(
 6702        &"
 6703            ˇ)}
 6704            ˇ)}
 6705            ˇ)}
 6706        "
 6707        .unindent(),
 6708    );
 6709
 6710    // skip-over closing brackets at multiple cursors
 6711    cx.update_editor(|editor, window, cx| {
 6712        editor.handle_input(")", window, cx);
 6713        editor.handle_input("}", window, cx);
 6714    });
 6715    cx.assert_editor_state(
 6716        &"
 6717            )}ˇ
 6718            )}ˇ
 6719            )}ˇ
 6720        "
 6721        .unindent(),
 6722    );
 6723
 6724    // ignore non-close brackets
 6725    cx.update_editor(|editor, window, cx| {
 6726        editor.handle_input("]", window, cx);
 6727        editor.move_left(&MoveLeft, window, cx);
 6728        editor.handle_input("]", window, cx);
 6729    });
 6730    cx.assert_editor_state(
 6731        &"
 6732            )}]ˇ]
 6733            )}]ˇ]
 6734            )}]ˇ]
 6735        "
 6736        .unindent(),
 6737    );
 6738}
 6739
 6740#[gpui::test]
 6741async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 6742    init_test(cx, |_| {});
 6743
 6744    let mut cx = EditorTestContext::new(cx).await;
 6745
 6746    let html_language = Arc::new(
 6747        Language::new(
 6748            LanguageConfig {
 6749                name: "HTML".into(),
 6750                brackets: BracketPairConfig {
 6751                    pairs: vec![
 6752                        BracketPair {
 6753                            start: "<".into(),
 6754                            end: ">".into(),
 6755                            close: true,
 6756                            ..Default::default()
 6757                        },
 6758                        BracketPair {
 6759                            start: "{".into(),
 6760                            end: "}".into(),
 6761                            close: true,
 6762                            ..Default::default()
 6763                        },
 6764                        BracketPair {
 6765                            start: "(".into(),
 6766                            end: ")".into(),
 6767                            close: true,
 6768                            ..Default::default()
 6769                        },
 6770                    ],
 6771                    ..Default::default()
 6772                },
 6773                autoclose_before: "})]>".into(),
 6774                ..Default::default()
 6775            },
 6776            Some(tree_sitter_html::LANGUAGE.into()),
 6777        )
 6778        .with_injection_query(
 6779            r#"
 6780            (script_element
 6781                (raw_text) @injection.content
 6782                (#set! injection.language "javascript"))
 6783            "#,
 6784        )
 6785        .unwrap(),
 6786    );
 6787
 6788    let javascript_language = Arc::new(Language::new(
 6789        LanguageConfig {
 6790            name: "JavaScript".into(),
 6791            brackets: BracketPairConfig {
 6792                pairs: vec![
 6793                    BracketPair {
 6794                        start: "/*".into(),
 6795                        end: " */".into(),
 6796                        close: true,
 6797                        ..Default::default()
 6798                    },
 6799                    BracketPair {
 6800                        start: "{".into(),
 6801                        end: "}".into(),
 6802                        close: true,
 6803                        ..Default::default()
 6804                    },
 6805                    BracketPair {
 6806                        start: "(".into(),
 6807                        end: ")".into(),
 6808                        close: true,
 6809                        ..Default::default()
 6810                    },
 6811                ],
 6812                ..Default::default()
 6813            },
 6814            autoclose_before: "})]>".into(),
 6815            ..Default::default()
 6816        },
 6817        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 6818    ));
 6819
 6820    cx.language_registry().add(html_language.clone());
 6821    cx.language_registry().add(javascript_language.clone());
 6822
 6823    cx.update_buffer(|buffer, cx| {
 6824        buffer.set_language(Some(html_language), cx);
 6825    });
 6826
 6827    cx.set_state(
 6828        &r#"
 6829            <body>ˇ
 6830                <script>
 6831                    var x = 1;ˇ
 6832                </script>
 6833            </body>ˇ
 6834        "#
 6835        .unindent(),
 6836    );
 6837
 6838    // Precondition: different languages are active at different locations.
 6839    cx.update_editor(|editor, window, cx| {
 6840        let snapshot = editor.snapshot(window, cx);
 6841        let cursors = editor.selections.ranges::<usize>(cx);
 6842        let languages = cursors
 6843            .iter()
 6844            .map(|c| snapshot.language_at(c.start).unwrap().name())
 6845            .collect::<Vec<_>>();
 6846        assert_eq!(
 6847            languages,
 6848            &["HTML".into(), "JavaScript".into(), "HTML".into()]
 6849        );
 6850    });
 6851
 6852    // Angle brackets autoclose in HTML, but not JavaScript.
 6853    cx.update_editor(|editor, window, cx| {
 6854        editor.handle_input("<", window, cx);
 6855        editor.handle_input("a", window, cx);
 6856    });
 6857    cx.assert_editor_state(
 6858        &r#"
 6859            <body><aˇ>
 6860                <script>
 6861                    var x = 1;<aˇ
 6862                </script>
 6863            </body><aˇ>
 6864        "#
 6865        .unindent(),
 6866    );
 6867
 6868    // Curly braces and parens autoclose in both HTML and JavaScript.
 6869    cx.update_editor(|editor, window, cx| {
 6870        editor.handle_input(" b=", window, cx);
 6871        editor.handle_input("{", window, cx);
 6872        editor.handle_input("c", window, cx);
 6873        editor.handle_input("(", window, cx);
 6874    });
 6875    cx.assert_editor_state(
 6876        &r#"
 6877            <body><a b={c(ˇ)}>
 6878                <script>
 6879                    var x = 1;<a b={c(ˇ)}
 6880                </script>
 6881            </body><a b={c(ˇ)}>
 6882        "#
 6883        .unindent(),
 6884    );
 6885
 6886    // Brackets that were already autoclosed are skipped.
 6887    cx.update_editor(|editor, window, cx| {
 6888        editor.handle_input(")", window, cx);
 6889        editor.handle_input("d", window, cx);
 6890        editor.handle_input("}", window, cx);
 6891    });
 6892    cx.assert_editor_state(
 6893        &r#"
 6894            <body><a b={c()d}ˇ>
 6895                <script>
 6896                    var x = 1;<a b={c()d}ˇ
 6897                </script>
 6898            </body><a b={c()d}ˇ>
 6899        "#
 6900        .unindent(),
 6901    );
 6902    cx.update_editor(|editor, window, cx| {
 6903        editor.handle_input(">", window, cx);
 6904    });
 6905    cx.assert_editor_state(
 6906        &r#"
 6907            <body><a b={c()d}>ˇ
 6908                <script>
 6909                    var x = 1;<a b={c()d}>ˇ
 6910                </script>
 6911            </body><a b={c()d}>ˇ
 6912        "#
 6913        .unindent(),
 6914    );
 6915
 6916    // Reset
 6917    cx.set_state(
 6918        &r#"
 6919            <body>ˇ
 6920                <script>
 6921                    var x = 1;ˇ
 6922                </script>
 6923            </body>ˇ
 6924        "#
 6925        .unindent(),
 6926    );
 6927
 6928    cx.update_editor(|editor, window, cx| {
 6929        editor.handle_input("<", window, cx);
 6930    });
 6931    cx.assert_editor_state(
 6932        &r#"
 6933            <body><ˇ>
 6934                <script>
 6935                    var x = 1;<ˇ
 6936                </script>
 6937            </body><ˇ>
 6938        "#
 6939        .unindent(),
 6940    );
 6941
 6942    // When backspacing, the closing angle brackets are removed.
 6943    cx.update_editor(|editor, window, cx| {
 6944        editor.backspace(&Backspace, window, cx);
 6945    });
 6946    cx.assert_editor_state(
 6947        &r#"
 6948            <body>ˇ
 6949                <script>
 6950                    var x = 1;ˇ
 6951                </script>
 6952            </body>ˇ
 6953        "#
 6954        .unindent(),
 6955    );
 6956
 6957    // Block comments autoclose in JavaScript, but not HTML.
 6958    cx.update_editor(|editor, window, cx| {
 6959        editor.handle_input("/", window, cx);
 6960        editor.handle_input("*", window, cx);
 6961    });
 6962    cx.assert_editor_state(
 6963        &r#"
 6964            <body>/*ˇ
 6965                <script>
 6966                    var x = 1;/*ˇ */
 6967                </script>
 6968            </body>/*ˇ
 6969        "#
 6970        .unindent(),
 6971    );
 6972}
 6973
 6974#[gpui::test]
 6975async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
 6976    init_test(cx, |_| {});
 6977
 6978    let mut cx = EditorTestContext::new(cx).await;
 6979
 6980    let rust_language = Arc::new(
 6981        Language::new(
 6982            LanguageConfig {
 6983                name: "Rust".into(),
 6984                brackets: serde_json::from_value(json!([
 6985                    { "start": "{", "end": "}", "close": true, "newline": true },
 6986                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
 6987                ]))
 6988                .unwrap(),
 6989                autoclose_before: "})]>".into(),
 6990                ..Default::default()
 6991            },
 6992            Some(tree_sitter_rust::LANGUAGE.into()),
 6993        )
 6994        .with_override_query("(string_literal) @string")
 6995        .unwrap(),
 6996    );
 6997
 6998    cx.language_registry().add(rust_language.clone());
 6999    cx.update_buffer(|buffer, cx| {
 7000        buffer.set_language(Some(rust_language), cx);
 7001    });
 7002
 7003    cx.set_state(
 7004        &r#"
 7005            let x = ˇ
 7006        "#
 7007        .unindent(),
 7008    );
 7009
 7010    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
 7011    cx.update_editor(|editor, window, cx| {
 7012        editor.handle_input("\"", window, cx);
 7013    });
 7014    cx.assert_editor_state(
 7015        &r#"
 7016            let x = "ˇ"
 7017        "#
 7018        .unindent(),
 7019    );
 7020
 7021    // Inserting another quotation mark. The cursor moves across the existing
 7022    // automatically-inserted quotation mark.
 7023    cx.update_editor(|editor, window, cx| {
 7024        editor.handle_input("\"", window, cx);
 7025    });
 7026    cx.assert_editor_state(
 7027        &r#"
 7028            let x = ""ˇ
 7029        "#
 7030        .unindent(),
 7031    );
 7032
 7033    // Reset
 7034    cx.set_state(
 7035        &r#"
 7036            let x = ˇ
 7037        "#
 7038        .unindent(),
 7039    );
 7040
 7041    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
 7042    cx.update_editor(|editor, window, cx| {
 7043        editor.handle_input("\"", window, cx);
 7044        editor.handle_input(" ", window, cx);
 7045        editor.move_left(&Default::default(), window, cx);
 7046        editor.handle_input("\\", window, cx);
 7047        editor.handle_input("\"", window, cx);
 7048    });
 7049    cx.assert_editor_state(
 7050        &r#"
 7051            let x = "\"ˇ "
 7052        "#
 7053        .unindent(),
 7054    );
 7055
 7056    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
 7057    // mark. Nothing is inserted.
 7058    cx.update_editor(|editor, window, cx| {
 7059        editor.move_right(&Default::default(), window, cx);
 7060        editor.handle_input("\"", window, cx);
 7061    });
 7062    cx.assert_editor_state(
 7063        &r#"
 7064            let x = "\" "ˇ
 7065        "#
 7066        .unindent(),
 7067    );
 7068}
 7069
 7070#[gpui::test]
 7071async fn test_surround_with_pair(cx: &mut TestAppContext) {
 7072    init_test(cx, |_| {});
 7073
 7074    let language = Arc::new(Language::new(
 7075        LanguageConfig {
 7076            brackets: BracketPairConfig {
 7077                pairs: vec![
 7078                    BracketPair {
 7079                        start: "{".to_string(),
 7080                        end: "}".to_string(),
 7081                        close: true,
 7082                        surround: true,
 7083                        newline: true,
 7084                    },
 7085                    BracketPair {
 7086                        start: "/* ".to_string(),
 7087                        end: "*/".to_string(),
 7088                        close: true,
 7089                        surround: true,
 7090                        ..Default::default()
 7091                    },
 7092                ],
 7093                ..Default::default()
 7094            },
 7095            ..Default::default()
 7096        },
 7097        Some(tree_sitter_rust::LANGUAGE.into()),
 7098    ));
 7099
 7100    let text = r#"
 7101        a
 7102        b
 7103        c
 7104    "#
 7105    .unindent();
 7106
 7107    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7108    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7109    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7110    editor
 7111        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7112        .await;
 7113
 7114    editor.update_in(cx, |editor, window, cx| {
 7115        editor.change_selections(None, window, cx, |s| {
 7116            s.select_display_ranges([
 7117                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7118                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7119                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
 7120            ])
 7121        });
 7122
 7123        editor.handle_input("{", window, cx);
 7124        editor.handle_input("{", window, cx);
 7125        editor.handle_input("{", window, cx);
 7126        assert_eq!(
 7127            editor.text(cx),
 7128            "
 7129                {{{a}}}
 7130                {{{b}}}
 7131                {{{c}}}
 7132            "
 7133            .unindent()
 7134        );
 7135        assert_eq!(
 7136            editor.selections.display_ranges(cx),
 7137            [
 7138                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
 7139                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
 7140                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
 7141            ]
 7142        );
 7143
 7144        editor.undo(&Undo, window, cx);
 7145        editor.undo(&Undo, window, cx);
 7146        editor.undo(&Undo, window, cx);
 7147        assert_eq!(
 7148            editor.text(cx),
 7149            "
 7150                a
 7151                b
 7152                c
 7153            "
 7154            .unindent()
 7155        );
 7156        assert_eq!(
 7157            editor.selections.display_ranges(cx),
 7158            [
 7159                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7160                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7161                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 7162            ]
 7163        );
 7164
 7165        // Ensure inserting the first character of a multi-byte bracket pair
 7166        // doesn't surround the selections with the bracket.
 7167        editor.handle_input("/", window, cx);
 7168        assert_eq!(
 7169            editor.text(cx),
 7170            "
 7171                /
 7172                /
 7173                /
 7174            "
 7175            .unindent()
 7176        );
 7177        assert_eq!(
 7178            editor.selections.display_ranges(cx),
 7179            [
 7180                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 7181                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7182                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 7183            ]
 7184        );
 7185
 7186        editor.undo(&Undo, window, cx);
 7187        assert_eq!(
 7188            editor.text(cx),
 7189            "
 7190                a
 7191                b
 7192                c
 7193            "
 7194            .unindent()
 7195        );
 7196        assert_eq!(
 7197            editor.selections.display_ranges(cx),
 7198            [
 7199                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7200                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 7201                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
 7202            ]
 7203        );
 7204
 7205        // Ensure inserting the last character of a multi-byte bracket pair
 7206        // doesn't surround the selections with the bracket.
 7207        editor.handle_input("*", window, cx);
 7208        assert_eq!(
 7209            editor.text(cx),
 7210            "
 7211                *
 7212                *
 7213                *
 7214            "
 7215            .unindent()
 7216        );
 7217        assert_eq!(
 7218            editor.selections.display_ranges(cx),
 7219            [
 7220                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 7221                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 7222                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
 7223            ]
 7224        );
 7225    });
 7226}
 7227
 7228#[gpui::test]
 7229async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
 7230    init_test(cx, |_| {});
 7231
 7232    let language = Arc::new(Language::new(
 7233        LanguageConfig {
 7234            brackets: BracketPairConfig {
 7235                pairs: vec![BracketPair {
 7236                    start: "{".to_string(),
 7237                    end: "}".to_string(),
 7238                    close: true,
 7239                    surround: true,
 7240                    newline: true,
 7241                }],
 7242                ..Default::default()
 7243            },
 7244            autoclose_before: "}".to_string(),
 7245            ..Default::default()
 7246        },
 7247        Some(tree_sitter_rust::LANGUAGE.into()),
 7248    ));
 7249
 7250    let text = r#"
 7251        a
 7252        b
 7253        c
 7254    "#
 7255    .unindent();
 7256
 7257    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 7258    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7259    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7260    editor
 7261        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7262        .await;
 7263
 7264    editor.update_in(cx, |editor, window, cx| {
 7265        editor.change_selections(None, window, cx, |s| {
 7266            s.select_ranges([
 7267                Point::new(0, 1)..Point::new(0, 1),
 7268                Point::new(1, 1)..Point::new(1, 1),
 7269                Point::new(2, 1)..Point::new(2, 1),
 7270            ])
 7271        });
 7272
 7273        editor.handle_input("{", window, cx);
 7274        editor.handle_input("{", window, cx);
 7275        editor.handle_input("_", window, cx);
 7276        assert_eq!(
 7277            editor.text(cx),
 7278            "
 7279                a{{_}}
 7280                b{{_}}
 7281                c{{_}}
 7282            "
 7283            .unindent()
 7284        );
 7285        assert_eq!(
 7286            editor.selections.ranges::<Point>(cx),
 7287            [
 7288                Point::new(0, 4)..Point::new(0, 4),
 7289                Point::new(1, 4)..Point::new(1, 4),
 7290                Point::new(2, 4)..Point::new(2, 4)
 7291            ]
 7292        );
 7293
 7294        editor.backspace(&Default::default(), window, cx);
 7295        editor.backspace(&Default::default(), window, cx);
 7296        assert_eq!(
 7297            editor.text(cx),
 7298            "
 7299                a{}
 7300                b{}
 7301                c{}
 7302            "
 7303            .unindent()
 7304        );
 7305        assert_eq!(
 7306            editor.selections.ranges::<Point>(cx),
 7307            [
 7308                Point::new(0, 2)..Point::new(0, 2),
 7309                Point::new(1, 2)..Point::new(1, 2),
 7310                Point::new(2, 2)..Point::new(2, 2)
 7311            ]
 7312        );
 7313
 7314        editor.delete_to_previous_word_start(&Default::default(), window, cx);
 7315        assert_eq!(
 7316            editor.text(cx),
 7317            "
 7318                a
 7319                b
 7320                c
 7321            "
 7322            .unindent()
 7323        );
 7324        assert_eq!(
 7325            editor.selections.ranges::<Point>(cx),
 7326            [
 7327                Point::new(0, 1)..Point::new(0, 1),
 7328                Point::new(1, 1)..Point::new(1, 1),
 7329                Point::new(2, 1)..Point::new(2, 1)
 7330            ]
 7331        );
 7332    });
 7333}
 7334
 7335#[gpui::test]
 7336async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
 7337    init_test(cx, |settings| {
 7338        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 7339    });
 7340
 7341    let mut cx = EditorTestContext::new(cx).await;
 7342
 7343    let language = Arc::new(Language::new(
 7344        LanguageConfig {
 7345            brackets: BracketPairConfig {
 7346                pairs: vec![
 7347                    BracketPair {
 7348                        start: "{".to_string(),
 7349                        end: "}".to_string(),
 7350                        close: true,
 7351                        surround: true,
 7352                        newline: true,
 7353                    },
 7354                    BracketPair {
 7355                        start: "(".to_string(),
 7356                        end: ")".to_string(),
 7357                        close: true,
 7358                        surround: true,
 7359                        newline: true,
 7360                    },
 7361                    BracketPair {
 7362                        start: "[".to_string(),
 7363                        end: "]".to_string(),
 7364                        close: false,
 7365                        surround: true,
 7366                        newline: true,
 7367                    },
 7368                ],
 7369                ..Default::default()
 7370            },
 7371            autoclose_before: "})]".to_string(),
 7372            ..Default::default()
 7373        },
 7374        Some(tree_sitter_rust::LANGUAGE.into()),
 7375    ));
 7376
 7377    cx.language_registry().add(language.clone());
 7378    cx.update_buffer(|buffer, cx| {
 7379        buffer.set_language(Some(language), cx);
 7380    });
 7381
 7382    cx.set_state(
 7383        &"
 7384            {(ˇ)}
 7385            [[ˇ]]
 7386            {(ˇ)}
 7387        "
 7388        .unindent(),
 7389    );
 7390
 7391    cx.update_editor(|editor, window, cx| {
 7392        editor.backspace(&Default::default(), window, cx);
 7393        editor.backspace(&Default::default(), window, cx);
 7394    });
 7395
 7396    cx.assert_editor_state(
 7397        &"
 7398            ˇ
 7399            ˇ]]
 7400            ˇ
 7401        "
 7402        .unindent(),
 7403    );
 7404
 7405    cx.update_editor(|editor, window, cx| {
 7406        editor.handle_input("{", window, cx);
 7407        editor.handle_input("{", window, cx);
 7408        editor.move_right(&MoveRight, window, cx);
 7409        editor.move_right(&MoveRight, window, cx);
 7410        editor.move_left(&MoveLeft, window, cx);
 7411        editor.move_left(&MoveLeft, window, cx);
 7412        editor.backspace(&Default::default(), window, cx);
 7413    });
 7414
 7415    cx.assert_editor_state(
 7416        &"
 7417            {ˇ}
 7418            {ˇ}]]
 7419            {ˇ}
 7420        "
 7421        .unindent(),
 7422    );
 7423
 7424    cx.update_editor(|editor, window, cx| {
 7425        editor.backspace(&Default::default(), window, cx);
 7426    });
 7427
 7428    cx.assert_editor_state(
 7429        &"
 7430            ˇ
 7431            ˇ]]
 7432            ˇ
 7433        "
 7434        .unindent(),
 7435    );
 7436}
 7437
 7438#[gpui::test]
 7439async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
 7440    init_test(cx, |_| {});
 7441
 7442    let language = Arc::new(Language::new(
 7443        LanguageConfig::default(),
 7444        Some(tree_sitter_rust::LANGUAGE.into()),
 7445    ));
 7446
 7447    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
 7448    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7449    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7450    editor
 7451        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 7452        .await;
 7453
 7454    editor.update_in(cx, |editor, window, cx| {
 7455        editor.set_auto_replace_emoji_shortcode(true);
 7456
 7457        editor.handle_input("Hello ", window, cx);
 7458        editor.handle_input(":wave", window, cx);
 7459        assert_eq!(editor.text(cx), "Hello :wave".unindent());
 7460
 7461        editor.handle_input(":", window, cx);
 7462        assert_eq!(editor.text(cx), "Hello 👋".unindent());
 7463
 7464        editor.handle_input(" :smile", window, cx);
 7465        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
 7466
 7467        editor.handle_input(":", window, cx);
 7468        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
 7469
 7470        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
 7471        editor.handle_input(":wave", window, cx);
 7472        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
 7473
 7474        editor.handle_input(":", window, cx);
 7475        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
 7476
 7477        editor.handle_input(":1", window, cx);
 7478        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
 7479
 7480        editor.handle_input(":", window, cx);
 7481        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
 7482
 7483        // Ensure shortcode does not get replaced when it is part of a word
 7484        editor.handle_input(" Test:wave", window, cx);
 7485        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
 7486
 7487        editor.handle_input(":", window, cx);
 7488        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
 7489
 7490        editor.set_auto_replace_emoji_shortcode(false);
 7491
 7492        // Ensure shortcode does not get replaced when auto replace is off
 7493        editor.handle_input(" :wave", window, cx);
 7494        assert_eq!(
 7495            editor.text(cx),
 7496            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
 7497        );
 7498
 7499        editor.handle_input(":", window, cx);
 7500        assert_eq!(
 7501            editor.text(cx),
 7502            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
 7503        );
 7504    });
 7505}
 7506
 7507#[gpui::test]
 7508async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
 7509    init_test(cx, |_| {});
 7510
 7511    let (text, insertion_ranges) = marked_text_ranges(
 7512        indoc! {"
 7513            ˇ
 7514        "},
 7515        false,
 7516    );
 7517
 7518    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 7519    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7520
 7521    _ = editor.update_in(cx, |editor, window, cx| {
 7522        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
 7523
 7524        editor
 7525            .insert_snippet(&insertion_ranges, snippet, window, cx)
 7526            .unwrap();
 7527
 7528        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 7529            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 7530            assert_eq!(editor.text(cx), expected_text);
 7531            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 7532        }
 7533
 7534        assert(
 7535            editor,
 7536            cx,
 7537            indoc! {"
 7538            type «» =•
 7539            "},
 7540        );
 7541
 7542        assert!(editor.context_menu_visible(), "There should be a matches");
 7543    });
 7544}
 7545
 7546#[gpui::test]
 7547async fn test_snippets(cx: &mut TestAppContext) {
 7548    init_test(cx, |_| {});
 7549
 7550    let (text, insertion_ranges) = marked_text_ranges(
 7551        indoc! {"
 7552            a.ˇ b
 7553            a.ˇ b
 7554            a.ˇ b
 7555        "},
 7556        false,
 7557    );
 7558
 7559    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
 7560    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 7561
 7562    editor.update_in(cx, |editor, window, cx| {
 7563        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
 7564
 7565        editor
 7566            .insert_snippet(&insertion_ranges, snippet, window, cx)
 7567            .unwrap();
 7568
 7569        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
 7570            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
 7571            assert_eq!(editor.text(cx), expected_text);
 7572            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
 7573        }
 7574
 7575        assert(
 7576            editor,
 7577            cx,
 7578            indoc! {"
 7579                a.f(«one», two, «three») b
 7580                a.f(«one», two, «three») b
 7581                a.f(«one», two, «three») b
 7582            "},
 7583        );
 7584
 7585        // Can't move earlier than the first tab stop
 7586        assert!(!editor.move_to_prev_snippet_tabstop(window, cx));
 7587        assert(
 7588            editor,
 7589            cx,
 7590            indoc! {"
 7591                a.f(«one», two, «three») b
 7592                a.f(«one», two, «three») b
 7593                a.f(«one», two, «three») b
 7594            "},
 7595        );
 7596
 7597        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 7598        assert(
 7599            editor,
 7600            cx,
 7601            indoc! {"
 7602                a.f(one, «two», three) b
 7603                a.f(one, «two», three) b
 7604                a.f(one, «two», three) b
 7605            "},
 7606        );
 7607
 7608        editor.move_to_prev_snippet_tabstop(window, cx);
 7609        assert(
 7610            editor,
 7611            cx,
 7612            indoc! {"
 7613                a.f(«one», two, «three») b
 7614                a.f(«one», two, «three») b
 7615                a.f(«one», two, «three») b
 7616            "},
 7617        );
 7618
 7619        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 7620        assert(
 7621            editor,
 7622            cx,
 7623            indoc! {"
 7624                a.f(one, «two», three) b
 7625                a.f(one, «two», three) b
 7626                a.f(one, «two», three) b
 7627            "},
 7628        );
 7629        assert!(editor.move_to_next_snippet_tabstop(window, cx));
 7630        assert(
 7631            editor,
 7632            cx,
 7633            indoc! {"
 7634                a.f(one, two, three)ˇ b
 7635                a.f(one, two, three)ˇ b
 7636                a.f(one, two, three)ˇ b
 7637            "},
 7638        );
 7639
 7640        // As soon as the last tab stop is reached, snippet state is gone
 7641        editor.move_to_prev_snippet_tabstop(window, cx);
 7642        assert(
 7643            editor,
 7644            cx,
 7645            indoc! {"
 7646                a.f(one, two, three)ˇ b
 7647                a.f(one, two, three)ˇ b
 7648                a.f(one, two, three)ˇ b
 7649            "},
 7650        );
 7651    });
 7652}
 7653
 7654#[gpui::test]
 7655async fn test_document_format_during_save(cx: &mut TestAppContext) {
 7656    init_test(cx, |_| {});
 7657
 7658    let fs = FakeFs::new(cx.executor());
 7659    fs.insert_file(path!("/file.rs"), Default::default()).await;
 7660
 7661    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
 7662
 7663    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 7664    language_registry.add(rust_lang());
 7665    let mut fake_servers = language_registry.register_fake_lsp(
 7666        "Rust",
 7667        FakeLspAdapter {
 7668            capabilities: lsp::ServerCapabilities {
 7669                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7670                ..Default::default()
 7671            },
 7672            ..Default::default()
 7673        },
 7674    );
 7675
 7676    let buffer = project
 7677        .update(cx, |project, cx| {
 7678            project.open_local_buffer(path!("/file.rs"), cx)
 7679        })
 7680        .await
 7681        .unwrap();
 7682
 7683    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 7684    let (editor, cx) = cx.add_window_view(|window, cx| {
 7685        build_editor_with_project(project.clone(), buffer, window, cx)
 7686    });
 7687    editor.update_in(cx, |editor, window, cx| {
 7688        editor.set_text("one\ntwo\nthree\n", window, cx)
 7689    });
 7690    assert!(cx.read(|cx| editor.is_dirty(cx)));
 7691
 7692    cx.executor().start_waiting();
 7693    let fake_server = fake_servers.next().await.unwrap();
 7694
 7695    let save = editor
 7696        .update_in(cx, |editor, window, cx| {
 7697            editor.save(true, project.clone(), window, cx)
 7698        })
 7699        .unwrap();
 7700    fake_server
 7701        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 7702            assert_eq!(
 7703                params.text_document.uri,
 7704                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 7705            );
 7706            assert_eq!(params.options.tab_size, 4);
 7707            Ok(Some(vec![lsp::TextEdit::new(
 7708                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 7709                ", ".to_string(),
 7710            )]))
 7711        })
 7712        .next()
 7713        .await;
 7714    cx.executor().start_waiting();
 7715    save.await;
 7716
 7717    assert_eq!(
 7718        editor.update(cx, |editor, cx| editor.text(cx)),
 7719        "one, two\nthree\n"
 7720    );
 7721    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 7722
 7723    editor.update_in(cx, |editor, window, cx| {
 7724        editor.set_text("one\ntwo\nthree\n", window, cx)
 7725    });
 7726    assert!(cx.read(|cx| editor.is_dirty(cx)));
 7727
 7728    // Ensure we can still save even if formatting hangs.
 7729    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 7730        assert_eq!(
 7731            params.text_document.uri,
 7732            lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 7733        );
 7734        futures::future::pending::<()>().await;
 7735        unreachable!()
 7736    });
 7737    let save = editor
 7738        .update_in(cx, |editor, window, cx| {
 7739            editor.save(true, project.clone(), window, cx)
 7740        })
 7741        .unwrap();
 7742    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 7743    cx.executor().start_waiting();
 7744    save.await;
 7745    assert_eq!(
 7746        editor.update(cx, |editor, cx| editor.text(cx)),
 7747        "one\ntwo\nthree\n"
 7748    );
 7749    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 7750
 7751    // For non-dirty buffer, no formatting request should be sent
 7752    let save = editor
 7753        .update_in(cx, |editor, window, cx| {
 7754            editor.save(true, project.clone(), window, cx)
 7755        })
 7756        .unwrap();
 7757    let _pending_format_request = fake_server
 7758        .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 7759            panic!("Should not be invoked on non-dirty buffer");
 7760        })
 7761        .next();
 7762    cx.executor().start_waiting();
 7763    save.await;
 7764
 7765    // Set rust language override and assert overridden tabsize is sent to language server
 7766    update_test_language_settings(cx, |settings| {
 7767        settings.languages.insert(
 7768            "Rust".into(),
 7769            LanguageSettingsContent {
 7770                tab_size: NonZeroU32::new(8),
 7771                ..Default::default()
 7772            },
 7773        );
 7774    });
 7775
 7776    editor.update_in(cx, |editor, window, cx| {
 7777        editor.set_text("somehting_new\n", window, cx)
 7778    });
 7779    assert!(cx.read(|cx| editor.is_dirty(cx)));
 7780    let save = editor
 7781        .update_in(cx, |editor, window, cx| {
 7782            editor.save(true, project.clone(), window, cx)
 7783        })
 7784        .unwrap();
 7785    fake_server
 7786        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 7787            assert_eq!(
 7788                params.text_document.uri,
 7789                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 7790            );
 7791            assert_eq!(params.options.tab_size, 8);
 7792            Ok(Some(vec![]))
 7793        })
 7794        .next()
 7795        .await;
 7796    cx.executor().start_waiting();
 7797    save.await;
 7798}
 7799
 7800#[gpui::test]
 7801async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
 7802    init_test(cx, |_| {});
 7803
 7804    let cols = 4;
 7805    let rows = 10;
 7806    let sample_text_1 = sample_text(rows, cols, 'a');
 7807    assert_eq!(
 7808        sample_text_1,
 7809        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
 7810    );
 7811    let sample_text_2 = sample_text(rows, cols, 'l');
 7812    assert_eq!(
 7813        sample_text_2,
 7814        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
 7815    );
 7816    let sample_text_3 = sample_text(rows, cols, 'v');
 7817    assert_eq!(
 7818        sample_text_3,
 7819        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
 7820    );
 7821
 7822    let fs = FakeFs::new(cx.executor());
 7823    fs.insert_tree(
 7824        path!("/a"),
 7825        json!({
 7826            "main.rs": sample_text_1,
 7827            "other.rs": sample_text_2,
 7828            "lib.rs": sample_text_3,
 7829        }),
 7830    )
 7831    .await;
 7832
 7833    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 7834    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 7835    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
 7836
 7837    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 7838    language_registry.add(rust_lang());
 7839    let mut fake_servers = language_registry.register_fake_lsp(
 7840        "Rust",
 7841        FakeLspAdapter {
 7842            capabilities: lsp::ServerCapabilities {
 7843                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 7844                ..Default::default()
 7845            },
 7846            ..Default::default()
 7847        },
 7848    );
 7849
 7850    let worktree = project.update(cx, |project, cx| {
 7851        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
 7852        assert_eq!(worktrees.len(), 1);
 7853        worktrees.pop().unwrap()
 7854    });
 7855    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
 7856
 7857    let buffer_1 = project
 7858        .update(cx, |project, cx| {
 7859            project.open_buffer((worktree_id, "main.rs"), cx)
 7860        })
 7861        .await
 7862        .unwrap();
 7863    let buffer_2 = project
 7864        .update(cx, |project, cx| {
 7865            project.open_buffer((worktree_id, "other.rs"), cx)
 7866        })
 7867        .await
 7868        .unwrap();
 7869    let buffer_3 = project
 7870        .update(cx, |project, cx| {
 7871            project.open_buffer((worktree_id, "lib.rs"), cx)
 7872        })
 7873        .await
 7874        .unwrap();
 7875
 7876    let multi_buffer = cx.new(|cx| {
 7877        let mut multi_buffer = MultiBuffer::new(ReadWrite);
 7878        multi_buffer.push_excerpts(
 7879            buffer_1.clone(),
 7880            [
 7881                ExcerptRange {
 7882                    context: Point::new(0, 0)..Point::new(3, 0),
 7883                    primary: None,
 7884                },
 7885                ExcerptRange {
 7886                    context: Point::new(5, 0)..Point::new(7, 0),
 7887                    primary: None,
 7888                },
 7889                ExcerptRange {
 7890                    context: Point::new(9, 0)..Point::new(10, 4),
 7891                    primary: None,
 7892                },
 7893            ],
 7894            cx,
 7895        );
 7896        multi_buffer.push_excerpts(
 7897            buffer_2.clone(),
 7898            [
 7899                ExcerptRange {
 7900                    context: Point::new(0, 0)..Point::new(3, 0),
 7901                    primary: None,
 7902                },
 7903                ExcerptRange {
 7904                    context: Point::new(5, 0)..Point::new(7, 0),
 7905                    primary: None,
 7906                },
 7907                ExcerptRange {
 7908                    context: Point::new(9, 0)..Point::new(10, 4),
 7909                    primary: None,
 7910                },
 7911            ],
 7912            cx,
 7913        );
 7914        multi_buffer.push_excerpts(
 7915            buffer_3.clone(),
 7916            [
 7917                ExcerptRange {
 7918                    context: Point::new(0, 0)..Point::new(3, 0),
 7919                    primary: None,
 7920                },
 7921                ExcerptRange {
 7922                    context: Point::new(5, 0)..Point::new(7, 0),
 7923                    primary: None,
 7924                },
 7925                ExcerptRange {
 7926                    context: Point::new(9, 0)..Point::new(10, 4),
 7927                    primary: None,
 7928                },
 7929            ],
 7930            cx,
 7931        );
 7932        multi_buffer
 7933    });
 7934    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
 7935        Editor::new(
 7936            EditorMode::Full,
 7937            multi_buffer,
 7938            Some(project.clone()),
 7939            window,
 7940            cx,
 7941        )
 7942    });
 7943
 7944    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 7945        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 7946            s.select_ranges(Some(1..2))
 7947        });
 7948        editor.insert("|one|two|three|", window, cx);
 7949    });
 7950    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 7951    multi_buffer_editor.update_in(cx, |editor, window, cx| {
 7952        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
 7953            s.select_ranges(Some(60..70))
 7954        });
 7955        editor.insert("|four|five|six|", window, cx);
 7956    });
 7957    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
 7958
 7959    // First two buffers should be edited, but not the third one.
 7960    assert_eq!(
 7961        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 7962        "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}",
 7963    );
 7964    buffer_1.update(cx, |buffer, _| {
 7965        assert!(buffer.is_dirty());
 7966        assert_eq!(
 7967            buffer.text(),
 7968            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
 7969        )
 7970    });
 7971    buffer_2.update(cx, |buffer, _| {
 7972        assert!(buffer.is_dirty());
 7973        assert_eq!(
 7974            buffer.text(),
 7975            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
 7976        )
 7977    });
 7978    buffer_3.update(cx, |buffer, _| {
 7979        assert!(!buffer.is_dirty());
 7980        assert_eq!(buffer.text(), sample_text_3,)
 7981    });
 7982    cx.executor().run_until_parked();
 7983
 7984    cx.executor().start_waiting();
 7985    let save = multi_buffer_editor
 7986        .update_in(cx, |editor, window, cx| {
 7987            editor.save(true, project.clone(), window, cx)
 7988        })
 7989        .unwrap();
 7990
 7991    let fake_server = fake_servers.next().await.unwrap();
 7992    fake_server
 7993        .server
 7994        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 7995            Ok(Some(vec![lsp::TextEdit::new(
 7996                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 7997                format!("[{} formatted]", params.text_document.uri),
 7998            )]))
 7999        })
 8000        .detach();
 8001    save.await;
 8002
 8003    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
 8004    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
 8005    assert_eq!(
 8006        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
 8007        uri!("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}"),
 8008    );
 8009    buffer_1.update(cx, |buffer, _| {
 8010        assert!(!buffer.is_dirty());
 8011        assert_eq!(
 8012            buffer.text(),
 8013            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
 8014        )
 8015    });
 8016    buffer_2.update(cx, |buffer, _| {
 8017        assert!(!buffer.is_dirty());
 8018        assert_eq!(
 8019            buffer.text(),
 8020            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
 8021        )
 8022    });
 8023    buffer_3.update(cx, |buffer, _| {
 8024        assert!(!buffer.is_dirty());
 8025        assert_eq!(buffer.text(), sample_text_3,)
 8026    });
 8027}
 8028
 8029#[gpui::test]
 8030async fn test_range_format_during_save(cx: &mut TestAppContext) {
 8031    init_test(cx, |_| {});
 8032
 8033    let fs = FakeFs::new(cx.executor());
 8034    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8035
 8036    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8037
 8038    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8039    language_registry.add(rust_lang());
 8040    let mut fake_servers = language_registry.register_fake_lsp(
 8041        "Rust",
 8042        FakeLspAdapter {
 8043            capabilities: lsp::ServerCapabilities {
 8044                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
 8045                ..Default::default()
 8046            },
 8047            ..Default::default()
 8048        },
 8049    );
 8050
 8051    let buffer = project
 8052        .update(cx, |project, cx| {
 8053            project.open_local_buffer(path!("/file.rs"), cx)
 8054        })
 8055        .await
 8056        .unwrap();
 8057
 8058    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8059    let (editor, cx) = cx.add_window_view(|window, cx| {
 8060        build_editor_with_project(project.clone(), buffer, window, cx)
 8061    });
 8062    editor.update_in(cx, |editor, window, cx| {
 8063        editor.set_text("one\ntwo\nthree\n", window, cx)
 8064    });
 8065    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8066
 8067    cx.executor().start_waiting();
 8068    let fake_server = fake_servers.next().await.unwrap();
 8069
 8070    let save = editor
 8071        .update_in(cx, |editor, window, cx| {
 8072            editor.save(true, project.clone(), window, cx)
 8073        })
 8074        .unwrap();
 8075    fake_server
 8076        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8077            assert_eq!(
 8078                params.text_document.uri,
 8079                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8080            );
 8081            assert_eq!(params.options.tab_size, 4);
 8082            Ok(Some(vec![lsp::TextEdit::new(
 8083                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8084                ", ".to_string(),
 8085            )]))
 8086        })
 8087        .next()
 8088        .await;
 8089    cx.executor().start_waiting();
 8090    save.await;
 8091    assert_eq!(
 8092        editor.update(cx, |editor, cx| editor.text(cx)),
 8093        "one, two\nthree\n"
 8094    );
 8095    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8096
 8097    editor.update_in(cx, |editor, window, cx| {
 8098        editor.set_text("one\ntwo\nthree\n", window, cx)
 8099    });
 8100    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8101
 8102    // Ensure we can still save even if formatting hangs.
 8103    fake_server.handle_request::<lsp::request::RangeFormatting, _, _>(
 8104        move |params, _| async move {
 8105            assert_eq!(
 8106                params.text_document.uri,
 8107                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8108            );
 8109            futures::future::pending::<()>().await;
 8110            unreachable!()
 8111        },
 8112    );
 8113    let save = editor
 8114        .update_in(cx, |editor, window, cx| {
 8115            editor.save(true, project.clone(), window, cx)
 8116        })
 8117        .unwrap();
 8118    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8119    cx.executor().start_waiting();
 8120    save.await;
 8121    assert_eq!(
 8122        editor.update(cx, |editor, cx| editor.text(cx)),
 8123        "one\ntwo\nthree\n"
 8124    );
 8125    assert!(!cx.read(|cx| editor.is_dirty(cx)));
 8126
 8127    // For non-dirty buffer, no formatting request should be sent
 8128    let save = editor
 8129        .update_in(cx, |editor, window, cx| {
 8130            editor.save(true, project.clone(), window, cx)
 8131        })
 8132        .unwrap();
 8133    let _pending_format_request = fake_server
 8134        .handle_request::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
 8135            panic!("Should not be invoked on non-dirty buffer");
 8136        })
 8137        .next();
 8138    cx.executor().start_waiting();
 8139    save.await;
 8140
 8141    // Set Rust language override and assert overridden tabsize is sent to language server
 8142    update_test_language_settings(cx, |settings| {
 8143        settings.languages.insert(
 8144            "Rust".into(),
 8145            LanguageSettingsContent {
 8146                tab_size: NonZeroU32::new(8),
 8147                ..Default::default()
 8148            },
 8149        );
 8150    });
 8151
 8152    editor.update_in(cx, |editor, window, cx| {
 8153        editor.set_text("somehting_new\n", window, cx)
 8154    });
 8155    assert!(cx.read(|cx| editor.is_dirty(cx)));
 8156    let save = editor
 8157        .update_in(cx, |editor, window, cx| {
 8158            editor.save(true, project.clone(), window, cx)
 8159        })
 8160        .unwrap();
 8161    fake_server
 8162        .handle_request::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
 8163            assert_eq!(
 8164                params.text_document.uri,
 8165                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8166            );
 8167            assert_eq!(params.options.tab_size, 8);
 8168            Ok(Some(vec![]))
 8169        })
 8170        .next()
 8171        .await;
 8172    cx.executor().start_waiting();
 8173    save.await;
 8174}
 8175
 8176#[gpui::test]
 8177async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
 8178    init_test(cx, |settings| {
 8179        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 8180            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 8181        ))
 8182    });
 8183
 8184    let fs = FakeFs::new(cx.executor());
 8185    fs.insert_file(path!("/file.rs"), Default::default()).await;
 8186
 8187    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8188
 8189    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8190    language_registry.add(Arc::new(Language::new(
 8191        LanguageConfig {
 8192            name: "Rust".into(),
 8193            matcher: LanguageMatcher {
 8194                path_suffixes: vec!["rs".to_string()],
 8195                ..Default::default()
 8196            },
 8197            ..LanguageConfig::default()
 8198        },
 8199        Some(tree_sitter_rust::LANGUAGE.into()),
 8200    )));
 8201    update_test_language_settings(cx, |settings| {
 8202        // Enable Prettier formatting for the same buffer, and ensure
 8203        // LSP is called instead of Prettier.
 8204        settings.defaults.prettier = Some(PrettierSettings {
 8205            allowed: true,
 8206            ..PrettierSettings::default()
 8207        });
 8208    });
 8209    let mut fake_servers = language_registry.register_fake_lsp(
 8210        "Rust",
 8211        FakeLspAdapter {
 8212            capabilities: lsp::ServerCapabilities {
 8213                document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8214                ..Default::default()
 8215            },
 8216            ..Default::default()
 8217        },
 8218    );
 8219
 8220    let buffer = project
 8221        .update(cx, |project, cx| {
 8222            project.open_local_buffer(path!("/file.rs"), cx)
 8223        })
 8224        .await
 8225        .unwrap();
 8226
 8227    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8228    let (editor, cx) = cx.add_window_view(|window, cx| {
 8229        build_editor_with_project(project.clone(), buffer, window, cx)
 8230    });
 8231    editor.update_in(cx, |editor, window, cx| {
 8232        editor.set_text("one\ntwo\nthree\n", window, cx)
 8233    });
 8234
 8235    cx.executor().start_waiting();
 8236    let fake_server = fake_servers.next().await.unwrap();
 8237
 8238    let format = editor
 8239        .update_in(cx, |editor, window, cx| {
 8240            editor.perform_format(
 8241                project.clone(),
 8242                FormatTrigger::Manual,
 8243                FormatTarget::Buffers,
 8244                window,
 8245                cx,
 8246            )
 8247        })
 8248        .unwrap();
 8249    fake_server
 8250        .handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8251            assert_eq!(
 8252                params.text_document.uri,
 8253                lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8254            );
 8255            assert_eq!(params.options.tab_size, 4);
 8256            Ok(Some(vec![lsp::TextEdit::new(
 8257                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
 8258                ", ".to_string(),
 8259            )]))
 8260        })
 8261        .next()
 8262        .await;
 8263    cx.executor().start_waiting();
 8264    format.await;
 8265    assert_eq!(
 8266        editor.update(cx, |editor, cx| editor.text(cx)),
 8267        "one, two\nthree\n"
 8268    );
 8269
 8270    editor.update_in(cx, |editor, window, cx| {
 8271        editor.set_text("one\ntwo\nthree\n", window, cx)
 8272    });
 8273    // Ensure we don't lock if formatting hangs.
 8274    fake_server.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
 8275        assert_eq!(
 8276            params.text_document.uri,
 8277            lsp::Url::from_file_path(path!("/file.rs")).unwrap()
 8278        );
 8279        futures::future::pending::<()>().await;
 8280        unreachable!()
 8281    });
 8282    let format = editor
 8283        .update_in(cx, |editor, window, cx| {
 8284            editor.perform_format(
 8285                project,
 8286                FormatTrigger::Manual,
 8287                FormatTarget::Buffers,
 8288                window,
 8289                cx,
 8290            )
 8291        })
 8292        .unwrap();
 8293    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
 8294    cx.executor().start_waiting();
 8295    format.await;
 8296    assert_eq!(
 8297        editor.update(cx, |editor, cx| editor.text(cx)),
 8298        "one\ntwo\nthree\n"
 8299    );
 8300}
 8301
 8302#[gpui::test]
 8303async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
 8304    init_test(cx, |settings| {
 8305        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
 8306            FormatterList(vec![Formatter::LanguageServer { name: None }].into()),
 8307        ))
 8308    });
 8309
 8310    let fs = FakeFs::new(cx.executor());
 8311    fs.insert_file(path!("/file.ts"), Default::default()).await;
 8312
 8313    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
 8314
 8315    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 8316    language_registry.add(Arc::new(Language::new(
 8317        LanguageConfig {
 8318            name: "TypeScript".into(),
 8319            matcher: LanguageMatcher {
 8320                path_suffixes: vec!["ts".to_string()],
 8321                ..Default::default()
 8322            },
 8323            ..LanguageConfig::default()
 8324        },
 8325        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 8326    )));
 8327    update_test_language_settings(cx, |settings| {
 8328        settings.defaults.prettier = Some(PrettierSettings {
 8329            allowed: true,
 8330            ..PrettierSettings::default()
 8331        });
 8332    });
 8333    let mut fake_servers = language_registry.register_fake_lsp(
 8334        "TypeScript",
 8335        FakeLspAdapter {
 8336            capabilities: lsp::ServerCapabilities {
 8337                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
 8338                ..Default::default()
 8339            },
 8340            ..Default::default()
 8341        },
 8342    );
 8343
 8344    let buffer = project
 8345        .update(cx, |project, cx| {
 8346            project.open_local_buffer(path!("/file.ts"), cx)
 8347        })
 8348        .await
 8349        .unwrap();
 8350
 8351    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8352    let (editor, cx) = cx.add_window_view(|window, cx| {
 8353        build_editor_with_project(project.clone(), buffer, window, cx)
 8354    });
 8355    editor.update_in(cx, |editor, window, cx| {
 8356        editor.set_text(
 8357            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 8358            window,
 8359            cx,
 8360        )
 8361    });
 8362
 8363    cx.executor().start_waiting();
 8364    let fake_server = fake_servers.next().await.unwrap();
 8365
 8366    let format = editor
 8367        .update_in(cx, |editor, window, cx| {
 8368            editor.perform_code_action_kind(
 8369                project.clone(),
 8370                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 8371                window,
 8372                cx,
 8373            )
 8374        })
 8375        .unwrap();
 8376    fake_server
 8377        .handle_request::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
 8378            assert_eq!(
 8379                params.text_document.uri,
 8380                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 8381            );
 8382            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 8383                lsp::CodeAction {
 8384                    title: "Organize Imports".to_string(),
 8385                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
 8386                    edit: Some(lsp::WorkspaceEdit {
 8387                        changes: Some(
 8388                            [(
 8389                                params.text_document.uri.clone(),
 8390                                vec![lsp::TextEdit::new(
 8391                                    lsp::Range::new(
 8392                                        lsp::Position::new(1, 0),
 8393                                        lsp::Position::new(2, 0),
 8394                                    ),
 8395                                    "".to_string(),
 8396                                )],
 8397                            )]
 8398                            .into_iter()
 8399                            .collect(),
 8400                        ),
 8401                        ..Default::default()
 8402                    }),
 8403                    ..Default::default()
 8404                },
 8405            )]))
 8406        })
 8407        .next()
 8408        .await;
 8409    cx.executor().start_waiting();
 8410    format.await;
 8411    assert_eq!(
 8412        editor.update(cx, |editor, cx| editor.text(cx)),
 8413        "import { a } from 'module';\n\nconst x = a;\n"
 8414    );
 8415
 8416    editor.update_in(cx, |editor, window, cx| {
 8417        editor.set_text(
 8418            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
 8419            window,
 8420            cx,
 8421        )
 8422    });
 8423    // Ensure we don't lock if code action hangs.
 8424    fake_server.handle_request::<lsp::request::CodeActionRequest, _, _>(
 8425        move |params, _| async move {
 8426            assert_eq!(
 8427                params.text_document.uri,
 8428                lsp::Url::from_file_path(path!("/file.ts")).unwrap()
 8429            );
 8430            futures::future::pending::<()>().await;
 8431            unreachable!()
 8432        },
 8433    );
 8434    let format = editor
 8435        .update_in(cx, |editor, window, cx| {
 8436            editor.perform_code_action_kind(
 8437                project,
 8438                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
 8439                window,
 8440                cx,
 8441            )
 8442        })
 8443        .unwrap();
 8444    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
 8445    cx.executor().start_waiting();
 8446    format.await;
 8447    assert_eq!(
 8448        editor.update(cx, |editor, cx| editor.text(cx)),
 8449        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
 8450    );
 8451}
 8452
 8453#[gpui::test]
 8454async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
 8455    init_test(cx, |_| {});
 8456
 8457    let mut cx = EditorLspTestContext::new_rust(
 8458        lsp::ServerCapabilities {
 8459            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8460            ..Default::default()
 8461        },
 8462        cx,
 8463    )
 8464    .await;
 8465
 8466    cx.set_state(indoc! {"
 8467        one.twoˇ
 8468    "});
 8469
 8470    // The format request takes a long time. When it completes, it inserts
 8471    // a newline and an indent before the `.`
 8472    cx.lsp
 8473        .handle_request::<lsp::request::Formatting, _, _>(move |_, cx| {
 8474            let executor = cx.background_executor().clone();
 8475            async move {
 8476                executor.timer(Duration::from_millis(100)).await;
 8477                Ok(Some(vec![lsp::TextEdit {
 8478                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
 8479                    new_text: "\n    ".into(),
 8480                }]))
 8481            }
 8482        });
 8483
 8484    // Submit a format request.
 8485    let format_1 = cx
 8486        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 8487        .unwrap();
 8488    cx.executor().run_until_parked();
 8489
 8490    // Submit a second format request.
 8491    let format_2 = cx
 8492        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 8493        .unwrap();
 8494    cx.executor().run_until_parked();
 8495
 8496    // Wait for both format requests to complete
 8497    cx.executor().advance_clock(Duration::from_millis(200));
 8498    cx.executor().start_waiting();
 8499    format_1.await.unwrap();
 8500    cx.executor().start_waiting();
 8501    format_2.await.unwrap();
 8502
 8503    // The formatting edits only happens once.
 8504    cx.assert_editor_state(indoc! {"
 8505        one
 8506            .twoˇ
 8507    "});
 8508}
 8509
 8510#[gpui::test]
 8511async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
 8512    init_test(cx, |settings| {
 8513        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
 8514    });
 8515
 8516    let mut cx = EditorLspTestContext::new_rust(
 8517        lsp::ServerCapabilities {
 8518            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8519            ..Default::default()
 8520        },
 8521        cx,
 8522    )
 8523    .await;
 8524
 8525    // Set up a buffer white some trailing whitespace and no trailing newline.
 8526    cx.set_state(
 8527        &[
 8528            "one ",   //
 8529            "twoˇ",   //
 8530            "three ", //
 8531            "four",   //
 8532        ]
 8533        .join("\n"),
 8534    );
 8535
 8536    // Submit a format request.
 8537    let format = cx
 8538        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
 8539        .unwrap();
 8540
 8541    // Record which buffer changes have been sent to the language server
 8542    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
 8543    cx.lsp
 8544        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
 8545            let buffer_changes = buffer_changes.clone();
 8546            move |params, _| {
 8547                buffer_changes.lock().extend(
 8548                    params
 8549                        .content_changes
 8550                        .into_iter()
 8551                        .map(|e| (e.range.unwrap(), e.text)),
 8552                );
 8553            }
 8554        });
 8555
 8556    // Handle formatting requests to the language server.
 8557    cx.lsp.handle_request::<lsp::request::Formatting, _, _>({
 8558        let buffer_changes = buffer_changes.clone();
 8559        move |_, _| {
 8560            // When formatting is requested, trailing whitespace has already been stripped,
 8561            // and the trailing newline has already been added.
 8562            assert_eq!(
 8563                &buffer_changes.lock()[1..],
 8564                &[
 8565                    (
 8566                        lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
 8567                        "".into()
 8568                    ),
 8569                    (
 8570                        lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
 8571                        "".into()
 8572                    ),
 8573                    (
 8574                        lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
 8575                        "\n".into()
 8576                    ),
 8577                ]
 8578            );
 8579
 8580            // Insert blank lines between each line of the buffer.
 8581            async move {
 8582                Ok(Some(vec![
 8583                    lsp::TextEdit {
 8584                        range: lsp::Range::new(lsp::Position::new(1, 0), lsp::Position::new(1, 0)),
 8585                        new_text: "\n".into(),
 8586                    },
 8587                    lsp::TextEdit {
 8588                        range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(2, 0)),
 8589                        new_text: "\n".into(),
 8590                    },
 8591                ]))
 8592            }
 8593        }
 8594    });
 8595
 8596    // After formatting the buffer, the trailing whitespace is stripped,
 8597    // a newline is appended, and the edits provided by the language server
 8598    // have been applied.
 8599    format.await.unwrap();
 8600    cx.assert_editor_state(
 8601        &[
 8602            "one",   //
 8603            "",      //
 8604            "twoˇ",  //
 8605            "",      //
 8606            "three", //
 8607            "four",  //
 8608            "",      //
 8609        ]
 8610        .join("\n"),
 8611    );
 8612
 8613    // Undoing the formatting undoes the trailing whitespace removal, the
 8614    // trailing newline, and the LSP edits.
 8615    cx.update_buffer(|buffer, cx| buffer.undo(cx));
 8616    cx.assert_editor_state(
 8617        &[
 8618            "one ",   //
 8619            "twoˇ",   //
 8620            "three ", //
 8621            "four",   //
 8622        ]
 8623        .join("\n"),
 8624    );
 8625}
 8626
 8627#[gpui::test]
 8628async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
 8629    cx: &mut TestAppContext,
 8630) {
 8631    init_test(cx, |_| {});
 8632
 8633    cx.update(|cx| {
 8634        cx.update_global::<SettingsStore, _>(|settings, cx| {
 8635            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 8636                settings.auto_signature_help = Some(true);
 8637            });
 8638        });
 8639    });
 8640
 8641    let mut cx = EditorLspTestContext::new_rust(
 8642        lsp::ServerCapabilities {
 8643            signature_help_provider: Some(lsp::SignatureHelpOptions {
 8644                ..Default::default()
 8645            }),
 8646            ..Default::default()
 8647        },
 8648        cx,
 8649    )
 8650    .await;
 8651
 8652    let language = Language::new(
 8653        LanguageConfig {
 8654            name: "Rust".into(),
 8655            brackets: BracketPairConfig {
 8656                pairs: vec![
 8657                    BracketPair {
 8658                        start: "{".to_string(),
 8659                        end: "}".to_string(),
 8660                        close: true,
 8661                        surround: true,
 8662                        newline: true,
 8663                    },
 8664                    BracketPair {
 8665                        start: "(".to_string(),
 8666                        end: ")".to_string(),
 8667                        close: true,
 8668                        surround: true,
 8669                        newline: true,
 8670                    },
 8671                    BracketPair {
 8672                        start: "/*".to_string(),
 8673                        end: " */".to_string(),
 8674                        close: true,
 8675                        surround: true,
 8676                        newline: true,
 8677                    },
 8678                    BracketPair {
 8679                        start: "[".to_string(),
 8680                        end: "]".to_string(),
 8681                        close: false,
 8682                        surround: false,
 8683                        newline: true,
 8684                    },
 8685                    BracketPair {
 8686                        start: "\"".to_string(),
 8687                        end: "\"".to_string(),
 8688                        close: true,
 8689                        surround: true,
 8690                        newline: false,
 8691                    },
 8692                    BracketPair {
 8693                        start: "<".to_string(),
 8694                        end: ">".to_string(),
 8695                        close: false,
 8696                        surround: true,
 8697                        newline: true,
 8698                    },
 8699                ],
 8700                ..Default::default()
 8701            },
 8702            autoclose_before: "})]".to_string(),
 8703            ..Default::default()
 8704        },
 8705        Some(tree_sitter_rust::LANGUAGE.into()),
 8706    );
 8707    let language = Arc::new(language);
 8708
 8709    cx.language_registry().add(language.clone());
 8710    cx.update_buffer(|buffer, cx| {
 8711        buffer.set_language(Some(language), cx);
 8712    });
 8713
 8714    cx.set_state(
 8715        &r#"
 8716            fn main() {
 8717                sampleˇ
 8718            }
 8719        "#
 8720        .unindent(),
 8721    );
 8722
 8723    cx.update_editor(|editor, window, cx| {
 8724        editor.handle_input("(", window, cx);
 8725    });
 8726    cx.assert_editor_state(
 8727        &"
 8728            fn main() {
 8729                sample(ˇ)
 8730            }
 8731        "
 8732        .unindent(),
 8733    );
 8734
 8735    let mocked_response = lsp::SignatureHelp {
 8736        signatures: vec![lsp::SignatureInformation {
 8737            label: "fn sample(param1: u8, param2: u8)".to_string(),
 8738            documentation: None,
 8739            parameters: Some(vec![
 8740                lsp::ParameterInformation {
 8741                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 8742                    documentation: None,
 8743                },
 8744                lsp::ParameterInformation {
 8745                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 8746                    documentation: None,
 8747                },
 8748            ]),
 8749            active_parameter: None,
 8750        }],
 8751        active_signature: Some(0),
 8752        active_parameter: Some(0),
 8753    };
 8754    handle_signature_help_request(&mut cx, mocked_response).await;
 8755
 8756    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 8757        .await;
 8758
 8759    cx.editor(|editor, _, _| {
 8760        let signature_help_state = editor.signature_help_state.popover().cloned();
 8761        assert_eq!(
 8762            signature_help_state.unwrap().label,
 8763            "param1: u8, param2: u8"
 8764        );
 8765    });
 8766}
 8767
 8768#[gpui::test]
 8769async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
 8770    init_test(cx, |_| {});
 8771
 8772    cx.update(|cx| {
 8773        cx.update_global::<SettingsStore, _>(|settings, cx| {
 8774            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 8775                settings.auto_signature_help = Some(false);
 8776                settings.show_signature_help_after_edits = Some(false);
 8777            });
 8778        });
 8779    });
 8780
 8781    let mut cx = EditorLspTestContext::new_rust(
 8782        lsp::ServerCapabilities {
 8783            signature_help_provider: Some(lsp::SignatureHelpOptions {
 8784                ..Default::default()
 8785            }),
 8786            ..Default::default()
 8787        },
 8788        cx,
 8789    )
 8790    .await;
 8791
 8792    let language = Language::new(
 8793        LanguageConfig {
 8794            name: "Rust".into(),
 8795            brackets: BracketPairConfig {
 8796                pairs: vec![
 8797                    BracketPair {
 8798                        start: "{".to_string(),
 8799                        end: "}".to_string(),
 8800                        close: true,
 8801                        surround: true,
 8802                        newline: true,
 8803                    },
 8804                    BracketPair {
 8805                        start: "(".to_string(),
 8806                        end: ")".to_string(),
 8807                        close: true,
 8808                        surround: true,
 8809                        newline: true,
 8810                    },
 8811                    BracketPair {
 8812                        start: "/*".to_string(),
 8813                        end: " */".to_string(),
 8814                        close: true,
 8815                        surround: true,
 8816                        newline: true,
 8817                    },
 8818                    BracketPair {
 8819                        start: "[".to_string(),
 8820                        end: "]".to_string(),
 8821                        close: false,
 8822                        surround: false,
 8823                        newline: true,
 8824                    },
 8825                    BracketPair {
 8826                        start: "\"".to_string(),
 8827                        end: "\"".to_string(),
 8828                        close: true,
 8829                        surround: true,
 8830                        newline: false,
 8831                    },
 8832                    BracketPair {
 8833                        start: "<".to_string(),
 8834                        end: ">".to_string(),
 8835                        close: false,
 8836                        surround: true,
 8837                        newline: true,
 8838                    },
 8839                ],
 8840                ..Default::default()
 8841            },
 8842            autoclose_before: "})]".to_string(),
 8843            ..Default::default()
 8844        },
 8845        Some(tree_sitter_rust::LANGUAGE.into()),
 8846    );
 8847    let language = Arc::new(language);
 8848
 8849    cx.language_registry().add(language.clone());
 8850    cx.update_buffer(|buffer, cx| {
 8851        buffer.set_language(Some(language), cx);
 8852    });
 8853
 8854    // Ensure that signature_help is not called when no signature help is enabled.
 8855    cx.set_state(
 8856        &r#"
 8857            fn main() {
 8858                sampleˇ
 8859            }
 8860        "#
 8861        .unindent(),
 8862    );
 8863    cx.update_editor(|editor, window, cx| {
 8864        editor.handle_input("(", window, cx);
 8865    });
 8866    cx.assert_editor_state(
 8867        &"
 8868            fn main() {
 8869                sample(ˇ)
 8870            }
 8871        "
 8872        .unindent(),
 8873    );
 8874    cx.editor(|editor, _, _| {
 8875        assert!(editor.signature_help_state.task().is_none());
 8876    });
 8877
 8878    let mocked_response = lsp::SignatureHelp {
 8879        signatures: vec![lsp::SignatureInformation {
 8880            label: "fn sample(param1: u8, param2: u8)".to_string(),
 8881            documentation: None,
 8882            parameters: Some(vec![
 8883                lsp::ParameterInformation {
 8884                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 8885                    documentation: None,
 8886                },
 8887                lsp::ParameterInformation {
 8888                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 8889                    documentation: None,
 8890                },
 8891            ]),
 8892            active_parameter: None,
 8893        }],
 8894        active_signature: Some(0),
 8895        active_parameter: Some(0),
 8896    };
 8897
 8898    // Ensure that signature_help is called when enabled afte edits
 8899    cx.update(|_, cx| {
 8900        cx.update_global::<SettingsStore, _>(|settings, cx| {
 8901            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 8902                settings.auto_signature_help = Some(false);
 8903                settings.show_signature_help_after_edits = Some(true);
 8904            });
 8905        });
 8906    });
 8907    cx.set_state(
 8908        &r#"
 8909            fn main() {
 8910                sampleˇ
 8911            }
 8912        "#
 8913        .unindent(),
 8914    );
 8915    cx.update_editor(|editor, window, cx| {
 8916        editor.handle_input("(", window, cx);
 8917    });
 8918    cx.assert_editor_state(
 8919        &"
 8920            fn main() {
 8921                sample(ˇ)
 8922            }
 8923        "
 8924        .unindent(),
 8925    );
 8926    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 8927    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 8928        .await;
 8929    cx.update_editor(|editor, _, _| {
 8930        let signature_help_state = editor.signature_help_state.popover().cloned();
 8931        assert!(signature_help_state.is_some());
 8932        assert_eq!(
 8933            signature_help_state.unwrap().label,
 8934            "param1: u8, param2: u8"
 8935        );
 8936        editor.signature_help_state = SignatureHelpState::default();
 8937    });
 8938
 8939    // Ensure that signature_help is called when auto signature help override is enabled
 8940    cx.update(|_, cx| {
 8941        cx.update_global::<SettingsStore, _>(|settings, cx| {
 8942            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 8943                settings.auto_signature_help = Some(true);
 8944                settings.show_signature_help_after_edits = Some(false);
 8945            });
 8946        });
 8947    });
 8948    cx.set_state(
 8949        &r#"
 8950            fn main() {
 8951                sampleˇ
 8952            }
 8953        "#
 8954        .unindent(),
 8955    );
 8956    cx.update_editor(|editor, window, cx| {
 8957        editor.handle_input("(", window, cx);
 8958    });
 8959    cx.assert_editor_state(
 8960        &"
 8961            fn main() {
 8962                sample(ˇ)
 8963            }
 8964        "
 8965        .unindent(),
 8966    );
 8967    handle_signature_help_request(&mut cx, mocked_response).await;
 8968    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 8969        .await;
 8970    cx.editor(|editor, _, _| {
 8971        let signature_help_state = editor.signature_help_state.popover().cloned();
 8972        assert!(signature_help_state.is_some());
 8973        assert_eq!(
 8974            signature_help_state.unwrap().label,
 8975            "param1: u8, param2: u8"
 8976        );
 8977    });
 8978}
 8979
 8980#[gpui::test]
 8981async fn test_signature_help(cx: &mut TestAppContext) {
 8982    init_test(cx, |_| {});
 8983    cx.update(|cx| {
 8984        cx.update_global::<SettingsStore, _>(|settings, cx| {
 8985            settings.update_user_settings::<EditorSettings>(cx, |settings| {
 8986                settings.auto_signature_help = Some(true);
 8987            });
 8988        });
 8989    });
 8990
 8991    let mut cx = EditorLspTestContext::new_rust(
 8992        lsp::ServerCapabilities {
 8993            signature_help_provider: Some(lsp::SignatureHelpOptions {
 8994                ..Default::default()
 8995            }),
 8996            ..Default::default()
 8997        },
 8998        cx,
 8999    )
 9000    .await;
 9001
 9002    // A test that directly calls `show_signature_help`
 9003    cx.update_editor(|editor, window, cx| {
 9004        editor.show_signature_help(&ShowSignatureHelp, window, cx);
 9005    });
 9006
 9007    let mocked_response = lsp::SignatureHelp {
 9008        signatures: vec![lsp::SignatureInformation {
 9009            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9010            documentation: None,
 9011            parameters: Some(vec![
 9012                lsp::ParameterInformation {
 9013                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9014                    documentation: None,
 9015                },
 9016                lsp::ParameterInformation {
 9017                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9018                    documentation: None,
 9019                },
 9020            ]),
 9021            active_parameter: None,
 9022        }],
 9023        active_signature: Some(0),
 9024        active_parameter: Some(0),
 9025    };
 9026    handle_signature_help_request(&mut cx, mocked_response).await;
 9027
 9028    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9029        .await;
 9030
 9031    cx.editor(|editor, _, _| {
 9032        let signature_help_state = editor.signature_help_state.popover().cloned();
 9033        assert!(signature_help_state.is_some());
 9034        assert_eq!(
 9035            signature_help_state.unwrap().label,
 9036            "param1: u8, param2: u8"
 9037        );
 9038    });
 9039
 9040    // When exiting outside from inside the brackets, `signature_help` is closed.
 9041    cx.set_state(indoc! {"
 9042        fn main() {
 9043            sample(ˇ);
 9044        }
 9045
 9046        fn sample(param1: u8, param2: u8) {}
 9047    "});
 9048
 9049    cx.update_editor(|editor, window, cx| {
 9050        editor.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
 9051    });
 9052
 9053    let mocked_response = lsp::SignatureHelp {
 9054        signatures: Vec::new(),
 9055        active_signature: None,
 9056        active_parameter: None,
 9057    };
 9058    handle_signature_help_request(&mut cx, mocked_response).await;
 9059
 9060    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
 9061        .await;
 9062
 9063    cx.editor(|editor, _, _| {
 9064        assert!(!editor.signature_help_state.is_shown());
 9065    });
 9066
 9067    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
 9068    cx.set_state(indoc! {"
 9069        fn main() {
 9070            sample(ˇ);
 9071        }
 9072
 9073        fn sample(param1: u8, param2: u8) {}
 9074    "});
 9075
 9076    let mocked_response = lsp::SignatureHelp {
 9077        signatures: vec![lsp::SignatureInformation {
 9078            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9079            documentation: None,
 9080            parameters: Some(vec![
 9081                lsp::ParameterInformation {
 9082                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9083                    documentation: None,
 9084                },
 9085                lsp::ParameterInformation {
 9086                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9087                    documentation: None,
 9088                },
 9089            ]),
 9090            active_parameter: None,
 9091        }],
 9092        active_signature: Some(0),
 9093        active_parameter: Some(0),
 9094    };
 9095    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9096    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9097        .await;
 9098    cx.editor(|editor, _, _| {
 9099        assert!(editor.signature_help_state.is_shown());
 9100    });
 9101
 9102    // Restore the popover with more parameter input
 9103    cx.set_state(indoc! {"
 9104        fn main() {
 9105            sample(param1, param2ˇ);
 9106        }
 9107
 9108        fn sample(param1: u8, param2: u8) {}
 9109    "});
 9110
 9111    let mocked_response = lsp::SignatureHelp {
 9112        signatures: vec![lsp::SignatureInformation {
 9113            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9114            documentation: None,
 9115            parameters: Some(vec![
 9116                lsp::ParameterInformation {
 9117                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9118                    documentation: None,
 9119                },
 9120                lsp::ParameterInformation {
 9121                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9122                    documentation: None,
 9123                },
 9124            ]),
 9125            active_parameter: None,
 9126        }],
 9127        active_signature: Some(0),
 9128        active_parameter: Some(1),
 9129    };
 9130    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9131    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9132        .await;
 9133
 9134    // When selecting a range, the popover is gone.
 9135    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
 9136    cx.update_editor(|editor, window, cx| {
 9137        editor.change_selections(None, window, cx, |s| {
 9138            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
 9139        })
 9140    });
 9141    cx.assert_editor_state(indoc! {"
 9142        fn main() {
 9143            sample(param1, «ˇparam2»);
 9144        }
 9145
 9146        fn sample(param1: u8, param2: u8) {}
 9147    "});
 9148    cx.editor(|editor, _, _| {
 9149        assert!(!editor.signature_help_state.is_shown());
 9150    });
 9151
 9152    // When unselecting again, the popover is back if within the brackets.
 9153    cx.update_editor(|editor, window, cx| {
 9154        editor.change_selections(None, window, cx, |s| {
 9155            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
 9156        })
 9157    });
 9158    cx.assert_editor_state(indoc! {"
 9159        fn main() {
 9160            sample(param1, ˇparam2);
 9161        }
 9162
 9163        fn sample(param1: u8, param2: u8) {}
 9164    "});
 9165    handle_signature_help_request(&mut cx, mocked_response).await;
 9166    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9167        .await;
 9168    cx.editor(|editor, _, _| {
 9169        assert!(editor.signature_help_state.is_shown());
 9170    });
 9171
 9172    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
 9173    cx.update_editor(|editor, window, cx| {
 9174        editor.change_selections(None, window, cx, |s| {
 9175            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
 9176            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
 9177        })
 9178    });
 9179    cx.assert_editor_state(indoc! {"
 9180        fn main() {
 9181            sample(param1, ˇparam2);
 9182        }
 9183
 9184        fn sample(param1: u8, param2: u8) {}
 9185    "});
 9186
 9187    let mocked_response = lsp::SignatureHelp {
 9188        signatures: vec![lsp::SignatureInformation {
 9189            label: "fn sample(param1: u8, param2: u8)".to_string(),
 9190            documentation: None,
 9191            parameters: Some(vec![
 9192                lsp::ParameterInformation {
 9193                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
 9194                    documentation: None,
 9195                },
 9196                lsp::ParameterInformation {
 9197                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
 9198                    documentation: None,
 9199                },
 9200            ]),
 9201            active_parameter: None,
 9202        }],
 9203        active_signature: Some(0),
 9204        active_parameter: Some(1),
 9205    };
 9206    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
 9207    cx.condition(|editor, _| editor.signature_help_state.is_shown())
 9208        .await;
 9209    cx.update_editor(|editor, _, cx| {
 9210        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
 9211    });
 9212    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
 9213        .await;
 9214    cx.update_editor(|editor, window, cx| {
 9215        editor.change_selections(None, window, cx, |s| {
 9216            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
 9217        })
 9218    });
 9219    cx.assert_editor_state(indoc! {"
 9220        fn main() {
 9221            sample(param1, «ˇparam2»);
 9222        }
 9223
 9224        fn sample(param1: u8, param2: u8) {}
 9225    "});
 9226    cx.update_editor(|editor, window, cx| {
 9227        editor.change_selections(None, window, cx, |s| {
 9228            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
 9229        })
 9230    });
 9231    cx.assert_editor_state(indoc! {"
 9232        fn main() {
 9233            sample(param1, ˇparam2);
 9234        }
 9235
 9236        fn sample(param1: u8, param2: u8) {}
 9237    "});
 9238    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
 9239        .await;
 9240}
 9241
 9242#[gpui::test]
 9243async fn test_completion(cx: &mut TestAppContext) {
 9244    init_test(cx, |_| {});
 9245
 9246    let mut cx = EditorLspTestContext::new_rust(
 9247        lsp::ServerCapabilities {
 9248            completion_provider: Some(lsp::CompletionOptions {
 9249                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 9250                resolve_provider: Some(true),
 9251                ..Default::default()
 9252            }),
 9253            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
 9254            ..Default::default()
 9255        },
 9256        cx,
 9257    )
 9258    .await;
 9259    let counter = Arc::new(AtomicUsize::new(0));
 9260
 9261    cx.set_state(indoc! {"
 9262        oneˇ
 9263        two
 9264        three
 9265    "});
 9266    cx.simulate_keystroke(".");
 9267    handle_completion_request(
 9268        &mut cx,
 9269        indoc! {"
 9270            one.|<>
 9271            two
 9272            three
 9273        "},
 9274        vec!["first_completion", "second_completion"],
 9275        counter.clone(),
 9276    )
 9277    .await;
 9278    cx.condition(|editor, _| editor.context_menu_visible())
 9279        .await;
 9280    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
 9281
 9282    let _handler = handle_signature_help_request(
 9283        &mut cx,
 9284        lsp::SignatureHelp {
 9285            signatures: vec![lsp::SignatureInformation {
 9286                label: "test signature".to_string(),
 9287                documentation: None,
 9288                parameters: Some(vec![lsp::ParameterInformation {
 9289                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
 9290                    documentation: None,
 9291                }]),
 9292                active_parameter: None,
 9293            }],
 9294            active_signature: None,
 9295            active_parameter: None,
 9296        },
 9297    );
 9298    cx.update_editor(|editor, window, cx| {
 9299        assert!(
 9300            !editor.signature_help_state.is_shown(),
 9301            "No signature help was called for"
 9302        );
 9303        editor.show_signature_help(&ShowSignatureHelp, window, cx);
 9304    });
 9305    cx.run_until_parked();
 9306    cx.update_editor(|editor, _, _| {
 9307        assert!(
 9308            !editor.signature_help_state.is_shown(),
 9309            "No signature help should be shown when completions menu is open"
 9310        );
 9311    });
 9312
 9313    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
 9314        editor.context_menu_next(&Default::default(), window, cx);
 9315        editor
 9316            .confirm_completion(&ConfirmCompletion::default(), window, cx)
 9317            .unwrap()
 9318    });
 9319    cx.assert_editor_state(indoc! {"
 9320        one.second_completionˇ
 9321        two
 9322        three
 9323    "});
 9324
 9325    handle_resolve_completion_request(
 9326        &mut cx,
 9327        Some(vec![
 9328            (
 9329                //This overlaps with the primary completion edit which is
 9330                //misbehavior from the LSP spec, test that we filter it out
 9331                indoc! {"
 9332                    one.second_ˇcompletion
 9333                    two
 9334                    threeˇ
 9335                "},
 9336                "overlapping additional edit",
 9337            ),
 9338            (
 9339                indoc! {"
 9340                    one.second_completion
 9341                    two
 9342                    threeˇ
 9343                "},
 9344                "\nadditional edit",
 9345            ),
 9346        ]),
 9347    )
 9348    .await;
 9349    apply_additional_edits.await.unwrap();
 9350    cx.assert_editor_state(indoc! {"
 9351        one.second_completionˇ
 9352        two
 9353        three
 9354        additional edit
 9355    "});
 9356
 9357    cx.set_state(indoc! {"
 9358        one.second_completion
 9359        twoˇ
 9360        threeˇ
 9361        additional edit
 9362    "});
 9363    cx.simulate_keystroke(" ");
 9364    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
 9365    cx.simulate_keystroke("s");
 9366    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
 9367
 9368    cx.assert_editor_state(indoc! {"
 9369        one.second_completion
 9370        two sˇ
 9371        three sˇ
 9372        additional edit
 9373    "});
 9374    handle_completion_request(
 9375        &mut cx,
 9376        indoc! {"
 9377            one.second_completion
 9378            two s
 9379            three <s|>
 9380            additional edit
 9381        "},
 9382        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
 9383        counter.clone(),
 9384    )
 9385    .await;
 9386    cx.condition(|editor, _| editor.context_menu_visible())
 9387        .await;
 9388    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
 9389
 9390    cx.simulate_keystroke("i");
 9391
 9392    handle_completion_request(
 9393        &mut cx,
 9394        indoc! {"
 9395            one.second_completion
 9396            two si
 9397            three <si|>
 9398            additional edit
 9399        "},
 9400        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
 9401        counter.clone(),
 9402    )
 9403    .await;
 9404    cx.condition(|editor, _| editor.context_menu_visible())
 9405        .await;
 9406    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
 9407
 9408    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
 9409        editor
 9410            .confirm_completion(&ConfirmCompletion::default(), window, cx)
 9411            .unwrap()
 9412    });
 9413    cx.assert_editor_state(indoc! {"
 9414        one.second_completion
 9415        two sixth_completionˇ
 9416        three sixth_completionˇ
 9417        additional edit
 9418    "});
 9419
 9420    apply_additional_edits.await.unwrap();
 9421
 9422    update_test_language_settings(&mut cx, |settings| {
 9423        settings.defaults.show_completions_on_input = Some(false);
 9424    });
 9425    cx.set_state("editorˇ");
 9426    cx.simulate_keystroke(".");
 9427    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
 9428    cx.simulate_keystroke("c");
 9429    cx.simulate_keystroke("l");
 9430    cx.simulate_keystroke("o");
 9431    cx.assert_editor_state("editor.cloˇ");
 9432    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
 9433    cx.update_editor(|editor, window, cx| {
 9434        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
 9435    });
 9436    handle_completion_request(
 9437        &mut cx,
 9438        "editor.<clo|>",
 9439        vec!["close", "clobber"],
 9440        counter.clone(),
 9441    )
 9442    .await;
 9443    cx.condition(|editor, _| editor.context_menu_visible())
 9444        .await;
 9445    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
 9446
 9447    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
 9448        editor
 9449            .confirm_completion(&ConfirmCompletion::default(), window, cx)
 9450            .unwrap()
 9451    });
 9452    cx.assert_editor_state("editor.closeˇ");
 9453    handle_resolve_completion_request(&mut cx, None).await;
 9454    apply_additional_edits.await.unwrap();
 9455}
 9456
 9457#[gpui::test]
 9458async fn test_word_completion(cx: &mut TestAppContext) {
 9459    let lsp_fetch_timeout_ms = 10;
 9460    init_test(cx, |language_settings| {
 9461        language_settings.defaults.completions = Some(CompletionSettings {
 9462            words: WordsCompletionMode::Fallback,
 9463            lsp: true,
 9464            lsp_fetch_timeout_ms: 10,
 9465        });
 9466    });
 9467
 9468    let mut cx = EditorLspTestContext::new_rust(
 9469        lsp::ServerCapabilities {
 9470            completion_provider: Some(lsp::CompletionOptions {
 9471                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 9472                ..lsp::CompletionOptions::default()
 9473            }),
 9474            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
 9475            ..lsp::ServerCapabilities::default()
 9476        },
 9477        cx,
 9478    )
 9479    .await;
 9480
 9481    let throttle_completions = Arc::new(AtomicBool::new(false));
 9482
 9483    let lsp_throttle_completions = throttle_completions.clone();
 9484    let _completion_requests_handler =
 9485        cx.lsp
 9486            .server
 9487            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
 9488                let lsp_throttle_completions = lsp_throttle_completions.clone();
 9489                let cx = cx.clone();
 9490                async move {
 9491                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
 9492                        cx.background_executor()
 9493                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
 9494                            .await;
 9495                    }
 9496                    Ok(Some(lsp::CompletionResponse::Array(vec![
 9497                        lsp::CompletionItem {
 9498                            label: "first".into(),
 9499                            ..lsp::CompletionItem::default()
 9500                        },
 9501                        lsp::CompletionItem {
 9502                            label: "last".into(),
 9503                            ..lsp::CompletionItem::default()
 9504                        },
 9505                    ])))
 9506                }
 9507            });
 9508
 9509    cx.set_state(indoc! {"
 9510        oneˇ
 9511        two
 9512        three
 9513    "});
 9514    cx.simulate_keystroke(".");
 9515    cx.executor().run_until_parked();
 9516    cx.condition(|editor, _| editor.context_menu_visible())
 9517        .await;
 9518    cx.update_editor(|editor, window, cx| {
 9519        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
 9520        {
 9521            assert_eq!(
 9522                completion_menu_entries(&menu),
 9523                &["first", "last"],
 9524                "When LSP server is fast to reply, no fallback word completions are used"
 9525            );
 9526        } else {
 9527            panic!("expected completion menu to be open");
 9528        }
 9529        editor.cancel(&Cancel, window, cx);
 9530    });
 9531    cx.executor().run_until_parked();
 9532    cx.condition(|editor, _| !editor.context_menu_visible())
 9533        .await;
 9534
 9535    throttle_completions.store(true, atomic::Ordering::Release);
 9536    cx.simulate_keystroke(".");
 9537    cx.executor()
 9538        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
 9539    cx.executor().run_until_parked();
 9540    cx.condition(|editor, _| editor.context_menu_visible())
 9541        .await;
 9542    cx.update_editor(|editor, _, _| {
 9543        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
 9544        {
 9545            assert_eq!(completion_menu_entries(&menu), &["one", "three", "two"],
 9546                "When LSP server is slow, document words can be shown instead, if configured accordingly");
 9547        } else {
 9548            panic!("expected completion menu to be open");
 9549        }
 9550    });
 9551}
 9552
 9553#[gpui::test]
 9554async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
 9555    init_test(cx, |language_settings| {
 9556        language_settings.defaults.completions = Some(CompletionSettings {
 9557            words: WordsCompletionMode::Enabled,
 9558            lsp: true,
 9559            lsp_fetch_timeout_ms: 0,
 9560        });
 9561    });
 9562
 9563    let mut cx = EditorLspTestContext::new_rust(
 9564        lsp::ServerCapabilities {
 9565            completion_provider: Some(lsp::CompletionOptions {
 9566                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 9567                ..lsp::CompletionOptions::default()
 9568            }),
 9569            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
 9570            ..lsp::ServerCapabilities::default()
 9571        },
 9572        cx,
 9573    )
 9574    .await;
 9575
 9576    let _completion_requests_handler =
 9577        cx.lsp
 9578            .server
 9579            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
 9580                Ok(Some(lsp::CompletionResponse::Array(vec![
 9581                    lsp::CompletionItem {
 9582                        label: "first".into(),
 9583                        ..lsp::CompletionItem::default()
 9584                    },
 9585                    lsp::CompletionItem {
 9586                        label: "last".into(),
 9587                        ..lsp::CompletionItem::default()
 9588                    },
 9589                ])))
 9590            });
 9591
 9592    cx.set_state(indoc! {"ˇ
 9593        first
 9594        last
 9595        second
 9596    "});
 9597    cx.simulate_keystroke(".");
 9598    cx.executor().run_until_parked();
 9599    cx.condition(|editor, _| editor.context_menu_visible())
 9600        .await;
 9601    cx.update_editor(|editor, _, _| {
 9602        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
 9603        {
 9604            assert_eq!(
 9605                completion_menu_entries(&menu),
 9606                &["first", "last", "second"],
 9607                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
 9608            );
 9609        } else {
 9610            panic!("expected completion menu to be open");
 9611        }
 9612    });
 9613}
 9614
 9615#[gpui::test]
 9616async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
 9617    init_test(cx, |language_settings| {
 9618        language_settings.defaults.completions = Some(CompletionSettings {
 9619            words: WordsCompletionMode::Disabled,
 9620            lsp: true,
 9621            lsp_fetch_timeout_ms: 0,
 9622        });
 9623    });
 9624
 9625    let mut cx = EditorLspTestContext::new_rust(
 9626        lsp::ServerCapabilities {
 9627            completion_provider: Some(lsp::CompletionOptions {
 9628                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 9629                ..lsp::CompletionOptions::default()
 9630            }),
 9631            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
 9632            ..lsp::ServerCapabilities::default()
 9633        },
 9634        cx,
 9635    )
 9636    .await;
 9637
 9638    let _completion_requests_handler =
 9639        cx.lsp
 9640            .server
 9641            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
 9642                panic!("LSP completions should not be queried when dealing with word completions")
 9643            });
 9644
 9645    cx.set_state(indoc! {"ˇ
 9646        first
 9647        last
 9648        second
 9649    "});
 9650    cx.update_editor(|editor, window, cx| {
 9651        editor.show_word_completions(&ShowWordCompletions, window, cx);
 9652    });
 9653    cx.executor().run_until_parked();
 9654    cx.condition(|editor, _| editor.context_menu_visible())
 9655        .await;
 9656    cx.update_editor(|editor, _, _| {
 9657        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
 9658        {
 9659            assert_eq!(
 9660                completion_menu_entries(&menu),
 9661                &["first", "last", "second"],
 9662                "`ShowWordCompletions` action should show word completions"
 9663            );
 9664        } else {
 9665            panic!("expected completion menu to be open");
 9666        }
 9667    });
 9668
 9669    cx.simulate_keystroke("l");
 9670    cx.executor().run_until_parked();
 9671    cx.condition(|editor, _| editor.context_menu_visible())
 9672        .await;
 9673    cx.update_editor(|editor, _, _| {
 9674        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
 9675        {
 9676            assert_eq!(
 9677                completion_menu_entries(&menu),
 9678                &["last"],
 9679                "After showing word completions, further editing should filter them and not query the LSP"
 9680            );
 9681        } else {
 9682            panic!("expected completion menu to be open");
 9683        }
 9684    });
 9685}
 9686
 9687#[gpui::test]
 9688async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
 9689    init_test(cx, |language_settings| {
 9690        language_settings.defaults.completions = Some(CompletionSettings {
 9691            words: WordsCompletionMode::Fallback,
 9692            lsp: false,
 9693            lsp_fetch_timeout_ms: 0,
 9694        });
 9695    });
 9696
 9697    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
 9698
 9699    cx.set_state(indoc! {"ˇ
 9700        0_usize
 9701        let
 9702        33
 9703        4.5f32
 9704    "});
 9705    cx.update_editor(|editor, window, cx| {
 9706        editor.show_completions(&ShowCompletions::default(), window, cx);
 9707    });
 9708    cx.executor().run_until_parked();
 9709    cx.condition(|editor, _| editor.context_menu_visible())
 9710        .await;
 9711    cx.update_editor(|editor, window, cx| {
 9712        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
 9713        {
 9714            assert_eq!(
 9715                completion_menu_entries(&menu),
 9716                &["let"],
 9717                "With no digits in the completion query, no digits should be in the word completions"
 9718            );
 9719        } else {
 9720            panic!("expected completion menu to be open");
 9721        }
 9722        editor.cancel(&Cancel, window, cx);
 9723    });
 9724
 9725    cx.set_state(indoc! {" 9726        0_usize
 9727        let
 9728        3
 9729        33.35f32
 9730    "});
 9731    cx.update_editor(|editor, window, cx| {
 9732        editor.show_completions(&ShowCompletions::default(), window, cx);
 9733    });
 9734    cx.executor().run_until_parked();
 9735    cx.condition(|editor, _| editor.context_menu_visible())
 9736        .await;
 9737    cx.update_editor(|editor, _, _| {
 9738        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
 9739        {
 9740            assert_eq!(completion_menu_entries(&menu), &["33", "35f32"], "The digit is in the completion query, \
 9741                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
 9742        } else {
 9743            panic!("expected completion menu to be open");
 9744        }
 9745    });
 9746}
 9747
 9748#[gpui::test]
 9749async fn test_multiline_completion(cx: &mut TestAppContext) {
 9750    init_test(cx, |_| {});
 9751
 9752    let fs = FakeFs::new(cx.executor());
 9753    fs.insert_tree(
 9754        path!("/a"),
 9755        json!({
 9756            "main.ts": "a",
 9757        }),
 9758    )
 9759    .await;
 9760
 9761    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
 9762    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
 9763    let typescript_language = Arc::new(Language::new(
 9764        LanguageConfig {
 9765            name: "TypeScript".into(),
 9766            matcher: LanguageMatcher {
 9767                path_suffixes: vec!["ts".to_string()],
 9768                ..LanguageMatcher::default()
 9769            },
 9770            line_comments: vec!["// ".into()],
 9771            ..LanguageConfig::default()
 9772        },
 9773        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
 9774    ));
 9775    language_registry.add(typescript_language.clone());
 9776    let mut fake_servers = language_registry.register_fake_lsp(
 9777        "TypeScript",
 9778        FakeLspAdapter {
 9779            capabilities: lsp::ServerCapabilities {
 9780                completion_provider: Some(lsp::CompletionOptions {
 9781                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
 9782                    ..lsp::CompletionOptions::default()
 9783                }),
 9784                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
 9785                ..lsp::ServerCapabilities::default()
 9786            },
 9787            // Emulate vtsls label generation
 9788            label_for_completion: Some(Box::new(|item, _| {
 9789                let text = if let Some(description) = item
 9790                    .label_details
 9791                    .as_ref()
 9792                    .and_then(|label_details| label_details.description.as_ref())
 9793                {
 9794                    format!("{} {}", item.label, description)
 9795                } else if let Some(detail) = &item.detail {
 9796                    format!("{} {}", item.label, detail)
 9797                } else {
 9798                    item.label.clone()
 9799                };
 9800                let len = text.len();
 9801                Some(language::CodeLabel {
 9802                    text,
 9803                    runs: Vec::new(),
 9804                    filter_range: 0..len,
 9805                })
 9806            })),
 9807            ..FakeLspAdapter::default()
 9808        },
 9809    );
 9810    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
 9811    let cx = &mut VisualTestContext::from_window(*workspace, cx);
 9812    let worktree_id = workspace
 9813        .update(cx, |workspace, _window, cx| {
 9814            workspace.project().update(cx, |project, cx| {
 9815                project.worktrees(cx).next().unwrap().read(cx).id()
 9816            })
 9817        })
 9818        .unwrap();
 9819    let _buffer = project
 9820        .update(cx, |project, cx| {
 9821            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
 9822        })
 9823        .await
 9824        .unwrap();
 9825    let editor = workspace
 9826        .update(cx, |workspace, window, cx| {
 9827            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
 9828        })
 9829        .unwrap()
 9830        .await
 9831        .unwrap()
 9832        .downcast::<Editor>()
 9833        .unwrap();
 9834    let fake_server = fake_servers.next().await.unwrap();
 9835
 9836    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
 9837    let multiline_label_2 = "a\nb\nc\n";
 9838    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
 9839    let multiline_description = "d\ne\nf\n";
 9840    let multiline_detail_2 = "g\nh\ni\n";
 9841
 9842    let mut completion_handle =
 9843        fake_server.handle_request::<lsp::request::Completion, _, _>(move |params, _| async move {
 9844            Ok(Some(lsp::CompletionResponse::Array(vec![
 9845                lsp::CompletionItem {
 9846                    label: multiline_label.to_string(),
 9847                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 9848                        range: lsp::Range {
 9849                            start: lsp::Position {
 9850                                line: params.text_document_position.position.line,
 9851                                character: params.text_document_position.position.character,
 9852                            },
 9853                            end: lsp::Position {
 9854                                line: params.text_document_position.position.line,
 9855                                character: params.text_document_position.position.character,
 9856                            },
 9857                        },
 9858                        new_text: "new_text_1".to_string(),
 9859                    })),
 9860                    ..lsp::CompletionItem::default()
 9861                },
 9862                lsp::CompletionItem {
 9863                    label: "single line label 1".to_string(),
 9864                    detail: Some(multiline_detail.to_string()),
 9865                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 9866                        range: lsp::Range {
 9867                            start: lsp::Position {
 9868                                line: params.text_document_position.position.line,
 9869                                character: params.text_document_position.position.character,
 9870                            },
 9871                            end: lsp::Position {
 9872                                line: params.text_document_position.position.line,
 9873                                character: params.text_document_position.position.character,
 9874                            },
 9875                        },
 9876                        new_text: "new_text_2".to_string(),
 9877                    })),
 9878                    ..lsp::CompletionItem::default()
 9879                },
 9880                lsp::CompletionItem {
 9881                    label: "single line label 2".to_string(),
 9882                    label_details: Some(lsp::CompletionItemLabelDetails {
 9883                        description: Some(multiline_description.to_string()),
 9884                        detail: None,
 9885                    }),
 9886                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 9887                        range: lsp::Range {
 9888                            start: lsp::Position {
 9889                                line: params.text_document_position.position.line,
 9890                                character: params.text_document_position.position.character,
 9891                            },
 9892                            end: lsp::Position {
 9893                                line: params.text_document_position.position.line,
 9894                                character: params.text_document_position.position.character,
 9895                            },
 9896                        },
 9897                        new_text: "new_text_2".to_string(),
 9898                    })),
 9899                    ..lsp::CompletionItem::default()
 9900                },
 9901                lsp::CompletionItem {
 9902                    label: multiline_label_2.to_string(),
 9903                    detail: Some(multiline_detail_2.to_string()),
 9904                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 9905                        range: lsp::Range {
 9906                            start: lsp::Position {
 9907                                line: params.text_document_position.position.line,
 9908                                character: params.text_document_position.position.character,
 9909                            },
 9910                            end: lsp::Position {
 9911                                line: params.text_document_position.position.line,
 9912                                character: params.text_document_position.position.character,
 9913                            },
 9914                        },
 9915                        new_text: "new_text_3".to_string(),
 9916                    })),
 9917                    ..lsp::CompletionItem::default()
 9918                },
 9919                lsp::CompletionItem {
 9920                    label: "Label with many     spaces and \t but without newlines".to_string(),
 9921                    detail: Some(
 9922                        "Details with many     spaces and \t but without newlines".to_string(),
 9923                    ),
 9924                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 9925                        range: lsp::Range {
 9926                            start: lsp::Position {
 9927                                line: params.text_document_position.position.line,
 9928                                character: params.text_document_position.position.character,
 9929                            },
 9930                            end: lsp::Position {
 9931                                line: params.text_document_position.position.line,
 9932                                character: params.text_document_position.position.character,
 9933                            },
 9934                        },
 9935                        new_text: "new_text_4".to_string(),
 9936                    })),
 9937                    ..lsp::CompletionItem::default()
 9938                },
 9939            ])))
 9940        });
 9941
 9942    editor.update_in(cx, |editor, window, cx| {
 9943        cx.focus_self(window);
 9944        editor.move_to_end(&MoveToEnd, window, cx);
 9945        editor.handle_input(".", window, cx);
 9946    });
 9947    cx.run_until_parked();
 9948    completion_handle.next().await.unwrap();
 9949
 9950    editor.update(cx, |editor, _| {
 9951        assert!(editor.context_menu_visible());
 9952        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
 9953        {
 9954            let completion_labels = menu
 9955                .completions
 9956                .borrow()
 9957                .iter()
 9958                .map(|c| c.label.text.clone())
 9959                .collect::<Vec<_>>();
 9960            assert_eq!(
 9961                completion_labels,
 9962                &[
 9963                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
 9964                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
 9965                    "single line label 2 d e f ",
 9966                    "a b c g h i ",
 9967                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
 9968                ],
 9969                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
 9970            );
 9971
 9972            for completion in menu
 9973                .completions
 9974                .borrow()
 9975                .iter() {
 9976                    assert_eq!(
 9977                        completion.label.filter_range,
 9978                        0..completion.label.text.len(),
 9979                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
 9980                    );
 9981                }
 9982
 9983        } else {
 9984            panic!("expected completion menu to be open");
 9985        }
 9986    });
 9987}
 9988
 9989#[gpui::test]
 9990async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
 9991    init_test(cx, |_| {});
 9992    let mut cx = EditorLspTestContext::new_rust(
 9993        lsp::ServerCapabilities {
 9994            completion_provider: Some(lsp::CompletionOptions {
 9995                trigger_characters: Some(vec![".".to_string()]),
 9996                ..Default::default()
 9997            }),
 9998            ..Default::default()
 9999        },
10000        cx,
10001    )
10002    .await;
10003    cx.lsp
10004        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10005            Ok(Some(lsp::CompletionResponse::Array(vec![
10006                lsp::CompletionItem {
10007                    label: "first".into(),
10008                    ..Default::default()
10009                },
10010                lsp::CompletionItem {
10011                    label: "last".into(),
10012                    ..Default::default()
10013                },
10014            ])))
10015        });
10016    cx.set_state("variableˇ");
10017    cx.simulate_keystroke(".");
10018    cx.executor().run_until_parked();
10019
10020    cx.update_editor(|editor, _, _| {
10021        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10022        {
10023            assert_eq!(completion_menu_entries(&menu), &["first", "last"]);
10024        } else {
10025            panic!("expected completion menu to be open");
10026        }
10027    });
10028
10029    cx.update_editor(|editor, window, cx| {
10030        editor.move_page_down(&MovePageDown::default(), window, cx);
10031        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10032        {
10033            assert!(
10034                menu.selected_item == 1,
10035                "expected PageDown to select the last item from the context menu"
10036            );
10037        } else {
10038            panic!("expected completion menu to stay open after PageDown");
10039        }
10040    });
10041
10042    cx.update_editor(|editor, window, cx| {
10043        editor.move_page_up(&MovePageUp::default(), window, cx);
10044        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10045        {
10046            assert!(
10047                menu.selected_item == 0,
10048                "expected PageUp to select the first item from the context menu"
10049            );
10050        } else {
10051            panic!("expected completion menu to stay open after PageUp");
10052        }
10053    });
10054}
10055
10056#[gpui::test]
10057async fn test_completion_sort(cx: &mut TestAppContext) {
10058    init_test(cx, |_| {});
10059    let mut cx = EditorLspTestContext::new_rust(
10060        lsp::ServerCapabilities {
10061            completion_provider: Some(lsp::CompletionOptions {
10062                trigger_characters: Some(vec![".".to_string()]),
10063                ..Default::default()
10064            }),
10065            ..Default::default()
10066        },
10067        cx,
10068    )
10069    .await;
10070    cx.lsp
10071        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
10072            Ok(Some(lsp::CompletionResponse::Array(vec![
10073                lsp::CompletionItem {
10074                    label: "Range".into(),
10075                    sort_text: Some("a".into()),
10076                    ..Default::default()
10077                },
10078                lsp::CompletionItem {
10079                    label: "r".into(),
10080                    sort_text: Some("b".into()),
10081                    ..Default::default()
10082                },
10083                lsp::CompletionItem {
10084                    label: "ret".into(),
10085                    sort_text: Some("c".into()),
10086                    ..Default::default()
10087                },
10088                lsp::CompletionItem {
10089                    label: "return".into(),
10090                    sort_text: Some("d".into()),
10091                    ..Default::default()
10092                },
10093                lsp::CompletionItem {
10094                    label: "slice".into(),
10095                    sort_text: Some("d".into()),
10096                    ..Default::default()
10097                },
10098            ])))
10099        });
10100    cx.set_state("");
10101    cx.executor().run_until_parked();
10102    cx.update_editor(|editor, window, cx| {
10103        editor.show_completions(
10104            &ShowCompletions {
10105                trigger: Some("r".into()),
10106            },
10107            window,
10108            cx,
10109        );
10110    });
10111    cx.executor().run_until_parked();
10112
10113    cx.update_editor(|editor, _, _| {
10114        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
10115        {
10116            assert_eq!(
10117                completion_menu_entries(&menu),
10118                &["r", "ret", "Range", "return"]
10119            );
10120        } else {
10121            panic!("expected completion menu to be open");
10122        }
10123    });
10124}
10125
10126#[gpui::test]
10127async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
10128    init_test(cx, |_| {});
10129
10130    let mut cx = EditorLspTestContext::new_rust(
10131        lsp::ServerCapabilities {
10132            completion_provider: Some(lsp::CompletionOptions {
10133                trigger_characters: Some(vec![".".to_string()]),
10134                resolve_provider: Some(true),
10135                ..Default::default()
10136            }),
10137            ..Default::default()
10138        },
10139        cx,
10140    )
10141    .await;
10142
10143    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
10144    cx.simulate_keystroke(".");
10145    let completion_item = lsp::CompletionItem {
10146        label: "Some".into(),
10147        kind: Some(lsp::CompletionItemKind::SNIPPET),
10148        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
10149        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
10150            kind: lsp::MarkupKind::Markdown,
10151            value: "```rust\nSome(2)\n```".to_string(),
10152        })),
10153        deprecated: Some(false),
10154        sort_text: Some("Some".to_string()),
10155        filter_text: Some("Some".to_string()),
10156        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
10157        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
10158            range: lsp::Range {
10159                start: lsp::Position {
10160                    line: 0,
10161                    character: 22,
10162                },
10163                end: lsp::Position {
10164                    line: 0,
10165                    character: 22,
10166                },
10167            },
10168            new_text: "Some(2)".to_string(),
10169        })),
10170        additional_text_edits: Some(vec![lsp::TextEdit {
10171            range: lsp::Range {
10172                start: lsp::Position {
10173                    line: 0,
10174                    character: 20,
10175                },
10176                end: lsp::Position {
10177                    line: 0,
10178                    character: 22,
10179                },
10180            },
10181            new_text: "".to_string(),
10182        }]),
10183        ..Default::default()
10184    };
10185
10186    let closure_completion_item = completion_item.clone();
10187    let counter = Arc::new(AtomicUsize::new(0));
10188    let counter_clone = counter.clone();
10189    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
10190        let task_completion_item = closure_completion_item.clone();
10191        counter_clone.fetch_add(1, atomic::Ordering::Release);
10192        async move {
10193            Ok(Some(lsp::CompletionResponse::Array(vec![
10194                task_completion_item,
10195            ])))
10196        }
10197    });
10198
10199    cx.condition(|editor, _| editor.context_menu_visible())
10200        .await;
10201    cx.assert_editor_state(indoc! {"fn main() { let a = 2.ˇ; }"});
10202    assert!(request.next().await.is_some());
10203    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
10204
10205    cx.simulate_keystroke("S");
10206    cx.simulate_keystroke("o");
10207    cx.simulate_keystroke("m");
10208    cx.condition(|editor, _| editor.context_menu_visible())
10209        .await;
10210    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Somˇ; }"});
10211    assert!(request.next().await.is_some());
10212    assert!(request.next().await.is_some());
10213    assert!(request.next().await.is_some());
10214    request.close();
10215    assert!(request.next().await.is_none());
10216    assert_eq!(
10217        counter.load(atomic::Ordering::Acquire),
10218        4,
10219        "With the completions menu open, only one LSP request should happen per input"
10220    );
10221}
10222
10223#[gpui::test]
10224async fn test_toggle_comment(cx: &mut TestAppContext) {
10225    init_test(cx, |_| {});
10226    let mut cx = EditorTestContext::new(cx).await;
10227    let language = Arc::new(Language::new(
10228        LanguageConfig {
10229            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10230            ..Default::default()
10231        },
10232        Some(tree_sitter_rust::LANGUAGE.into()),
10233    ));
10234    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10235
10236    // If multiple selections intersect a line, the line is only toggled once.
10237    cx.set_state(indoc! {"
10238        fn a() {
10239            «//b();
10240            ˇ»// «c();
10241            //ˇ»  d();
10242        }
10243    "});
10244
10245    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10246
10247    cx.assert_editor_state(indoc! {"
10248        fn a() {
10249            «b();
10250            c();
10251            ˇ» d();
10252        }
10253    "});
10254
10255    // The comment prefix is inserted at the same column for every line in a
10256    // selection.
10257    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10258
10259    cx.assert_editor_state(indoc! {"
10260        fn a() {
10261            // «b();
10262            // c();
10263            ˇ»//  d();
10264        }
10265    "});
10266
10267    // If a selection ends at the beginning of a line, that line is not toggled.
10268    cx.set_selections_state(indoc! {"
10269        fn a() {
10270            // b();
10271            «// c();
10272        ˇ»    //  d();
10273        }
10274    "});
10275
10276    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10277
10278    cx.assert_editor_state(indoc! {"
10279        fn a() {
10280            // b();
10281            «c();
10282        ˇ»    //  d();
10283        }
10284    "});
10285
10286    // If a selection span a single line and is empty, the line is toggled.
10287    cx.set_state(indoc! {"
10288        fn a() {
10289            a();
10290            b();
10291        ˇ
10292        }
10293    "});
10294
10295    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10296
10297    cx.assert_editor_state(indoc! {"
10298        fn a() {
10299            a();
10300            b();
10301        //•ˇ
10302        }
10303    "});
10304
10305    // If a selection span multiple lines, empty lines are not toggled.
10306    cx.set_state(indoc! {"
10307        fn a() {
10308            «a();
10309
10310            c();ˇ»
10311        }
10312    "});
10313
10314    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10315
10316    cx.assert_editor_state(indoc! {"
10317        fn a() {
10318            // «a();
10319
10320            // c();ˇ»
10321        }
10322    "});
10323
10324    // If a selection includes multiple comment prefixes, all lines are uncommented.
10325    cx.set_state(indoc! {"
10326        fn a() {
10327            «// a();
10328            /// b();
10329            //! c();ˇ»
10330        }
10331    "});
10332
10333    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
10334
10335    cx.assert_editor_state(indoc! {"
10336        fn a() {
10337            «a();
10338            b();
10339            c();ˇ»
10340        }
10341    "});
10342}
10343
10344#[gpui::test]
10345async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
10346    init_test(cx, |_| {});
10347    let mut cx = EditorTestContext::new(cx).await;
10348    let language = Arc::new(Language::new(
10349        LanguageConfig {
10350            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
10351            ..Default::default()
10352        },
10353        Some(tree_sitter_rust::LANGUAGE.into()),
10354    ));
10355    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10356
10357    let toggle_comments = &ToggleComments {
10358        advance_downwards: false,
10359        ignore_indent: true,
10360    };
10361
10362    // If multiple selections intersect a line, the line is only toggled once.
10363    cx.set_state(indoc! {"
10364        fn a() {
10365        //    «b();
10366        //    c();
10367        //    ˇ» d();
10368        }
10369    "});
10370
10371    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10372
10373    cx.assert_editor_state(indoc! {"
10374        fn a() {
10375            «b();
10376            c();
10377            ˇ» d();
10378        }
10379    "});
10380
10381    // The comment prefix is inserted at the beginning of each line
10382    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10383
10384    cx.assert_editor_state(indoc! {"
10385        fn a() {
10386        //    «b();
10387        //    c();
10388        //    ˇ» d();
10389        }
10390    "});
10391
10392    // If a selection ends at the beginning of a line, that line is not toggled.
10393    cx.set_selections_state(indoc! {"
10394        fn a() {
10395        //    b();
10396        //    «c();
10397        ˇ»//     d();
10398        }
10399    "});
10400
10401    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10402
10403    cx.assert_editor_state(indoc! {"
10404        fn a() {
10405        //    b();
10406            «c();
10407        ˇ»//     d();
10408        }
10409    "});
10410
10411    // If a selection span a single line and is empty, the line is toggled.
10412    cx.set_state(indoc! {"
10413        fn a() {
10414            a();
10415            b();
10416        ˇ
10417        }
10418    "});
10419
10420    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10421
10422    cx.assert_editor_state(indoc! {"
10423        fn a() {
10424            a();
10425            b();
10426        //ˇ
10427        }
10428    "});
10429
10430    // If a selection span multiple lines, empty lines are not toggled.
10431    cx.set_state(indoc! {"
10432        fn a() {
10433            «a();
10434
10435            c();ˇ»
10436        }
10437    "});
10438
10439    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10440
10441    cx.assert_editor_state(indoc! {"
10442        fn a() {
10443        //    «a();
10444
10445        //    c();ˇ»
10446        }
10447    "});
10448
10449    // If a selection includes multiple comment prefixes, all lines are uncommented.
10450    cx.set_state(indoc! {"
10451        fn a() {
10452        //    «a();
10453        ///    b();
10454        //!    c();ˇ»
10455        }
10456    "});
10457
10458    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
10459
10460    cx.assert_editor_state(indoc! {"
10461        fn a() {
10462            «a();
10463            b();
10464            c();ˇ»
10465        }
10466    "});
10467}
10468
10469#[gpui::test]
10470async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
10471    init_test(cx, |_| {});
10472
10473    let language = Arc::new(Language::new(
10474        LanguageConfig {
10475            line_comments: vec!["// ".into()],
10476            ..Default::default()
10477        },
10478        Some(tree_sitter_rust::LANGUAGE.into()),
10479    ));
10480
10481    let mut cx = EditorTestContext::new(cx).await;
10482
10483    cx.language_registry().add(language.clone());
10484    cx.update_buffer(|buffer, cx| {
10485        buffer.set_language(Some(language), cx);
10486    });
10487
10488    let toggle_comments = &ToggleComments {
10489        advance_downwards: true,
10490        ignore_indent: false,
10491    };
10492
10493    // Single cursor on one line -> advance
10494    // Cursor moves horizontally 3 characters as well on non-blank line
10495    cx.set_state(indoc!(
10496        "fn a() {
10497             ˇdog();
10498             cat();
10499        }"
10500    ));
10501    cx.update_editor(|editor, window, cx| {
10502        editor.toggle_comments(toggle_comments, window, cx);
10503    });
10504    cx.assert_editor_state(indoc!(
10505        "fn a() {
10506             // dog();
10507             catˇ();
10508        }"
10509    ));
10510
10511    // Single selection on one line -> don't advance
10512    cx.set_state(indoc!(
10513        "fn a() {
10514             «dog()ˇ»;
10515             cat();
10516        }"
10517    ));
10518    cx.update_editor(|editor, window, cx| {
10519        editor.toggle_comments(toggle_comments, window, cx);
10520    });
10521    cx.assert_editor_state(indoc!(
10522        "fn a() {
10523             // «dog()ˇ»;
10524             cat();
10525        }"
10526    ));
10527
10528    // Multiple cursors on one line -> advance
10529    cx.set_state(indoc!(
10530        "fn a() {
10531             ˇdˇog();
10532             cat();
10533        }"
10534    ));
10535    cx.update_editor(|editor, window, cx| {
10536        editor.toggle_comments(toggle_comments, window, cx);
10537    });
10538    cx.assert_editor_state(indoc!(
10539        "fn a() {
10540             // dog();
10541             catˇ(ˇ);
10542        }"
10543    ));
10544
10545    // Multiple cursors on one line, with selection -> don't advance
10546    cx.set_state(indoc!(
10547        "fn a() {
10548             ˇdˇog«()ˇ»;
10549             cat();
10550        }"
10551    ));
10552    cx.update_editor(|editor, window, cx| {
10553        editor.toggle_comments(toggle_comments, window, cx);
10554    });
10555    cx.assert_editor_state(indoc!(
10556        "fn a() {
10557             // ˇdˇog«()ˇ»;
10558             cat();
10559        }"
10560    ));
10561
10562    // Single cursor on one line -> advance
10563    // Cursor moves to column 0 on blank line
10564    cx.set_state(indoc!(
10565        "fn a() {
10566             ˇdog();
10567
10568             cat();
10569        }"
10570    ));
10571    cx.update_editor(|editor, window, cx| {
10572        editor.toggle_comments(toggle_comments, window, cx);
10573    });
10574    cx.assert_editor_state(indoc!(
10575        "fn a() {
10576             // dog();
10577        ˇ
10578             cat();
10579        }"
10580    ));
10581
10582    // Single cursor on one line -> advance
10583    // Cursor starts and ends at column 0
10584    cx.set_state(indoc!(
10585        "fn a() {
10586         ˇ    dog();
10587             cat();
10588        }"
10589    ));
10590    cx.update_editor(|editor, window, cx| {
10591        editor.toggle_comments(toggle_comments, window, cx);
10592    });
10593    cx.assert_editor_state(indoc!(
10594        "fn a() {
10595             // dog();
10596         ˇ    cat();
10597        }"
10598    ));
10599}
10600
10601#[gpui::test]
10602async fn test_toggle_block_comment(cx: &mut TestAppContext) {
10603    init_test(cx, |_| {});
10604
10605    let mut cx = EditorTestContext::new(cx).await;
10606
10607    let html_language = Arc::new(
10608        Language::new(
10609            LanguageConfig {
10610                name: "HTML".into(),
10611                block_comment: Some(("<!-- ".into(), " -->".into())),
10612                ..Default::default()
10613            },
10614            Some(tree_sitter_html::LANGUAGE.into()),
10615        )
10616        .with_injection_query(
10617            r#"
10618            (script_element
10619                (raw_text) @injection.content
10620                (#set! injection.language "javascript"))
10621            "#,
10622        )
10623        .unwrap(),
10624    );
10625
10626    let javascript_language = Arc::new(Language::new(
10627        LanguageConfig {
10628            name: "JavaScript".into(),
10629            line_comments: vec!["// ".into()],
10630            ..Default::default()
10631        },
10632        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10633    ));
10634
10635    cx.language_registry().add(html_language.clone());
10636    cx.language_registry().add(javascript_language.clone());
10637    cx.update_buffer(|buffer, cx| {
10638        buffer.set_language(Some(html_language), cx);
10639    });
10640
10641    // Toggle comments for empty selections
10642    cx.set_state(
10643        &r#"
10644            <p>A</p>ˇ
10645            <p>B</p>ˇ
10646            <p>C</p>ˇ
10647        "#
10648        .unindent(),
10649    );
10650    cx.update_editor(|editor, window, cx| {
10651        editor.toggle_comments(&ToggleComments::default(), window, cx)
10652    });
10653    cx.assert_editor_state(
10654        &r#"
10655            <!-- <p>A</p>ˇ -->
10656            <!-- <p>B</p>ˇ -->
10657            <!-- <p>C</p>ˇ -->
10658        "#
10659        .unindent(),
10660    );
10661    cx.update_editor(|editor, window, cx| {
10662        editor.toggle_comments(&ToggleComments::default(), window, cx)
10663    });
10664    cx.assert_editor_state(
10665        &r#"
10666            <p>A</p>ˇ
10667            <p>B</p>ˇ
10668            <p>C</p>ˇ
10669        "#
10670        .unindent(),
10671    );
10672
10673    // Toggle comments for mixture of empty and non-empty selections, where
10674    // multiple selections occupy a given line.
10675    cx.set_state(
10676        &r#"
10677            <p>A«</p>
10678            <p>ˇ»B</p>ˇ
10679            <p>C«</p>
10680            <p>ˇ»D</p>ˇ
10681        "#
10682        .unindent(),
10683    );
10684
10685    cx.update_editor(|editor, window, cx| {
10686        editor.toggle_comments(&ToggleComments::default(), window, cx)
10687    });
10688    cx.assert_editor_state(
10689        &r#"
10690            <!-- <p>A«</p>
10691            <p>ˇ»B</p>ˇ -->
10692            <!-- <p>C«</p>
10693            <p>ˇ»D</p>ˇ -->
10694        "#
10695        .unindent(),
10696    );
10697    cx.update_editor(|editor, window, cx| {
10698        editor.toggle_comments(&ToggleComments::default(), window, cx)
10699    });
10700    cx.assert_editor_state(
10701        &r#"
10702            <p>A«</p>
10703            <p>ˇ»B</p>ˇ
10704            <p>C«</p>
10705            <p>ˇ»D</p>ˇ
10706        "#
10707        .unindent(),
10708    );
10709
10710    // Toggle comments when different languages are active for different
10711    // selections.
10712    cx.set_state(
10713        &r#"
10714            ˇ<script>
10715                ˇvar x = new Y();
10716            ˇ</script>
10717        "#
10718        .unindent(),
10719    );
10720    cx.executor().run_until_parked();
10721    cx.update_editor(|editor, window, cx| {
10722        editor.toggle_comments(&ToggleComments::default(), window, cx)
10723    });
10724    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
10725    // Uncommenting and commenting from this position brings in even more wrong artifacts.
10726    cx.assert_editor_state(
10727        &r#"
10728            <!-- ˇ<script> -->
10729                // ˇvar x = new Y();
10730            <!-- ˇ</script> -->
10731        "#
10732        .unindent(),
10733    );
10734}
10735
10736#[gpui::test]
10737fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
10738    init_test(cx, |_| {});
10739
10740    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10741    let multibuffer = cx.new(|cx| {
10742        let mut multibuffer = MultiBuffer::new(ReadWrite);
10743        multibuffer.push_excerpts(
10744            buffer.clone(),
10745            [
10746                ExcerptRange {
10747                    context: Point::new(0, 0)..Point::new(0, 4),
10748                    primary: None,
10749                },
10750                ExcerptRange {
10751                    context: Point::new(1, 0)..Point::new(1, 4),
10752                    primary: None,
10753                },
10754            ],
10755            cx,
10756        );
10757        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
10758        multibuffer
10759    });
10760
10761    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10762    editor.update_in(cx, |editor, window, cx| {
10763        assert_eq!(editor.text(cx), "aaaa\nbbbb");
10764        editor.change_selections(None, window, cx, |s| {
10765            s.select_ranges([
10766                Point::new(0, 0)..Point::new(0, 0),
10767                Point::new(1, 0)..Point::new(1, 0),
10768            ])
10769        });
10770
10771        editor.handle_input("X", window, cx);
10772        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
10773        assert_eq!(
10774            editor.selections.ranges(cx),
10775            [
10776                Point::new(0, 1)..Point::new(0, 1),
10777                Point::new(1, 1)..Point::new(1, 1),
10778            ]
10779        );
10780
10781        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
10782        editor.change_selections(None, window, cx, |s| {
10783            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
10784        });
10785        editor.backspace(&Default::default(), window, cx);
10786        assert_eq!(editor.text(cx), "Xa\nbbb");
10787        assert_eq!(
10788            editor.selections.ranges(cx),
10789            [Point::new(1, 0)..Point::new(1, 0)]
10790        );
10791
10792        editor.change_selections(None, window, cx, |s| {
10793            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
10794        });
10795        editor.backspace(&Default::default(), window, cx);
10796        assert_eq!(editor.text(cx), "X\nbb");
10797        assert_eq!(
10798            editor.selections.ranges(cx),
10799            [Point::new(0, 1)..Point::new(0, 1)]
10800        );
10801    });
10802}
10803
10804#[gpui::test]
10805fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
10806    init_test(cx, |_| {});
10807
10808    let markers = vec![('[', ']').into(), ('(', ')').into()];
10809    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
10810        indoc! {"
10811            [aaaa
10812            (bbbb]
10813            cccc)",
10814        },
10815        markers.clone(),
10816    );
10817    let excerpt_ranges = markers.into_iter().map(|marker| {
10818        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
10819        ExcerptRange {
10820            context,
10821            primary: None,
10822        }
10823    });
10824    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
10825    let multibuffer = cx.new(|cx| {
10826        let mut multibuffer = MultiBuffer::new(ReadWrite);
10827        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
10828        multibuffer
10829    });
10830
10831    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
10832    editor.update_in(cx, |editor, window, cx| {
10833        let (expected_text, selection_ranges) = marked_text_ranges(
10834            indoc! {"
10835                aaaa
10836                bˇbbb
10837                bˇbbˇb
10838                cccc"
10839            },
10840            true,
10841        );
10842        assert_eq!(editor.text(cx), expected_text);
10843        editor.change_selections(None, window, cx, |s| s.select_ranges(selection_ranges));
10844
10845        editor.handle_input("X", window, cx);
10846
10847        let (expected_text, expected_selections) = marked_text_ranges(
10848            indoc! {"
10849                aaaa
10850                bXˇbbXb
10851                bXˇbbXˇb
10852                cccc"
10853            },
10854            false,
10855        );
10856        assert_eq!(editor.text(cx), expected_text);
10857        assert_eq!(editor.selections.ranges(cx), expected_selections);
10858
10859        editor.newline(&Newline, window, cx);
10860        let (expected_text, expected_selections) = marked_text_ranges(
10861            indoc! {"
10862                aaaa
10863                bX
10864                ˇbbX
10865                b
10866                bX
10867                ˇbbX
10868                ˇb
10869                cccc"
10870            },
10871            false,
10872        );
10873        assert_eq!(editor.text(cx), expected_text);
10874        assert_eq!(editor.selections.ranges(cx), expected_selections);
10875    });
10876}
10877
10878#[gpui::test]
10879fn test_refresh_selections(cx: &mut TestAppContext) {
10880    init_test(cx, |_| {});
10881
10882    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10883    let mut excerpt1_id = None;
10884    let multibuffer = cx.new(|cx| {
10885        let mut multibuffer = MultiBuffer::new(ReadWrite);
10886        excerpt1_id = multibuffer
10887            .push_excerpts(
10888                buffer.clone(),
10889                [
10890                    ExcerptRange {
10891                        context: Point::new(0, 0)..Point::new(1, 4),
10892                        primary: None,
10893                    },
10894                    ExcerptRange {
10895                        context: Point::new(1, 0)..Point::new(2, 4),
10896                        primary: None,
10897                    },
10898                ],
10899                cx,
10900            )
10901            .into_iter()
10902            .next();
10903        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10904        multibuffer
10905    });
10906
10907    let editor = cx.add_window(|window, cx| {
10908        let mut editor = build_editor(multibuffer.clone(), window, cx);
10909        let snapshot = editor.snapshot(window, cx);
10910        editor.change_selections(None, window, cx, |s| {
10911            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
10912        });
10913        editor.begin_selection(
10914            Point::new(2, 1).to_display_point(&snapshot),
10915            true,
10916            1,
10917            window,
10918            cx,
10919        );
10920        assert_eq!(
10921            editor.selections.ranges(cx),
10922            [
10923                Point::new(1, 3)..Point::new(1, 3),
10924                Point::new(2, 1)..Point::new(2, 1),
10925            ]
10926        );
10927        editor
10928    });
10929
10930    // Refreshing selections is a no-op when excerpts haven't changed.
10931    _ = editor.update(cx, |editor, window, cx| {
10932        editor.change_selections(None, window, cx, |s| s.refresh());
10933        assert_eq!(
10934            editor.selections.ranges(cx),
10935            [
10936                Point::new(1, 3)..Point::new(1, 3),
10937                Point::new(2, 1)..Point::new(2, 1),
10938            ]
10939        );
10940    });
10941
10942    multibuffer.update(cx, |multibuffer, cx| {
10943        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
10944    });
10945    _ = editor.update(cx, |editor, window, cx| {
10946        // Removing an excerpt causes the first selection to become degenerate.
10947        assert_eq!(
10948            editor.selections.ranges(cx),
10949            [
10950                Point::new(0, 0)..Point::new(0, 0),
10951                Point::new(0, 1)..Point::new(0, 1)
10952            ]
10953        );
10954
10955        // Refreshing selections will relocate the first selection to the original buffer
10956        // location.
10957        editor.change_selections(None, window, cx, |s| s.refresh());
10958        assert_eq!(
10959            editor.selections.ranges(cx),
10960            [
10961                Point::new(0, 1)..Point::new(0, 1),
10962                Point::new(0, 3)..Point::new(0, 3)
10963            ]
10964        );
10965        assert!(editor.selections.pending_anchor().is_some());
10966    });
10967}
10968
10969#[gpui::test]
10970fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
10971    init_test(cx, |_| {});
10972
10973    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
10974    let mut excerpt1_id = None;
10975    let multibuffer = cx.new(|cx| {
10976        let mut multibuffer = MultiBuffer::new(ReadWrite);
10977        excerpt1_id = multibuffer
10978            .push_excerpts(
10979                buffer.clone(),
10980                [
10981                    ExcerptRange {
10982                        context: Point::new(0, 0)..Point::new(1, 4),
10983                        primary: None,
10984                    },
10985                    ExcerptRange {
10986                        context: Point::new(1, 0)..Point::new(2, 4),
10987                        primary: None,
10988                    },
10989                ],
10990                cx,
10991            )
10992            .into_iter()
10993            .next();
10994        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
10995        multibuffer
10996    });
10997
10998    let editor = cx.add_window(|window, cx| {
10999        let mut editor = build_editor(multibuffer.clone(), window, cx);
11000        let snapshot = editor.snapshot(window, cx);
11001        editor.begin_selection(
11002            Point::new(1, 3).to_display_point(&snapshot),
11003            false,
11004            1,
11005            window,
11006            cx,
11007        );
11008        assert_eq!(
11009            editor.selections.ranges(cx),
11010            [Point::new(1, 3)..Point::new(1, 3)]
11011        );
11012        editor
11013    });
11014
11015    multibuffer.update(cx, |multibuffer, cx| {
11016        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
11017    });
11018    _ = editor.update(cx, |editor, window, cx| {
11019        assert_eq!(
11020            editor.selections.ranges(cx),
11021            [Point::new(0, 0)..Point::new(0, 0)]
11022        );
11023
11024        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
11025        editor.change_selections(None, window, cx, |s| s.refresh());
11026        assert_eq!(
11027            editor.selections.ranges(cx),
11028            [Point::new(0, 3)..Point::new(0, 3)]
11029        );
11030        assert!(editor.selections.pending_anchor().is_some());
11031    });
11032}
11033
11034#[gpui::test]
11035async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
11036    init_test(cx, |_| {});
11037
11038    let language = Arc::new(
11039        Language::new(
11040            LanguageConfig {
11041                brackets: BracketPairConfig {
11042                    pairs: vec![
11043                        BracketPair {
11044                            start: "{".to_string(),
11045                            end: "}".to_string(),
11046                            close: true,
11047                            surround: true,
11048                            newline: true,
11049                        },
11050                        BracketPair {
11051                            start: "/* ".to_string(),
11052                            end: " */".to_string(),
11053                            close: true,
11054                            surround: true,
11055                            newline: true,
11056                        },
11057                    ],
11058                    ..Default::default()
11059                },
11060                ..Default::default()
11061            },
11062            Some(tree_sitter_rust::LANGUAGE.into()),
11063        )
11064        .with_indents_query("")
11065        .unwrap(),
11066    );
11067
11068    let text = concat!(
11069        "{   }\n",     //
11070        "  x\n",       //
11071        "  /*   */\n", //
11072        "x\n",         //
11073        "{{} }\n",     //
11074    );
11075
11076    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11077    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11078    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11079    editor
11080        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11081        .await;
11082
11083    editor.update_in(cx, |editor, window, cx| {
11084        editor.change_selections(None, window, cx, |s| {
11085            s.select_display_ranges([
11086                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
11087                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
11088                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
11089            ])
11090        });
11091        editor.newline(&Newline, window, cx);
11092
11093        assert_eq!(
11094            editor.buffer().read(cx).read(cx).text(),
11095            concat!(
11096                "{ \n",    // Suppress rustfmt
11097                "\n",      //
11098                "}\n",     //
11099                "  x\n",   //
11100                "  /* \n", //
11101                "  \n",    //
11102                "  */\n",  //
11103                "x\n",     //
11104                "{{} \n",  //
11105                "}\n",     //
11106            )
11107        );
11108    });
11109}
11110
11111#[gpui::test]
11112fn test_highlighted_ranges(cx: &mut TestAppContext) {
11113    init_test(cx, |_| {});
11114
11115    let editor = cx.add_window(|window, cx| {
11116        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
11117        build_editor(buffer.clone(), window, cx)
11118    });
11119
11120    _ = editor.update(cx, |editor, window, cx| {
11121        struct Type1;
11122        struct Type2;
11123
11124        let buffer = editor.buffer.read(cx).snapshot(cx);
11125
11126        let anchor_range =
11127            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
11128
11129        editor.highlight_background::<Type1>(
11130            &[
11131                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
11132                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
11133                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
11134                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
11135            ],
11136            |_| Hsla::red(),
11137            cx,
11138        );
11139        editor.highlight_background::<Type2>(
11140            &[
11141                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
11142                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
11143                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
11144                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
11145            ],
11146            |_| Hsla::green(),
11147            cx,
11148        );
11149
11150        let snapshot = editor.snapshot(window, cx);
11151        let mut highlighted_ranges = editor.background_highlights_in_range(
11152            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
11153            &snapshot,
11154            cx.theme().colors(),
11155        );
11156        // Enforce a consistent ordering based on color without relying on the ordering of the
11157        // highlight's `TypeId` which is non-executor.
11158        highlighted_ranges.sort_unstable_by_key(|(_, color)| *color);
11159        assert_eq!(
11160            highlighted_ranges,
11161            &[
11162                (
11163                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
11164                    Hsla::red(),
11165                ),
11166                (
11167                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
11168                    Hsla::red(),
11169                ),
11170                (
11171                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
11172                    Hsla::green(),
11173                ),
11174                (
11175                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
11176                    Hsla::green(),
11177                ),
11178            ]
11179        );
11180        assert_eq!(
11181            editor.background_highlights_in_range(
11182                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
11183                &snapshot,
11184                cx.theme().colors(),
11185            ),
11186            &[(
11187                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
11188                Hsla::red(),
11189            )]
11190        );
11191    });
11192}
11193
11194#[gpui::test]
11195async fn test_following(cx: &mut TestAppContext) {
11196    init_test(cx, |_| {});
11197
11198    let fs = FakeFs::new(cx.executor());
11199    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11200
11201    let buffer = project.update(cx, |project, cx| {
11202        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, cx);
11203        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
11204    });
11205    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
11206    let follower = cx.update(|cx| {
11207        cx.open_window(
11208            WindowOptions {
11209                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
11210                    gpui::Point::new(px(0.), px(0.)),
11211                    gpui::Point::new(px(10.), px(80.)),
11212                ))),
11213                ..Default::default()
11214            },
11215            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
11216        )
11217        .unwrap()
11218    });
11219
11220    let is_still_following = Rc::new(RefCell::new(true));
11221    let follower_edit_event_count = Rc::new(RefCell::new(0));
11222    let pending_update = Rc::new(RefCell::new(None));
11223    let leader_entity = leader.root(cx).unwrap();
11224    let follower_entity = follower.root(cx).unwrap();
11225    _ = follower.update(cx, {
11226        let update = pending_update.clone();
11227        let is_still_following = is_still_following.clone();
11228        let follower_edit_event_count = follower_edit_event_count.clone();
11229        |_, window, cx| {
11230            cx.subscribe_in(
11231                &leader_entity,
11232                window,
11233                move |_, leader, event, window, cx| {
11234                    leader.read(cx).add_event_to_update_proto(
11235                        event,
11236                        &mut update.borrow_mut(),
11237                        window,
11238                        cx,
11239                    );
11240                },
11241            )
11242            .detach();
11243
11244            cx.subscribe_in(
11245                &follower_entity,
11246                window,
11247                move |_, _, event: &EditorEvent, _window, _cx| {
11248                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
11249                        *is_still_following.borrow_mut() = false;
11250                    }
11251
11252                    if let EditorEvent::BufferEdited = event {
11253                        *follower_edit_event_count.borrow_mut() += 1;
11254                    }
11255                },
11256            )
11257            .detach();
11258        }
11259    });
11260
11261    // Update the selections only
11262    _ = leader.update(cx, |leader, window, cx| {
11263        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11264    });
11265    follower
11266        .update(cx, |follower, window, cx| {
11267            follower.apply_update_proto(
11268                &project,
11269                pending_update.borrow_mut().take().unwrap(),
11270                window,
11271                cx,
11272            )
11273        })
11274        .unwrap()
11275        .await
11276        .unwrap();
11277    _ = follower.update(cx, |follower, _, cx| {
11278        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
11279    });
11280    assert!(*is_still_following.borrow());
11281    assert_eq!(*follower_edit_event_count.borrow(), 0);
11282
11283    // Update the scroll position only
11284    _ = leader.update(cx, |leader, window, cx| {
11285        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11286    });
11287    follower
11288        .update(cx, |follower, window, cx| {
11289            follower.apply_update_proto(
11290                &project,
11291                pending_update.borrow_mut().take().unwrap(),
11292                window,
11293                cx,
11294            )
11295        })
11296        .unwrap()
11297        .await
11298        .unwrap();
11299    assert_eq!(
11300        follower
11301            .update(cx, |follower, _, cx| follower.scroll_position(cx))
11302            .unwrap(),
11303        gpui::Point::new(1.5, 3.5)
11304    );
11305    assert!(*is_still_following.borrow());
11306    assert_eq!(*follower_edit_event_count.borrow(), 0);
11307
11308    // Update the selections and scroll position. The follower's scroll position is updated
11309    // via autoscroll, not via the leader's exact scroll position.
11310    _ = leader.update(cx, |leader, window, cx| {
11311        leader.change_selections(None, window, cx, |s| s.select_ranges([0..0]));
11312        leader.request_autoscroll(Autoscroll::newest(), cx);
11313        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
11314    });
11315    follower
11316        .update(cx, |follower, window, cx| {
11317            follower.apply_update_proto(
11318                &project,
11319                pending_update.borrow_mut().take().unwrap(),
11320                window,
11321                cx,
11322            )
11323        })
11324        .unwrap()
11325        .await
11326        .unwrap();
11327    _ = follower.update(cx, |follower, _, cx| {
11328        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
11329        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
11330    });
11331    assert!(*is_still_following.borrow());
11332
11333    // Creating a pending selection that precedes another selection
11334    _ = leader.update(cx, |leader, window, cx| {
11335        leader.change_selections(None, window, cx, |s| s.select_ranges([1..1]));
11336        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
11337    });
11338    follower
11339        .update(cx, |follower, window, cx| {
11340            follower.apply_update_proto(
11341                &project,
11342                pending_update.borrow_mut().take().unwrap(),
11343                window,
11344                cx,
11345            )
11346        })
11347        .unwrap()
11348        .await
11349        .unwrap();
11350    _ = follower.update(cx, |follower, _, cx| {
11351        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
11352    });
11353    assert!(*is_still_following.borrow());
11354
11355    // Extend the pending selection so that it surrounds another selection
11356    _ = leader.update(cx, |leader, window, cx| {
11357        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
11358    });
11359    follower
11360        .update(cx, |follower, window, cx| {
11361            follower.apply_update_proto(
11362                &project,
11363                pending_update.borrow_mut().take().unwrap(),
11364                window,
11365                cx,
11366            )
11367        })
11368        .unwrap()
11369        .await
11370        .unwrap();
11371    _ = follower.update(cx, |follower, _, cx| {
11372        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
11373    });
11374
11375    // Scrolling locally breaks the follow
11376    _ = follower.update(cx, |follower, window, cx| {
11377        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
11378        follower.set_scroll_anchor(
11379            ScrollAnchor {
11380                anchor: top_anchor,
11381                offset: gpui::Point::new(0.0, 0.5),
11382            },
11383            window,
11384            cx,
11385        );
11386    });
11387    assert!(!(*is_still_following.borrow()));
11388}
11389
11390#[gpui::test]
11391async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
11392    init_test(cx, |_| {});
11393
11394    let fs = FakeFs::new(cx.executor());
11395    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
11396    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11397    let pane = workspace
11398        .update(cx, |workspace, _, _| workspace.active_pane().clone())
11399        .unwrap();
11400
11401    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11402
11403    let leader = pane.update_in(cx, |_, window, cx| {
11404        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
11405        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
11406    });
11407
11408    // Start following the editor when it has no excerpts.
11409    let mut state_message =
11410        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11411    let workspace_entity = workspace.root(cx).unwrap();
11412    let follower_1 = cx
11413        .update_window(*workspace.deref(), |_, window, cx| {
11414            Editor::from_state_proto(
11415                workspace_entity,
11416                ViewId {
11417                    creator: Default::default(),
11418                    id: 0,
11419                },
11420                &mut state_message,
11421                window,
11422                cx,
11423            )
11424        })
11425        .unwrap()
11426        .unwrap()
11427        .await
11428        .unwrap();
11429
11430    let update_message = Rc::new(RefCell::new(None));
11431    follower_1.update_in(cx, {
11432        let update = update_message.clone();
11433        |_, window, cx| {
11434            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
11435                leader.read(cx).add_event_to_update_proto(
11436                    event,
11437                    &mut update.borrow_mut(),
11438                    window,
11439                    cx,
11440                );
11441            })
11442            .detach();
11443        }
11444    });
11445
11446    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
11447        (
11448            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, cx),
11449            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, cx),
11450        )
11451    });
11452
11453    // Insert some excerpts.
11454    leader.update(cx, |leader, cx| {
11455        leader.buffer.update(cx, |multibuffer, cx| {
11456            let excerpt_ids = multibuffer.push_excerpts(
11457                buffer_1.clone(),
11458                [
11459                    ExcerptRange {
11460                        context: 1..6,
11461                        primary: None,
11462                    },
11463                    ExcerptRange {
11464                        context: 12..15,
11465                        primary: None,
11466                    },
11467                    ExcerptRange {
11468                        context: 0..3,
11469                        primary: None,
11470                    },
11471                ],
11472                cx,
11473            );
11474            multibuffer.insert_excerpts_after(
11475                excerpt_ids[0],
11476                buffer_2.clone(),
11477                [
11478                    ExcerptRange {
11479                        context: 8..12,
11480                        primary: None,
11481                    },
11482                    ExcerptRange {
11483                        context: 0..6,
11484                        primary: None,
11485                    },
11486                ],
11487                cx,
11488            );
11489        });
11490    });
11491
11492    // Apply the update of adding the excerpts.
11493    follower_1
11494        .update_in(cx, |follower, window, cx| {
11495            follower.apply_update_proto(
11496                &project,
11497                update_message.borrow().clone().unwrap(),
11498                window,
11499                cx,
11500            )
11501        })
11502        .await
11503        .unwrap();
11504    assert_eq!(
11505        follower_1.update(cx, |editor, cx| editor.text(cx)),
11506        leader.update(cx, |editor, cx| editor.text(cx))
11507    );
11508    update_message.borrow_mut().take();
11509
11510    // Start following separately after it already has excerpts.
11511    let mut state_message =
11512        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
11513    let workspace_entity = workspace.root(cx).unwrap();
11514    let follower_2 = cx
11515        .update_window(*workspace.deref(), |_, window, cx| {
11516            Editor::from_state_proto(
11517                workspace_entity,
11518                ViewId {
11519                    creator: Default::default(),
11520                    id: 0,
11521                },
11522                &mut state_message,
11523                window,
11524                cx,
11525            )
11526        })
11527        .unwrap()
11528        .unwrap()
11529        .await
11530        .unwrap();
11531    assert_eq!(
11532        follower_2.update(cx, |editor, cx| editor.text(cx)),
11533        leader.update(cx, |editor, cx| editor.text(cx))
11534    );
11535
11536    // Remove some excerpts.
11537    leader.update(cx, |leader, cx| {
11538        leader.buffer.update(cx, |multibuffer, cx| {
11539            let excerpt_ids = multibuffer.excerpt_ids();
11540            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
11541            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
11542        });
11543    });
11544
11545    // Apply the update of removing the excerpts.
11546    follower_1
11547        .update_in(cx, |follower, window, cx| {
11548            follower.apply_update_proto(
11549                &project,
11550                update_message.borrow().clone().unwrap(),
11551                window,
11552                cx,
11553            )
11554        })
11555        .await
11556        .unwrap();
11557    follower_2
11558        .update_in(cx, |follower, window, cx| {
11559            follower.apply_update_proto(
11560                &project,
11561                update_message.borrow().clone().unwrap(),
11562                window,
11563                cx,
11564            )
11565        })
11566        .await
11567        .unwrap();
11568    update_message.borrow_mut().take();
11569    assert_eq!(
11570        follower_1.update(cx, |editor, cx| editor.text(cx)),
11571        leader.update(cx, |editor, cx| editor.text(cx))
11572    );
11573}
11574
11575#[gpui::test]
11576async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11577    init_test(cx, |_| {});
11578
11579    let mut cx = EditorTestContext::new(cx).await;
11580    let lsp_store =
11581        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11582
11583    cx.set_state(indoc! {"
11584        ˇfn func(abc def: i32) -> u32 {
11585        }
11586    "});
11587
11588    cx.update(|_, cx| {
11589        lsp_store.update(cx, |lsp_store, cx| {
11590            lsp_store
11591                .update_diagnostics(
11592                    LanguageServerId(0),
11593                    lsp::PublishDiagnosticsParams {
11594                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11595                        version: None,
11596                        diagnostics: vec![
11597                            lsp::Diagnostic {
11598                                range: lsp::Range::new(
11599                                    lsp::Position::new(0, 11),
11600                                    lsp::Position::new(0, 12),
11601                                ),
11602                                severity: Some(lsp::DiagnosticSeverity::ERROR),
11603                                ..Default::default()
11604                            },
11605                            lsp::Diagnostic {
11606                                range: lsp::Range::new(
11607                                    lsp::Position::new(0, 12),
11608                                    lsp::Position::new(0, 15),
11609                                ),
11610                                severity: Some(lsp::DiagnosticSeverity::ERROR),
11611                                ..Default::default()
11612                            },
11613                            lsp::Diagnostic {
11614                                range: lsp::Range::new(
11615                                    lsp::Position::new(0, 25),
11616                                    lsp::Position::new(0, 28),
11617                                ),
11618                                severity: Some(lsp::DiagnosticSeverity::ERROR),
11619                                ..Default::default()
11620                            },
11621                        ],
11622                    },
11623                    &[],
11624                    cx,
11625                )
11626                .unwrap()
11627        });
11628    });
11629
11630    executor.run_until_parked();
11631
11632    cx.update_editor(|editor, window, cx| {
11633        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11634    });
11635
11636    cx.assert_editor_state(indoc! {"
11637        fn func(abc def: i32) -> ˇu32 {
11638        }
11639    "});
11640
11641    cx.update_editor(|editor, window, cx| {
11642        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11643    });
11644
11645    cx.assert_editor_state(indoc! {"
11646        fn func(abc ˇdef: i32) -> u32 {
11647        }
11648    "});
11649
11650    cx.update_editor(|editor, window, cx| {
11651        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11652    });
11653
11654    cx.assert_editor_state(indoc! {"
11655        fn func(abcˇ def: i32) -> u32 {
11656        }
11657    "});
11658
11659    cx.update_editor(|editor, window, cx| {
11660        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11661    });
11662
11663    cx.assert_editor_state(indoc! {"
11664        fn func(abc def: i32) -> ˇu32 {
11665        }
11666    "});
11667}
11668
11669#[gpui::test]
11670async fn cycle_through_same_place_diagnostics(
11671    executor: BackgroundExecutor,
11672    cx: &mut TestAppContext,
11673) {
11674    init_test(cx, |_| {});
11675
11676    let mut cx = EditorTestContext::new(cx).await;
11677    let lsp_store =
11678        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11679
11680    cx.set_state(indoc! {"
11681        ˇfn func(abc def: i32) -> u32 {
11682        }
11683    "});
11684
11685    cx.update(|_, cx| {
11686        lsp_store.update(cx, |lsp_store, cx| {
11687            lsp_store
11688                .update_diagnostics(
11689                    LanguageServerId(0),
11690                    lsp::PublishDiagnosticsParams {
11691                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11692                        version: None,
11693                        diagnostics: vec![
11694                            lsp::Diagnostic {
11695                                range: lsp::Range::new(
11696                                    lsp::Position::new(0, 11),
11697                                    lsp::Position::new(0, 12),
11698                                ),
11699                                severity: Some(lsp::DiagnosticSeverity::ERROR),
11700                                ..Default::default()
11701                            },
11702                            lsp::Diagnostic {
11703                                range: lsp::Range::new(
11704                                    lsp::Position::new(0, 12),
11705                                    lsp::Position::new(0, 15),
11706                                ),
11707                                severity: Some(lsp::DiagnosticSeverity::ERROR),
11708                                ..Default::default()
11709                            },
11710                            lsp::Diagnostic {
11711                                range: lsp::Range::new(
11712                                    lsp::Position::new(0, 12),
11713                                    lsp::Position::new(0, 15),
11714                                ),
11715                                severity: Some(lsp::DiagnosticSeverity::ERROR),
11716                                ..Default::default()
11717                            },
11718                            lsp::Diagnostic {
11719                                range: lsp::Range::new(
11720                                    lsp::Position::new(0, 25),
11721                                    lsp::Position::new(0, 28),
11722                                ),
11723                                severity: Some(lsp::DiagnosticSeverity::ERROR),
11724                                ..Default::default()
11725                            },
11726                        ],
11727                    },
11728                    &[],
11729                    cx,
11730                )
11731                .unwrap()
11732        });
11733    });
11734    executor.run_until_parked();
11735
11736    //// Backward
11737
11738    // Fourth diagnostic
11739    cx.update_editor(|editor, window, cx| {
11740        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11741    });
11742    cx.assert_editor_state(indoc! {"
11743        fn func(abc def: i32) -> ˇu32 {
11744        }
11745    "});
11746
11747    // Third diagnostic
11748    cx.update_editor(|editor, window, cx| {
11749        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11750    });
11751    cx.assert_editor_state(indoc! {"
11752        fn func(abc ˇdef: i32) -> u32 {
11753        }
11754    "});
11755
11756    // Second diagnostic, same place
11757    cx.update_editor(|editor, window, cx| {
11758        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11759    });
11760    cx.assert_editor_state(indoc! {"
11761        fn func(abc ˇdef: i32) -> u32 {
11762        }
11763    "});
11764
11765    // First diagnostic
11766    cx.update_editor(|editor, window, cx| {
11767        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11768    });
11769    cx.assert_editor_state(indoc! {"
11770        fn func(abcˇ def: i32) -> u32 {
11771        }
11772    "});
11773
11774    // Wrapped over, fourth diagnostic
11775    cx.update_editor(|editor, window, cx| {
11776        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic, window, cx);
11777    });
11778    cx.assert_editor_state(indoc! {"
11779        fn func(abc def: i32) -> ˇu32 {
11780        }
11781    "});
11782
11783    cx.update_editor(|editor, window, cx| {
11784        editor.move_to_beginning(&MoveToBeginning, window, cx);
11785    });
11786    cx.assert_editor_state(indoc! {"
11787        ˇfn func(abc def: i32) -> u32 {
11788        }
11789    "});
11790
11791    //// Forward
11792
11793    // First diagnostic
11794    cx.update_editor(|editor, window, cx| {
11795        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11796    });
11797    cx.assert_editor_state(indoc! {"
11798        fn func(abcˇ def: i32) -> u32 {
11799        }
11800    "});
11801
11802    // Second diagnostic
11803    cx.update_editor(|editor, window, cx| {
11804        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11805    });
11806    cx.assert_editor_state(indoc! {"
11807        fn func(abc ˇdef: i32) -> u32 {
11808        }
11809    "});
11810
11811    // Third diagnostic, same place
11812    cx.update_editor(|editor, window, cx| {
11813        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11814    });
11815    cx.assert_editor_state(indoc! {"
11816        fn func(abc ˇdef: i32) -> u32 {
11817        }
11818    "});
11819
11820    // Fourth diagnostic
11821    cx.update_editor(|editor, window, cx| {
11822        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11823    });
11824    cx.assert_editor_state(indoc! {"
11825        fn func(abc def: i32) -> ˇu32 {
11826        }
11827    "});
11828
11829    // Wrapped around, first diagnostic
11830    cx.update_editor(|editor, window, cx| {
11831        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11832    });
11833    cx.assert_editor_state(indoc! {"
11834        fn func(abcˇ def: i32) -> u32 {
11835        }
11836    "});
11837}
11838
11839#[gpui::test]
11840async fn active_diagnostics_dismiss_after_invalidation(
11841    executor: BackgroundExecutor,
11842    cx: &mut TestAppContext,
11843) {
11844    init_test(cx, |_| {});
11845
11846    let mut cx = EditorTestContext::new(cx).await;
11847    let lsp_store =
11848        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11849
11850    cx.set_state(indoc! {"
11851        ˇfn func(abc def: i32) -> u32 {
11852        }
11853    "});
11854
11855    let message = "Something's wrong!";
11856    cx.update(|_, cx| {
11857        lsp_store.update(cx, |lsp_store, cx| {
11858            lsp_store
11859                .update_diagnostics(
11860                    LanguageServerId(0),
11861                    lsp::PublishDiagnosticsParams {
11862                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11863                        version: None,
11864                        diagnostics: vec![lsp::Diagnostic {
11865                            range: lsp::Range::new(
11866                                lsp::Position::new(0, 11),
11867                                lsp::Position::new(0, 12),
11868                            ),
11869                            severity: Some(lsp::DiagnosticSeverity::ERROR),
11870                            message: message.to_string(),
11871                            ..Default::default()
11872                        }],
11873                    },
11874                    &[],
11875                    cx,
11876                )
11877                .unwrap()
11878        });
11879    });
11880    executor.run_until_parked();
11881
11882    cx.update_editor(|editor, window, cx| {
11883        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11884        assert_eq!(
11885            editor
11886                .active_diagnostics
11887                .as_ref()
11888                .map(|diagnostics_group| diagnostics_group.primary_message.as_str()),
11889            Some(message),
11890            "Should have a diagnostics group activated"
11891        );
11892    });
11893    cx.assert_editor_state(indoc! {"
11894        fn func(abcˇ def: i32) -> u32 {
11895        }
11896    "});
11897
11898    cx.update(|_, cx| {
11899        lsp_store.update(cx, |lsp_store, cx| {
11900            lsp_store
11901                .update_diagnostics(
11902                    LanguageServerId(0),
11903                    lsp::PublishDiagnosticsParams {
11904                        uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11905                        version: None,
11906                        diagnostics: Vec::new(),
11907                    },
11908                    &[],
11909                    cx,
11910                )
11911                .unwrap()
11912        });
11913    });
11914    executor.run_until_parked();
11915    cx.update_editor(|editor, _, _| {
11916        assert_eq!(
11917            editor.active_diagnostics, None,
11918            "After no diagnostics set to the editor, no diagnostics should be active"
11919        );
11920    });
11921    cx.assert_editor_state(indoc! {"
11922        fn func(abcˇ def: i32) -> u32 {
11923        }
11924    "});
11925
11926    cx.update_editor(|editor, window, cx| {
11927        editor.go_to_diagnostic(&GoToDiagnostic, window, cx);
11928        assert_eq!(
11929            editor.active_diagnostics, None,
11930            "Should be no diagnostics to go to and activate"
11931        );
11932    });
11933    cx.assert_editor_state(indoc! {"
11934        fn func(abcˇ def: i32) -> u32 {
11935        }
11936    "});
11937}
11938
11939#[gpui::test]
11940async fn test_diagnostics_with_links(cx: &mut TestAppContext) {
11941    init_test(cx, |_| {});
11942
11943    let mut cx = EditorTestContext::new(cx).await;
11944
11945    cx.set_state(indoc! {"
11946        fn func(abˇc def: i32) -> u32 {
11947        }
11948    "});
11949    let lsp_store =
11950        cx.update_editor(|editor, _, cx| editor.project.as_ref().unwrap().read(cx).lsp_store());
11951
11952    cx.update(|_, cx| {
11953        lsp_store.update(cx, |lsp_store, cx| {
11954            lsp_store.update_diagnostics(
11955                LanguageServerId(0),
11956                lsp::PublishDiagnosticsParams {
11957                    uri: lsp::Url::from_file_path(path!("/root/file")).unwrap(),
11958                    version: None,
11959                    diagnostics: vec![lsp::Diagnostic {
11960                        range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 12)),
11961                        severity: Some(lsp::DiagnosticSeverity::ERROR),
11962                        message: "we've had problems with <https://link.one>, and <https://link.two> is broken".to_string(),
11963                        ..Default::default()
11964                    }],
11965                },
11966                &[],
11967                cx,
11968            )
11969        })
11970    }).unwrap();
11971    cx.run_until_parked();
11972    cx.update_editor(|editor, window, cx| {
11973        hover_popover::hover(editor, &Default::default(), window, cx)
11974    });
11975    cx.run_until_parked();
11976    cx.update_editor(|editor, _, _| assert!(editor.hover_state.diagnostic_popover.is_some()))
11977}
11978
11979#[gpui::test]
11980async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
11981    init_test(cx, |_| {});
11982
11983    let mut cx = EditorTestContext::new(cx).await;
11984
11985    let diff_base = r#"
11986        use some::mod;
11987
11988        const A: u32 = 42;
11989
11990        fn main() {
11991            println!("hello");
11992
11993            println!("world");
11994        }
11995        "#
11996    .unindent();
11997
11998    // Edits are modified, removed, modified, added
11999    cx.set_state(
12000        &r#"
12001        use some::modified;
12002
12003        ˇ
12004        fn main() {
12005            println!("hello there");
12006
12007            println!("around the");
12008            println!("world");
12009        }
12010        "#
12011        .unindent(),
12012    );
12013
12014    cx.set_head_text(&diff_base);
12015    executor.run_until_parked();
12016
12017    cx.update_editor(|editor, window, cx| {
12018        //Wrap around the bottom of the buffer
12019        for _ in 0..3 {
12020            editor.go_to_next_hunk(&GoToHunk, window, cx);
12021        }
12022    });
12023
12024    cx.assert_editor_state(
12025        &r#"
12026        ˇuse some::modified;
12027
12028
12029        fn main() {
12030            println!("hello there");
12031
12032            println!("around the");
12033            println!("world");
12034        }
12035        "#
12036        .unindent(),
12037    );
12038
12039    cx.update_editor(|editor, window, cx| {
12040        //Wrap around the top of the buffer
12041        for _ in 0..2 {
12042            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12043        }
12044    });
12045
12046    cx.assert_editor_state(
12047        &r#"
12048        use some::modified;
12049
12050
12051        fn main() {
12052        ˇ    println!("hello there");
12053
12054            println!("around the");
12055            println!("world");
12056        }
12057        "#
12058        .unindent(),
12059    );
12060
12061    cx.update_editor(|editor, window, cx| {
12062        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12063    });
12064
12065    cx.assert_editor_state(
12066        &r#"
12067        use some::modified;
12068
12069        ˇ
12070        fn main() {
12071            println!("hello there");
12072
12073            println!("around the");
12074            println!("world");
12075        }
12076        "#
12077        .unindent(),
12078    );
12079
12080    cx.update_editor(|editor, window, cx| {
12081        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12082    });
12083
12084    cx.assert_editor_state(
12085        &r#"
12086        ˇuse some::modified;
12087
12088
12089        fn main() {
12090            println!("hello there");
12091
12092            println!("around the");
12093            println!("world");
12094        }
12095        "#
12096        .unindent(),
12097    );
12098
12099    cx.update_editor(|editor, window, cx| {
12100        for _ in 0..2 {
12101            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
12102        }
12103    });
12104
12105    cx.assert_editor_state(
12106        &r#"
12107        use some::modified;
12108
12109
12110        fn main() {
12111        ˇ    println!("hello there");
12112
12113            println!("around the");
12114            println!("world");
12115        }
12116        "#
12117        .unindent(),
12118    );
12119
12120    cx.update_editor(|editor, window, cx| {
12121        editor.fold(&Fold, window, cx);
12122    });
12123
12124    cx.update_editor(|editor, window, cx| {
12125        editor.go_to_next_hunk(&GoToHunk, window, cx);
12126    });
12127
12128    cx.assert_editor_state(
12129        &r#"
12130        ˇuse some::modified;
12131
12132
12133        fn main() {
12134            println!("hello there");
12135
12136            println!("around the");
12137            println!("world");
12138        }
12139        "#
12140        .unindent(),
12141    );
12142}
12143
12144#[test]
12145fn test_split_words() {
12146    fn split(text: &str) -> Vec<&str> {
12147        split_words(text).collect()
12148    }
12149
12150    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
12151    assert_eq!(split("hello_world"), &["hello_", "world"]);
12152    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
12153    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
12154    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
12155    assert_eq!(split("helloworld"), &["helloworld"]);
12156
12157    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
12158}
12159
12160#[gpui::test]
12161async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
12162    init_test(cx, |_| {});
12163
12164    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
12165    let mut assert = |before, after| {
12166        let _state_context = cx.set_state(before);
12167        cx.run_until_parked();
12168        cx.update_editor(|editor, window, cx| {
12169            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
12170        });
12171        cx.run_until_parked();
12172        cx.assert_editor_state(after);
12173    };
12174
12175    // Outside bracket jumps to outside of matching bracket
12176    assert("console.logˇ(var);", "console.log(var)ˇ;");
12177    assert("console.log(var)ˇ;", "console.logˇ(var);");
12178
12179    // Inside bracket jumps to inside of matching bracket
12180    assert("console.log(ˇvar);", "console.log(varˇ);");
12181    assert("console.log(varˇ);", "console.log(ˇvar);");
12182
12183    // When outside a bracket and inside, favor jumping to the inside bracket
12184    assert(
12185        "console.log('foo', [1, 2, 3]ˇ);",
12186        "console.log(ˇ'foo', [1, 2, 3]);",
12187    );
12188    assert(
12189        "console.log(ˇ'foo', [1, 2, 3]);",
12190        "console.log('foo', [1, 2, 3]ˇ);",
12191    );
12192
12193    // Bias forward if two options are equally likely
12194    assert(
12195        "let result = curried_fun()ˇ();",
12196        "let result = curried_fun()()ˇ;",
12197    );
12198
12199    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
12200    assert(
12201        indoc! {"
12202            function test() {
12203                console.log('test')ˇ
12204            }"},
12205        indoc! {"
12206            function test() {
12207                console.logˇ('test')
12208            }"},
12209    );
12210}
12211
12212#[gpui::test]
12213async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
12214    init_test(cx, |_| {});
12215
12216    let fs = FakeFs::new(cx.executor());
12217    fs.insert_tree(
12218        path!("/a"),
12219        json!({
12220            "main.rs": "fn main() { let a = 5; }",
12221            "other.rs": "// Test file",
12222        }),
12223    )
12224    .await;
12225    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12226
12227    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12228    language_registry.add(Arc::new(Language::new(
12229        LanguageConfig {
12230            name: "Rust".into(),
12231            matcher: LanguageMatcher {
12232                path_suffixes: vec!["rs".to_string()],
12233                ..Default::default()
12234            },
12235            brackets: BracketPairConfig {
12236                pairs: vec![BracketPair {
12237                    start: "{".to_string(),
12238                    end: "}".to_string(),
12239                    close: true,
12240                    surround: true,
12241                    newline: true,
12242                }],
12243                disabled_scopes_by_bracket_ix: Vec::new(),
12244            },
12245            ..Default::default()
12246        },
12247        Some(tree_sitter_rust::LANGUAGE.into()),
12248    )));
12249    let mut fake_servers = language_registry.register_fake_lsp(
12250        "Rust",
12251        FakeLspAdapter {
12252            capabilities: lsp::ServerCapabilities {
12253                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
12254                    first_trigger_character: "{".to_string(),
12255                    more_trigger_character: None,
12256                }),
12257                ..Default::default()
12258            },
12259            ..Default::default()
12260        },
12261    );
12262
12263    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12264
12265    let cx = &mut VisualTestContext::from_window(*workspace, cx);
12266
12267    let worktree_id = workspace
12268        .update(cx, |workspace, _, cx| {
12269            workspace.project().update(cx, |project, cx| {
12270                project.worktrees(cx).next().unwrap().read(cx).id()
12271            })
12272        })
12273        .unwrap();
12274
12275    let buffer = project
12276        .update(cx, |project, cx| {
12277            project.open_local_buffer(path!("/a/main.rs"), cx)
12278        })
12279        .await
12280        .unwrap();
12281    let editor_handle = workspace
12282        .update(cx, |workspace, window, cx| {
12283            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
12284        })
12285        .unwrap()
12286        .await
12287        .unwrap()
12288        .downcast::<Editor>()
12289        .unwrap();
12290
12291    cx.executor().start_waiting();
12292    let fake_server = fake_servers.next().await.unwrap();
12293
12294    fake_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
12295        assert_eq!(
12296            params.text_document_position.text_document.uri,
12297            lsp::Url::from_file_path(path!("/a/main.rs")).unwrap(),
12298        );
12299        assert_eq!(
12300            params.text_document_position.position,
12301            lsp::Position::new(0, 21),
12302        );
12303
12304        Ok(Some(vec![lsp::TextEdit {
12305            new_text: "]".to_string(),
12306            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12307        }]))
12308    });
12309
12310    editor_handle.update_in(cx, |editor, window, cx| {
12311        window.focus(&editor.focus_handle(cx));
12312        editor.change_selections(None, window, cx, |s| {
12313            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
12314        });
12315        editor.handle_input("{", window, cx);
12316    });
12317
12318    cx.executor().run_until_parked();
12319
12320    buffer.update(cx, |buffer, _| {
12321        assert_eq!(
12322            buffer.text(),
12323            "fn main() { let a = {5}; }",
12324            "No extra braces from on type formatting should appear in the buffer"
12325        )
12326    });
12327}
12328
12329#[gpui::test]
12330async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
12331    init_test(cx, |_| {});
12332
12333    let fs = FakeFs::new(cx.executor());
12334    fs.insert_tree(
12335        path!("/a"),
12336        json!({
12337            "main.rs": "fn main() { let a = 5; }",
12338            "other.rs": "// Test file",
12339        }),
12340    )
12341    .await;
12342
12343    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12344
12345    let server_restarts = Arc::new(AtomicUsize::new(0));
12346    let closure_restarts = Arc::clone(&server_restarts);
12347    let language_server_name = "test language server";
12348    let language_name: LanguageName = "Rust".into();
12349
12350    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12351    language_registry.add(Arc::new(Language::new(
12352        LanguageConfig {
12353            name: language_name.clone(),
12354            matcher: LanguageMatcher {
12355                path_suffixes: vec!["rs".to_string()],
12356                ..Default::default()
12357            },
12358            ..Default::default()
12359        },
12360        Some(tree_sitter_rust::LANGUAGE.into()),
12361    )));
12362    let mut fake_servers = language_registry.register_fake_lsp(
12363        "Rust",
12364        FakeLspAdapter {
12365            name: language_server_name,
12366            initialization_options: Some(json!({
12367                "testOptionValue": true
12368            })),
12369            initializer: Some(Box::new(move |fake_server| {
12370                let task_restarts = Arc::clone(&closure_restarts);
12371                fake_server.handle_request::<lsp::request::Shutdown, _, _>(move |_, _| {
12372                    task_restarts.fetch_add(1, atomic::Ordering::Release);
12373                    futures::future::ready(Ok(()))
12374                });
12375            })),
12376            ..Default::default()
12377        },
12378    );
12379
12380    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12381    let _buffer = project
12382        .update(cx, |project, cx| {
12383            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
12384        })
12385        .await
12386        .unwrap();
12387    let _fake_server = fake_servers.next().await.unwrap();
12388    update_test_language_settings(cx, |language_settings| {
12389        language_settings.languages.insert(
12390            language_name.clone(),
12391            LanguageSettingsContent {
12392                tab_size: NonZeroU32::new(8),
12393                ..Default::default()
12394            },
12395        );
12396    });
12397    cx.executor().run_until_parked();
12398    assert_eq!(
12399        server_restarts.load(atomic::Ordering::Acquire),
12400        0,
12401        "Should not restart LSP server on an unrelated change"
12402    );
12403
12404    update_test_project_settings(cx, |project_settings| {
12405        project_settings.lsp.insert(
12406            "Some other server name".into(),
12407            LspSettings {
12408                binary: None,
12409                settings: None,
12410                initialization_options: Some(json!({
12411                    "some other init value": false
12412                })),
12413            },
12414        );
12415    });
12416    cx.executor().run_until_parked();
12417    assert_eq!(
12418        server_restarts.load(atomic::Ordering::Acquire),
12419        0,
12420        "Should not restart LSP server on an unrelated LSP settings change"
12421    );
12422
12423    update_test_project_settings(cx, |project_settings| {
12424        project_settings.lsp.insert(
12425            language_server_name.into(),
12426            LspSettings {
12427                binary: None,
12428                settings: None,
12429                initialization_options: Some(json!({
12430                    "anotherInitValue": false
12431                })),
12432            },
12433        );
12434    });
12435    cx.executor().run_until_parked();
12436    assert_eq!(
12437        server_restarts.load(atomic::Ordering::Acquire),
12438        1,
12439        "Should restart LSP server on a related LSP settings change"
12440    );
12441
12442    update_test_project_settings(cx, |project_settings| {
12443        project_settings.lsp.insert(
12444            language_server_name.into(),
12445            LspSettings {
12446                binary: None,
12447                settings: None,
12448                initialization_options: Some(json!({
12449                    "anotherInitValue": false
12450                })),
12451            },
12452        );
12453    });
12454    cx.executor().run_until_parked();
12455    assert_eq!(
12456        server_restarts.load(atomic::Ordering::Acquire),
12457        1,
12458        "Should not restart LSP server on a related LSP settings change that is the same"
12459    );
12460
12461    update_test_project_settings(cx, |project_settings| {
12462        project_settings.lsp.insert(
12463            language_server_name.into(),
12464            LspSettings {
12465                binary: None,
12466                settings: None,
12467                initialization_options: None,
12468            },
12469        );
12470    });
12471    cx.executor().run_until_parked();
12472    assert_eq!(
12473        server_restarts.load(atomic::Ordering::Acquire),
12474        2,
12475        "Should restart LSP server on another related LSP settings change"
12476    );
12477}
12478
12479#[gpui::test]
12480async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
12481    init_test(cx, |_| {});
12482
12483    let mut cx = EditorLspTestContext::new_rust(
12484        lsp::ServerCapabilities {
12485            completion_provider: Some(lsp::CompletionOptions {
12486                trigger_characters: Some(vec![".".to_string()]),
12487                resolve_provider: Some(true),
12488                ..Default::default()
12489            }),
12490            ..Default::default()
12491        },
12492        cx,
12493    )
12494    .await;
12495
12496    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12497    cx.simulate_keystroke(".");
12498    let completion_item = lsp::CompletionItem {
12499        label: "some".into(),
12500        kind: Some(lsp::CompletionItemKind::SNIPPET),
12501        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
12502        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
12503            kind: lsp::MarkupKind::Markdown,
12504            value: "```rust\nSome(2)\n```".to_string(),
12505        })),
12506        deprecated: Some(false),
12507        sort_text: Some("fffffff2".to_string()),
12508        filter_text: Some("some".to_string()),
12509        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
12510        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12511            range: lsp::Range {
12512                start: lsp::Position {
12513                    line: 0,
12514                    character: 22,
12515                },
12516                end: lsp::Position {
12517                    line: 0,
12518                    character: 22,
12519                },
12520            },
12521            new_text: "Some(2)".to_string(),
12522        })),
12523        additional_text_edits: Some(vec![lsp::TextEdit {
12524            range: lsp::Range {
12525                start: lsp::Position {
12526                    line: 0,
12527                    character: 20,
12528                },
12529                end: lsp::Position {
12530                    line: 0,
12531                    character: 22,
12532                },
12533            },
12534            new_text: "".to_string(),
12535        }]),
12536        ..Default::default()
12537    };
12538
12539    let closure_completion_item = completion_item.clone();
12540    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12541        let task_completion_item = closure_completion_item.clone();
12542        async move {
12543            Ok(Some(lsp::CompletionResponse::Array(vec![
12544                task_completion_item,
12545            ])))
12546        }
12547    });
12548
12549    request.next().await;
12550
12551    cx.condition(|editor, _| editor.context_menu_visible())
12552        .await;
12553    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
12554        editor
12555            .confirm_completion(&ConfirmCompletion::default(), window, cx)
12556            .unwrap()
12557    });
12558    cx.assert_editor_state(indoc! {"fn main() { let a = 2.Some(2)ˇ; }"});
12559
12560    cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
12561        let task_completion_item = completion_item.clone();
12562        async move { Ok(task_completion_item) }
12563    })
12564    .next()
12565    .await
12566    .unwrap();
12567    apply_additional_edits.await.unwrap();
12568    cx.assert_editor_state(indoc! {"fn main() { let a = Some(2)ˇ; }"});
12569}
12570
12571#[gpui::test]
12572async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
12573    init_test(cx, |_| {});
12574
12575    let mut cx = EditorLspTestContext::new_rust(
12576        lsp::ServerCapabilities {
12577            completion_provider: Some(lsp::CompletionOptions {
12578                trigger_characters: Some(vec![".".to_string()]),
12579                resolve_provider: Some(true),
12580                ..Default::default()
12581            }),
12582            ..Default::default()
12583        },
12584        cx,
12585    )
12586    .await;
12587
12588    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12589    cx.simulate_keystroke(".");
12590
12591    let item1 = lsp::CompletionItem {
12592        label: "method id()".to_string(),
12593        filter_text: Some("id".to_string()),
12594        detail: None,
12595        documentation: None,
12596        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12597            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12598            new_text: ".id".to_string(),
12599        })),
12600        ..lsp::CompletionItem::default()
12601    };
12602
12603    let item2 = lsp::CompletionItem {
12604        label: "other".to_string(),
12605        filter_text: Some("other".to_string()),
12606        detail: None,
12607        documentation: None,
12608        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12609            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12610            new_text: ".other".to_string(),
12611        })),
12612        ..lsp::CompletionItem::default()
12613    };
12614
12615    let item1 = item1.clone();
12616    cx.handle_request::<lsp::request::Completion, _, _>({
12617        let item1 = item1.clone();
12618        move |_, _, _| {
12619            let item1 = item1.clone();
12620            let item2 = item2.clone();
12621            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
12622        }
12623    })
12624    .next()
12625    .await;
12626
12627    cx.condition(|editor, _| editor.context_menu_visible())
12628        .await;
12629    cx.update_editor(|editor, _, _| {
12630        let context_menu = editor.context_menu.borrow_mut();
12631        let context_menu = context_menu
12632            .as_ref()
12633            .expect("Should have the context menu deployed");
12634        match context_menu {
12635            CodeContextMenu::Completions(completions_menu) => {
12636                let completions = completions_menu.completions.borrow_mut();
12637                assert_eq!(
12638                    completions
12639                        .iter()
12640                        .map(|completion| &completion.label.text)
12641                        .collect::<Vec<_>>(),
12642                    vec!["method id()", "other"]
12643                )
12644            }
12645            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12646        }
12647    });
12648
12649    cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>({
12650        let item1 = item1.clone();
12651        move |_, item_to_resolve, _| {
12652            let item1 = item1.clone();
12653            async move {
12654                if item1 == item_to_resolve {
12655                    Ok(lsp::CompletionItem {
12656                        label: "method id()".to_string(),
12657                        filter_text: Some("id".to_string()),
12658                        detail: Some("Now resolved!".to_string()),
12659                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
12660                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12661                            range: lsp::Range::new(
12662                                lsp::Position::new(0, 22),
12663                                lsp::Position::new(0, 22),
12664                            ),
12665                            new_text: ".id".to_string(),
12666                        })),
12667                        ..lsp::CompletionItem::default()
12668                    })
12669                } else {
12670                    Ok(item_to_resolve)
12671                }
12672            }
12673        }
12674    })
12675    .next()
12676    .await
12677    .unwrap();
12678    cx.run_until_parked();
12679
12680    cx.update_editor(|editor, window, cx| {
12681        editor.context_menu_next(&Default::default(), window, cx);
12682    });
12683
12684    cx.update_editor(|editor, _, _| {
12685        let context_menu = editor.context_menu.borrow_mut();
12686        let context_menu = context_menu
12687            .as_ref()
12688            .expect("Should have the context menu deployed");
12689        match context_menu {
12690            CodeContextMenu::Completions(completions_menu) => {
12691                let completions = completions_menu.completions.borrow_mut();
12692                assert_eq!(
12693                    completions
12694                        .iter()
12695                        .map(|completion| &completion.label.text)
12696                        .collect::<Vec<_>>(),
12697                    vec!["method id() Now resolved!", "other"],
12698                    "Should update first completion label, but not second as the filter text did not match."
12699                );
12700            }
12701            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12702        }
12703    });
12704}
12705
12706#[gpui::test]
12707async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
12708    init_test(cx, |_| {});
12709
12710    let mut cx = EditorLspTestContext::new_rust(
12711        lsp::ServerCapabilities {
12712            completion_provider: Some(lsp::CompletionOptions {
12713                trigger_characters: Some(vec![".".to_string()]),
12714                resolve_provider: Some(true),
12715                ..Default::default()
12716            }),
12717            ..Default::default()
12718        },
12719        cx,
12720    )
12721    .await;
12722
12723    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12724    cx.simulate_keystroke(".");
12725
12726    let unresolved_item_1 = lsp::CompletionItem {
12727        label: "id".to_string(),
12728        filter_text: Some("id".to_string()),
12729        detail: None,
12730        documentation: None,
12731        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12732            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12733            new_text: ".id".to_string(),
12734        })),
12735        ..lsp::CompletionItem::default()
12736    };
12737    let resolved_item_1 = lsp::CompletionItem {
12738        additional_text_edits: Some(vec![lsp::TextEdit {
12739            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12740            new_text: "!!".to_string(),
12741        }]),
12742        ..unresolved_item_1.clone()
12743    };
12744    let unresolved_item_2 = lsp::CompletionItem {
12745        label: "other".to_string(),
12746        filter_text: Some("other".to_string()),
12747        detail: None,
12748        documentation: None,
12749        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
12750            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
12751            new_text: ".other".to_string(),
12752        })),
12753        ..lsp::CompletionItem::default()
12754    };
12755    let resolved_item_2 = lsp::CompletionItem {
12756        additional_text_edits: Some(vec![lsp::TextEdit {
12757            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
12758            new_text: "??".to_string(),
12759        }]),
12760        ..unresolved_item_2.clone()
12761    };
12762
12763    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
12764    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
12765    cx.lsp
12766        .server
12767        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12768            let unresolved_item_1 = unresolved_item_1.clone();
12769            let resolved_item_1 = resolved_item_1.clone();
12770            let unresolved_item_2 = unresolved_item_2.clone();
12771            let resolved_item_2 = resolved_item_2.clone();
12772            let resolve_requests_1 = resolve_requests_1.clone();
12773            let resolve_requests_2 = resolve_requests_2.clone();
12774            move |unresolved_request, _| {
12775                let unresolved_item_1 = unresolved_item_1.clone();
12776                let resolved_item_1 = resolved_item_1.clone();
12777                let unresolved_item_2 = unresolved_item_2.clone();
12778                let resolved_item_2 = resolved_item_2.clone();
12779                let resolve_requests_1 = resolve_requests_1.clone();
12780                let resolve_requests_2 = resolve_requests_2.clone();
12781                async move {
12782                    if unresolved_request == unresolved_item_1 {
12783                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
12784                        Ok(resolved_item_1.clone())
12785                    } else if unresolved_request == unresolved_item_2 {
12786                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
12787                        Ok(resolved_item_2.clone())
12788                    } else {
12789                        panic!("Unexpected completion item {unresolved_request:?}")
12790                    }
12791                }
12792            }
12793        })
12794        .detach();
12795
12796    cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12797        let unresolved_item_1 = unresolved_item_1.clone();
12798        let unresolved_item_2 = unresolved_item_2.clone();
12799        async move {
12800            Ok(Some(lsp::CompletionResponse::Array(vec![
12801                unresolved_item_1,
12802                unresolved_item_2,
12803            ])))
12804        }
12805    })
12806    .next()
12807    .await;
12808
12809    cx.condition(|editor, _| editor.context_menu_visible())
12810        .await;
12811    cx.update_editor(|editor, _, _| {
12812        let context_menu = editor.context_menu.borrow_mut();
12813        let context_menu = context_menu
12814            .as_ref()
12815            .expect("Should have the context menu deployed");
12816        match context_menu {
12817            CodeContextMenu::Completions(completions_menu) => {
12818                let completions = completions_menu.completions.borrow_mut();
12819                assert_eq!(
12820                    completions
12821                        .iter()
12822                        .map(|completion| &completion.label.text)
12823                        .collect::<Vec<_>>(),
12824                    vec!["id", "other"]
12825                )
12826            }
12827            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
12828        }
12829    });
12830    cx.run_until_parked();
12831
12832    cx.update_editor(|editor, window, cx| {
12833        editor.context_menu_next(&ContextMenuNext, window, cx);
12834    });
12835    cx.run_until_parked();
12836    cx.update_editor(|editor, window, cx| {
12837        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
12838    });
12839    cx.run_until_parked();
12840    cx.update_editor(|editor, window, cx| {
12841        editor.context_menu_next(&ContextMenuNext, window, cx);
12842    });
12843    cx.run_until_parked();
12844    cx.update_editor(|editor, window, cx| {
12845        editor
12846            .compose_completion(&ComposeCompletion::default(), window, cx)
12847            .expect("No task returned")
12848    })
12849    .await
12850    .expect("Completion failed");
12851    cx.run_until_parked();
12852
12853    cx.update_editor(|editor, _, cx| {
12854        assert_eq!(
12855            resolve_requests_1.load(atomic::Ordering::Acquire),
12856            1,
12857            "Should always resolve once despite multiple selections"
12858        );
12859        assert_eq!(
12860            resolve_requests_2.load(atomic::Ordering::Acquire),
12861            1,
12862            "Should always resolve once after multiple selections and applying the completion"
12863        );
12864        assert_eq!(
12865            editor.text(cx),
12866            "fn main() { let a = ??.other; }",
12867            "Should use resolved data when applying the completion"
12868        );
12869    });
12870}
12871
12872#[gpui::test]
12873async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
12874    init_test(cx, |_| {});
12875
12876    let item_0 = lsp::CompletionItem {
12877        label: "abs".into(),
12878        insert_text: Some("abs".into()),
12879        data: Some(json!({ "very": "special"})),
12880        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
12881        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
12882            lsp::InsertReplaceEdit {
12883                new_text: "abs".to_string(),
12884                insert: lsp::Range::default(),
12885                replace: lsp::Range::default(),
12886            },
12887        )),
12888        ..lsp::CompletionItem::default()
12889    };
12890    let items = iter::once(item_0.clone())
12891        .chain((11..51).map(|i| lsp::CompletionItem {
12892            label: format!("item_{}", i),
12893            insert_text: Some(format!("item_{}", i)),
12894            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
12895            ..lsp::CompletionItem::default()
12896        }))
12897        .collect::<Vec<_>>();
12898
12899    let default_commit_characters = vec!["?".to_string()];
12900    let default_data = json!({ "default": "data"});
12901    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
12902    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
12903    let default_edit_range = lsp::Range {
12904        start: lsp::Position {
12905            line: 0,
12906            character: 5,
12907        },
12908        end: lsp::Position {
12909            line: 0,
12910            character: 5,
12911        },
12912    };
12913
12914    let mut cx = EditorLspTestContext::new_rust(
12915        lsp::ServerCapabilities {
12916            completion_provider: Some(lsp::CompletionOptions {
12917                trigger_characters: Some(vec![".".to_string()]),
12918                resolve_provider: Some(true),
12919                ..Default::default()
12920            }),
12921            ..Default::default()
12922        },
12923        cx,
12924    )
12925    .await;
12926
12927    cx.set_state(indoc! {"fn main() { let a = 2ˇ; }"});
12928    cx.simulate_keystroke(".");
12929
12930    let completion_data = default_data.clone();
12931    let completion_characters = default_commit_characters.clone();
12932    let completion_items = items.clone();
12933    cx.handle_request::<lsp::request::Completion, _, _>(move |_, _, _| {
12934        let default_data = completion_data.clone();
12935        let default_commit_characters = completion_characters.clone();
12936        let items = completion_items.clone();
12937        async move {
12938            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
12939                items,
12940                item_defaults: Some(lsp::CompletionListItemDefaults {
12941                    data: Some(default_data.clone()),
12942                    commit_characters: Some(default_commit_characters.clone()),
12943                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
12944                        default_edit_range,
12945                    )),
12946                    insert_text_format: Some(default_insert_text_format),
12947                    insert_text_mode: Some(default_insert_text_mode),
12948                }),
12949                ..lsp::CompletionList::default()
12950            })))
12951        }
12952    })
12953    .next()
12954    .await;
12955
12956    let resolved_items = Arc::new(Mutex::new(Vec::new()));
12957    cx.lsp
12958        .server
12959        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
12960            let closure_resolved_items = resolved_items.clone();
12961            move |item_to_resolve, _| {
12962                let closure_resolved_items = closure_resolved_items.clone();
12963                async move {
12964                    closure_resolved_items.lock().push(item_to_resolve.clone());
12965                    Ok(item_to_resolve)
12966                }
12967            }
12968        })
12969        .detach();
12970
12971    cx.condition(|editor, _| editor.context_menu_visible())
12972        .await;
12973    cx.run_until_parked();
12974    cx.update_editor(|editor, _, _| {
12975        let menu = editor.context_menu.borrow_mut();
12976        match menu.as_ref().expect("should have the completions menu") {
12977            CodeContextMenu::Completions(completions_menu) => {
12978                assert_eq!(
12979                    completions_menu
12980                        .entries
12981                        .borrow()
12982                        .iter()
12983                        .map(|mat| mat.string.clone())
12984                        .collect::<Vec<String>>(),
12985                    items
12986                        .iter()
12987                        .map(|completion| completion.label.clone())
12988                        .collect::<Vec<String>>()
12989                );
12990            }
12991            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
12992        }
12993    });
12994    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
12995    // with 4 from the end.
12996    assert_eq!(
12997        *resolved_items.lock(),
12998        [&items[0..16], &items[items.len() - 4..items.len()]]
12999            .concat()
13000            .iter()
13001            .cloned()
13002            .map(|mut item| {
13003                if item.data.is_none() {
13004                    item.data = Some(default_data.clone());
13005                }
13006                item
13007            })
13008            .collect::<Vec<lsp::CompletionItem>>(),
13009        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
13010    );
13011    resolved_items.lock().clear();
13012
13013    cx.update_editor(|editor, window, cx| {
13014        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
13015    });
13016    cx.run_until_parked();
13017    // Completions that have already been resolved are skipped.
13018    assert_eq!(
13019        *resolved_items.lock(),
13020        items[items.len() - 16..items.len() - 4]
13021            .iter()
13022            .cloned()
13023            .map(|mut item| {
13024                if item.data.is_none() {
13025                    item.data = Some(default_data.clone());
13026                }
13027                item
13028            })
13029            .collect::<Vec<lsp::CompletionItem>>()
13030    );
13031    resolved_items.lock().clear();
13032}
13033
13034#[gpui::test]
13035async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
13036    init_test(cx, |_| {});
13037
13038    let mut cx = EditorLspTestContext::new(
13039        Language::new(
13040            LanguageConfig {
13041                matcher: LanguageMatcher {
13042                    path_suffixes: vec!["jsx".into()],
13043                    ..Default::default()
13044                },
13045                overrides: [(
13046                    "element".into(),
13047                    LanguageConfigOverride {
13048                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
13049                        ..Default::default()
13050                    },
13051                )]
13052                .into_iter()
13053                .collect(),
13054                ..Default::default()
13055            },
13056            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
13057        )
13058        .with_override_query("(jsx_self_closing_element) @element")
13059        .unwrap(),
13060        lsp::ServerCapabilities {
13061            completion_provider: Some(lsp::CompletionOptions {
13062                trigger_characters: Some(vec![":".to_string()]),
13063                ..Default::default()
13064            }),
13065            ..Default::default()
13066        },
13067        cx,
13068    )
13069    .await;
13070
13071    cx.lsp
13072        .handle_request::<lsp::request::Completion, _, _>(move |_, _| async move {
13073            Ok(Some(lsp::CompletionResponse::Array(vec![
13074                lsp::CompletionItem {
13075                    label: "bg-blue".into(),
13076                    ..Default::default()
13077                },
13078                lsp::CompletionItem {
13079                    label: "bg-red".into(),
13080                    ..Default::default()
13081                },
13082                lsp::CompletionItem {
13083                    label: "bg-yellow".into(),
13084                    ..Default::default()
13085                },
13086            ])))
13087        });
13088
13089    cx.set_state(r#"<p class="bgˇ" />"#);
13090
13091    // Trigger completion when typing a dash, because the dash is an extra
13092    // word character in the 'element' scope, which contains the cursor.
13093    cx.simulate_keystroke("-");
13094    cx.executor().run_until_parked();
13095    cx.update_editor(|editor, _, _| {
13096        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13097        {
13098            assert_eq!(
13099                completion_menu_entries(&menu),
13100                &["bg-red", "bg-blue", "bg-yellow"]
13101            );
13102        } else {
13103            panic!("expected completion menu to be open");
13104        }
13105    });
13106
13107    cx.simulate_keystroke("l");
13108    cx.executor().run_until_parked();
13109    cx.update_editor(|editor, _, _| {
13110        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13111        {
13112            assert_eq!(completion_menu_entries(&menu), &["bg-blue", "bg-yellow"]);
13113        } else {
13114            panic!("expected completion menu to be open");
13115        }
13116    });
13117
13118    // When filtering completions, consider the character after the '-' to
13119    // be the start of a subword.
13120    cx.set_state(r#"<p class="yelˇ" />"#);
13121    cx.simulate_keystroke("l");
13122    cx.executor().run_until_parked();
13123    cx.update_editor(|editor, _, _| {
13124        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
13125        {
13126            assert_eq!(completion_menu_entries(&menu), &["bg-yellow"]);
13127        } else {
13128            panic!("expected completion menu to be open");
13129        }
13130    });
13131}
13132
13133fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
13134    let entries = menu.entries.borrow();
13135    entries.iter().map(|mat| mat.string.clone()).collect()
13136}
13137
13138#[gpui::test]
13139async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
13140    init_test(cx, |settings| {
13141        settings.defaults.formatter = Some(language_settings::SelectedFormatter::List(
13142            FormatterList(vec![Formatter::Prettier].into()),
13143        ))
13144    });
13145
13146    let fs = FakeFs::new(cx.executor());
13147    fs.insert_file(path!("/file.ts"), Default::default()).await;
13148
13149    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
13150    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13151
13152    language_registry.add(Arc::new(Language::new(
13153        LanguageConfig {
13154            name: "TypeScript".into(),
13155            matcher: LanguageMatcher {
13156                path_suffixes: vec!["ts".to_string()],
13157                ..Default::default()
13158            },
13159            ..Default::default()
13160        },
13161        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13162    )));
13163    update_test_language_settings(cx, |settings| {
13164        settings.defaults.prettier = Some(PrettierSettings {
13165            allowed: true,
13166            ..PrettierSettings::default()
13167        });
13168    });
13169
13170    let test_plugin = "test_plugin";
13171    let _ = language_registry.register_fake_lsp(
13172        "TypeScript",
13173        FakeLspAdapter {
13174            prettier_plugins: vec![test_plugin],
13175            ..Default::default()
13176        },
13177    );
13178
13179    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
13180    let buffer = project
13181        .update(cx, |project, cx| {
13182            project.open_local_buffer(path!("/file.ts"), cx)
13183        })
13184        .await
13185        .unwrap();
13186
13187    let buffer_text = "one\ntwo\nthree\n";
13188    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13189    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
13190    editor.update_in(cx, |editor, window, cx| {
13191        editor.set_text(buffer_text, window, cx)
13192    });
13193
13194    editor
13195        .update_in(cx, |editor, window, cx| {
13196            editor.perform_format(
13197                project.clone(),
13198                FormatTrigger::Manual,
13199                FormatTarget::Buffers,
13200                window,
13201                cx,
13202            )
13203        })
13204        .unwrap()
13205        .await;
13206    assert_eq!(
13207        editor.update(cx, |editor, cx| editor.text(cx)),
13208        buffer_text.to_string() + prettier_format_suffix,
13209        "Test prettier formatting was not applied to the original buffer text",
13210    );
13211
13212    update_test_language_settings(cx, |settings| {
13213        settings.defaults.formatter = Some(language_settings::SelectedFormatter::Auto)
13214    });
13215    let format = editor.update_in(cx, |editor, window, cx| {
13216        editor.perform_format(
13217            project.clone(),
13218            FormatTrigger::Manual,
13219            FormatTarget::Buffers,
13220            window,
13221            cx,
13222        )
13223    });
13224    format.await.unwrap();
13225    assert_eq!(
13226        editor.update(cx, |editor, cx| editor.text(cx)),
13227        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
13228        "Autoformatting (via test prettier) was not applied to the original buffer text",
13229    );
13230}
13231
13232#[gpui::test]
13233async fn test_addition_reverts(cx: &mut TestAppContext) {
13234    init_test(cx, |_| {});
13235    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13236    let base_text = indoc! {r#"
13237        struct Row;
13238        struct Row1;
13239        struct Row2;
13240
13241        struct Row4;
13242        struct Row5;
13243        struct Row6;
13244
13245        struct Row8;
13246        struct Row9;
13247        struct Row10;"#};
13248
13249    // When addition hunks are not adjacent to carets, no hunk revert is performed
13250    assert_hunk_revert(
13251        indoc! {r#"struct Row;
13252                   struct Row1;
13253                   struct Row1.1;
13254                   struct Row1.2;
13255                   struct Row2;ˇ
13256
13257                   struct Row4;
13258                   struct Row5;
13259                   struct Row6;
13260
13261                   struct Row8;
13262                   ˇstruct Row9;
13263                   struct Row9.1;
13264                   struct Row9.2;
13265                   struct Row9.3;
13266                   struct Row10;"#},
13267        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13268        indoc! {r#"struct Row;
13269                   struct Row1;
13270                   struct Row1.1;
13271                   struct Row1.2;
13272                   struct Row2;ˇ
13273
13274                   struct Row4;
13275                   struct Row5;
13276                   struct Row6;
13277
13278                   struct Row8;
13279                   ˇstruct Row9;
13280                   struct Row9.1;
13281                   struct Row9.2;
13282                   struct Row9.3;
13283                   struct Row10;"#},
13284        base_text,
13285        &mut cx,
13286    );
13287    // Same for selections
13288    assert_hunk_revert(
13289        indoc! {r#"struct Row;
13290                   struct Row1;
13291                   struct Row2;
13292                   struct Row2.1;
13293                   struct Row2.2;
13294                   «ˇ
13295                   struct Row4;
13296                   struct» Row5;
13297                   «struct Row6;
13298                   ˇ»
13299                   struct Row9.1;
13300                   struct Row9.2;
13301                   struct Row9.3;
13302                   struct Row8;
13303                   struct Row9;
13304                   struct Row10;"#},
13305        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
13306        indoc! {r#"struct Row;
13307                   struct Row1;
13308                   struct Row2;
13309                   struct Row2.1;
13310                   struct Row2.2;
13311                   «ˇ
13312                   struct Row4;
13313                   struct» Row5;
13314                   «struct Row6;
13315                   ˇ»
13316                   struct Row9.1;
13317                   struct Row9.2;
13318                   struct Row9.3;
13319                   struct Row8;
13320                   struct Row9;
13321                   struct Row10;"#},
13322        base_text,
13323        &mut cx,
13324    );
13325
13326    // When carets and selections intersect the addition hunks, those are reverted.
13327    // Adjacent carets got merged.
13328    assert_hunk_revert(
13329        indoc! {r#"struct Row;
13330                   ˇ// something on the top
13331                   struct Row1;
13332                   struct Row2;
13333                   struct Roˇw3.1;
13334                   struct Row2.2;
13335                   struct Row2.3;ˇ
13336
13337                   struct Row4;
13338                   struct ˇRow5.1;
13339                   struct Row5.2;
13340                   struct «Rowˇ»5.3;
13341                   struct Row5;
13342                   struct Row6;
13343                   ˇ
13344                   struct Row9.1;
13345                   struct «Rowˇ»9.2;
13346                   struct «ˇRow»9.3;
13347                   struct Row8;
13348                   struct Row9;
13349                   «ˇ// something on bottom»
13350                   struct Row10;"#},
13351        vec![
13352            DiffHunkStatusKind::Added,
13353            DiffHunkStatusKind::Added,
13354            DiffHunkStatusKind::Added,
13355            DiffHunkStatusKind::Added,
13356            DiffHunkStatusKind::Added,
13357        ],
13358        indoc! {r#"struct Row;
13359                   ˇstruct Row1;
13360                   struct Row2;
13361                   ˇ
13362                   struct Row4;
13363                   ˇstruct Row5;
13364                   struct Row6;
13365                   ˇ
13366                   ˇstruct Row8;
13367                   struct Row9;
13368                   ˇstruct Row10;"#},
13369        base_text,
13370        &mut cx,
13371    );
13372}
13373
13374#[gpui::test]
13375async fn test_modification_reverts(cx: &mut TestAppContext) {
13376    init_test(cx, |_| {});
13377    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13378    let base_text = indoc! {r#"
13379        struct Row;
13380        struct Row1;
13381        struct Row2;
13382
13383        struct Row4;
13384        struct Row5;
13385        struct Row6;
13386
13387        struct Row8;
13388        struct Row9;
13389        struct Row10;"#};
13390
13391    // Modification hunks behave the same as the addition ones.
13392    assert_hunk_revert(
13393        indoc! {r#"struct Row;
13394                   struct Row1;
13395                   struct Row33;
13396                   ˇ
13397                   struct Row4;
13398                   struct Row5;
13399                   struct Row6;
13400                   ˇ
13401                   struct Row99;
13402                   struct Row9;
13403                   struct Row10;"#},
13404        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13405        indoc! {r#"struct Row;
13406                   struct Row1;
13407                   struct Row33;
13408                   ˇ
13409                   struct Row4;
13410                   struct Row5;
13411                   struct Row6;
13412                   ˇ
13413                   struct Row99;
13414                   struct Row9;
13415                   struct Row10;"#},
13416        base_text,
13417        &mut cx,
13418    );
13419    assert_hunk_revert(
13420        indoc! {r#"struct Row;
13421                   struct Row1;
13422                   struct Row33;
13423                   «ˇ
13424                   struct Row4;
13425                   struct» Row5;
13426                   «struct Row6;
13427                   ˇ»
13428                   struct Row99;
13429                   struct Row9;
13430                   struct Row10;"#},
13431        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
13432        indoc! {r#"struct Row;
13433                   struct Row1;
13434                   struct Row33;
13435                   «ˇ
13436                   struct Row4;
13437                   struct» Row5;
13438                   «struct Row6;
13439                   ˇ»
13440                   struct Row99;
13441                   struct Row9;
13442                   struct Row10;"#},
13443        base_text,
13444        &mut cx,
13445    );
13446
13447    assert_hunk_revert(
13448        indoc! {r#"ˇstruct Row1.1;
13449                   struct Row1;
13450                   «ˇstr»uct Row22;
13451
13452                   struct ˇRow44;
13453                   struct Row5;
13454                   struct «Rˇ»ow66;ˇ
13455
13456                   «struˇ»ct Row88;
13457                   struct Row9;
13458                   struct Row1011;ˇ"#},
13459        vec![
13460            DiffHunkStatusKind::Modified,
13461            DiffHunkStatusKind::Modified,
13462            DiffHunkStatusKind::Modified,
13463            DiffHunkStatusKind::Modified,
13464            DiffHunkStatusKind::Modified,
13465            DiffHunkStatusKind::Modified,
13466        ],
13467        indoc! {r#"struct Row;
13468                   ˇstruct Row1;
13469                   struct Row2;
13470                   ˇ
13471                   struct Row4;
13472                   ˇstruct Row5;
13473                   struct Row6;
13474                   ˇ
13475                   struct Row8;
13476                   ˇstruct Row9;
13477                   struct Row10;ˇ"#},
13478        base_text,
13479        &mut cx,
13480    );
13481}
13482
13483#[gpui::test]
13484async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
13485    init_test(cx, |_| {});
13486    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13487    let base_text = indoc! {r#"
13488        one
13489
13490        two
13491        three
13492        "#};
13493
13494    cx.set_head_text(base_text);
13495    cx.set_state("\nˇ\n");
13496    cx.executor().run_until_parked();
13497    cx.update_editor(|editor, _window, cx| {
13498        editor.expand_selected_diff_hunks(cx);
13499    });
13500    cx.executor().run_until_parked();
13501    cx.update_editor(|editor, window, cx| {
13502        editor.backspace(&Default::default(), window, cx);
13503    });
13504    cx.run_until_parked();
13505    cx.assert_state_with_diff(
13506        indoc! {r#"
13507
13508        - two
13509        - threeˇ
13510        +
13511        "#}
13512        .to_string(),
13513    );
13514}
13515
13516#[gpui::test]
13517async fn test_deletion_reverts(cx: &mut TestAppContext) {
13518    init_test(cx, |_| {});
13519    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
13520    let base_text = indoc! {r#"struct Row;
13521struct Row1;
13522struct Row2;
13523
13524struct Row4;
13525struct Row5;
13526struct Row6;
13527
13528struct Row8;
13529struct Row9;
13530struct Row10;"#};
13531
13532    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
13533    assert_hunk_revert(
13534        indoc! {r#"struct Row;
13535                   struct Row2;
13536
13537                   ˇstruct Row4;
13538                   struct Row5;
13539                   struct Row6;
13540                   ˇ
13541                   struct Row8;
13542                   struct Row10;"#},
13543        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13544        indoc! {r#"struct Row;
13545                   struct Row2;
13546
13547                   ˇstruct Row4;
13548                   struct Row5;
13549                   struct Row6;
13550                   ˇ
13551                   struct Row8;
13552                   struct Row10;"#},
13553        base_text,
13554        &mut cx,
13555    );
13556    assert_hunk_revert(
13557        indoc! {r#"struct Row;
13558                   struct Row2;
13559
13560                   «ˇstruct Row4;
13561                   struct» Row5;
13562                   «struct Row6;
13563                   ˇ»
13564                   struct Row8;
13565                   struct Row10;"#},
13566        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13567        indoc! {r#"struct Row;
13568                   struct Row2;
13569
13570                   «ˇstruct Row4;
13571                   struct» Row5;
13572                   «struct Row6;
13573                   ˇ»
13574                   struct Row8;
13575                   struct Row10;"#},
13576        base_text,
13577        &mut cx,
13578    );
13579
13580    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
13581    assert_hunk_revert(
13582        indoc! {r#"struct Row;
13583                   ˇstruct Row2;
13584
13585                   struct Row4;
13586                   struct Row5;
13587                   struct Row6;
13588
13589                   struct Row8;ˇ
13590                   struct Row10;"#},
13591        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
13592        indoc! {r#"struct Row;
13593                   struct Row1;
13594                   ˇstruct Row2;
13595
13596                   struct Row4;
13597                   struct Row5;
13598                   struct Row6;
13599
13600                   struct Row8;ˇ
13601                   struct Row9;
13602                   struct Row10;"#},
13603        base_text,
13604        &mut cx,
13605    );
13606    assert_hunk_revert(
13607        indoc! {r#"struct Row;
13608                   struct Row2«ˇ;
13609                   struct Row4;
13610                   struct» Row5;
13611                   «struct Row6;
13612
13613                   struct Row8;ˇ»
13614                   struct Row10;"#},
13615        vec![
13616            DiffHunkStatusKind::Deleted,
13617            DiffHunkStatusKind::Deleted,
13618            DiffHunkStatusKind::Deleted,
13619        ],
13620        indoc! {r#"struct Row;
13621                   struct Row1;
13622                   struct Row2«ˇ;
13623
13624                   struct Row4;
13625                   struct» Row5;
13626                   «struct Row6;
13627
13628                   struct Row8;ˇ»
13629                   struct Row9;
13630                   struct Row10;"#},
13631        base_text,
13632        &mut cx,
13633    );
13634}
13635
13636#[gpui::test]
13637async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
13638    init_test(cx, |_| {});
13639
13640    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
13641    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
13642    let base_text_3 =
13643        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
13644
13645    let text_1 = edit_first_char_of_every_line(base_text_1);
13646    let text_2 = edit_first_char_of_every_line(base_text_2);
13647    let text_3 = edit_first_char_of_every_line(base_text_3);
13648
13649    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
13650    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
13651    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
13652
13653    let multibuffer = cx.new(|cx| {
13654        let mut multibuffer = MultiBuffer::new(ReadWrite);
13655        multibuffer.push_excerpts(
13656            buffer_1.clone(),
13657            [
13658                ExcerptRange {
13659                    context: Point::new(0, 0)..Point::new(3, 0),
13660                    primary: None,
13661                },
13662                ExcerptRange {
13663                    context: Point::new(5, 0)..Point::new(7, 0),
13664                    primary: None,
13665                },
13666                ExcerptRange {
13667                    context: Point::new(9, 0)..Point::new(10, 4),
13668                    primary: None,
13669                },
13670            ],
13671            cx,
13672        );
13673        multibuffer.push_excerpts(
13674            buffer_2.clone(),
13675            [
13676                ExcerptRange {
13677                    context: Point::new(0, 0)..Point::new(3, 0),
13678                    primary: None,
13679                },
13680                ExcerptRange {
13681                    context: Point::new(5, 0)..Point::new(7, 0),
13682                    primary: None,
13683                },
13684                ExcerptRange {
13685                    context: Point::new(9, 0)..Point::new(10, 4),
13686                    primary: None,
13687                },
13688            ],
13689            cx,
13690        );
13691        multibuffer.push_excerpts(
13692            buffer_3.clone(),
13693            [
13694                ExcerptRange {
13695                    context: Point::new(0, 0)..Point::new(3, 0),
13696                    primary: None,
13697                },
13698                ExcerptRange {
13699                    context: Point::new(5, 0)..Point::new(7, 0),
13700                    primary: None,
13701                },
13702                ExcerptRange {
13703                    context: Point::new(9, 0)..Point::new(10, 4),
13704                    primary: None,
13705                },
13706            ],
13707            cx,
13708        );
13709        multibuffer
13710    });
13711
13712    let fs = FakeFs::new(cx.executor());
13713    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
13714    let (editor, cx) = cx
13715        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
13716    editor.update_in(cx, |editor, _window, cx| {
13717        for (buffer, diff_base) in [
13718            (buffer_1.clone(), base_text_1),
13719            (buffer_2.clone(), base_text_2),
13720            (buffer_3.clone(), base_text_3),
13721        ] {
13722            let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
13723            editor
13724                .buffer
13725                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
13726        }
13727    });
13728    cx.executor().run_until_parked();
13729
13730    editor.update_in(cx, |editor, window, cx| {
13731        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}");
13732        editor.select_all(&SelectAll, window, cx);
13733        editor.git_restore(&Default::default(), window, cx);
13734    });
13735    cx.executor().run_until_parked();
13736
13737    // When all ranges are selected, all buffer hunks are reverted.
13738    editor.update(cx, |editor, cx| {
13739        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");
13740    });
13741    buffer_1.update(cx, |buffer, _| {
13742        assert_eq!(buffer.text(), base_text_1);
13743    });
13744    buffer_2.update(cx, |buffer, _| {
13745        assert_eq!(buffer.text(), base_text_2);
13746    });
13747    buffer_3.update(cx, |buffer, _| {
13748        assert_eq!(buffer.text(), base_text_3);
13749    });
13750
13751    editor.update_in(cx, |editor, window, cx| {
13752        editor.undo(&Default::default(), window, cx);
13753    });
13754
13755    editor.update_in(cx, |editor, window, cx| {
13756        editor.change_selections(None, window, cx, |s| {
13757            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
13758        });
13759        editor.git_restore(&Default::default(), window, cx);
13760    });
13761
13762    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
13763    // but not affect buffer_2 and its related excerpts.
13764    editor.update(cx, |editor, cx| {
13765        assert_eq!(
13766            editor.text(cx),
13767            "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}"
13768        );
13769    });
13770    buffer_1.update(cx, |buffer, _| {
13771        assert_eq!(buffer.text(), base_text_1);
13772    });
13773    buffer_2.update(cx, |buffer, _| {
13774        assert_eq!(
13775            buffer.text(),
13776            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
13777        );
13778    });
13779    buffer_3.update(cx, |buffer, _| {
13780        assert_eq!(
13781            buffer.text(),
13782            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
13783        );
13784    });
13785
13786    fn edit_first_char_of_every_line(text: &str) -> String {
13787        text.split('\n')
13788            .map(|line| format!("X{}", &line[1..]))
13789            .collect::<Vec<_>>()
13790            .join("\n")
13791    }
13792}
13793
13794#[gpui::test]
13795async fn test_mutlibuffer_in_navigation_history(cx: &mut TestAppContext) {
13796    init_test(cx, |_| {});
13797
13798    let cols = 4;
13799    let rows = 10;
13800    let sample_text_1 = sample_text(rows, cols, 'a');
13801    assert_eq!(
13802        sample_text_1,
13803        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
13804    );
13805    let sample_text_2 = sample_text(rows, cols, 'l');
13806    assert_eq!(
13807        sample_text_2,
13808        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
13809    );
13810    let sample_text_3 = sample_text(rows, cols, 'v');
13811    assert_eq!(
13812        sample_text_3,
13813        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
13814    );
13815
13816    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
13817    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
13818    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
13819
13820    let multi_buffer = cx.new(|cx| {
13821        let mut multibuffer = MultiBuffer::new(ReadWrite);
13822        multibuffer.push_excerpts(
13823            buffer_1.clone(),
13824            [
13825                ExcerptRange {
13826                    context: Point::new(0, 0)..Point::new(3, 0),
13827                    primary: None,
13828                },
13829                ExcerptRange {
13830                    context: Point::new(5, 0)..Point::new(7, 0),
13831                    primary: None,
13832                },
13833                ExcerptRange {
13834                    context: Point::new(9, 0)..Point::new(10, 4),
13835                    primary: None,
13836                },
13837            ],
13838            cx,
13839        );
13840        multibuffer.push_excerpts(
13841            buffer_2.clone(),
13842            [
13843                ExcerptRange {
13844                    context: Point::new(0, 0)..Point::new(3, 0),
13845                    primary: None,
13846                },
13847                ExcerptRange {
13848                    context: Point::new(5, 0)..Point::new(7, 0),
13849                    primary: None,
13850                },
13851                ExcerptRange {
13852                    context: Point::new(9, 0)..Point::new(10, 4),
13853                    primary: None,
13854                },
13855            ],
13856            cx,
13857        );
13858        multibuffer.push_excerpts(
13859            buffer_3.clone(),
13860            [
13861                ExcerptRange {
13862                    context: Point::new(0, 0)..Point::new(3, 0),
13863                    primary: None,
13864                },
13865                ExcerptRange {
13866                    context: Point::new(5, 0)..Point::new(7, 0),
13867                    primary: None,
13868                },
13869                ExcerptRange {
13870                    context: Point::new(9, 0)..Point::new(10, 4),
13871                    primary: None,
13872                },
13873            ],
13874            cx,
13875        );
13876        multibuffer
13877    });
13878
13879    let fs = FakeFs::new(cx.executor());
13880    fs.insert_tree(
13881        "/a",
13882        json!({
13883            "main.rs": sample_text_1,
13884            "other.rs": sample_text_2,
13885            "lib.rs": sample_text_3,
13886        }),
13887    )
13888    .await;
13889    let project = Project::test(fs, ["/a".as_ref()], cx).await;
13890    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13891    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
13892    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
13893        Editor::new(
13894            EditorMode::Full,
13895            multi_buffer,
13896            Some(project.clone()),
13897            window,
13898            cx,
13899        )
13900    });
13901    let multibuffer_item_id = workspace
13902        .update(cx, |workspace, window, cx| {
13903            assert!(
13904                workspace.active_item(cx).is_none(),
13905                "active item should be None before the first item is added"
13906            );
13907            workspace.add_item_to_active_pane(
13908                Box::new(multi_buffer_editor.clone()),
13909                None,
13910                true,
13911                window,
13912                cx,
13913            );
13914            let active_item = workspace
13915                .active_item(cx)
13916                .expect("should have an active item after adding the multi buffer");
13917            assert!(
13918                !active_item.is_singleton(cx),
13919                "A multi buffer was expected to active after adding"
13920            );
13921            active_item.item_id()
13922        })
13923        .unwrap();
13924    cx.executor().run_until_parked();
13925
13926    multi_buffer_editor.update_in(cx, |editor, window, cx| {
13927        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13928            s.select_ranges(Some(1..2))
13929        });
13930        editor.open_excerpts(&OpenExcerpts, window, cx);
13931    });
13932    cx.executor().run_until_parked();
13933    let first_item_id = workspace
13934        .update(cx, |workspace, window, cx| {
13935            let active_item = workspace
13936                .active_item(cx)
13937                .expect("should have an active item after navigating into the 1st buffer");
13938            let first_item_id = active_item.item_id();
13939            assert_ne!(
13940                first_item_id, multibuffer_item_id,
13941                "Should navigate into the 1st buffer and activate it"
13942            );
13943            assert!(
13944                active_item.is_singleton(cx),
13945                "New active item should be a singleton buffer"
13946            );
13947            assert_eq!(
13948                active_item
13949                    .act_as::<Editor>(cx)
13950                    .expect("should have navigated into an editor for the 1st buffer")
13951                    .read(cx)
13952                    .text(cx),
13953                sample_text_1
13954            );
13955
13956            workspace
13957                .go_back(workspace.active_pane().downgrade(), window, cx)
13958                .detach_and_log_err(cx);
13959
13960            first_item_id
13961        })
13962        .unwrap();
13963    cx.executor().run_until_parked();
13964    workspace
13965        .update(cx, |workspace, _, cx| {
13966            let active_item = workspace
13967                .active_item(cx)
13968                .expect("should have an active item after navigating back");
13969            assert_eq!(
13970                active_item.item_id(),
13971                multibuffer_item_id,
13972                "Should navigate back to the multi buffer"
13973            );
13974            assert!(!active_item.is_singleton(cx));
13975        })
13976        .unwrap();
13977
13978    multi_buffer_editor.update_in(cx, |editor, window, cx| {
13979        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
13980            s.select_ranges(Some(39..40))
13981        });
13982        editor.open_excerpts(&OpenExcerpts, window, cx);
13983    });
13984    cx.executor().run_until_parked();
13985    let second_item_id = workspace
13986        .update(cx, |workspace, window, cx| {
13987            let active_item = workspace
13988                .active_item(cx)
13989                .expect("should have an active item after navigating into the 2nd buffer");
13990            let second_item_id = active_item.item_id();
13991            assert_ne!(
13992                second_item_id, multibuffer_item_id,
13993                "Should navigate away from the multibuffer"
13994            );
13995            assert_ne!(
13996                second_item_id, first_item_id,
13997                "Should navigate into the 2nd buffer and activate it"
13998            );
13999            assert!(
14000                active_item.is_singleton(cx),
14001                "New active item should be a singleton buffer"
14002            );
14003            assert_eq!(
14004                active_item
14005                    .act_as::<Editor>(cx)
14006                    .expect("should have navigated into an editor")
14007                    .read(cx)
14008                    .text(cx),
14009                sample_text_2
14010            );
14011
14012            workspace
14013                .go_back(workspace.active_pane().downgrade(), window, cx)
14014                .detach_and_log_err(cx);
14015
14016            second_item_id
14017        })
14018        .unwrap();
14019    cx.executor().run_until_parked();
14020    workspace
14021        .update(cx, |workspace, _, cx| {
14022            let active_item = workspace
14023                .active_item(cx)
14024                .expect("should have an active item after navigating back from the 2nd buffer");
14025            assert_eq!(
14026                active_item.item_id(),
14027                multibuffer_item_id,
14028                "Should navigate back from the 2nd buffer to the multi buffer"
14029            );
14030            assert!(!active_item.is_singleton(cx));
14031        })
14032        .unwrap();
14033
14034    multi_buffer_editor.update_in(cx, |editor, window, cx| {
14035        editor.change_selections(Some(Autoscroll::Next), window, cx, |s| {
14036            s.select_ranges(Some(70..70))
14037        });
14038        editor.open_excerpts(&OpenExcerpts, window, cx);
14039    });
14040    cx.executor().run_until_parked();
14041    workspace
14042        .update(cx, |workspace, window, cx| {
14043            let active_item = workspace
14044                .active_item(cx)
14045                .expect("should have an active item after navigating into the 3rd buffer");
14046            let third_item_id = active_item.item_id();
14047            assert_ne!(
14048                third_item_id, multibuffer_item_id,
14049                "Should navigate into the 3rd buffer and activate it"
14050            );
14051            assert_ne!(third_item_id, first_item_id);
14052            assert_ne!(third_item_id, second_item_id);
14053            assert!(
14054                active_item.is_singleton(cx),
14055                "New active item should be a singleton buffer"
14056            );
14057            assert_eq!(
14058                active_item
14059                    .act_as::<Editor>(cx)
14060                    .expect("should have navigated into an editor")
14061                    .read(cx)
14062                    .text(cx),
14063                sample_text_3
14064            );
14065
14066            workspace
14067                .go_back(workspace.active_pane().downgrade(), window, cx)
14068                .detach_and_log_err(cx);
14069        })
14070        .unwrap();
14071    cx.executor().run_until_parked();
14072    workspace
14073        .update(cx, |workspace, _, cx| {
14074            let active_item = workspace
14075                .active_item(cx)
14076                .expect("should have an active item after navigating back from the 3rd buffer");
14077            assert_eq!(
14078                active_item.item_id(),
14079                multibuffer_item_id,
14080                "Should navigate back from the 3rd buffer to the multi buffer"
14081            );
14082            assert!(!active_item.is_singleton(cx));
14083        })
14084        .unwrap();
14085}
14086
14087#[gpui::test]
14088async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14089    init_test(cx, |_| {});
14090
14091    let mut cx = EditorTestContext::new(cx).await;
14092
14093    let diff_base = r#"
14094        use some::mod;
14095
14096        const A: u32 = 42;
14097
14098        fn main() {
14099            println!("hello");
14100
14101            println!("world");
14102        }
14103        "#
14104    .unindent();
14105
14106    cx.set_state(
14107        &r#"
14108        use some::modified;
14109
14110        ˇ
14111        fn main() {
14112            println!("hello there");
14113
14114            println!("around the");
14115            println!("world");
14116        }
14117        "#
14118        .unindent(),
14119    );
14120
14121    cx.set_head_text(&diff_base);
14122    executor.run_until_parked();
14123
14124    cx.update_editor(|editor, window, cx| {
14125        editor.go_to_next_hunk(&GoToHunk, window, cx);
14126        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14127    });
14128    executor.run_until_parked();
14129    cx.assert_state_with_diff(
14130        r#"
14131          use some::modified;
14132
14133
14134          fn main() {
14135        -     println!("hello");
14136        + ˇ    println!("hello there");
14137
14138              println!("around the");
14139              println!("world");
14140          }
14141        "#
14142        .unindent(),
14143    );
14144
14145    cx.update_editor(|editor, window, cx| {
14146        for _ in 0..2 {
14147            editor.go_to_next_hunk(&GoToHunk, window, cx);
14148            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14149        }
14150    });
14151    executor.run_until_parked();
14152    cx.assert_state_with_diff(
14153        r#"
14154        - use some::mod;
14155        + ˇuse some::modified;
14156
14157
14158          fn main() {
14159        -     println!("hello");
14160        +     println!("hello there");
14161
14162        +     println!("around the");
14163              println!("world");
14164          }
14165        "#
14166        .unindent(),
14167    );
14168
14169    cx.update_editor(|editor, window, cx| {
14170        editor.go_to_next_hunk(&GoToHunk, window, cx);
14171        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14172    });
14173    executor.run_until_parked();
14174    cx.assert_state_with_diff(
14175        r#"
14176        - use some::mod;
14177        + use some::modified;
14178
14179        - const A: u32 = 42;
14180          ˇ
14181          fn main() {
14182        -     println!("hello");
14183        +     println!("hello there");
14184
14185        +     println!("around the");
14186              println!("world");
14187          }
14188        "#
14189        .unindent(),
14190    );
14191
14192    cx.update_editor(|editor, window, cx| {
14193        editor.cancel(&Cancel, window, cx);
14194    });
14195
14196    cx.assert_state_with_diff(
14197        r#"
14198          use some::modified;
14199
14200          ˇ
14201          fn main() {
14202              println!("hello there");
14203
14204              println!("around the");
14205              println!("world");
14206          }
14207        "#
14208        .unindent(),
14209    );
14210}
14211
14212#[gpui::test]
14213async fn test_diff_base_change_with_expanded_diff_hunks(
14214    executor: BackgroundExecutor,
14215    cx: &mut TestAppContext,
14216) {
14217    init_test(cx, |_| {});
14218
14219    let mut cx = EditorTestContext::new(cx).await;
14220
14221    let diff_base = r#"
14222        use some::mod1;
14223        use some::mod2;
14224
14225        const A: u32 = 42;
14226        const B: u32 = 42;
14227        const C: u32 = 42;
14228
14229        fn main() {
14230            println!("hello");
14231
14232            println!("world");
14233        }
14234        "#
14235    .unindent();
14236
14237    cx.set_state(
14238        &r#"
14239        use some::mod2;
14240
14241        const A: u32 = 42;
14242        const C: u32 = 42;
14243
14244        fn main(ˇ) {
14245            //println!("hello");
14246
14247            println!("world");
14248            //
14249            //
14250        }
14251        "#
14252        .unindent(),
14253    );
14254
14255    cx.set_head_text(&diff_base);
14256    executor.run_until_parked();
14257
14258    cx.update_editor(|editor, window, cx| {
14259        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14260    });
14261    executor.run_until_parked();
14262    cx.assert_state_with_diff(
14263        r#"
14264        - use some::mod1;
14265          use some::mod2;
14266
14267          const A: u32 = 42;
14268        - const B: u32 = 42;
14269          const C: u32 = 42;
14270
14271          fn main(ˇ) {
14272        -     println!("hello");
14273        +     //println!("hello");
14274
14275              println!("world");
14276        +     //
14277        +     //
14278          }
14279        "#
14280        .unindent(),
14281    );
14282
14283    cx.set_head_text("new diff base!");
14284    executor.run_until_parked();
14285    cx.assert_state_with_diff(
14286        r#"
14287        - new diff base!
14288        + use some::mod2;
14289        +
14290        + const A: u32 = 42;
14291        + const C: u32 = 42;
14292        +
14293        + fn main(ˇ) {
14294        +     //println!("hello");
14295        +
14296        +     println!("world");
14297        +     //
14298        +     //
14299        + }
14300        "#
14301        .unindent(),
14302    );
14303}
14304
14305#[gpui::test]
14306async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
14307    init_test(cx, |_| {});
14308
14309    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14310    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
14311    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14312    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
14313    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
14314    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
14315
14316    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
14317    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
14318    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
14319
14320    let multi_buffer = cx.new(|cx| {
14321        let mut multibuffer = MultiBuffer::new(ReadWrite);
14322        multibuffer.push_excerpts(
14323            buffer_1.clone(),
14324            [
14325                ExcerptRange {
14326                    context: Point::new(0, 0)..Point::new(3, 0),
14327                    primary: None,
14328                },
14329                ExcerptRange {
14330                    context: Point::new(5, 0)..Point::new(7, 0),
14331                    primary: None,
14332                },
14333                ExcerptRange {
14334                    context: Point::new(9, 0)..Point::new(10, 3),
14335                    primary: None,
14336                },
14337            ],
14338            cx,
14339        );
14340        multibuffer.push_excerpts(
14341            buffer_2.clone(),
14342            [
14343                ExcerptRange {
14344                    context: Point::new(0, 0)..Point::new(3, 0),
14345                    primary: None,
14346                },
14347                ExcerptRange {
14348                    context: Point::new(5, 0)..Point::new(7, 0),
14349                    primary: None,
14350                },
14351                ExcerptRange {
14352                    context: Point::new(9, 0)..Point::new(10, 3),
14353                    primary: None,
14354                },
14355            ],
14356            cx,
14357        );
14358        multibuffer.push_excerpts(
14359            buffer_3.clone(),
14360            [
14361                ExcerptRange {
14362                    context: Point::new(0, 0)..Point::new(3, 0),
14363                    primary: None,
14364                },
14365                ExcerptRange {
14366                    context: Point::new(5, 0)..Point::new(7, 0),
14367                    primary: None,
14368                },
14369                ExcerptRange {
14370                    context: Point::new(9, 0)..Point::new(10, 3),
14371                    primary: None,
14372                },
14373            ],
14374            cx,
14375        );
14376        multibuffer
14377    });
14378
14379    let editor =
14380        cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14381    editor
14382        .update(cx, |editor, _window, cx| {
14383            for (buffer, diff_base) in [
14384                (buffer_1.clone(), file_1_old),
14385                (buffer_2.clone(), file_2_old),
14386                (buffer_3.clone(), file_3_old),
14387            ] {
14388                let diff = cx.new(|cx| BufferDiff::new_with_base_text(&diff_base, &buffer, cx));
14389                editor
14390                    .buffer
14391                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
14392            }
14393        })
14394        .unwrap();
14395
14396    let mut cx = EditorTestContext::for_editor(editor, cx).await;
14397    cx.run_until_parked();
14398
14399    cx.assert_editor_state(
14400        &"
14401            ˇaaa
14402            ccc
14403            ddd
14404
14405            ggg
14406            hhh
14407
14408
14409            lll
14410            mmm
14411            NNN
14412
14413            qqq
14414            rrr
14415
14416            uuu
14417            111
14418            222
14419            333
14420
14421            666
14422            777
14423
14424            000
14425            !!!"
14426        .unindent(),
14427    );
14428
14429    cx.update_editor(|editor, window, cx| {
14430        editor.select_all(&SelectAll, window, cx);
14431        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
14432    });
14433    cx.executor().run_until_parked();
14434
14435    cx.assert_state_with_diff(
14436        "
14437            «aaa
14438          - bbb
14439            ccc
14440            ddd
14441
14442            ggg
14443            hhh
14444
14445
14446            lll
14447            mmm
14448          - nnn
14449          + NNN
14450
14451            qqq
14452            rrr
14453
14454            uuu
14455            111
14456            222
14457            333
14458
14459          + 666
14460            777
14461
14462            000
14463            !!!ˇ»"
14464            .unindent(),
14465    );
14466}
14467
14468#[gpui::test]
14469async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
14470    init_test(cx, |_| {});
14471
14472    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
14473    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
14474
14475    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
14476    let multi_buffer = cx.new(|cx| {
14477        let mut multibuffer = MultiBuffer::new(ReadWrite);
14478        multibuffer.push_excerpts(
14479            buffer.clone(),
14480            [
14481                ExcerptRange {
14482                    context: Point::new(0, 0)..Point::new(2, 0),
14483                    primary: None,
14484                },
14485                ExcerptRange {
14486                    context: Point::new(4, 0)..Point::new(7, 0),
14487                    primary: None,
14488                },
14489                ExcerptRange {
14490                    context: Point::new(9, 0)..Point::new(10, 0),
14491                    primary: None,
14492                },
14493            ],
14494            cx,
14495        );
14496        multibuffer
14497    });
14498
14499    let editor =
14500        cx.add_window(|window, cx| Editor::new(EditorMode::Full, multi_buffer, None, window, cx));
14501    editor
14502        .update(cx, |editor, _window, cx| {
14503            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
14504            editor
14505                .buffer
14506                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
14507        })
14508        .unwrap();
14509
14510    let mut cx = EditorTestContext::for_editor(editor, cx).await;
14511    cx.run_until_parked();
14512
14513    cx.update_editor(|editor, window, cx| {
14514        editor.expand_all_diff_hunks(&Default::default(), window, cx)
14515    });
14516    cx.executor().run_until_parked();
14517
14518    // When the start of a hunk coincides with the start of its excerpt,
14519    // the hunk is expanded. When the start of a a hunk is earlier than
14520    // the start of its excerpt, the hunk is not expanded.
14521    cx.assert_state_with_diff(
14522        "
14523            ˇaaa
14524          - bbb
14525          + BBB
14526
14527          - ddd
14528          - eee
14529          + DDD
14530          + EEE
14531            fff
14532
14533            iii
14534        "
14535        .unindent(),
14536    );
14537}
14538
14539#[gpui::test]
14540async fn test_edits_around_expanded_insertion_hunks(
14541    executor: BackgroundExecutor,
14542    cx: &mut TestAppContext,
14543) {
14544    init_test(cx, |_| {});
14545
14546    let mut cx = EditorTestContext::new(cx).await;
14547
14548    let diff_base = r#"
14549        use some::mod1;
14550        use some::mod2;
14551
14552        const A: u32 = 42;
14553
14554        fn main() {
14555            println!("hello");
14556
14557            println!("world");
14558        }
14559        "#
14560    .unindent();
14561    executor.run_until_parked();
14562    cx.set_state(
14563        &r#"
14564        use some::mod1;
14565        use some::mod2;
14566
14567        const A: u32 = 42;
14568        const B: u32 = 42;
14569        const C: u32 = 42;
14570        ˇ
14571
14572        fn main() {
14573            println!("hello");
14574
14575            println!("world");
14576        }
14577        "#
14578        .unindent(),
14579    );
14580
14581    cx.set_head_text(&diff_base);
14582    executor.run_until_parked();
14583
14584    cx.update_editor(|editor, window, cx| {
14585        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14586    });
14587    executor.run_until_parked();
14588
14589    cx.assert_state_with_diff(
14590        r#"
14591        use some::mod1;
14592        use some::mod2;
14593
14594        const A: u32 = 42;
14595      + const B: u32 = 42;
14596      + const C: u32 = 42;
14597      + ˇ
14598
14599        fn main() {
14600            println!("hello");
14601
14602            println!("world");
14603        }
14604      "#
14605        .unindent(),
14606    );
14607
14608    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
14609    executor.run_until_parked();
14610
14611    cx.assert_state_with_diff(
14612        r#"
14613        use some::mod1;
14614        use some::mod2;
14615
14616        const A: u32 = 42;
14617      + const B: u32 = 42;
14618      + const C: u32 = 42;
14619      + const D: u32 = 42;
14620      + ˇ
14621
14622        fn main() {
14623            println!("hello");
14624
14625            println!("world");
14626        }
14627      "#
14628        .unindent(),
14629    );
14630
14631    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
14632    executor.run_until_parked();
14633
14634    cx.assert_state_with_diff(
14635        r#"
14636        use some::mod1;
14637        use some::mod2;
14638
14639        const A: u32 = 42;
14640      + const B: u32 = 42;
14641      + const C: u32 = 42;
14642      + const D: u32 = 42;
14643      + const E: u32 = 42;
14644      + ˇ
14645
14646        fn main() {
14647            println!("hello");
14648
14649            println!("world");
14650        }
14651      "#
14652        .unindent(),
14653    );
14654
14655    cx.update_editor(|editor, window, cx| {
14656        editor.delete_line(&DeleteLine, window, cx);
14657    });
14658    executor.run_until_parked();
14659
14660    cx.assert_state_with_diff(
14661        r#"
14662        use some::mod1;
14663        use some::mod2;
14664
14665        const A: u32 = 42;
14666      + const B: u32 = 42;
14667      + const C: u32 = 42;
14668      + const D: u32 = 42;
14669      + const E: u32 = 42;
14670        ˇ
14671        fn main() {
14672            println!("hello");
14673
14674            println!("world");
14675        }
14676      "#
14677        .unindent(),
14678    );
14679
14680    cx.update_editor(|editor, window, cx| {
14681        editor.move_up(&MoveUp, window, cx);
14682        editor.delete_line(&DeleteLine, window, cx);
14683        editor.move_up(&MoveUp, window, cx);
14684        editor.delete_line(&DeleteLine, window, cx);
14685        editor.move_up(&MoveUp, window, cx);
14686        editor.delete_line(&DeleteLine, window, cx);
14687    });
14688    executor.run_until_parked();
14689    cx.assert_state_with_diff(
14690        r#"
14691        use some::mod1;
14692        use some::mod2;
14693
14694        const A: u32 = 42;
14695      + const B: u32 = 42;
14696        ˇ
14697        fn main() {
14698            println!("hello");
14699
14700            println!("world");
14701        }
14702      "#
14703        .unindent(),
14704    );
14705
14706    cx.update_editor(|editor, window, cx| {
14707        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
14708        editor.delete_line(&DeleteLine, window, cx);
14709    });
14710    executor.run_until_parked();
14711    cx.assert_state_with_diff(
14712        r#"
14713        ˇ
14714        fn main() {
14715            println!("hello");
14716
14717            println!("world");
14718        }
14719      "#
14720        .unindent(),
14721    );
14722}
14723
14724#[gpui::test]
14725async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
14726    init_test(cx, |_| {});
14727
14728    let mut cx = EditorTestContext::new(cx).await;
14729    cx.set_head_text(indoc! { "
14730        one
14731        two
14732        three
14733        four
14734        five
14735        "
14736    });
14737    cx.set_state(indoc! { "
14738        one
14739        ˇthree
14740        five
14741    "});
14742    cx.run_until_parked();
14743    cx.update_editor(|editor, window, cx| {
14744        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14745    });
14746    cx.assert_state_with_diff(
14747        indoc! { "
14748        one
14749      - two
14750        ˇthree
14751      - four
14752        five
14753    "}
14754        .to_string(),
14755    );
14756    cx.update_editor(|editor, window, cx| {
14757        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14758    });
14759
14760    cx.assert_state_with_diff(
14761        indoc! { "
14762        one
14763        ˇthree
14764        five
14765    "}
14766        .to_string(),
14767    );
14768
14769    cx.set_state(indoc! { "
14770        one
14771        ˇTWO
14772        three
14773        four
14774        five
14775    "});
14776    cx.run_until_parked();
14777    cx.update_editor(|editor, window, cx| {
14778        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14779    });
14780
14781    cx.assert_state_with_diff(
14782        indoc! { "
14783            one
14784          - two
14785          + ˇTWO
14786            three
14787            four
14788            five
14789        "}
14790        .to_string(),
14791    );
14792    cx.update_editor(|editor, window, cx| {
14793        editor.move_up(&Default::default(), window, cx);
14794        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
14795    });
14796    cx.assert_state_with_diff(
14797        indoc! { "
14798            one
14799            ˇTWO
14800            three
14801            four
14802            five
14803        "}
14804        .to_string(),
14805    );
14806}
14807
14808#[gpui::test]
14809async fn test_edits_around_expanded_deletion_hunks(
14810    executor: BackgroundExecutor,
14811    cx: &mut TestAppContext,
14812) {
14813    init_test(cx, |_| {});
14814
14815    let mut cx = EditorTestContext::new(cx).await;
14816
14817    let diff_base = r#"
14818        use some::mod1;
14819        use some::mod2;
14820
14821        const A: u32 = 42;
14822        const B: u32 = 42;
14823        const C: u32 = 42;
14824
14825
14826        fn main() {
14827            println!("hello");
14828
14829            println!("world");
14830        }
14831    "#
14832    .unindent();
14833    executor.run_until_parked();
14834    cx.set_state(
14835        &r#"
14836        use some::mod1;
14837        use some::mod2;
14838
14839        ˇconst B: u32 = 42;
14840        const C: u32 = 42;
14841
14842
14843        fn main() {
14844            println!("hello");
14845
14846            println!("world");
14847        }
14848        "#
14849        .unindent(),
14850    );
14851
14852    cx.set_head_text(&diff_base);
14853    executor.run_until_parked();
14854
14855    cx.update_editor(|editor, window, cx| {
14856        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14857    });
14858    executor.run_until_parked();
14859
14860    cx.assert_state_with_diff(
14861        r#"
14862        use some::mod1;
14863        use some::mod2;
14864
14865      - const A: u32 = 42;
14866        ˇconst B: u32 = 42;
14867        const C: u32 = 42;
14868
14869
14870        fn main() {
14871            println!("hello");
14872
14873            println!("world");
14874        }
14875      "#
14876        .unindent(),
14877    );
14878
14879    cx.update_editor(|editor, window, cx| {
14880        editor.delete_line(&DeleteLine, window, cx);
14881    });
14882    executor.run_until_parked();
14883    cx.assert_state_with_diff(
14884        r#"
14885        use some::mod1;
14886        use some::mod2;
14887
14888      - const A: u32 = 42;
14889      - const B: u32 = 42;
14890        ˇconst C: u32 = 42;
14891
14892
14893        fn main() {
14894            println!("hello");
14895
14896            println!("world");
14897        }
14898      "#
14899        .unindent(),
14900    );
14901
14902    cx.update_editor(|editor, window, cx| {
14903        editor.delete_line(&DeleteLine, window, cx);
14904    });
14905    executor.run_until_parked();
14906    cx.assert_state_with_diff(
14907        r#"
14908        use some::mod1;
14909        use some::mod2;
14910
14911      - const A: u32 = 42;
14912      - const B: u32 = 42;
14913      - const C: u32 = 42;
14914        ˇ
14915
14916        fn main() {
14917            println!("hello");
14918
14919            println!("world");
14920        }
14921      "#
14922        .unindent(),
14923    );
14924
14925    cx.update_editor(|editor, window, cx| {
14926        editor.handle_input("replacement", window, cx);
14927    });
14928    executor.run_until_parked();
14929    cx.assert_state_with_diff(
14930        r#"
14931        use some::mod1;
14932        use some::mod2;
14933
14934      - const A: u32 = 42;
14935      - const B: u32 = 42;
14936      - const C: u32 = 42;
14937      -
14938      + replacementˇ
14939
14940        fn main() {
14941            println!("hello");
14942
14943            println!("world");
14944        }
14945      "#
14946        .unindent(),
14947    );
14948}
14949
14950#[gpui::test]
14951async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
14952    init_test(cx, |_| {});
14953
14954    let mut cx = EditorTestContext::new(cx).await;
14955
14956    let base_text = r#"
14957        one
14958        two
14959        three
14960        four
14961        five
14962    "#
14963    .unindent();
14964    executor.run_until_parked();
14965    cx.set_state(
14966        &r#"
14967        one
14968        two
14969        fˇour
14970        five
14971        "#
14972        .unindent(),
14973    );
14974
14975    cx.set_head_text(&base_text);
14976    executor.run_until_parked();
14977
14978    cx.update_editor(|editor, window, cx| {
14979        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
14980    });
14981    executor.run_until_parked();
14982
14983    cx.assert_state_with_diff(
14984        r#"
14985          one
14986          two
14987        - three
14988          fˇour
14989          five
14990        "#
14991        .unindent(),
14992    );
14993
14994    cx.update_editor(|editor, window, cx| {
14995        editor.backspace(&Backspace, window, cx);
14996        editor.backspace(&Backspace, window, cx);
14997    });
14998    executor.run_until_parked();
14999    cx.assert_state_with_diff(
15000        r#"
15001          one
15002          two
15003        - threeˇ
15004        - four
15005        + our
15006          five
15007        "#
15008        .unindent(),
15009    );
15010}
15011
15012#[gpui::test]
15013async fn test_edit_after_expanded_modification_hunk(
15014    executor: BackgroundExecutor,
15015    cx: &mut TestAppContext,
15016) {
15017    init_test(cx, |_| {});
15018
15019    let mut cx = EditorTestContext::new(cx).await;
15020
15021    let diff_base = r#"
15022        use some::mod1;
15023        use some::mod2;
15024
15025        const A: u32 = 42;
15026        const B: u32 = 42;
15027        const C: u32 = 42;
15028        const D: u32 = 42;
15029
15030
15031        fn main() {
15032            println!("hello");
15033
15034            println!("world");
15035        }"#
15036    .unindent();
15037
15038    cx.set_state(
15039        &r#"
15040        use some::mod1;
15041        use some::mod2;
15042
15043        const A: u32 = 42;
15044        const B: u32 = 42;
15045        const C: u32 = 43ˇ
15046        const D: u32 = 42;
15047
15048
15049        fn main() {
15050            println!("hello");
15051
15052            println!("world");
15053        }"#
15054        .unindent(),
15055    );
15056
15057    cx.set_head_text(&diff_base);
15058    executor.run_until_parked();
15059    cx.update_editor(|editor, window, cx| {
15060        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15061    });
15062    executor.run_until_parked();
15063
15064    cx.assert_state_with_diff(
15065        r#"
15066        use some::mod1;
15067        use some::mod2;
15068
15069        const A: u32 = 42;
15070        const B: u32 = 42;
15071      - const C: u32 = 42;
15072      + const C: u32 = 43ˇ
15073        const D: u32 = 42;
15074
15075
15076        fn main() {
15077            println!("hello");
15078
15079            println!("world");
15080        }"#
15081        .unindent(),
15082    );
15083
15084    cx.update_editor(|editor, window, cx| {
15085        editor.handle_input("\nnew_line\n", window, cx);
15086    });
15087    executor.run_until_parked();
15088
15089    cx.assert_state_with_diff(
15090        r#"
15091        use some::mod1;
15092        use some::mod2;
15093
15094        const A: u32 = 42;
15095        const B: u32 = 42;
15096      - const C: u32 = 42;
15097      + const C: u32 = 43
15098      + new_line
15099      + ˇ
15100        const D: u32 = 42;
15101
15102
15103        fn main() {
15104            println!("hello");
15105
15106            println!("world");
15107        }"#
15108        .unindent(),
15109    );
15110}
15111
15112#[gpui::test]
15113async fn test_stage_and_unstage_added_file_hunk(
15114    executor: BackgroundExecutor,
15115    cx: &mut TestAppContext,
15116) {
15117    init_test(cx, |_| {});
15118
15119    let mut cx = EditorTestContext::new(cx).await;
15120    cx.update_editor(|editor, _, cx| {
15121        editor.set_expand_all_diff_hunks(cx);
15122    });
15123
15124    let working_copy = r#"
15125            ˇfn main() {
15126                println!("hello, world!");
15127            }
15128        "#
15129    .unindent();
15130
15131    cx.set_state(&working_copy);
15132    executor.run_until_parked();
15133
15134    cx.assert_state_with_diff(
15135        r#"
15136            + ˇfn main() {
15137            +     println!("hello, world!");
15138            + }
15139        "#
15140        .unindent(),
15141    );
15142    cx.assert_index_text(None);
15143
15144    cx.update_editor(|editor, window, cx| {
15145        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15146    });
15147    executor.run_until_parked();
15148    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
15149    cx.assert_state_with_diff(
15150        r#"
15151            + ˇfn main() {
15152            +     println!("hello, world!");
15153            + }
15154        "#
15155        .unindent(),
15156    );
15157
15158    cx.update_editor(|editor, window, cx| {
15159        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
15160    });
15161    executor.run_until_parked();
15162    cx.assert_index_text(None);
15163}
15164
15165async fn setup_indent_guides_editor(
15166    text: &str,
15167    cx: &mut TestAppContext,
15168) -> (BufferId, EditorTestContext) {
15169    init_test(cx, |_| {});
15170
15171    let mut cx = EditorTestContext::new(cx).await;
15172
15173    let buffer_id = cx.update_editor(|editor, window, cx| {
15174        editor.set_text(text, window, cx);
15175        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
15176
15177        buffer_ids[0]
15178    });
15179
15180    (buffer_id, cx)
15181}
15182
15183fn assert_indent_guides(
15184    range: Range<u32>,
15185    expected: Vec<IndentGuide>,
15186    active_indices: Option<Vec<usize>>,
15187    cx: &mut EditorTestContext,
15188) {
15189    let indent_guides = cx.update_editor(|editor, window, cx| {
15190        let snapshot = editor.snapshot(window, cx).display_snapshot;
15191        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
15192            editor,
15193            MultiBufferRow(range.start)..MultiBufferRow(range.end),
15194            true,
15195            &snapshot,
15196            cx,
15197        );
15198
15199        indent_guides.sort_by(|a, b| {
15200            a.depth.cmp(&b.depth).then(
15201                a.start_row
15202                    .cmp(&b.start_row)
15203                    .then(a.end_row.cmp(&b.end_row)),
15204            )
15205        });
15206        indent_guides
15207    });
15208
15209    if let Some(expected) = active_indices {
15210        let active_indices = cx.update_editor(|editor, window, cx| {
15211            let snapshot = editor.snapshot(window, cx).display_snapshot;
15212            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
15213        });
15214
15215        assert_eq!(
15216            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
15217            expected,
15218            "Active indent guide indices do not match"
15219        );
15220    }
15221
15222    assert_eq!(indent_guides, expected, "Indent guides do not match");
15223}
15224
15225fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
15226    IndentGuide {
15227        buffer_id,
15228        start_row: MultiBufferRow(start_row),
15229        end_row: MultiBufferRow(end_row),
15230        depth,
15231        tab_size: 4,
15232        settings: IndentGuideSettings {
15233            enabled: true,
15234            line_width: 1,
15235            active_line_width: 1,
15236            ..Default::default()
15237        },
15238    }
15239}
15240
15241#[gpui::test]
15242async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
15243    let (buffer_id, mut cx) = setup_indent_guides_editor(
15244        &"
15245    fn main() {
15246        let a = 1;
15247    }"
15248        .unindent(),
15249        cx,
15250    )
15251    .await;
15252
15253    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15254}
15255
15256#[gpui::test]
15257async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
15258    let (buffer_id, mut cx) = setup_indent_guides_editor(
15259        &"
15260    fn main() {
15261        let a = 1;
15262        let b = 2;
15263    }"
15264        .unindent(),
15265        cx,
15266    )
15267    .await;
15268
15269    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
15270}
15271
15272#[gpui::test]
15273async fn test_indent_guide_nested(cx: &mut TestAppContext) {
15274    let (buffer_id, mut cx) = setup_indent_guides_editor(
15275        &"
15276    fn main() {
15277        let a = 1;
15278        if a == 3 {
15279            let b = 2;
15280        } else {
15281            let c = 3;
15282        }
15283    }"
15284        .unindent(),
15285        cx,
15286    )
15287    .await;
15288
15289    assert_indent_guides(
15290        0..8,
15291        vec![
15292            indent_guide(buffer_id, 1, 6, 0),
15293            indent_guide(buffer_id, 3, 3, 1),
15294            indent_guide(buffer_id, 5, 5, 1),
15295        ],
15296        None,
15297        &mut cx,
15298    );
15299}
15300
15301#[gpui::test]
15302async fn test_indent_guide_tab(cx: &mut TestAppContext) {
15303    let (buffer_id, mut cx) = setup_indent_guides_editor(
15304        &"
15305    fn main() {
15306        let a = 1;
15307            let b = 2;
15308        let c = 3;
15309    }"
15310        .unindent(),
15311        cx,
15312    )
15313    .await;
15314
15315    assert_indent_guides(
15316        0..5,
15317        vec![
15318            indent_guide(buffer_id, 1, 3, 0),
15319            indent_guide(buffer_id, 2, 2, 1),
15320        ],
15321        None,
15322        &mut cx,
15323    );
15324}
15325
15326#[gpui::test]
15327async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
15328    let (buffer_id, mut cx) = setup_indent_guides_editor(
15329        &"
15330        fn main() {
15331            let a = 1;
15332
15333            let c = 3;
15334        }"
15335        .unindent(),
15336        cx,
15337    )
15338    .await;
15339
15340    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
15341}
15342
15343#[gpui::test]
15344async fn test_indent_guide_complex(cx: &mut TestAppContext) {
15345    let (buffer_id, mut cx) = setup_indent_guides_editor(
15346        &"
15347        fn main() {
15348            let a = 1;
15349
15350            let c = 3;
15351
15352            if a == 3 {
15353                let b = 2;
15354            } else {
15355                let c = 3;
15356            }
15357        }"
15358        .unindent(),
15359        cx,
15360    )
15361    .await;
15362
15363    assert_indent_guides(
15364        0..11,
15365        vec![
15366            indent_guide(buffer_id, 1, 9, 0),
15367            indent_guide(buffer_id, 6, 6, 1),
15368            indent_guide(buffer_id, 8, 8, 1),
15369        ],
15370        None,
15371        &mut cx,
15372    );
15373}
15374
15375#[gpui::test]
15376async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
15377    let (buffer_id, mut cx) = setup_indent_guides_editor(
15378        &"
15379        fn main() {
15380            let a = 1;
15381
15382            let c = 3;
15383
15384            if a == 3 {
15385                let b = 2;
15386            } else {
15387                let c = 3;
15388            }
15389        }"
15390        .unindent(),
15391        cx,
15392    )
15393    .await;
15394
15395    assert_indent_guides(
15396        1..11,
15397        vec![
15398            indent_guide(buffer_id, 1, 9, 0),
15399            indent_guide(buffer_id, 6, 6, 1),
15400            indent_guide(buffer_id, 8, 8, 1),
15401        ],
15402        None,
15403        &mut cx,
15404    );
15405}
15406
15407#[gpui::test]
15408async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
15409    let (buffer_id, mut cx) = setup_indent_guides_editor(
15410        &"
15411        fn main() {
15412            let a = 1;
15413
15414            let c = 3;
15415
15416            if a == 3 {
15417                let b = 2;
15418            } else {
15419                let c = 3;
15420            }
15421        }"
15422        .unindent(),
15423        cx,
15424    )
15425    .await;
15426
15427    assert_indent_guides(
15428        1..10,
15429        vec![
15430            indent_guide(buffer_id, 1, 9, 0),
15431            indent_guide(buffer_id, 6, 6, 1),
15432            indent_guide(buffer_id, 8, 8, 1),
15433        ],
15434        None,
15435        &mut cx,
15436    );
15437}
15438
15439#[gpui::test]
15440async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
15441    let (buffer_id, mut cx) = setup_indent_guides_editor(
15442        &"
15443        block1
15444            block2
15445                block3
15446                    block4
15447            block2
15448        block1
15449        block1"
15450            .unindent(),
15451        cx,
15452    )
15453    .await;
15454
15455    assert_indent_guides(
15456        1..10,
15457        vec![
15458            indent_guide(buffer_id, 1, 4, 0),
15459            indent_guide(buffer_id, 2, 3, 1),
15460            indent_guide(buffer_id, 3, 3, 2),
15461        ],
15462        None,
15463        &mut cx,
15464    );
15465}
15466
15467#[gpui::test]
15468async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
15469    let (buffer_id, mut cx) = setup_indent_guides_editor(
15470        &"
15471        block1
15472            block2
15473                block3
15474
15475        block1
15476        block1"
15477            .unindent(),
15478        cx,
15479    )
15480    .await;
15481
15482    assert_indent_guides(
15483        0..6,
15484        vec![
15485            indent_guide(buffer_id, 1, 2, 0),
15486            indent_guide(buffer_id, 2, 2, 1),
15487        ],
15488        None,
15489        &mut cx,
15490    );
15491}
15492
15493#[gpui::test]
15494async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
15495    let (buffer_id, mut cx) = setup_indent_guides_editor(
15496        &"
15497        block1
15498
15499
15500
15501            block2
15502        "
15503        .unindent(),
15504        cx,
15505    )
15506    .await;
15507
15508    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
15509}
15510
15511#[gpui::test]
15512async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
15513    let (buffer_id, mut cx) = setup_indent_guides_editor(
15514        &"
15515        def a:
15516        \tb = 3
15517        \tif True:
15518        \t\tc = 4
15519        \t\td = 5
15520        \tprint(b)
15521        "
15522        .unindent(),
15523        cx,
15524    )
15525    .await;
15526
15527    assert_indent_guides(
15528        0..6,
15529        vec![
15530            indent_guide(buffer_id, 1, 6, 0),
15531            indent_guide(buffer_id, 3, 4, 1),
15532        ],
15533        None,
15534        &mut cx,
15535    );
15536}
15537
15538#[gpui::test]
15539async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
15540    let (buffer_id, mut cx) = setup_indent_guides_editor(
15541        &"
15542    fn main() {
15543        let a = 1;
15544    }"
15545        .unindent(),
15546        cx,
15547    )
15548    .await;
15549
15550    cx.update_editor(|editor, window, cx| {
15551        editor.change_selections(None, window, cx, |s| {
15552            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15553        });
15554    });
15555
15556    assert_indent_guides(
15557        0..3,
15558        vec![indent_guide(buffer_id, 1, 1, 0)],
15559        Some(vec![0]),
15560        &mut cx,
15561    );
15562}
15563
15564#[gpui::test]
15565async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
15566    let (buffer_id, mut cx) = setup_indent_guides_editor(
15567        &"
15568    fn main() {
15569        if 1 == 2 {
15570            let a = 1;
15571        }
15572    }"
15573        .unindent(),
15574        cx,
15575    )
15576    .await;
15577
15578    cx.update_editor(|editor, window, cx| {
15579        editor.change_selections(None, window, cx, |s| {
15580            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15581        });
15582    });
15583
15584    assert_indent_guides(
15585        0..4,
15586        vec![
15587            indent_guide(buffer_id, 1, 3, 0),
15588            indent_guide(buffer_id, 2, 2, 1),
15589        ],
15590        Some(vec![1]),
15591        &mut cx,
15592    );
15593
15594    cx.update_editor(|editor, window, cx| {
15595        editor.change_selections(None, window, cx, |s| {
15596            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15597        });
15598    });
15599
15600    assert_indent_guides(
15601        0..4,
15602        vec![
15603            indent_guide(buffer_id, 1, 3, 0),
15604            indent_guide(buffer_id, 2, 2, 1),
15605        ],
15606        Some(vec![1]),
15607        &mut cx,
15608    );
15609
15610    cx.update_editor(|editor, window, cx| {
15611        editor.change_selections(None, window, cx, |s| {
15612            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
15613        });
15614    });
15615
15616    assert_indent_guides(
15617        0..4,
15618        vec![
15619            indent_guide(buffer_id, 1, 3, 0),
15620            indent_guide(buffer_id, 2, 2, 1),
15621        ],
15622        Some(vec![0]),
15623        &mut cx,
15624    );
15625}
15626
15627#[gpui::test]
15628async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
15629    let (buffer_id, mut cx) = setup_indent_guides_editor(
15630        &"
15631    fn main() {
15632        let a = 1;
15633
15634        let b = 2;
15635    }"
15636        .unindent(),
15637        cx,
15638    )
15639    .await;
15640
15641    cx.update_editor(|editor, window, cx| {
15642        editor.change_selections(None, window, cx, |s| {
15643            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
15644        });
15645    });
15646
15647    assert_indent_guides(
15648        0..5,
15649        vec![indent_guide(buffer_id, 1, 3, 0)],
15650        Some(vec![0]),
15651        &mut cx,
15652    );
15653}
15654
15655#[gpui::test]
15656async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
15657    let (buffer_id, mut cx) = setup_indent_guides_editor(
15658        &"
15659    def m:
15660        a = 1
15661        pass"
15662            .unindent(),
15663        cx,
15664    )
15665    .await;
15666
15667    cx.update_editor(|editor, window, cx| {
15668        editor.change_selections(None, window, cx, |s| {
15669            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
15670        });
15671    });
15672
15673    assert_indent_guides(
15674        0..3,
15675        vec![indent_guide(buffer_id, 1, 2, 0)],
15676        Some(vec![0]),
15677        &mut cx,
15678    );
15679}
15680
15681#[gpui::test]
15682async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
15683    init_test(cx, |_| {});
15684    let mut cx = EditorTestContext::new(cx).await;
15685    let text = indoc! {
15686        "
15687        impl A {
15688            fn b() {
15689                0;
15690                3;
15691                5;
15692                6;
15693                7;
15694            }
15695        }
15696        "
15697    };
15698    let base_text = indoc! {
15699        "
15700        impl A {
15701            fn b() {
15702                0;
15703                1;
15704                2;
15705                3;
15706                4;
15707            }
15708            fn c() {
15709                5;
15710                6;
15711                7;
15712            }
15713        }
15714        "
15715    };
15716
15717    cx.update_editor(|editor, window, cx| {
15718        editor.set_text(text, window, cx);
15719
15720        editor.buffer().update(cx, |multibuffer, cx| {
15721            let buffer = multibuffer.as_singleton().unwrap();
15722            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
15723
15724            multibuffer.set_all_diff_hunks_expanded(cx);
15725            multibuffer.add_diff(diff, cx);
15726
15727            buffer.read(cx).remote_id()
15728        })
15729    });
15730    cx.run_until_parked();
15731
15732    cx.assert_state_with_diff(
15733        indoc! { "
15734          impl A {
15735              fn b() {
15736                  0;
15737        -         1;
15738        -         2;
15739                  3;
15740        -         4;
15741        -     }
15742        -     fn c() {
15743                  5;
15744                  6;
15745                  7;
15746              }
15747          }
15748          ˇ"
15749        }
15750        .to_string(),
15751    );
15752
15753    let mut actual_guides = cx.update_editor(|editor, window, cx| {
15754        editor
15755            .snapshot(window, cx)
15756            .buffer_snapshot
15757            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
15758            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
15759            .collect::<Vec<_>>()
15760    });
15761    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
15762    assert_eq!(
15763        actual_guides,
15764        vec![
15765            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
15766            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
15767            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
15768        ]
15769    );
15770}
15771
15772#[gpui::test]
15773async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
15774    init_test(cx, |_| {});
15775    let mut cx = EditorTestContext::new(cx).await;
15776
15777    let diff_base = r#"
15778        a
15779        b
15780        c
15781        "#
15782    .unindent();
15783
15784    cx.set_state(
15785        &r#"
15786        ˇA
15787        b
15788        C
15789        "#
15790        .unindent(),
15791    );
15792    cx.set_head_text(&diff_base);
15793    cx.update_editor(|editor, window, cx| {
15794        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15795    });
15796    executor.run_until_parked();
15797
15798    let both_hunks_expanded = r#"
15799        - a
15800        + ˇA
15801          b
15802        - c
15803        + C
15804        "#
15805    .unindent();
15806
15807    cx.assert_state_with_diff(both_hunks_expanded.clone());
15808
15809    let hunk_ranges = cx.update_editor(|editor, window, cx| {
15810        let snapshot = editor.snapshot(window, cx);
15811        let hunks = editor
15812            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15813            .collect::<Vec<_>>();
15814        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15815        let buffer_id = hunks[0].buffer_id;
15816        hunks
15817            .into_iter()
15818            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15819            .collect::<Vec<_>>()
15820    });
15821    assert_eq!(hunk_ranges.len(), 2);
15822
15823    cx.update_editor(|editor, _, cx| {
15824        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15825    });
15826    executor.run_until_parked();
15827
15828    let second_hunk_expanded = r#"
15829          ˇA
15830          b
15831        - c
15832        + C
15833        "#
15834    .unindent();
15835
15836    cx.assert_state_with_diff(second_hunk_expanded);
15837
15838    cx.update_editor(|editor, _, cx| {
15839        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15840    });
15841    executor.run_until_parked();
15842
15843    cx.assert_state_with_diff(both_hunks_expanded.clone());
15844
15845    cx.update_editor(|editor, _, cx| {
15846        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15847    });
15848    executor.run_until_parked();
15849
15850    let first_hunk_expanded = r#"
15851        - a
15852        + ˇA
15853          b
15854          C
15855        "#
15856    .unindent();
15857
15858    cx.assert_state_with_diff(first_hunk_expanded);
15859
15860    cx.update_editor(|editor, _, cx| {
15861        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15862    });
15863    executor.run_until_parked();
15864
15865    cx.assert_state_with_diff(both_hunks_expanded);
15866
15867    cx.set_state(
15868        &r#"
15869        ˇA
15870        b
15871        "#
15872        .unindent(),
15873    );
15874    cx.run_until_parked();
15875
15876    // TODO this cursor position seems bad
15877    cx.assert_state_with_diff(
15878        r#"
15879        - ˇa
15880        + A
15881          b
15882        "#
15883        .unindent(),
15884    );
15885
15886    cx.update_editor(|editor, window, cx| {
15887        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15888    });
15889
15890    cx.assert_state_with_diff(
15891        r#"
15892            - ˇa
15893            + A
15894              b
15895            - c
15896            "#
15897        .unindent(),
15898    );
15899
15900    let hunk_ranges = cx.update_editor(|editor, window, cx| {
15901        let snapshot = editor.snapshot(window, cx);
15902        let hunks = editor
15903            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15904            .collect::<Vec<_>>();
15905        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15906        let buffer_id = hunks[0].buffer_id;
15907        hunks
15908            .into_iter()
15909            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15910            .collect::<Vec<_>>()
15911    });
15912    assert_eq!(hunk_ranges.len(), 2);
15913
15914    cx.update_editor(|editor, _, cx| {
15915        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
15916    });
15917    executor.run_until_parked();
15918
15919    cx.assert_state_with_diff(
15920        r#"
15921        - ˇa
15922        + A
15923          b
15924        "#
15925        .unindent(),
15926    );
15927}
15928
15929#[gpui::test]
15930async fn test_toggle_deletion_hunk_at_start_of_file(
15931    executor: BackgroundExecutor,
15932    cx: &mut TestAppContext,
15933) {
15934    init_test(cx, |_| {});
15935    let mut cx = EditorTestContext::new(cx).await;
15936
15937    let diff_base = r#"
15938        a
15939        b
15940        c
15941        "#
15942    .unindent();
15943
15944    cx.set_state(
15945        &r#"
15946        ˇb
15947        c
15948        "#
15949        .unindent(),
15950    );
15951    cx.set_head_text(&diff_base);
15952    cx.update_editor(|editor, window, cx| {
15953        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
15954    });
15955    executor.run_until_parked();
15956
15957    let hunk_expanded = r#"
15958        - a
15959          ˇb
15960          c
15961        "#
15962    .unindent();
15963
15964    cx.assert_state_with_diff(hunk_expanded.clone());
15965
15966    let hunk_ranges = cx.update_editor(|editor, window, cx| {
15967        let snapshot = editor.snapshot(window, cx);
15968        let hunks = editor
15969            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
15970            .collect::<Vec<_>>();
15971        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
15972        let buffer_id = hunks[0].buffer_id;
15973        hunks
15974            .into_iter()
15975            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range.clone()))
15976            .collect::<Vec<_>>()
15977    });
15978    assert_eq!(hunk_ranges.len(), 1);
15979
15980    cx.update_editor(|editor, _, cx| {
15981        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15982    });
15983    executor.run_until_parked();
15984
15985    let hunk_collapsed = r#"
15986          ˇb
15987          c
15988        "#
15989    .unindent();
15990
15991    cx.assert_state_with_diff(hunk_collapsed);
15992
15993    cx.update_editor(|editor, _, cx| {
15994        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
15995    });
15996    executor.run_until_parked();
15997
15998    cx.assert_state_with_diff(hunk_expanded.clone());
15999}
16000
16001#[gpui::test]
16002async fn test_display_diff_hunks(cx: &mut TestAppContext) {
16003    init_test(cx, |_| {});
16004
16005    let fs = FakeFs::new(cx.executor());
16006    fs.insert_tree(
16007        path!("/test"),
16008        json!({
16009            ".git": {},
16010            "file-1": "ONE\n",
16011            "file-2": "TWO\n",
16012            "file-3": "THREE\n",
16013        }),
16014    )
16015    .await;
16016
16017    fs.set_head_for_repo(
16018        path!("/test/.git").as_ref(),
16019        &[
16020            ("file-1".into(), "one\n".into()),
16021            ("file-2".into(), "two\n".into()),
16022            ("file-3".into(), "three\n".into()),
16023        ],
16024    );
16025
16026    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
16027    let mut buffers = vec![];
16028    for i in 1..=3 {
16029        let buffer = project
16030            .update(cx, |project, cx| {
16031                let path = format!(path!("/test/file-{}"), i);
16032                project.open_local_buffer(path, cx)
16033            })
16034            .await
16035            .unwrap();
16036        buffers.push(buffer);
16037    }
16038
16039    let multibuffer = cx.new(|cx| {
16040        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
16041        multibuffer.set_all_diff_hunks_expanded(cx);
16042        for buffer in &buffers {
16043            let snapshot = buffer.read(cx).snapshot();
16044            multibuffer.set_excerpts_for_path(
16045                PathKey::namespaced("", buffer.read(cx).file().unwrap().path().clone()),
16046                buffer.clone(),
16047                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
16048                DEFAULT_MULTIBUFFER_CONTEXT,
16049                cx,
16050            );
16051        }
16052        multibuffer
16053    });
16054
16055    let editor = cx.add_window(|window, cx| {
16056        Editor::new(EditorMode::Full, multibuffer, Some(project), window, cx)
16057    });
16058    cx.run_until_parked();
16059
16060    let snapshot = editor
16061        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16062        .unwrap();
16063    let hunks = snapshot
16064        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
16065        .map(|hunk| match hunk {
16066            DisplayDiffHunk::Unfolded {
16067                display_row_range, ..
16068            } => display_row_range,
16069            DisplayDiffHunk::Folded { .. } => unreachable!(),
16070        })
16071        .collect::<Vec<_>>();
16072    assert_eq!(
16073        hunks,
16074        [
16075            DisplayRow(2)..DisplayRow(4),
16076            DisplayRow(7)..DisplayRow(9),
16077            DisplayRow(12)..DisplayRow(14),
16078        ]
16079    );
16080}
16081
16082#[gpui::test]
16083async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
16084    init_test(cx, |_| {});
16085
16086    let mut cx = EditorTestContext::new(cx).await;
16087    cx.set_head_text(indoc! { "
16088        one
16089        two
16090        three
16091        four
16092        five
16093        "
16094    });
16095    cx.set_index_text(indoc! { "
16096        one
16097        two
16098        three
16099        four
16100        five
16101        "
16102    });
16103    cx.set_state(indoc! {"
16104        one
16105        TWO
16106        ˇTHREE
16107        FOUR
16108        five
16109    "});
16110    cx.run_until_parked();
16111    cx.update_editor(|editor, window, cx| {
16112        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16113    });
16114    cx.run_until_parked();
16115    cx.assert_index_text(Some(indoc! {"
16116        one
16117        TWO
16118        THREE
16119        FOUR
16120        five
16121    "}));
16122    cx.set_state(indoc! { "
16123        one
16124        TWO
16125        ˇTHREE-HUNDRED
16126        FOUR
16127        five
16128    "});
16129    cx.run_until_parked();
16130    cx.update_editor(|editor, window, cx| {
16131        let snapshot = editor.snapshot(window, cx);
16132        let hunks = editor
16133            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
16134            .collect::<Vec<_>>();
16135        assert_eq!(hunks.len(), 1);
16136        assert_eq!(
16137            hunks[0].status(),
16138            DiffHunkStatus {
16139                kind: DiffHunkStatusKind::Modified,
16140                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
16141            }
16142        );
16143
16144        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
16145    });
16146    cx.run_until_parked();
16147    cx.assert_index_text(Some(indoc! {"
16148        one
16149        TWO
16150        THREE-HUNDRED
16151        FOUR
16152        five
16153    "}));
16154}
16155
16156#[gpui::test]
16157fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
16158    init_test(cx, |_| {});
16159
16160    let editor = cx.add_window(|window, cx| {
16161        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
16162        build_editor(buffer, window, cx)
16163    });
16164
16165    let render_args = Arc::new(Mutex::new(None));
16166    let snapshot = editor
16167        .update(cx, |editor, window, cx| {
16168            let snapshot = editor.buffer().read(cx).snapshot(cx);
16169            let range =
16170                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
16171
16172            struct RenderArgs {
16173                row: MultiBufferRow,
16174                folded: bool,
16175                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
16176            }
16177
16178            let crease = Crease::inline(
16179                range,
16180                FoldPlaceholder::test(),
16181                {
16182                    let toggle_callback = render_args.clone();
16183                    move |row, folded, callback, _window, _cx| {
16184                        *toggle_callback.lock() = Some(RenderArgs {
16185                            row,
16186                            folded,
16187                            callback,
16188                        });
16189                        div()
16190                    }
16191                },
16192                |_row, _folded, _window, _cx| div(),
16193            );
16194
16195            editor.insert_creases(Some(crease), cx);
16196            let snapshot = editor.snapshot(window, cx);
16197            let _div = snapshot.render_crease_toggle(
16198                MultiBufferRow(1),
16199                false,
16200                cx.entity().clone(),
16201                window,
16202                cx,
16203            );
16204            snapshot
16205        })
16206        .unwrap();
16207
16208    let render_args = render_args.lock().take().unwrap();
16209    assert_eq!(render_args.row, MultiBufferRow(1));
16210    assert!(!render_args.folded);
16211    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
16212
16213    cx.update_window(*editor, |_, window, cx| {
16214        (render_args.callback)(true, window, cx)
16215    })
16216    .unwrap();
16217    let snapshot = editor
16218        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16219        .unwrap();
16220    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
16221
16222    cx.update_window(*editor, |_, window, cx| {
16223        (render_args.callback)(false, window, cx)
16224    })
16225    .unwrap();
16226    let snapshot = editor
16227        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
16228        .unwrap();
16229    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
16230}
16231
16232#[gpui::test]
16233async fn test_input_text(cx: &mut TestAppContext) {
16234    init_test(cx, |_| {});
16235    let mut cx = EditorTestContext::new(cx).await;
16236
16237    cx.set_state(
16238        &r#"ˇone
16239        two
16240
16241        three
16242        fourˇ
16243        five
16244
16245        siˇx"#
16246            .unindent(),
16247    );
16248
16249    cx.dispatch_action(HandleInput(String::new()));
16250    cx.assert_editor_state(
16251        &r#"ˇone
16252        two
16253
16254        three
16255        fourˇ
16256        five
16257
16258        siˇx"#
16259            .unindent(),
16260    );
16261
16262    cx.dispatch_action(HandleInput("AAAA".to_string()));
16263    cx.assert_editor_state(
16264        &r#"AAAAˇone
16265        two
16266
16267        three
16268        fourAAAAˇ
16269        five
16270
16271        siAAAAˇx"#
16272            .unindent(),
16273    );
16274}
16275
16276#[gpui::test]
16277async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
16278    init_test(cx, |_| {});
16279
16280    let mut cx = EditorTestContext::new(cx).await;
16281    cx.set_state(
16282        r#"let foo = 1;
16283let foo = 2;
16284let foo = 3;
16285let fooˇ = 4;
16286let foo = 5;
16287let foo = 6;
16288let foo = 7;
16289let foo = 8;
16290let foo = 9;
16291let foo = 10;
16292let foo = 11;
16293let foo = 12;
16294let foo = 13;
16295let foo = 14;
16296let foo = 15;"#,
16297    );
16298
16299    cx.update_editor(|e, window, cx| {
16300        assert_eq!(
16301            e.next_scroll_position,
16302            NextScrollCursorCenterTopBottom::Center,
16303            "Default next scroll direction is center",
16304        );
16305
16306        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16307        assert_eq!(
16308            e.next_scroll_position,
16309            NextScrollCursorCenterTopBottom::Top,
16310            "After center, next scroll direction should be top",
16311        );
16312
16313        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16314        assert_eq!(
16315            e.next_scroll_position,
16316            NextScrollCursorCenterTopBottom::Bottom,
16317            "After top, next scroll direction should be bottom",
16318        );
16319
16320        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16321        assert_eq!(
16322            e.next_scroll_position,
16323            NextScrollCursorCenterTopBottom::Center,
16324            "After bottom, scrolling should start over",
16325        );
16326
16327        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
16328        assert_eq!(
16329            e.next_scroll_position,
16330            NextScrollCursorCenterTopBottom::Top,
16331            "Scrolling continues if retriggered fast enough"
16332        );
16333    });
16334
16335    cx.executor()
16336        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
16337    cx.executor().run_until_parked();
16338    cx.update_editor(|e, _, _| {
16339        assert_eq!(
16340            e.next_scroll_position,
16341            NextScrollCursorCenterTopBottom::Center,
16342            "If scrolling is not triggered fast enough, it should reset"
16343        );
16344    });
16345}
16346
16347#[gpui::test]
16348async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
16349    init_test(cx, |_| {});
16350    let mut cx = EditorLspTestContext::new_rust(
16351        lsp::ServerCapabilities {
16352            definition_provider: Some(lsp::OneOf::Left(true)),
16353            references_provider: Some(lsp::OneOf::Left(true)),
16354            ..lsp::ServerCapabilities::default()
16355        },
16356        cx,
16357    )
16358    .await;
16359
16360    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
16361        let go_to_definition = cx.lsp.handle_request::<lsp::request::GotoDefinition, _, _>(
16362            move |params, _| async move {
16363                if empty_go_to_definition {
16364                    Ok(None)
16365                } else {
16366                    Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
16367                        uri: params.text_document_position_params.text_document.uri,
16368                        range: lsp::Range::new(lsp::Position::new(4, 3), lsp::Position::new(4, 6)),
16369                    })))
16370                }
16371            },
16372        );
16373        let references =
16374            cx.lsp
16375                .handle_request::<lsp::request::References, _, _>(move |params, _| async move {
16376                    Ok(Some(vec![lsp::Location {
16377                        uri: params.text_document_position.text_document.uri,
16378                        range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
16379                    }]))
16380                });
16381        (go_to_definition, references)
16382    };
16383
16384    cx.set_state(
16385        &r#"fn one() {
16386            let mut a = ˇtwo();
16387        }
16388
16389        fn two() {}"#
16390            .unindent(),
16391    );
16392    set_up_lsp_handlers(false, &mut cx);
16393    let navigated = cx
16394        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16395        .await
16396        .expect("Failed to navigate to definition");
16397    assert_eq!(
16398        navigated,
16399        Navigated::Yes,
16400        "Should have navigated to definition from the GetDefinition response"
16401    );
16402    cx.assert_editor_state(
16403        &r#"fn one() {
16404            let mut a = two();
16405        }
16406
16407        fn «twoˇ»() {}"#
16408            .unindent(),
16409    );
16410
16411    let editors = cx.update_workspace(|workspace, _, cx| {
16412        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16413    });
16414    cx.update_editor(|_, _, test_editor_cx| {
16415        assert_eq!(
16416            editors.len(),
16417            1,
16418            "Initially, only one, test, editor should be open in the workspace"
16419        );
16420        assert_eq!(
16421            test_editor_cx.entity(),
16422            editors.last().expect("Asserted len is 1").clone()
16423        );
16424    });
16425
16426    set_up_lsp_handlers(true, &mut cx);
16427    let navigated = cx
16428        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
16429        .await
16430        .expect("Failed to navigate to lookup references");
16431    assert_eq!(
16432        navigated,
16433        Navigated::Yes,
16434        "Should have navigated to references as a fallback after empty GoToDefinition response"
16435    );
16436    // We should not change the selections in the existing file,
16437    // if opening another milti buffer with the references
16438    cx.assert_editor_state(
16439        &r#"fn one() {
16440            let mut a = two();
16441        }
16442
16443        fn «twoˇ»() {}"#
16444            .unindent(),
16445    );
16446    let editors = cx.update_workspace(|workspace, _, cx| {
16447        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
16448    });
16449    cx.update_editor(|_, _, test_editor_cx| {
16450        assert_eq!(
16451            editors.len(),
16452            2,
16453            "After falling back to references search, we open a new editor with the results"
16454        );
16455        let references_fallback_text = editors
16456            .into_iter()
16457            .find(|new_editor| *new_editor != test_editor_cx.entity())
16458            .expect("Should have one non-test editor now")
16459            .read(test_editor_cx)
16460            .text(test_editor_cx);
16461        assert_eq!(
16462            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
16463            "Should use the range from the references response and not the GoToDefinition one"
16464        );
16465    });
16466}
16467
16468#[gpui::test]
16469async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
16470    init_test(cx, |_| {});
16471
16472    let language = Arc::new(Language::new(
16473        LanguageConfig::default(),
16474        Some(tree_sitter_rust::LANGUAGE.into()),
16475    ));
16476
16477    let text = r#"
16478        #[cfg(test)]
16479        mod tests() {
16480            #[test]
16481            fn runnable_1() {
16482                let a = 1;
16483            }
16484
16485            #[test]
16486            fn runnable_2() {
16487                let a = 1;
16488                let b = 2;
16489            }
16490        }
16491    "#
16492    .unindent();
16493
16494    let fs = FakeFs::new(cx.executor());
16495    fs.insert_file("/file.rs", Default::default()).await;
16496
16497    let project = Project::test(fs, ["/a".as_ref()], cx).await;
16498    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16499    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16500    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16501    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
16502
16503    let editor = cx.new_window_entity(|window, cx| {
16504        Editor::new(
16505            EditorMode::Full,
16506            multi_buffer,
16507            Some(project.clone()),
16508            window,
16509            cx,
16510        )
16511    });
16512
16513    editor.update_in(cx, |editor, window, cx| {
16514        let snapshot = editor.buffer().read(cx).snapshot(cx);
16515        editor.tasks.insert(
16516            (buffer.read(cx).remote_id(), 3),
16517            RunnableTasks {
16518                templates: vec![],
16519                offset: snapshot.anchor_before(43),
16520                column: 0,
16521                extra_variables: HashMap::default(),
16522                context_range: BufferOffset(43)..BufferOffset(85),
16523            },
16524        );
16525        editor.tasks.insert(
16526            (buffer.read(cx).remote_id(), 8),
16527            RunnableTasks {
16528                templates: vec![],
16529                offset: snapshot.anchor_before(86),
16530                column: 0,
16531                extra_variables: HashMap::default(),
16532                context_range: BufferOffset(86)..BufferOffset(191),
16533            },
16534        );
16535
16536        // Test finding task when cursor is inside function body
16537        editor.change_selections(None, window, cx, |s| {
16538            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
16539        });
16540        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16541        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
16542
16543        // Test finding task when cursor is on function name
16544        editor.change_selections(None, window, cx, |s| {
16545            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
16546        });
16547        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
16548        assert_eq!(row, 8, "Should find task when cursor is on function name");
16549    });
16550}
16551
16552#[gpui::test]
16553async fn test_folding_buffers(cx: &mut TestAppContext) {
16554    init_test(cx, |_| {});
16555
16556    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16557    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
16558    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
16559
16560    let fs = FakeFs::new(cx.executor());
16561    fs.insert_tree(
16562        path!("/a"),
16563        json!({
16564            "first.rs": sample_text_1,
16565            "second.rs": sample_text_2,
16566            "third.rs": sample_text_3,
16567        }),
16568    )
16569    .await;
16570    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16571    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16572    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16573    let worktree = project.update(cx, |project, cx| {
16574        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16575        assert_eq!(worktrees.len(), 1);
16576        worktrees.pop().unwrap()
16577    });
16578    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16579
16580    let buffer_1 = project
16581        .update(cx, |project, cx| {
16582            project.open_buffer((worktree_id, "first.rs"), cx)
16583        })
16584        .await
16585        .unwrap();
16586    let buffer_2 = project
16587        .update(cx, |project, cx| {
16588            project.open_buffer((worktree_id, "second.rs"), cx)
16589        })
16590        .await
16591        .unwrap();
16592    let buffer_3 = project
16593        .update(cx, |project, cx| {
16594            project.open_buffer((worktree_id, "third.rs"), cx)
16595        })
16596        .await
16597        .unwrap();
16598
16599    let multi_buffer = cx.new(|cx| {
16600        let mut multi_buffer = MultiBuffer::new(ReadWrite);
16601        multi_buffer.push_excerpts(
16602            buffer_1.clone(),
16603            [
16604                ExcerptRange {
16605                    context: Point::new(0, 0)..Point::new(3, 0),
16606                    primary: None,
16607                },
16608                ExcerptRange {
16609                    context: Point::new(5, 0)..Point::new(7, 0),
16610                    primary: None,
16611                },
16612                ExcerptRange {
16613                    context: Point::new(9, 0)..Point::new(10, 4),
16614                    primary: None,
16615                },
16616            ],
16617            cx,
16618        );
16619        multi_buffer.push_excerpts(
16620            buffer_2.clone(),
16621            [
16622                ExcerptRange {
16623                    context: Point::new(0, 0)..Point::new(3, 0),
16624                    primary: None,
16625                },
16626                ExcerptRange {
16627                    context: Point::new(5, 0)..Point::new(7, 0),
16628                    primary: None,
16629                },
16630                ExcerptRange {
16631                    context: Point::new(9, 0)..Point::new(10, 4),
16632                    primary: None,
16633                },
16634            ],
16635            cx,
16636        );
16637        multi_buffer.push_excerpts(
16638            buffer_3.clone(),
16639            [
16640                ExcerptRange {
16641                    context: Point::new(0, 0)..Point::new(3, 0),
16642                    primary: None,
16643                },
16644                ExcerptRange {
16645                    context: Point::new(5, 0)..Point::new(7, 0),
16646                    primary: None,
16647                },
16648                ExcerptRange {
16649                    context: Point::new(9, 0)..Point::new(10, 4),
16650                    primary: None,
16651                },
16652            ],
16653            cx,
16654        );
16655        multi_buffer
16656    });
16657    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16658        Editor::new(
16659            EditorMode::Full,
16660            multi_buffer.clone(),
16661            Some(project.clone()),
16662            window,
16663            cx,
16664        )
16665    });
16666
16667    assert_eq!(
16668        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16669        "\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",
16670    );
16671
16672    multi_buffer_editor.update(cx, |editor, cx| {
16673        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16674    });
16675    assert_eq!(
16676        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16677        "\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",
16678        "After folding the first buffer, its text should not be displayed"
16679    );
16680
16681    multi_buffer_editor.update(cx, |editor, cx| {
16682        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16683    });
16684    assert_eq!(
16685        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16686        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
16687        "After folding the second buffer, its text should not be displayed"
16688    );
16689
16690    multi_buffer_editor.update(cx, |editor, cx| {
16691        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16692    });
16693    assert_eq!(
16694        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16695        "\n\n\n\n\n",
16696        "After folding the third buffer, its text should not be displayed"
16697    );
16698
16699    // Emulate selection inside the fold logic, that should work
16700    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16701        editor
16702            .snapshot(window, cx)
16703            .next_line_boundary(Point::new(0, 4));
16704    });
16705
16706    multi_buffer_editor.update(cx, |editor, cx| {
16707        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16708    });
16709    assert_eq!(
16710        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16711        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16712        "After unfolding the second buffer, its text should be displayed"
16713    );
16714
16715    // Typing inside of buffer 1 causes that buffer to be unfolded.
16716    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16717        assert_eq!(
16718            multi_buffer
16719                .read(cx)
16720                .snapshot(cx)
16721                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
16722                .collect::<String>(),
16723            "bbbb"
16724        );
16725        editor.change_selections(None, window, cx, |selections| {
16726            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
16727        });
16728        editor.handle_input("B", window, cx);
16729    });
16730
16731    assert_eq!(
16732        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16733        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
16734        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
16735    );
16736
16737    multi_buffer_editor.update(cx, |editor, cx| {
16738        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16739    });
16740    assert_eq!(
16741        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16742        "\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",
16743        "After unfolding the all buffers, all original text should be displayed"
16744    );
16745}
16746
16747#[gpui::test]
16748async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
16749    init_test(cx, |_| {});
16750
16751    let sample_text_1 = "1111\n2222\n3333".to_string();
16752    let sample_text_2 = "4444\n5555\n6666".to_string();
16753    let sample_text_3 = "7777\n8888\n9999".to_string();
16754
16755    let fs = FakeFs::new(cx.executor());
16756    fs.insert_tree(
16757        path!("/a"),
16758        json!({
16759            "first.rs": sample_text_1,
16760            "second.rs": sample_text_2,
16761            "third.rs": sample_text_3,
16762        }),
16763    )
16764    .await;
16765    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16766    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16767    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16768    let worktree = project.update(cx, |project, cx| {
16769        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16770        assert_eq!(worktrees.len(), 1);
16771        worktrees.pop().unwrap()
16772    });
16773    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16774
16775    let buffer_1 = project
16776        .update(cx, |project, cx| {
16777            project.open_buffer((worktree_id, "first.rs"), cx)
16778        })
16779        .await
16780        .unwrap();
16781    let buffer_2 = project
16782        .update(cx, |project, cx| {
16783            project.open_buffer((worktree_id, "second.rs"), cx)
16784        })
16785        .await
16786        .unwrap();
16787    let buffer_3 = project
16788        .update(cx, |project, cx| {
16789            project.open_buffer((worktree_id, "third.rs"), cx)
16790        })
16791        .await
16792        .unwrap();
16793
16794    let multi_buffer = cx.new(|cx| {
16795        let mut multi_buffer = MultiBuffer::new(ReadWrite);
16796        multi_buffer.push_excerpts(
16797            buffer_1.clone(),
16798            [ExcerptRange {
16799                context: Point::new(0, 0)..Point::new(3, 0),
16800                primary: None,
16801            }],
16802            cx,
16803        );
16804        multi_buffer.push_excerpts(
16805            buffer_2.clone(),
16806            [ExcerptRange {
16807                context: Point::new(0, 0)..Point::new(3, 0),
16808                primary: None,
16809            }],
16810            cx,
16811        );
16812        multi_buffer.push_excerpts(
16813            buffer_3.clone(),
16814            [ExcerptRange {
16815                context: Point::new(0, 0)..Point::new(3, 0),
16816                primary: None,
16817            }],
16818            cx,
16819        );
16820        multi_buffer
16821    });
16822
16823    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16824        Editor::new(
16825            EditorMode::Full,
16826            multi_buffer,
16827            Some(project.clone()),
16828            window,
16829            cx,
16830        )
16831    });
16832
16833    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
16834    assert_eq!(
16835        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16836        full_text,
16837    );
16838
16839    multi_buffer_editor.update(cx, |editor, cx| {
16840        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
16841    });
16842    assert_eq!(
16843        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16844        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
16845        "After folding the first buffer, its text should not be displayed"
16846    );
16847
16848    multi_buffer_editor.update(cx, |editor, cx| {
16849        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
16850    });
16851
16852    assert_eq!(
16853        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16854        "\n\n\n\n\n\n7777\n8888\n9999",
16855        "After folding the second buffer, its text should not be displayed"
16856    );
16857
16858    multi_buffer_editor.update(cx, |editor, cx| {
16859        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
16860    });
16861    assert_eq!(
16862        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16863        "\n\n\n\n\n",
16864        "After folding the third buffer, its text should not be displayed"
16865    );
16866
16867    multi_buffer_editor.update(cx, |editor, cx| {
16868        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
16869    });
16870    assert_eq!(
16871        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16872        "\n\n\n\n4444\n5555\n6666\n\n",
16873        "After unfolding the second buffer, its text should be displayed"
16874    );
16875
16876    multi_buffer_editor.update(cx, |editor, cx| {
16877        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
16878    });
16879    assert_eq!(
16880        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16881        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
16882        "After unfolding the first buffer, its text should be displayed"
16883    );
16884
16885    multi_buffer_editor.update(cx, |editor, cx| {
16886        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
16887    });
16888    assert_eq!(
16889        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16890        full_text,
16891        "After unfolding all buffers, all original text should be displayed"
16892    );
16893}
16894
16895#[gpui::test]
16896async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
16897    init_test(cx, |_| {});
16898
16899    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
16900
16901    let fs = FakeFs::new(cx.executor());
16902    fs.insert_tree(
16903        path!("/a"),
16904        json!({
16905            "main.rs": sample_text,
16906        }),
16907    )
16908    .await;
16909    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16910    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16911    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16912    let worktree = project.update(cx, |project, cx| {
16913        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
16914        assert_eq!(worktrees.len(), 1);
16915        worktrees.pop().unwrap()
16916    });
16917    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
16918
16919    let buffer_1 = project
16920        .update(cx, |project, cx| {
16921            project.open_buffer((worktree_id, "main.rs"), cx)
16922        })
16923        .await
16924        .unwrap();
16925
16926    let multi_buffer = cx.new(|cx| {
16927        let mut multi_buffer = MultiBuffer::new(ReadWrite);
16928        multi_buffer.push_excerpts(
16929            buffer_1.clone(),
16930            [ExcerptRange {
16931                context: Point::new(0, 0)
16932                    ..Point::new(
16933                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
16934                        0,
16935                    ),
16936                primary: None,
16937            }],
16938            cx,
16939        );
16940        multi_buffer
16941    });
16942    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
16943        Editor::new(
16944            EditorMode::Full,
16945            multi_buffer,
16946            Some(project.clone()),
16947            window,
16948            cx,
16949        )
16950    });
16951
16952    let selection_range = Point::new(1, 0)..Point::new(2, 0);
16953    multi_buffer_editor.update_in(cx, |editor, window, cx| {
16954        enum TestHighlight {}
16955        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
16956        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
16957        editor.highlight_text::<TestHighlight>(
16958            vec![highlight_range.clone()],
16959            HighlightStyle::color(Hsla::green()),
16960            cx,
16961        );
16962        editor.change_selections(None, window, cx, |s| s.select_ranges(Some(highlight_range)));
16963    });
16964
16965    let full_text = format!("\n\n{sample_text}");
16966    assert_eq!(
16967        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
16968        full_text,
16969    );
16970}
16971
16972#[gpui::test]
16973async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
16974    init_test(cx, |_| {});
16975    cx.update(|cx| {
16976        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
16977            "keymaps/default-linux.json",
16978            cx,
16979        )
16980        .unwrap();
16981        cx.bind_keys(default_key_bindings);
16982    });
16983
16984    let (editor, cx) = cx.add_window_view(|window, cx| {
16985        let multi_buffer = MultiBuffer::build_multi(
16986            [
16987                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
16988                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
16989                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
16990                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
16991            ],
16992            cx,
16993        );
16994        let mut editor = Editor::new(EditorMode::Full, multi_buffer.clone(), None, window, cx);
16995
16996        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
16997        // fold all but the second buffer, so that we test navigating between two
16998        // adjacent folded buffers, as well as folded buffers at the start and
16999        // end the multibuffer
17000        editor.fold_buffer(buffer_ids[0], cx);
17001        editor.fold_buffer(buffer_ids[2], cx);
17002        editor.fold_buffer(buffer_ids[3], cx);
17003
17004        editor
17005    });
17006    cx.simulate_resize(size(px(1000.), px(1000.)));
17007
17008    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
17009    cx.assert_excerpts_with_selections(indoc! {"
17010        [EXCERPT]
17011        ˇ[FOLDED]
17012        [EXCERPT]
17013        a1
17014        b1
17015        [EXCERPT]
17016        [FOLDED]
17017        [EXCERPT]
17018        [FOLDED]
17019        "
17020    });
17021    cx.simulate_keystroke("down");
17022    cx.assert_excerpts_with_selections(indoc! {"
17023        [EXCERPT]
17024        [FOLDED]
17025        [EXCERPT]
17026        ˇa1
17027        b1
17028        [EXCERPT]
17029        [FOLDED]
17030        [EXCERPT]
17031        [FOLDED]
17032        "
17033    });
17034    cx.simulate_keystroke("down");
17035    cx.assert_excerpts_with_selections(indoc! {"
17036        [EXCERPT]
17037        [FOLDED]
17038        [EXCERPT]
17039        a1
17040        ˇb1
17041        [EXCERPT]
17042        [FOLDED]
17043        [EXCERPT]
17044        [FOLDED]
17045        "
17046    });
17047    cx.simulate_keystroke("down");
17048    cx.assert_excerpts_with_selections(indoc! {"
17049        [EXCERPT]
17050        [FOLDED]
17051        [EXCERPT]
17052        a1
17053        b1
17054        ˇ[EXCERPT]
17055        [FOLDED]
17056        [EXCERPT]
17057        [FOLDED]
17058        "
17059    });
17060    cx.simulate_keystroke("down");
17061    cx.assert_excerpts_with_selections(indoc! {"
17062        [EXCERPT]
17063        [FOLDED]
17064        [EXCERPT]
17065        a1
17066        b1
17067        [EXCERPT]
17068        ˇ[FOLDED]
17069        [EXCERPT]
17070        [FOLDED]
17071        "
17072    });
17073    for _ in 0..5 {
17074        cx.simulate_keystroke("down");
17075        cx.assert_excerpts_with_selections(indoc! {"
17076            [EXCERPT]
17077            [FOLDED]
17078            [EXCERPT]
17079            a1
17080            b1
17081            [EXCERPT]
17082            [FOLDED]
17083            [EXCERPT]
17084            ˇ[FOLDED]
17085            "
17086        });
17087    }
17088
17089    cx.simulate_keystroke("up");
17090    cx.assert_excerpts_with_selections(indoc! {"
17091        [EXCERPT]
17092        [FOLDED]
17093        [EXCERPT]
17094        a1
17095        b1
17096        [EXCERPT]
17097        ˇ[FOLDED]
17098        [EXCERPT]
17099        [FOLDED]
17100        "
17101    });
17102    cx.simulate_keystroke("up");
17103    cx.assert_excerpts_with_selections(indoc! {"
17104        [EXCERPT]
17105        [FOLDED]
17106        [EXCERPT]
17107        a1
17108        b1
17109        ˇ[EXCERPT]
17110        [FOLDED]
17111        [EXCERPT]
17112        [FOLDED]
17113        "
17114    });
17115    cx.simulate_keystroke("up");
17116    cx.assert_excerpts_with_selections(indoc! {"
17117        [EXCERPT]
17118        [FOLDED]
17119        [EXCERPT]
17120        a1
17121        ˇb1
17122        [EXCERPT]
17123        [FOLDED]
17124        [EXCERPT]
17125        [FOLDED]
17126        "
17127    });
17128    cx.simulate_keystroke("up");
17129    cx.assert_excerpts_with_selections(indoc! {"
17130        [EXCERPT]
17131        [FOLDED]
17132        [EXCERPT]
17133        ˇa1
17134        b1
17135        [EXCERPT]
17136        [FOLDED]
17137        [EXCERPT]
17138        [FOLDED]
17139        "
17140    });
17141    for _ in 0..5 {
17142        cx.simulate_keystroke("up");
17143        cx.assert_excerpts_with_selections(indoc! {"
17144            [EXCERPT]
17145            ˇ[FOLDED]
17146            [EXCERPT]
17147            a1
17148            b1
17149            [EXCERPT]
17150            [FOLDED]
17151            [EXCERPT]
17152            [FOLDED]
17153            "
17154        });
17155    }
17156}
17157
17158#[gpui::test]
17159async fn test_inline_completion_text(cx: &mut TestAppContext) {
17160    init_test(cx, |_| {});
17161
17162    // Simple insertion
17163    assert_highlighted_edits(
17164        "Hello, world!",
17165        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
17166        true,
17167        cx,
17168        |highlighted_edits, cx| {
17169            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
17170            assert_eq!(highlighted_edits.highlights.len(), 1);
17171            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
17172            assert_eq!(
17173                highlighted_edits.highlights[0].1.background_color,
17174                Some(cx.theme().status().created_background)
17175            );
17176        },
17177    )
17178    .await;
17179
17180    // Replacement
17181    assert_highlighted_edits(
17182        "This is a test.",
17183        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
17184        false,
17185        cx,
17186        |highlighted_edits, cx| {
17187            assert_eq!(highlighted_edits.text, "That is a test.");
17188            assert_eq!(highlighted_edits.highlights.len(), 1);
17189            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
17190            assert_eq!(
17191                highlighted_edits.highlights[0].1.background_color,
17192                Some(cx.theme().status().created_background)
17193            );
17194        },
17195    )
17196    .await;
17197
17198    // Multiple edits
17199    assert_highlighted_edits(
17200        "Hello, world!",
17201        vec![
17202            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
17203            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
17204        ],
17205        false,
17206        cx,
17207        |highlighted_edits, cx| {
17208            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
17209            assert_eq!(highlighted_edits.highlights.len(), 2);
17210            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
17211            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
17212            assert_eq!(
17213                highlighted_edits.highlights[0].1.background_color,
17214                Some(cx.theme().status().created_background)
17215            );
17216            assert_eq!(
17217                highlighted_edits.highlights[1].1.background_color,
17218                Some(cx.theme().status().created_background)
17219            );
17220        },
17221    )
17222    .await;
17223
17224    // Multiple lines with edits
17225    assert_highlighted_edits(
17226        "First line\nSecond line\nThird line\nFourth line",
17227        vec![
17228            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
17229            (
17230                Point::new(2, 0)..Point::new(2, 10),
17231                "New third line".to_string(),
17232            ),
17233            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
17234        ],
17235        false,
17236        cx,
17237        |highlighted_edits, cx| {
17238            assert_eq!(
17239                highlighted_edits.text,
17240                "Second modified\nNew third line\nFourth updated line"
17241            );
17242            assert_eq!(highlighted_edits.highlights.len(), 3);
17243            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
17244            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
17245            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
17246            for highlight in &highlighted_edits.highlights {
17247                assert_eq!(
17248                    highlight.1.background_color,
17249                    Some(cx.theme().status().created_background)
17250                );
17251            }
17252        },
17253    )
17254    .await;
17255}
17256
17257#[gpui::test]
17258async fn test_inline_completion_text_with_deletions(cx: &mut TestAppContext) {
17259    init_test(cx, |_| {});
17260
17261    // Deletion
17262    assert_highlighted_edits(
17263        "Hello, world!",
17264        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
17265        true,
17266        cx,
17267        |highlighted_edits, cx| {
17268            assert_eq!(highlighted_edits.text, "Hello, world!");
17269            assert_eq!(highlighted_edits.highlights.len(), 1);
17270            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
17271            assert_eq!(
17272                highlighted_edits.highlights[0].1.background_color,
17273                Some(cx.theme().status().deleted_background)
17274            );
17275        },
17276    )
17277    .await;
17278
17279    // Insertion
17280    assert_highlighted_edits(
17281        "Hello, world!",
17282        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
17283        true,
17284        cx,
17285        |highlighted_edits, cx| {
17286            assert_eq!(highlighted_edits.highlights.len(), 1);
17287            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
17288            assert_eq!(
17289                highlighted_edits.highlights[0].1.background_color,
17290                Some(cx.theme().status().created_background)
17291            );
17292        },
17293    )
17294    .await;
17295}
17296
17297async fn assert_highlighted_edits(
17298    text: &str,
17299    edits: Vec<(Range<Point>, String)>,
17300    include_deletions: bool,
17301    cx: &mut TestAppContext,
17302    assertion_fn: impl Fn(HighlightedText, &App),
17303) {
17304    let window = cx.add_window(|window, cx| {
17305        let buffer = MultiBuffer::build_simple(text, cx);
17306        Editor::new(EditorMode::Full, buffer, None, window, cx)
17307    });
17308    let cx = &mut VisualTestContext::from_window(*window, cx);
17309
17310    let (buffer, snapshot) = window
17311        .update(cx, |editor, _window, cx| {
17312            (
17313                editor.buffer().clone(),
17314                editor.buffer().read(cx).snapshot(cx),
17315            )
17316        })
17317        .unwrap();
17318
17319    let edits = edits
17320        .into_iter()
17321        .map(|(range, edit)| {
17322            (
17323                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
17324                edit,
17325            )
17326        })
17327        .collect::<Vec<_>>();
17328
17329    let text_anchor_edits = edits
17330        .clone()
17331        .into_iter()
17332        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
17333        .collect::<Vec<_>>();
17334
17335    let edit_preview = window
17336        .update(cx, |_, _window, cx| {
17337            buffer
17338                .read(cx)
17339                .as_singleton()
17340                .unwrap()
17341                .read(cx)
17342                .preview_edits(text_anchor_edits.into(), cx)
17343        })
17344        .unwrap()
17345        .await;
17346
17347    cx.update(|_window, cx| {
17348        let highlighted_edits = inline_completion_edit_text(
17349            &snapshot.as_singleton().unwrap().2,
17350            &edits,
17351            &edit_preview,
17352            include_deletions,
17353            cx,
17354        );
17355        assertion_fn(highlighted_edits, cx)
17356    });
17357}
17358
17359#[track_caller]
17360fn assert_breakpoint(
17361    breakpoints: &BTreeMap<Arc<Path>, Vec<SerializedBreakpoint>>,
17362    path: &Arc<Path>,
17363    expected: Vec<(u32, BreakpointKind)>,
17364) {
17365    if expected.len() == 0usize {
17366        assert!(!breakpoints.contains_key(path));
17367    } else {
17368        let mut breakpoint = breakpoints
17369            .get(path)
17370            .unwrap()
17371            .into_iter()
17372            .map(|breakpoint| (breakpoint.position, breakpoint.kind.clone()))
17373            .collect::<Vec<_>>();
17374
17375        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
17376
17377        assert_eq!(expected, breakpoint);
17378    }
17379}
17380
17381fn add_log_breakpoint_at_cursor(
17382    editor: &mut Editor,
17383    log_message: &str,
17384    window: &mut Window,
17385    cx: &mut Context<Editor>,
17386) {
17387    let (anchor, bp) = editor
17388        .breakpoint_at_cursor_head(window, cx)
17389        .unwrap_or_else(|| {
17390            let cursor_position: Point = editor.selections.newest(cx).head();
17391
17392            let breakpoint_position = editor
17393                .snapshot(window, cx)
17394                .display_snapshot
17395                .buffer_snapshot
17396                .anchor_before(Point::new(cursor_position.row, 0));
17397
17398            let kind = BreakpointKind::Log(Arc::from(log_message));
17399
17400            (breakpoint_position, Breakpoint { kind })
17401        });
17402
17403    editor.edit_breakpoint_at_anchor(
17404        anchor,
17405        bp.kind,
17406        BreakpointEditAction::EditLogMessage(log_message.into()),
17407        cx,
17408    );
17409}
17410
17411#[gpui::test]
17412async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
17413    init_test(cx, |_| {});
17414
17415    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
17416    let fs = FakeFs::new(cx.executor());
17417    fs.insert_tree(
17418        path!("/a"),
17419        json!({
17420            "main.rs": sample_text,
17421        }),
17422    )
17423    .await;
17424    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17425    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17426    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17427
17428    let fs = FakeFs::new(cx.executor());
17429    fs.insert_tree(
17430        path!("/a"),
17431        json!({
17432            "main.rs": sample_text,
17433        }),
17434    )
17435    .await;
17436    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17437    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17438    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17439    let worktree_id = workspace
17440        .update(cx, |workspace, _window, cx| {
17441            workspace.project().update(cx, |project, cx| {
17442                project.worktrees(cx).next().unwrap().read(cx).id()
17443            })
17444        })
17445        .unwrap();
17446
17447    let buffer = project
17448        .update(cx, |project, cx| {
17449            project.open_buffer((worktree_id, "main.rs"), cx)
17450        })
17451        .await
17452        .unwrap();
17453
17454    let (editor, cx) = cx.add_window_view(|window, cx| {
17455        Editor::new(
17456            EditorMode::Full,
17457            MultiBuffer::build_from_buffer(buffer, cx),
17458            Some(project.clone()),
17459            window,
17460            cx,
17461        )
17462    });
17463
17464    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
17465    let abs_path = project.read_with(cx, |project, cx| {
17466        project
17467            .absolute_path(&project_path, cx)
17468            .map(|path_buf| Arc::from(path_buf.to_owned()))
17469            .unwrap()
17470    });
17471
17472    // assert we can add breakpoint on the first line
17473    editor.update_in(cx, |editor, window, cx| {
17474        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17475        editor.move_to_end(&MoveToEnd, window, cx);
17476        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17477    });
17478
17479    let breakpoints = editor.update(cx, |editor, cx| {
17480        editor
17481            .breakpoint_store()
17482            .as_ref()
17483            .unwrap()
17484            .read(cx)
17485            .all_breakpoints(cx)
17486            .clone()
17487    });
17488
17489    assert_eq!(1, breakpoints.len());
17490    assert_breakpoint(
17491        &breakpoints,
17492        &abs_path,
17493        vec![(0, BreakpointKind::Standard), (3, BreakpointKind::Standard)],
17494    );
17495
17496    editor.update_in(cx, |editor, window, cx| {
17497        editor.move_to_beginning(&MoveToBeginning, window, cx);
17498        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17499    });
17500
17501    let breakpoints = editor.update(cx, |editor, cx| {
17502        editor
17503            .breakpoint_store()
17504            .as_ref()
17505            .unwrap()
17506            .read(cx)
17507            .all_breakpoints(cx)
17508            .clone()
17509    });
17510
17511    assert_eq!(1, breakpoints.len());
17512    assert_breakpoint(&breakpoints, &abs_path, vec![(3, BreakpointKind::Standard)]);
17513
17514    editor.update_in(cx, |editor, window, cx| {
17515        editor.move_to_end(&MoveToEnd, window, cx);
17516        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17517    });
17518
17519    let breakpoints = editor.update(cx, |editor, cx| {
17520        editor
17521            .breakpoint_store()
17522            .as_ref()
17523            .unwrap()
17524            .read(cx)
17525            .all_breakpoints(cx)
17526            .clone()
17527    });
17528
17529    assert_eq!(0, breakpoints.len());
17530    assert_breakpoint(&breakpoints, &abs_path, vec![]);
17531}
17532
17533#[gpui::test]
17534async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
17535    init_test(cx, |_| {});
17536
17537    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
17538
17539    let fs = FakeFs::new(cx.executor());
17540    fs.insert_tree(
17541        path!("/a"),
17542        json!({
17543            "main.rs": sample_text,
17544        }),
17545    )
17546    .await;
17547    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17548    let (workspace, cx) =
17549        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
17550
17551    let worktree_id = workspace.update(cx, |workspace, cx| {
17552        workspace.project().update(cx, |project, cx| {
17553            project.worktrees(cx).next().unwrap().read(cx).id()
17554        })
17555    });
17556
17557    let buffer = project
17558        .update(cx, |project, cx| {
17559            project.open_buffer((worktree_id, "main.rs"), cx)
17560        })
17561        .await
17562        .unwrap();
17563
17564    let (editor, cx) = cx.add_window_view(|window, cx| {
17565        Editor::new(
17566            EditorMode::Full,
17567            MultiBuffer::build_from_buffer(buffer, cx),
17568            Some(project.clone()),
17569            window,
17570            cx,
17571        )
17572    });
17573
17574    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
17575    let abs_path = project.read_with(cx, |project, cx| {
17576        project
17577            .absolute_path(&project_path, cx)
17578            .map(|path_buf| Arc::from(path_buf.to_owned()))
17579            .unwrap()
17580    });
17581
17582    editor.update_in(cx, |editor, window, cx| {
17583        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
17584    });
17585
17586    let breakpoints = editor.update(cx, |editor, cx| {
17587        editor
17588            .breakpoint_store()
17589            .as_ref()
17590            .unwrap()
17591            .read(cx)
17592            .all_breakpoints(cx)
17593            .clone()
17594    });
17595
17596    assert_breakpoint(
17597        &breakpoints,
17598        &abs_path,
17599        vec![(0, BreakpointKind::Log("hello world".into()))],
17600    );
17601
17602    // Removing a log message from a log breakpoint should remove it
17603    editor.update_in(cx, |editor, window, cx| {
17604        add_log_breakpoint_at_cursor(editor, "", window, cx);
17605    });
17606
17607    let breakpoints = editor.update(cx, |editor, cx| {
17608        editor
17609            .breakpoint_store()
17610            .as_ref()
17611            .unwrap()
17612            .read(cx)
17613            .all_breakpoints(cx)
17614            .clone()
17615    });
17616
17617    assert_breakpoint(&breakpoints, &abs_path, vec![]);
17618
17619    editor.update_in(cx, |editor, window, cx| {
17620        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17621        editor.move_to_end(&MoveToEnd, window, cx);
17622        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
17623        // Not adding a log message to a standard breakpoint shouldn't remove it
17624        add_log_breakpoint_at_cursor(editor, "", window, cx);
17625    });
17626
17627    let breakpoints = editor.update(cx, |editor, cx| {
17628        editor
17629            .breakpoint_store()
17630            .as_ref()
17631            .unwrap()
17632            .read(cx)
17633            .all_breakpoints(cx)
17634            .clone()
17635    });
17636
17637    assert_breakpoint(
17638        &breakpoints,
17639        &abs_path,
17640        vec![(0, BreakpointKind::Standard), (3, BreakpointKind::Standard)],
17641    );
17642
17643    editor.update_in(cx, |editor, window, cx| {
17644        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
17645    });
17646
17647    let breakpoints = editor.update(cx, |editor, cx| {
17648        editor
17649            .breakpoint_store()
17650            .as_ref()
17651            .unwrap()
17652            .read(cx)
17653            .all_breakpoints(cx)
17654            .clone()
17655    });
17656
17657    assert_breakpoint(
17658        &breakpoints,
17659        &abs_path,
17660        vec![
17661            (0, BreakpointKind::Standard),
17662            (3, BreakpointKind::Log("hello world".into())),
17663        ],
17664    );
17665
17666    editor.update_in(cx, |editor, window, cx| {
17667        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
17668    });
17669
17670    let breakpoints = editor.update(cx, |editor, cx| {
17671        editor
17672            .breakpoint_store()
17673            .as_ref()
17674            .unwrap()
17675            .read(cx)
17676            .all_breakpoints(cx)
17677            .clone()
17678    });
17679
17680    assert_breakpoint(
17681        &breakpoints,
17682        &abs_path,
17683        vec![
17684            (0, BreakpointKind::Standard),
17685            (3, BreakpointKind::Log("hello Earth !!".into())),
17686        ],
17687    );
17688}
17689
17690#[gpui::test]
17691async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
17692    init_test(cx, |_| {});
17693    let capabilities = lsp::ServerCapabilities {
17694        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
17695            prepare_provider: Some(true),
17696            work_done_progress_options: Default::default(),
17697        })),
17698        ..Default::default()
17699    };
17700    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17701
17702    cx.set_state(indoc! {"
17703        struct Fˇoo {}
17704    "});
17705
17706    cx.update_editor(|editor, _, cx| {
17707        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17708        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17709        editor.highlight_background::<DocumentHighlightRead>(
17710            &[highlight_range],
17711            |c| c.editor_document_highlight_read_background,
17712            cx,
17713        );
17714    });
17715
17716    let mut prepare_rename_handler =
17717        cx.handle_request::<lsp::request::PrepareRenameRequest, _, _>(move |_, _, _| async move {
17718            Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
17719                start: lsp::Position {
17720                    line: 0,
17721                    character: 7,
17722                },
17723                end: lsp::Position {
17724                    line: 0,
17725                    character: 10,
17726                },
17727            })))
17728        });
17729    let prepare_rename_task = cx
17730        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17731        .expect("Prepare rename was not started");
17732    prepare_rename_handler.next().await.unwrap();
17733    prepare_rename_task.await.expect("Prepare rename failed");
17734
17735    let mut rename_handler =
17736        cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17737            let edit = lsp::TextEdit {
17738                range: lsp::Range {
17739                    start: lsp::Position {
17740                        line: 0,
17741                        character: 7,
17742                    },
17743                    end: lsp::Position {
17744                        line: 0,
17745                        character: 10,
17746                    },
17747                },
17748                new_text: "FooRenamed".to_string(),
17749            };
17750            Ok(Some(lsp::WorkspaceEdit::new(
17751                // Specify the same edit twice
17752                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
17753            )))
17754        });
17755    let rename_task = cx
17756        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17757        .expect("Confirm rename was not started");
17758    rename_handler.next().await.unwrap();
17759    rename_task.await.expect("Confirm rename failed");
17760    cx.run_until_parked();
17761
17762    // Despite two edits, only one is actually applied as those are identical
17763    cx.assert_editor_state(indoc! {"
17764        struct FooRenamedˇ {}
17765    "});
17766}
17767
17768#[gpui::test]
17769async fn test_rename_without_prepare(cx: &mut TestAppContext) {
17770    init_test(cx, |_| {});
17771    // These capabilities indicate that the server does not support prepare rename.
17772    let capabilities = lsp::ServerCapabilities {
17773        rename_provider: Some(lsp::OneOf::Left(true)),
17774        ..Default::default()
17775    };
17776    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
17777
17778    cx.set_state(indoc! {"
17779        struct Fˇoo {}
17780    "});
17781
17782    cx.update_editor(|editor, _window, cx| {
17783        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
17784        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
17785        editor.highlight_background::<DocumentHighlightRead>(
17786            &[highlight_range],
17787            |c| c.editor_document_highlight_read_background,
17788            cx,
17789        );
17790    });
17791
17792    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
17793        .expect("Prepare rename was not started")
17794        .await
17795        .expect("Prepare rename failed");
17796
17797    let mut rename_handler =
17798        cx.handle_request::<lsp::request::Rename, _, _>(move |url, _, _| async move {
17799            let edit = lsp::TextEdit {
17800                range: lsp::Range {
17801                    start: lsp::Position {
17802                        line: 0,
17803                        character: 7,
17804                    },
17805                    end: lsp::Position {
17806                        line: 0,
17807                        character: 10,
17808                    },
17809                },
17810                new_text: "FooRenamed".to_string(),
17811            };
17812            Ok(Some(lsp::WorkspaceEdit::new(
17813                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
17814            )))
17815        });
17816    let rename_task = cx
17817        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
17818        .expect("Confirm rename was not started");
17819    rename_handler.next().await.unwrap();
17820    rename_task.await.expect("Confirm rename failed");
17821    cx.run_until_parked();
17822
17823    // Correct range is renamed, as `surrounding_word` is used to find it.
17824    cx.assert_editor_state(indoc! {"
17825        struct FooRenamedˇ {}
17826    "});
17827}
17828
17829#[gpui::test]
17830async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
17831    init_test(cx, |_| {});
17832    let mut cx = EditorTestContext::new(cx).await;
17833
17834    let language = Arc::new(
17835        Language::new(
17836            LanguageConfig::default(),
17837            Some(tree_sitter_html::LANGUAGE.into()),
17838        )
17839        .with_brackets_query(
17840            r#"
17841            ("<" @open "/>" @close)
17842            ("</" @open ">" @close)
17843            ("<" @open ">" @close)
17844            ("\"" @open "\"" @close)
17845            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
17846        "#,
17847        )
17848        .unwrap(),
17849    );
17850    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
17851
17852    cx.set_state(indoc! {"
17853        <span>ˇ</span>
17854    "});
17855    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17856    cx.assert_editor_state(indoc! {"
17857        <span>
17858        ˇ
17859        </span>
17860    "});
17861
17862    cx.set_state(indoc! {"
17863        <span><span></span>ˇ</span>
17864    "});
17865    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17866    cx.assert_editor_state(indoc! {"
17867        <span><span></span>
17868        ˇ</span>
17869    "});
17870
17871    cx.set_state(indoc! {"
17872        <span>ˇ
17873        </span>
17874    "});
17875    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
17876    cx.assert_editor_state(indoc! {"
17877        <span>
17878        ˇ
17879        </span>
17880    "});
17881}
17882
17883#[gpui::test(iterations = 10)]
17884async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
17885    init_test(cx, |_| {});
17886
17887    let fs = FakeFs::new(cx.executor());
17888    fs.insert_tree(
17889        path!("/dir"),
17890        json!({
17891            "a.ts": "a",
17892        }),
17893    )
17894    .await;
17895
17896    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
17897    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17898    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17899
17900    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17901    language_registry.add(Arc::new(Language::new(
17902        LanguageConfig {
17903            name: "TypeScript".into(),
17904            matcher: LanguageMatcher {
17905                path_suffixes: vec!["ts".to_string()],
17906                ..Default::default()
17907            },
17908            ..Default::default()
17909        },
17910        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17911    )));
17912    let mut fake_language_servers = language_registry.register_fake_lsp(
17913        "TypeScript",
17914        FakeLspAdapter {
17915            capabilities: lsp::ServerCapabilities {
17916                code_lens_provider: Some(lsp::CodeLensOptions {
17917                    resolve_provider: Some(true),
17918                }),
17919                execute_command_provider: Some(lsp::ExecuteCommandOptions {
17920                    commands: vec!["_the/command".to_string()],
17921                    ..lsp::ExecuteCommandOptions::default()
17922                }),
17923                ..lsp::ServerCapabilities::default()
17924            },
17925            ..FakeLspAdapter::default()
17926        },
17927    );
17928
17929    let (buffer, _handle) = project
17930        .update(cx, |p, cx| {
17931            p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
17932        })
17933        .await
17934        .unwrap();
17935    cx.executor().run_until_parked();
17936
17937    let fake_server = fake_language_servers.next().await.unwrap();
17938
17939    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
17940    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
17941    drop(buffer_snapshot);
17942    let actions = cx
17943        .update_window(*workspace, |_, window, cx| {
17944            project.code_actions(&buffer, anchor..anchor, window, cx)
17945        })
17946        .unwrap();
17947
17948    fake_server
17949        .handle_request::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
17950            Ok(Some(vec![
17951                lsp::CodeLens {
17952                    range: lsp::Range::default(),
17953                    command: Some(lsp::Command {
17954                        title: "Code lens command".to_owned(),
17955                        command: "_the/command".to_owned(),
17956                        arguments: None,
17957                    }),
17958                    data: None,
17959                },
17960                lsp::CodeLens {
17961                    range: lsp::Range::default(),
17962                    command: Some(lsp::Command {
17963                        title: "Command not in capabilities".to_owned(),
17964                        command: "not in capabilities".to_owned(),
17965                        arguments: None,
17966                    }),
17967                    data: None,
17968                },
17969                lsp::CodeLens {
17970                    range: lsp::Range {
17971                        start: lsp::Position {
17972                            line: 1,
17973                            character: 1,
17974                        },
17975                        end: lsp::Position {
17976                            line: 1,
17977                            character: 1,
17978                        },
17979                    },
17980                    command: Some(lsp::Command {
17981                        title: "Command not in range".to_owned(),
17982                        command: "_the/command".to_owned(),
17983                        arguments: None,
17984                    }),
17985                    data: None,
17986                },
17987            ]))
17988        })
17989        .next()
17990        .await;
17991
17992    let actions = actions.await.unwrap();
17993    assert_eq!(
17994        actions.len(),
17995        1,
17996        "Should have only one valid action for the 0..0 range"
17997    );
17998    let action = actions[0].clone();
17999    let apply = project.update(cx, |project, cx| {
18000        project.apply_code_action(buffer.clone(), action, true, cx)
18001    });
18002
18003    // Resolving the code action does not populate its edits. In absence of
18004    // edits, we must execute the given command.
18005    fake_server.handle_request::<lsp::request::CodeLensResolve, _, _>(|mut lens, _| async move {
18006        let lens_command = lens.command.as_mut().expect("should have a command");
18007        assert_eq!(lens_command.title, "Code lens command");
18008        lens_command.arguments = Some(vec![json!("the-argument")]);
18009        Ok(lens)
18010    });
18011
18012    // While executing the command, the language server sends the editor
18013    // a `workspaceEdit` request.
18014    fake_server
18015        .handle_request::<lsp::request::ExecuteCommand, _, _>({
18016            let fake = fake_server.clone();
18017            move |params, _| {
18018                assert_eq!(params.command, "_the/command");
18019                let fake = fake.clone();
18020                async move {
18021                    fake.server
18022                        .request::<lsp::request::ApplyWorkspaceEdit>(
18023                            lsp::ApplyWorkspaceEditParams {
18024                                label: None,
18025                                edit: lsp::WorkspaceEdit {
18026                                    changes: Some(
18027                                        [(
18028                                            lsp::Url::from_file_path(path!("/dir/a.ts")).unwrap(),
18029                                            vec![lsp::TextEdit {
18030                                                range: lsp::Range::new(
18031                                                    lsp::Position::new(0, 0),
18032                                                    lsp::Position::new(0, 0),
18033                                                ),
18034                                                new_text: "X".into(),
18035                                            }],
18036                                        )]
18037                                        .into_iter()
18038                                        .collect(),
18039                                    ),
18040                                    ..Default::default()
18041                                },
18042                            },
18043                        )
18044                        .await
18045                        .unwrap();
18046                    Ok(Some(json!(null)))
18047                }
18048            }
18049        })
18050        .next()
18051        .await;
18052
18053    // Applying the code lens command returns a project transaction containing the edits
18054    // sent by the language server in its `workspaceEdit` request.
18055    let transaction = apply.await.unwrap();
18056    assert!(transaction.0.contains_key(&buffer));
18057    buffer.update(cx, |buffer, cx| {
18058        assert_eq!(buffer.text(), "Xa");
18059        buffer.undo(cx);
18060        assert_eq!(buffer.text(), "a");
18061    });
18062}
18063
18064mod autoclose_tags {
18065    use super::*;
18066    use language::language_settings::JsxTagAutoCloseSettings;
18067    use languages::language;
18068
18069    async fn test_setup(cx: &mut TestAppContext) -> EditorTestContext {
18070        init_test(cx, |settings| {
18071            settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
18072        });
18073
18074        let mut cx = EditorTestContext::new(cx).await;
18075        cx.update_buffer(|buffer, cx| {
18076            let language = language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into());
18077
18078            buffer.set_language(Some(language), cx)
18079        });
18080
18081        cx
18082    }
18083
18084    macro_rules! check {
18085        ($name:ident, $initial:literal + $input:literal => $expected:expr) => {
18086            #[gpui::test]
18087            async fn $name(cx: &mut TestAppContext) {
18088                let mut cx = test_setup(cx).await;
18089                cx.set_state($initial);
18090                cx.run_until_parked();
18091
18092                cx.update_editor(|editor, window, cx| {
18093                    editor.handle_input($input, window, cx);
18094                });
18095                cx.run_until_parked();
18096                cx.assert_editor_state($expected);
18097            }
18098        };
18099    }
18100
18101    check!(
18102        test_basic,
18103        "<divˇ" + ">" => "<div>ˇ</div>"
18104    );
18105
18106    check!(
18107        test_basic_nested,
18108        "<div><divˇ</div>" + ">" => "<div><div>ˇ</div></div>"
18109    );
18110
18111    check!(
18112        test_basic_ignore_already_closed,
18113        "<div><divˇ</div></div>" + ">" => "<div><div>ˇ</div></div>"
18114    );
18115
18116    check!(
18117        test_doesnt_autoclose_closing_tag,
18118        "</divˇ" + ">" => "</div>ˇ"
18119    );
18120
18121    check!(
18122        test_jsx_attr,
18123        "<div attr={</div>}ˇ" + ">" => "<div attr={</div>}>ˇ</div>"
18124    );
18125
18126    check!(
18127        test_ignores_closing_tags_in_expr_block,
18128        "<div><divˇ{</div>}</div>" + ">" => "<div><div>ˇ</div>{</div>}</div>"
18129    );
18130
18131    check!(
18132        test_doesnt_autoclose_on_gt_in_expr,
18133        "<div attr={1 ˇ" + ">" => "<div attr={1 >ˇ"
18134    );
18135
18136    check!(
18137        test_ignores_closing_tags_with_different_tag_names,
18138        "<div><divˇ</div></span>" + ">" => "<div><div>ˇ</div></div></span>"
18139    );
18140
18141    check!(
18142        test_autocloses_in_jsx_expression,
18143        "<div>{<divˇ}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
18144    );
18145
18146    check!(
18147        test_doesnt_autoclose_already_closed_in_jsx_expression,
18148        "<div>{<divˇ</div>}</div>" + ">" => "<div>{<div>ˇ</div>}</div>"
18149    );
18150
18151    check!(
18152        test_autocloses_fragment,
18153        "" + ">" => "<>ˇ</>"
18154    );
18155
18156    check!(
18157        test_does_not_include_type_argument_in_autoclose_tag_name,
18158        "<Component<T> attr={boolean_value}ˇ" + ">" => "<Component<T> attr={boolean_value}>ˇ</Component>"
18159    );
18160
18161    check!(
18162        test_does_not_autoclose_doctype,
18163        "<!DOCTYPE htmlˇ" + ">" => "<!DOCTYPE html>ˇ"
18164    );
18165
18166    check!(
18167        test_does_not_autoclose_comment,
18168        "<!-- comment --ˇ" + ">" => "<!-- comment -->ˇ"
18169    );
18170
18171    check!(
18172        test_multi_cursor_autoclose_same_tag,
18173        r#"
18174        <divˇ
18175        <divˇ
18176        "#
18177        + ">" =>
18178        r#"
18179        <div>ˇ</div>
18180        <div>ˇ</div>
18181        "#
18182    );
18183
18184    check!(
18185        test_multi_cursor_autoclose_different_tags,
18186        r#"
18187        <divˇ
18188        <spanˇ
18189        "#
18190        + ">" =>
18191        r#"
18192        <div>ˇ</div>
18193        <span>ˇ</span>
18194        "#
18195    );
18196
18197    check!(
18198        test_multi_cursor_autoclose_some_dont_autoclose_others,
18199        r#"
18200        <divˇ
18201        <div /ˇ
18202        <spanˇ</span>
18203        <!DOCTYPE htmlˇ
18204        </headˇ
18205        <Component<T>ˇ
18206        ˇ
18207        "#
18208        + ">" =>
18209        r#"
18210        <div>ˇ</div>
18211        <div />ˇ
18212        <span>ˇ</span>
18213        <!DOCTYPE html>ˇ
18214        </head>ˇ
18215        <Component<T>>ˇ</Component>
1821618217        "#
18218    );
18219
18220    check!(
18221        test_doesnt_mess_up_trailing_text,
18222        "<divˇfoobar" + ">" => "<div>ˇ</div>foobar"
18223    );
18224
18225    #[gpui::test]
18226    async fn test_multibuffer(cx: &mut TestAppContext) {
18227        init_test(cx, |settings| {
18228            settings.defaults.jsx_tag_auto_close = Some(JsxTagAutoCloseSettings { enabled: true });
18229        });
18230
18231        let buffer_a = cx.new(|cx| {
18232            let mut buf = language::Buffer::local("<div", cx);
18233            buf.set_language(
18234                Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
18235                cx,
18236            );
18237            buf
18238        });
18239        let buffer_b = cx.new(|cx| {
18240            let mut buf = language::Buffer::local("<pre", cx);
18241            buf.set_language(
18242                Some(language("tsx", tree_sitter_typescript::LANGUAGE_TSX.into())),
18243                cx,
18244            );
18245            buf
18246        });
18247        let buffer_c = cx.new(|cx| {
18248            let buf = language::Buffer::local("<span", cx);
18249            buf
18250        });
18251        let buffer = cx.new(|cx| {
18252            let mut buf = MultiBuffer::new(language::Capability::ReadWrite);
18253            buf.push_excerpts(
18254                buffer_a,
18255                [ExcerptRange {
18256                    context: text::Anchor::MIN..text::Anchor::MAX,
18257                    primary: None,
18258                }],
18259                cx,
18260            );
18261            buf.push_excerpts(
18262                buffer_b,
18263                [ExcerptRange {
18264                    context: text::Anchor::MIN..text::Anchor::MAX,
18265                    primary: None,
18266                }],
18267                cx,
18268            );
18269            buf.push_excerpts(
18270                buffer_c,
18271                [ExcerptRange {
18272                    context: text::Anchor::MIN..text::Anchor::MAX,
18273                    primary: None,
18274                }],
18275                cx,
18276            );
18277            buf
18278        });
18279        let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18280
18281        let mut cx = EditorTestContext::for_editor(editor, cx).await;
18282
18283        cx.update_editor(|editor, window, cx| {
18284            editor.change_selections(None, window, cx, |selections| {
18285                selections.select(vec![
18286                    Selection::from_offset(4),
18287                    Selection::from_offset(9),
18288                    Selection::from_offset(15),
18289                ])
18290            })
18291        });
18292        cx.run_until_parked();
18293
18294        cx.update_editor(|editor, window, cx| {
18295            editor.handle_input(">", window, cx);
18296        });
18297        cx.run_until_parked();
18298
18299        cx.assert_editor_state("<div>ˇ</div>\n<pre>ˇ</pre>\n<span>ˇ");
18300    }
18301}
18302
18303fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
18304    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
18305    point..point
18306}
18307
18308fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
18309    let (text, ranges) = marked_text_ranges(marked_text, true);
18310    assert_eq!(editor.text(cx), text);
18311    assert_eq!(
18312        editor.selections.ranges(cx),
18313        ranges,
18314        "Assert selections are {}",
18315        marked_text
18316    );
18317}
18318
18319pub fn handle_signature_help_request(
18320    cx: &mut EditorLspTestContext,
18321    mocked_response: lsp::SignatureHelp,
18322) -> impl Future<Output = ()> {
18323    let mut request =
18324        cx.handle_request::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
18325            let mocked_response = mocked_response.clone();
18326            async move { Ok(Some(mocked_response)) }
18327        });
18328
18329    async move {
18330        request.next().await;
18331    }
18332}
18333
18334/// Handle completion request passing a marked string specifying where the completion
18335/// should be triggered from using '|' character, what range should be replaced, and what completions
18336/// should be returned using '<' and '>' to delimit the range
18337pub fn handle_completion_request(
18338    cx: &mut EditorLspTestContext,
18339    marked_string: &str,
18340    completions: Vec<&'static str>,
18341    counter: Arc<AtomicUsize>,
18342) -> impl Future<Output = ()> {
18343    let complete_from_marker: TextRangeMarker = '|'.into();
18344    let replace_range_marker: TextRangeMarker = ('<', '>').into();
18345    let (_, mut marked_ranges) = marked_text_ranges_by(
18346        marked_string,
18347        vec![complete_from_marker.clone(), replace_range_marker.clone()],
18348    );
18349
18350    let complete_from_position =
18351        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
18352    let replace_range =
18353        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
18354
18355    let mut request = cx.handle_request::<lsp::request::Completion, _, _>(move |url, params, _| {
18356        let completions = completions.clone();
18357        counter.fetch_add(1, atomic::Ordering::Release);
18358        async move {
18359            assert_eq!(params.text_document_position.text_document.uri, url.clone());
18360            assert_eq!(
18361                params.text_document_position.position,
18362                complete_from_position
18363            );
18364            Ok(Some(lsp::CompletionResponse::Array(
18365                completions
18366                    .iter()
18367                    .map(|completion_text| lsp::CompletionItem {
18368                        label: completion_text.to_string(),
18369                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18370                            range: replace_range,
18371                            new_text: completion_text.to_string(),
18372                        })),
18373                        ..Default::default()
18374                    })
18375                    .collect(),
18376            )))
18377        }
18378    });
18379
18380    async move {
18381        request.next().await;
18382    }
18383}
18384
18385fn handle_resolve_completion_request(
18386    cx: &mut EditorLspTestContext,
18387    edits: Option<Vec<(&'static str, &'static str)>>,
18388) -> impl Future<Output = ()> {
18389    let edits = edits.map(|edits| {
18390        edits
18391            .iter()
18392            .map(|(marked_string, new_text)| {
18393                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
18394                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
18395                lsp::TextEdit::new(replace_range, new_text.to_string())
18396            })
18397            .collect::<Vec<_>>()
18398    });
18399
18400    let mut request =
18401        cx.handle_request::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18402            let edits = edits.clone();
18403            async move {
18404                Ok(lsp::CompletionItem {
18405                    additional_text_edits: edits,
18406                    ..Default::default()
18407                })
18408            }
18409        });
18410
18411    async move {
18412        request.next().await;
18413    }
18414}
18415
18416pub(crate) fn update_test_language_settings(
18417    cx: &mut TestAppContext,
18418    f: impl Fn(&mut AllLanguageSettingsContent),
18419) {
18420    cx.update(|cx| {
18421        SettingsStore::update_global(cx, |store, cx| {
18422            store.update_user_settings::<AllLanguageSettings>(cx, f);
18423        });
18424    });
18425}
18426
18427pub(crate) fn update_test_project_settings(
18428    cx: &mut TestAppContext,
18429    f: impl Fn(&mut ProjectSettings),
18430) {
18431    cx.update(|cx| {
18432        SettingsStore::update_global(cx, |store, cx| {
18433            store.update_user_settings::<ProjectSettings>(cx, f);
18434        });
18435    });
18436}
18437
18438pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
18439    cx.update(|cx| {
18440        assets::Assets.load_test_fonts(cx);
18441        let store = SettingsStore::test(cx);
18442        cx.set_global(store);
18443        theme::init(theme::LoadThemes::JustBase, cx);
18444        release_channel::init(SemanticVersion::default(), cx);
18445        client::init_settings(cx);
18446        language::init(cx);
18447        Project::init_settings(cx);
18448        workspace::init_settings(cx);
18449        crate::init(cx);
18450    });
18451
18452    update_test_language_settings(cx, f);
18453}
18454
18455#[track_caller]
18456fn assert_hunk_revert(
18457    not_reverted_text_with_selections: &str,
18458    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
18459    expected_reverted_text_with_selections: &str,
18460    base_text: &str,
18461    cx: &mut EditorLspTestContext,
18462) {
18463    cx.set_state(not_reverted_text_with_selections);
18464    cx.set_head_text(base_text);
18465    cx.executor().run_until_parked();
18466
18467    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
18468        let snapshot = editor.snapshot(window, cx);
18469        let reverted_hunk_statuses = snapshot
18470            .buffer_snapshot
18471            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
18472            .map(|hunk| hunk.status().kind)
18473            .collect::<Vec<_>>();
18474
18475        editor.git_restore(&Default::default(), window, cx);
18476        reverted_hunk_statuses
18477    });
18478    cx.executor().run_until_parked();
18479    cx.assert_editor_state(expected_reverted_text_with_selections);
18480    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
18481}