editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   26    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   27    language_settings::{
   28        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   29        SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::Formatter;
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::LspSettings,
   42};
   43use serde_json::{self, json};
   44use settings::{AllLanguageSettingsContent, ProjectSettingsContent};
   45use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   46use std::{
   47    iter,
   48    sync::atomic::{self, AtomicUsize},
   49};
   50use test::{build_editor_with_project, editor_lsp_test_context::rust_lang};
   51use text::ToPoint as _;
   52use unindent::Unindent;
   53use util::{
   54    assert_set_eq, path,
   55    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   56    uri,
   57};
   58use workspace::{
   59    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   60    OpenOptions, ViewId,
   61    invalid_buffer_view::InvalidBufferView,
   62    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   63    register_project_item,
   64};
   65
   66#[gpui::test]
   67fn test_edit_events(cx: &mut TestAppContext) {
   68    init_test(cx, |_| {});
   69
   70    let buffer = cx.new(|cx| {
   71        let mut buffer = language::Buffer::local("123456", cx);
   72        buffer.set_group_interval(Duration::from_secs(1));
   73        buffer
   74    });
   75
   76    let events = Rc::new(RefCell::new(Vec::new()));
   77    let editor1 = cx.add_window({
   78        let events = events.clone();
   79        |window, cx| {
   80            let entity = cx.entity();
   81            cx.subscribe_in(
   82                &entity,
   83                window,
   84                move |_, _, event: &EditorEvent, _, _| match event {
   85                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
   86                    EditorEvent::BufferEdited => {
   87                        events.borrow_mut().push(("editor1", "buffer edited"))
   88                    }
   89                    _ => {}
   90                },
   91            )
   92            .detach();
   93            Editor::for_buffer(buffer.clone(), None, window, cx)
   94        }
   95    });
   96
   97    let editor2 = cx.add_window({
   98        let events = events.clone();
   99        |window, cx| {
  100            cx.subscribe_in(
  101                &cx.entity(),
  102                window,
  103                move |_, _, event: &EditorEvent, _, _| match event {
  104                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  105                    EditorEvent::BufferEdited => {
  106                        events.borrow_mut().push(("editor2", "buffer edited"))
  107                    }
  108                    _ => {}
  109                },
  110            )
  111            .detach();
  112            Editor::for_buffer(buffer.clone(), None, window, cx)
  113        }
  114    });
  115
  116    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  117
  118    // Mutating editor 1 will emit an `Edited` event only for that editor.
  119    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  120    assert_eq!(
  121        mem::take(&mut *events.borrow_mut()),
  122        [
  123            ("editor1", "edited"),
  124            ("editor1", "buffer edited"),
  125            ("editor2", "buffer edited"),
  126        ]
  127    );
  128
  129    // Mutating editor 2 will emit an `Edited` event only for that editor.
  130    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  131    assert_eq!(
  132        mem::take(&mut *events.borrow_mut()),
  133        [
  134            ("editor2", "edited"),
  135            ("editor1", "buffer edited"),
  136            ("editor2", "buffer edited"),
  137        ]
  138    );
  139
  140    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  141    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  142    assert_eq!(
  143        mem::take(&mut *events.borrow_mut()),
  144        [
  145            ("editor1", "edited"),
  146            ("editor1", "buffer edited"),
  147            ("editor2", "buffer edited"),
  148        ]
  149    );
  150
  151    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  152    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  153    assert_eq!(
  154        mem::take(&mut *events.borrow_mut()),
  155        [
  156            ("editor1", "edited"),
  157            ("editor1", "buffer edited"),
  158            ("editor2", "buffer edited"),
  159        ]
  160    );
  161
  162    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  163    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  164    assert_eq!(
  165        mem::take(&mut *events.borrow_mut()),
  166        [
  167            ("editor2", "edited"),
  168            ("editor1", "buffer edited"),
  169            ("editor2", "buffer edited"),
  170        ]
  171    );
  172
  173    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  174    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  175    assert_eq!(
  176        mem::take(&mut *events.borrow_mut()),
  177        [
  178            ("editor2", "edited"),
  179            ("editor1", "buffer edited"),
  180            ("editor2", "buffer edited"),
  181        ]
  182    );
  183
  184    // No event is emitted when the mutation is a no-op.
  185    _ = editor2.update(cx, |editor, window, cx| {
  186        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  187            s.select_ranges([0..0])
  188        });
  189
  190        editor.backspace(&Backspace, window, cx);
  191    });
  192    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  193}
  194
  195#[gpui::test]
  196fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  197    init_test(cx, |_| {});
  198
  199    let mut now = Instant::now();
  200    let group_interval = Duration::from_millis(1);
  201    let buffer = cx.new(|cx| {
  202        let mut buf = language::Buffer::local("123456", cx);
  203        buf.set_group_interval(group_interval);
  204        buf
  205    });
  206    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  207    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  208
  209    _ = editor.update(cx, |editor, window, cx| {
  210        editor.start_transaction_at(now, window, cx);
  211        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  212            s.select_ranges([2..4])
  213        });
  214
  215        editor.insert("cd", window, cx);
  216        editor.end_transaction_at(now, cx);
  217        assert_eq!(editor.text(cx), "12cd56");
  218        assert_eq!(editor.selections.ranges(cx), vec![4..4]);
  219
  220        editor.start_transaction_at(now, window, cx);
  221        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  222            s.select_ranges([4..5])
  223        });
  224        editor.insert("e", window, cx);
  225        editor.end_transaction_at(now, cx);
  226        assert_eq!(editor.text(cx), "12cde6");
  227        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  228
  229        now += group_interval + Duration::from_millis(1);
  230        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  231            s.select_ranges([2..2])
  232        });
  233
  234        // Simulate an edit in another editor
  235        buffer.update(cx, |buffer, cx| {
  236            buffer.start_transaction_at(now, cx);
  237            buffer.edit([(0..1, "a")], None, cx);
  238            buffer.edit([(1..1, "b")], None, cx);
  239            buffer.end_transaction_at(now, cx);
  240        });
  241
  242        assert_eq!(editor.text(cx), "ab2cde6");
  243        assert_eq!(editor.selections.ranges(cx), vec![3..3]);
  244
  245        // Last transaction happened past the group interval in a different editor.
  246        // Undo it individually and don't restore selections.
  247        editor.undo(&Undo, window, cx);
  248        assert_eq!(editor.text(cx), "12cde6");
  249        assert_eq!(editor.selections.ranges(cx), vec![2..2]);
  250
  251        // First two transactions happened within the group interval in this editor.
  252        // Undo them together and restore selections.
  253        editor.undo(&Undo, window, cx);
  254        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  255        assert_eq!(editor.text(cx), "123456");
  256        assert_eq!(editor.selections.ranges(cx), vec![0..0]);
  257
  258        // Redo the first two transactions together.
  259        editor.redo(&Redo, window, cx);
  260        assert_eq!(editor.text(cx), "12cde6");
  261        assert_eq!(editor.selections.ranges(cx), vec![5..5]);
  262
  263        // Redo the last transaction on its own.
  264        editor.redo(&Redo, window, cx);
  265        assert_eq!(editor.text(cx), "ab2cde6");
  266        assert_eq!(editor.selections.ranges(cx), vec![6..6]);
  267
  268        // Test empty transactions.
  269        editor.start_transaction_at(now, window, cx);
  270        editor.end_transaction_at(now, cx);
  271        editor.undo(&Undo, window, cx);
  272        assert_eq!(editor.text(cx), "12cde6");
  273    });
  274}
  275
  276#[gpui::test]
  277fn test_ime_composition(cx: &mut TestAppContext) {
  278    init_test(cx, |_| {});
  279
  280    let buffer = cx.new(|cx| {
  281        let mut buffer = language::Buffer::local("abcde", cx);
  282        // Ensure automatic grouping doesn't occur.
  283        buffer.set_group_interval(Duration::ZERO);
  284        buffer
  285    });
  286
  287    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  288    cx.add_window(|window, cx| {
  289        let mut editor = build_editor(buffer.clone(), window, cx);
  290
  291        // Start a new IME composition.
  292        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  293        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  294        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  295        assert_eq!(editor.text(cx), "äbcde");
  296        assert_eq!(
  297            editor.marked_text_ranges(cx),
  298            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  299        );
  300
  301        // Finalize IME composition.
  302        editor.replace_text_in_range(None, "ā", window, cx);
  303        assert_eq!(editor.text(cx), "ābcde");
  304        assert_eq!(editor.marked_text_ranges(cx), None);
  305
  306        // IME composition edits are grouped and are undone/redone at once.
  307        editor.undo(&Default::default(), window, cx);
  308        assert_eq!(editor.text(cx), "abcde");
  309        assert_eq!(editor.marked_text_ranges(cx), None);
  310        editor.redo(&Default::default(), window, cx);
  311        assert_eq!(editor.text(cx), "ābcde");
  312        assert_eq!(editor.marked_text_ranges(cx), None);
  313
  314        // Start a new IME composition.
  315        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  316        assert_eq!(
  317            editor.marked_text_ranges(cx),
  318            Some(vec![OffsetUtf16(0)..OffsetUtf16(1)])
  319        );
  320
  321        // Undoing during an IME composition cancels it.
  322        editor.undo(&Default::default(), window, cx);
  323        assert_eq!(editor.text(cx), "ābcde");
  324        assert_eq!(editor.marked_text_ranges(cx), None);
  325
  326        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  327        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  328        assert_eq!(editor.text(cx), "ābcdè");
  329        assert_eq!(
  330            editor.marked_text_ranges(cx),
  331            Some(vec![OffsetUtf16(4)..OffsetUtf16(5)])
  332        );
  333
  334        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  335        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  336        assert_eq!(editor.text(cx), "ābcdę");
  337        assert_eq!(editor.marked_text_ranges(cx), None);
  338
  339        // Start a new IME composition with multiple cursors.
  340        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  341            s.select_ranges([
  342                OffsetUtf16(1)..OffsetUtf16(1),
  343                OffsetUtf16(3)..OffsetUtf16(3),
  344                OffsetUtf16(5)..OffsetUtf16(5),
  345            ])
  346        });
  347        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  348        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  349        assert_eq!(
  350            editor.marked_text_ranges(cx),
  351            Some(vec![
  352                OffsetUtf16(0)..OffsetUtf16(3),
  353                OffsetUtf16(4)..OffsetUtf16(7),
  354                OffsetUtf16(8)..OffsetUtf16(11)
  355            ])
  356        );
  357
  358        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  359        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  360        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  361        assert_eq!(
  362            editor.marked_text_ranges(cx),
  363            Some(vec![
  364                OffsetUtf16(1)..OffsetUtf16(2),
  365                OffsetUtf16(5)..OffsetUtf16(6),
  366                OffsetUtf16(9)..OffsetUtf16(10)
  367            ])
  368        );
  369
  370        // Finalize IME composition with multiple cursors.
  371        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  372        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  373        assert_eq!(editor.marked_text_ranges(cx), None);
  374
  375        editor
  376    });
  377}
  378
  379#[gpui::test]
  380fn test_selection_with_mouse(cx: &mut TestAppContext) {
  381    init_test(cx, |_| {});
  382
  383    let editor = cx.add_window(|window, cx| {
  384        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  385        build_editor(buffer, window, cx)
  386    });
  387
  388    _ = editor.update(cx, |editor, window, cx| {
  389        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  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(2), 2)]
  396    );
  397
  398    _ = editor.update(cx, |editor, window, cx| {
  399        editor.update_selection(
  400            DisplayPoint::new(DisplayRow(3), 3),
  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(3), 3)]
  413    );
  414
  415    _ = editor.update(cx, |editor, window, cx| {
  416        editor.update_selection(
  417            DisplayPoint::new(DisplayRow(1), 1),
  418            0,
  419            gpui::Point::<f32>::default(),
  420            window,
  421            cx,
  422        );
  423    });
  424
  425    assert_eq!(
  426        editor
  427            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  428            .unwrap(),
  429        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  430    );
  431
  432    _ = editor.update(cx, |editor, window, cx| {
  433        editor.end_selection(window, cx);
  434        editor.update_selection(
  435            DisplayPoint::new(DisplayRow(3), 3),
  436            0,
  437            gpui::Point::<f32>::default(),
  438            window,
  439            cx,
  440        );
  441    });
  442
  443    assert_eq!(
  444        editor
  445            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  446            .unwrap(),
  447        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  448    );
  449
  450    _ = editor.update(cx, |editor, window, cx| {
  451        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  452        editor.update_selection(
  453            DisplayPoint::new(DisplayRow(0), 0),
  454            0,
  455            gpui::Point::<f32>::default(),
  456            window,
  457            cx,
  458        );
  459    });
  460
  461    assert_eq!(
  462        editor
  463            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  464            .unwrap(),
  465        [
  466            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  467            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  468        ]
  469    );
  470
  471    _ = editor.update(cx, |editor, window, cx| {
  472        editor.end_selection(window, cx);
  473    });
  474
  475    assert_eq!(
  476        editor
  477            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  478            .unwrap(),
  479        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  480    );
  481}
  482
  483#[gpui::test]
  484fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  485    init_test(cx, |_| {});
  486
  487    let editor = cx.add_window(|window, cx| {
  488        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  489        build_editor(buffer, window, cx)
  490    });
  491
  492    _ = editor.update(cx, |editor, window, cx| {
  493        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  494    });
  495
  496    _ = editor.update(cx, |editor, window, cx| {
  497        editor.end_selection(window, cx);
  498    });
  499
  500    _ = editor.update(cx, |editor, window, cx| {
  501        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  502    });
  503
  504    _ = editor.update(cx, |editor, window, cx| {
  505        editor.end_selection(window, cx);
  506    });
  507
  508    assert_eq!(
  509        editor
  510            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  511            .unwrap(),
  512        [
  513            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  514            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  515        ]
  516    );
  517
  518    _ = editor.update(cx, |editor, window, cx| {
  519        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  520    });
  521
  522    _ = editor.update(cx, |editor, window, cx| {
  523        editor.end_selection(window, cx);
  524    });
  525
  526    assert_eq!(
  527        editor
  528            .update(cx, |editor, _, cx| editor.selections.display_ranges(cx))
  529            .unwrap(),
  530        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  531    );
  532}
  533
  534#[gpui::test]
  535fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  536    init_test(cx, |_| {});
  537
  538    let editor = cx.add_window(|window, cx| {
  539        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  540        build_editor(buffer, window, cx)
  541    });
  542
  543    _ = editor.update(cx, |editor, window, cx| {
  544        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  545        assert_eq!(
  546            editor.selections.display_ranges(cx),
  547            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  548        );
  549    });
  550
  551    _ = editor.update(cx, |editor, window, cx| {
  552        editor.update_selection(
  553            DisplayPoint::new(DisplayRow(3), 3),
  554            0,
  555            gpui::Point::<f32>::default(),
  556            window,
  557            cx,
  558        );
  559        assert_eq!(
  560            editor.selections.display_ranges(cx),
  561            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  562        );
  563    });
  564
  565    _ = editor.update(cx, |editor, window, cx| {
  566        editor.cancel(&Cancel, window, cx);
  567        editor.update_selection(
  568            DisplayPoint::new(DisplayRow(1), 1),
  569            0,
  570            gpui::Point::<f32>::default(),
  571            window,
  572            cx,
  573        );
  574        assert_eq!(
  575            editor.selections.display_ranges(cx),
  576            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  577        );
  578    });
  579}
  580
  581#[gpui::test]
  582fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  583    init_test(cx, |_| {});
  584
  585    let editor = cx.add_window(|window, cx| {
  586        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  587        build_editor(buffer, window, cx)
  588    });
  589
  590    _ = editor.update(cx, |editor, window, cx| {
  591        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  592        assert_eq!(
  593            editor.selections.display_ranges(cx),
  594            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  595        );
  596
  597        editor.move_down(&Default::default(), window, cx);
  598        assert_eq!(
  599            editor.selections.display_ranges(cx),
  600            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  601        );
  602
  603        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  604        assert_eq!(
  605            editor.selections.display_ranges(cx),
  606            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  607        );
  608
  609        editor.move_up(&Default::default(), window, cx);
  610        assert_eq!(
  611            editor.selections.display_ranges(cx),
  612            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  613        );
  614    });
  615}
  616
  617#[gpui::test]
  618fn test_clone(cx: &mut TestAppContext) {
  619    init_test(cx, |_| {});
  620
  621    let (text, selection_ranges) = marked_text_ranges(
  622        indoc! {"
  623            one
  624            two
  625            threeˇ
  626            four
  627            fiveˇ
  628        "},
  629        true,
  630    );
  631
  632    let editor = cx.add_window(|window, cx| {
  633        let buffer = MultiBuffer::build_simple(&text, cx);
  634        build_editor(buffer, window, cx)
  635    });
  636
  637    _ = editor.update(cx, |editor, window, cx| {
  638        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  639            s.select_ranges(selection_ranges.clone())
  640        });
  641        editor.fold_creases(
  642            vec![
  643                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  644                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  645            ],
  646            true,
  647            window,
  648            cx,
  649        );
  650    });
  651
  652    let cloned_editor = editor
  653        .update(cx, |editor, _, cx| {
  654            cx.open_window(Default::default(), |window, cx| {
  655                cx.new(|cx| editor.clone(window, cx))
  656            })
  657        })
  658        .unwrap()
  659        .unwrap();
  660
  661    let snapshot = editor
  662        .update(cx, |e, window, cx| e.snapshot(window, cx))
  663        .unwrap();
  664    let cloned_snapshot = cloned_editor
  665        .update(cx, |e, window, cx| e.snapshot(window, cx))
  666        .unwrap();
  667
  668    assert_eq!(
  669        cloned_editor
  670            .update(cx, |e, _, cx| e.display_text(cx))
  671            .unwrap(),
  672        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  673    );
  674    assert_eq!(
  675        cloned_snapshot
  676            .folds_in_range(0..text.len())
  677            .collect::<Vec<_>>(),
  678        snapshot.folds_in_range(0..text.len()).collect::<Vec<_>>(),
  679    );
  680    assert_set_eq!(
  681        cloned_editor
  682            .update(cx, |editor, _, cx| editor.selections.ranges::<Point>(cx))
  683            .unwrap(),
  684        editor
  685            .update(cx, |editor, _, cx| editor.selections.ranges(cx))
  686            .unwrap()
  687    );
  688    assert_set_eq!(
  689        cloned_editor
  690            .update(cx, |e, _window, cx| e.selections.display_ranges(cx))
  691            .unwrap(),
  692        editor
  693            .update(cx, |e, _, cx| e.selections.display_ranges(cx))
  694            .unwrap()
  695    );
  696}
  697
  698#[gpui::test]
  699async fn test_navigation_history(cx: &mut TestAppContext) {
  700    init_test(cx, |_| {});
  701
  702    use workspace::item::Item;
  703
  704    let fs = FakeFs::new(cx.executor());
  705    let project = Project::test(fs, [], cx).await;
  706    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  707    let pane = workspace
  708        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  709        .unwrap();
  710
  711    _ = workspace.update(cx, |_v, window, cx| {
  712        cx.new(|cx| {
  713            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  714            let mut editor = build_editor(buffer, window, cx);
  715            let handle = cx.entity();
  716            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  717
  718            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  719                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  720            }
  721
  722            // Move the cursor a small distance.
  723            // Nothing is added to the navigation history.
  724            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  725                s.select_display_ranges([
  726                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  727                ])
  728            });
  729            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  730                s.select_display_ranges([
  731                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  732                ])
  733            });
  734            assert!(pop_history(&mut editor, cx).is_none());
  735
  736            // Move the cursor a large distance.
  737            // The history can jump back to the previous position.
  738            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  739                s.select_display_ranges([
  740                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  741                ])
  742            });
  743            let nav_entry = pop_history(&mut editor, cx).unwrap();
  744            editor.navigate(nav_entry.data.unwrap(), window, cx);
  745            assert_eq!(nav_entry.item.id(), cx.entity_id());
  746            assert_eq!(
  747                editor.selections.display_ranges(cx),
  748                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  749            );
  750            assert!(pop_history(&mut editor, cx).is_none());
  751
  752            // Move the cursor a small distance via the mouse.
  753            // Nothing is added to the navigation history.
  754            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  755            editor.end_selection(window, cx);
  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            // Move the cursor a large distance via the mouse.
  763            // The history can jump back to the previous position.
  764            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  765            editor.end_selection(window, cx);
  766            assert_eq!(
  767                editor.selections.display_ranges(cx),
  768                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  769            );
  770            let nav_entry = pop_history(&mut editor, cx).unwrap();
  771            editor.navigate(nav_entry.data.unwrap(), window, cx);
  772            assert_eq!(nav_entry.item.id(), cx.entity_id());
  773            assert_eq!(
  774                editor.selections.display_ranges(cx),
  775                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  776            );
  777            assert!(pop_history(&mut editor, cx).is_none());
  778
  779            // Set scroll position to check later
  780            editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), window, cx);
  781            let original_scroll_position = editor.scroll_manager.anchor();
  782
  783            // Jump to the end of the document and adjust scroll
  784            editor.move_to_end(&MoveToEnd, window, cx);
  785            editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), window, cx);
  786            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  787
  788            let nav_entry = pop_history(&mut editor, cx).unwrap();
  789            editor.navigate(nav_entry.data.unwrap(), window, cx);
  790            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  791
  792            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  793            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  794            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  795            let invalid_point = Point::new(9999, 0);
  796            editor.navigate(
  797                Box::new(NavigationData {
  798                    cursor_anchor: invalid_anchor,
  799                    cursor_position: invalid_point,
  800                    scroll_anchor: ScrollAnchor {
  801                        anchor: invalid_anchor,
  802                        offset: Default::default(),
  803                    },
  804                    scroll_top_row: invalid_point.row,
  805                }),
  806                window,
  807                cx,
  808            );
  809            assert_eq!(
  810                editor.selections.display_ranges(cx),
  811                &[editor.max_point(cx)..editor.max_point(cx)]
  812            );
  813            assert_eq!(
  814                editor.scroll_position(cx),
  815                gpui::Point::new(0., editor.max_point(cx).row().as_f32())
  816            );
  817
  818            editor
  819        })
  820    });
  821}
  822
  823#[gpui::test]
  824fn test_cancel(cx: &mut TestAppContext) {
  825    init_test(cx, |_| {});
  826
  827    let editor = cx.add_window(|window, cx| {
  828        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  829        build_editor(buffer, window, cx)
  830    });
  831
  832    _ = editor.update(cx, |editor, window, cx| {
  833        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  834        editor.update_selection(
  835            DisplayPoint::new(DisplayRow(1), 1),
  836            0,
  837            gpui::Point::<f32>::default(),
  838            window,
  839            cx,
  840        );
  841        editor.end_selection(window, cx);
  842
  843        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
  844        editor.update_selection(
  845            DisplayPoint::new(DisplayRow(0), 3),
  846            0,
  847            gpui::Point::<f32>::default(),
  848            window,
  849            cx,
  850        );
  851        editor.end_selection(window, cx);
  852        assert_eq!(
  853            editor.selections.display_ranges(cx),
  854            [
  855                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
  856                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
  857            ]
  858        );
  859    });
  860
  861    _ = editor.update(cx, |editor, window, cx| {
  862        editor.cancel(&Cancel, window, cx);
  863        assert_eq!(
  864            editor.selections.display_ranges(cx),
  865            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
  866        );
  867    });
  868
  869    _ = editor.update(cx, |editor, window, cx| {
  870        editor.cancel(&Cancel, window, cx);
  871        assert_eq!(
  872            editor.selections.display_ranges(cx),
  873            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
  874        );
  875    });
  876}
  877
  878#[gpui::test]
  879fn test_fold_action(cx: &mut TestAppContext) {
  880    init_test(cx, |_| {});
  881
  882    let editor = cx.add_window(|window, cx| {
  883        let buffer = MultiBuffer::build_simple(
  884            &"
  885                impl Foo {
  886                    // Hello!
  887
  888                    fn a() {
  889                        1
  890                    }
  891
  892                    fn b() {
  893                        2
  894                    }
  895
  896                    fn c() {
  897                        3
  898                    }
  899                }
  900            "
  901            .unindent(),
  902            cx,
  903        );
  904        build_editor(buffer, window, cx)
  905    });
  906
  907    _ = editor.update(cx, |editor, window, cx| {
  908        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  909            s.select_display_ranges([
  910                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
  911            ]);
  912        });
  913        editor.fold(&Fold, window, cx);
  914        assert_eq!(
  915            editor.display_text(cx),
  916            "
  917                impl Foo {
  918                    // Hello!
  919
  920                    fn a() {
  921                        1
  922                    }
  923
  924                    fn b() {⋯
  925                    }
  926
  927                    fn c() {⋯
  928                    }
  929                }
  930            "
  931            .unindent(),
  932        );
  933
  934        editor.fold(&Fold, window, cx);
  935        assert_eq!(
  936            editor.display_text(cx),
  937            "
  938                impl Foo {⋯
  939                }
  940            "
  941            .unindent(),
  942        );
  943
  944        editor.unfold_lines(&UnfoldLines, window, cx);
  945        assert_eq!(
  946            editor.display_text(cx),
  947            "
  948                impl Foo {
  949                    // Hello!
  950
  951                    fn a() {
  952                        1
  953                    }
  954
  955                    fn b() {⋯
  956                    }
  957
  958                    fn c() {⋯
  959                    }
  960                }
  961            "
  962            .unindent(),
  963        );
  964
  965        editor.unfold_lines(&UnfoldLines, window, cx);
  966        assert_eq!(
  967            editor.display_text(cx),
  968            editor.buffer.read(cx).read(cx).text()
  969        );
  970    });
  971}
  972
  973#[gpui::test]
  974fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
  975    init_test(cx, |_| {});
  976
  977    let editor = cx.add_window(|window, cx| {
  978        let buffer = MultiBuffer::build_simple(
  979            &"
  980                class Foo:
  981                    # Hello!
  982
  983                    def a():
  984                        print(1)
  985
  986                    def b():
  987                        print(2)
  988
  989                    def c():
  990                        print(3)
  991            "
  992            .unindent(),
  993            cx,
  994        );
  995        build_editor(buffer, window, cx)
  996    });
  997
  998    _ = editor.update(cx, |editor, window, cx| {
  999        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1000            s.select_display_ranges([
 1001                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1002            ]);
 1003        });
 1004        editor.fold(&Fold, window, cx);
 1005        assert_eq!(
 1006            editor.display_text(cx),
 1007            "
 1008                class Foo:
 1009                    # Hello!
 1010
 1011                    def a():
 1012                        print(1)
 1013
 1014                    def b():⋯
 1015
 1016                    def c():⋯
 1017            "
 1018            .unindent(),
 1019        );
 1020
 1021        editor.fold(&Fold, window, cx);
 1022        assert_eq!(
 1023            editor.display_text(cx),
 1024            "
 1025                class Foo:⋯
 1026            "
 1027            .unindent(),
 1028        );
 1029
 1030        editor.unfold_lines(&UnfoldLines, window, cx);
 1031        assert_eq!(
 1032            editor.display_text(cx),
 1033            "
 1034                class Foo:
 1035                    # Hello!
 1036
 1037                    def a():
 1038                        print(1)
 1039
 1040                    def b():⋯
 1041
 1042                    def c():⋯
 1043            "
 1044            .unindent(),
 1045        );
 1046
 1047        editor.unfold_lines(&UnfoldLines, window, cx);
 1048        assert_eq!(
 1049            editor.display_text(cx),
 1050            editor.buffer.read(cx).read(cx).text()
 1051        );
 1052    });
 1053}
 1054
 1055#[gpui::test]
 1056fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1057    init_test(cx, |_| {});
 1058
 1059    let editor = cx.add_window(|window, cx| {
 1060        let buffer = MultiBuffer::build_simple(
 1061            &"
 1062                class Foo:
 1063                    # Hello!
 1064
 1065                    def a():
 1066                        print(1)
 1067
 1068                    def b():
 1069                        print(2)
 1070
 1071
 1072                    def c():
 1073                        print(3)
 1074
 1075
 1076            "
 1077            .unindent(),
 1078            cx,
 1079        );
 1080        build_editor(buffer, window, cx)
 1081    });
 1082
 1083    _ = editor.update(cx, |editor, window, cx| {
 1084        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1085            s.select_display_ranges([
 1086                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1087            ]);
 1088        });
 1089        editor.fold(&Fold, window, cx);
 1090        assert_eq!(
 1091            editor.display_text(cx),
 1092            "
 1093                class Foo:
 1094                    # Hello!
 1095
 1096                    def a():
 1097                        print(1)
 1098
 1099                    def b():⋯
 1100
 1101
 1102                    def c():⋯
 1103
 1104
 1105            "
 1106            .unindent(),
 1107        );
 1108
 1109        editor.fold(&Fold, window, cx);
 1110        assert_eq!(
 1111            editor.display_text(cx),
 1112            "
 1113                class Foo:⋯
 1114
 1115
 1116            "
 1117            .unindent(),
 1118        );
 1119
 1120        editor.unfold_lines(&UnfoldLines, window, cx);
 1121        assert_eq!(
 1122            editor.display_text(cx),
 1123            "
 1124                class Foo:
 1125                    # Hello!
 1126
 1127                    def a():
 1128                        print(1)
 1129
 1130                    def b():⋯
 1131
 1132
 1133                    def c():⋯
 1134
 1135
 1136            "
 1137            .unindent(),
 1138        );
 1139
 1140        editor.unfold_lines(&UnfoldLines, window, cx);
 1141        assert_eq!(
 1142            editor.display_text(cx),
 1143            editor.buffer.read(cx).read(cx).text()
 1144        );
 1145    });
 1146}
 1147
 1148#[gpui::test]
 1149fn test_fold_at_level(cx: &mut TestAppContext) {
 1150    init_test(cx, |_| {});
 1151
 1152    let editor = cx.add_window(|window, cx| {
 1153        let buffer = MultiBuffer::build_simple(
 1154            &"
 1155                class Foo:
 1156                    # Hello!
 1157
 1158                    def a():
 1159                        print(1)
 1160
 1161                    def b():
 1162                        print(2)
 1163
 1164
 1165                class Bar:
 1166                    # World!
 1167
 1168                    def a():
 1169                        print(1)
 1170
 1171                    def b():
 1172                        print(2)
 1173
 1174
 1175            "
 1176            .unindent(),
 1177            cx,
 1178        );
 1179        build_editor(buffer, window, cx)
 1180    });
 1181
 1182    _ = editor.update(cx, |editor, window, cx| {
 1183        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1184        assert_eq!(
 1185            editor.display_text(cx),
 1186            "
 1187                class Foo:
 1188                    # Hello!
 1189
 1190                    def a():⋯
 1191
 1192                    def b():⋯
 1193
 1194
 1195                class Bar:
 1196                    # World!
 1197
 1198                    def a():⋯
 1199
 1200                    def b():⋯
 1201
 1202
 1203            "
 1204            .unindent(),
 1205        );
 1206
 1207        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1208        assert_eq!(
 1209            editor.display_text(cx),
 1210            "
 1211                class Foo:⋯
 1212
 1213
 1214                class Bar:⋯
 1215
 1216
 1217            "
 1218            .unindent(),
 1219        );
 1220
 1221        editor.unfold_all(&UnfoldAll, window, cx);
 1222        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1223        assert_eq!(
 1224            editor.display_text(cx),
 1225            "
 1226                class Foo:
 1227                    # Hello!
 1228
 1229                    def a():
 1230                        print(1)
 1231
 1232                    def b():
 1233                        print(2)
 1234
 1235
 1236                class Bar:
 1237                    # World!
 1238
 1239                    def a():
 1240                        print(1)
 1241
 1242                    def b():
 1243                        print(2)
 1244
 1245
 1246            "
 1247            .unindent(),
 1248        );
 1249
 1250        assert_eq!(
 1251            editor.display_text(cx),
 1252            editor.buffer.read(cx).read(cx).text()
 1253        );
 1254    });
 1255}
 1256
 1257#[gpui::test]
 1258fn test_move_cursor(cx: &mut TestAppContext) {
 1259    init_test(cx, |_| {});
 1260
 1261    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1262    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1263
 1264    buffer.update(cx, |buffer, cx| {
 1265        buffer.edit(
 1266            vec![
 1267                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1268                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1269            ],
 1270            None,
 1271            cx,
 1272        );
 1273    });
 1274    _ = editor.update(cx, |editor, window, cx| {
 1275        assert_eq!(
 1276            editor.selections.display_ranges(cx),
 1277            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1278        );
 1279
 1280        editor.move_down(&MoveDown, window, cx);
 1281        assert_eq!(
 1282            editor.selections.display_ranges(cx),
 1283            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1284        );
 1285
 1286        editor.move_right(&MoveRight, window, cx);
 1287        assert_eq!(
 1288            editor.selections.display_ranges(cx),
 1289            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1290        );
 1291
 1292        editor.move_left(&MoveLeft, window, cx);
 1293        assert_eq!(
 1294            editor.selections.display_ranges(cx),
 1295            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1296        );
 1297
 1298        editor.move_up(&MoveUp, window, cx);
 1299        assert_eq!(
 1300            editor.selections.display_ranges(cx),
 1301            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1302        );
 1303
 1304        editor.move_to_end(&MoveToEnd, window, cx);
 1305        assert_eq!(
 1306            editor.selections.display_ranges(cx),
 1307            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1308        );
 1309
 1310        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1311        assert_eq!(
 1312            editor.selections.display_ranges(cx),
 1313            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1314        );
 1315
 1316        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1317            s.select_display_ranges([
 1318                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1319            ]);
 1320        });
 1321        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1322        assert_eq!(
 1323            editor.selections.display_ranges(cx),
 1324            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1325        );
 1326
 1327        editor.select_to_end(&SelectToEnd, window, cx);
 1328        assert_eq!(
 1329            editor.selections.display_ranges(cx),
 1330            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1331        );
 1332    });
 1333}
 1334
 1335#[gpui::test]
 1336fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1337    init_test(cx, |_| {});
 1338
 1339    let editor = cx.add_window(|window, cx| {
 1340        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1341        build_editor(buffer, window, cx)
 1342    });
 1343
 1344    assert_eq!('🟥'.len_utf8(), 4);
 1345    assert_eq!('α'.len_utf8(), 2);
 1346
 1347    _ = editor.update(cx, |editor, window, cx| {
 1348        editor.fold_creases(
 1349            vec![
 1350                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1351                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1352                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1353            ],
 1354            true,
 1355            window,
 1356            cx,
 1357        );
 1358        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1359
 1360        editor.move_right(&MoveRight, window, cx);
 1361        assert_eq!(
 1362            editor.selections.display_ranges(cx),
 1363            &[empty_range(0, "🟥".len())]
 1364        );
 1365        editor.move_right(&MoveRight, window, cx);
 1366        assert_eq!(
 1367            editor.selections.display_ranges(cx),
 1368            &[empty_range(0, "🟥🟧".len())]
 1369        );
 1370        editor.move_right(&MoveRight, window, cx);
 1371        assert_eq!(
 1372            editor.selections.display_ranges(cx),
 1373            &[empty_range(0, "🟥🟧⋯".len())]
 1374        );
 1375
 1376        editor.move_down(&MoveDown, window, cx);
 1377        assert_eq!(
 1378            editor.selections.display_ranges(cx),
 1379            &[empty_range(1, "ab⋯e".len())]
 1380        );
 1381        editor.move_left(&MoveLeft, window, cx);
 1382        assert_eq!(
 1383            editor.selections.display_ranges(cx),
 1384            &[empty_range(1, "ab⋯".len())]
 1385        );
 1386        editor.move_left(&MoveLeft, window, cx);
 1387        assert_eq!(
 1388            editor.selections.display_ranges(cx),
 1389            &[empty_range(1, "ab".len())]
 1390        );
 1391        editor.move_left(&MoveLeft, window, cx);
 1392        assert_eq!(
 1393            editor.selections.display_ranges(cx),
 1394            &[empty_range(1, "a".len())]
 1395        );
 1396
 1397        editor.move_down(&MoveDown, window, cx);
 1398        assert_eq!(
 1399            editor.selections.display_ranges(cx),
 1400            &[empty_range(2, "α".len())]
 1401        );
 1402        editor.move_right(&MoveRight, window, cx);
 1403        assert_eq!(
 1404            editor.selections.display_ranges(cx),
 1405            &[empty_range(2, "αβ".len())]
 1406        );
 1407        editor.move_right(&MoveRight, window, cx);
 1408        assert_eq!(
 1409            editor.selections.display_ranges(cx),
 1410            &[empty_range(2, "αβ⋯".len())]
 1411        );
 1412        editor.move_right(&MoveRight, window, cx);
 1413        assert_eq!(
 1414            editor.selections.display_ranges(cx),
 1415            &[empty_range(2, "αβ⋯ε".len())]
 1416        );
 1417
 1418        editor.move_up(&MoveUp, window, cx);
 1419        assert_eq!(
 1420            editor.selections.display_ranges(cx),
 1421            &[empty_range(1, "ab⋯e".len())]
 1422        );
 1423        editor.move_down(&MoveDown, window, cx);
 1424        assert_eq!(
 1425            editor.selections.display_ranges(cx),
 1426            &[empty_range(2, "αβ⋯ε".len())]
 1427        );
 1428        editor.move_up(&MoveUp, window, cx);
 1429        assert_eq!(
 1430            editor.selections.display_ranges(cx),
 1431            &[empty_range(1, "ab⋯e".len())]
 1432        );
 1433
 1434        editor.move_up(&MoveUp, window, cx);
 1435        assert_eq!(
 1436            editor.selections.display_ranges(cx),
 1437            &[empty_range(0, "🟥🟧".len())]
 1438        );
 1439        editor.move_left(&MoveLeft, window, cx);
 1440        assert_eq!(
 1441            editor.selections.display_ranges(cx),
 1442            &[empty_range(0, "🟥".len())]
 1443        );
 1444        editor.move_left(&MoveLeft, window, cx);
 1445        assert_eq!(
 1446            editor.selections.display_ranges(cx),
 1447            &[empty_range(0, "".len())]
 1448        );
 1449    });
 1450}
 1451
 1452#[gpui::test]
 1453fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1454    init_test(cx, |_| {});
 1455
 1456    let editor = cx.add_window(|window, cx| {
 1457        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1458        build_editor(buffer, window, cx)
 1459    });
 1460    _ = editor.update(cx, |editor, window, cx| {
 1461        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1462            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1463        });
 1464
 1465        // moving above start of document should move selection to start of document,
 1466        // but the next move down should still be at the original goal_x
 1467        editor.move_up(&MoveUp, window, cx);
 1468        assert_eq!(
 1469            editor.selections.display_ranges(cx),
 1470            &[empty_range(0, "".len())]
 1471        );
 1472
 1473        editor.move_down(&MoveDown, window, cx);
 1474        assert_eq!(
 1475            editor.selections.display_ranges(cx),
 1476            &[empty_range(1, "abcd".len())]
 1477        );
 1478
 1479        editor.move_down(&MoveDown, window, cx);
 1480        assert_eq!(
 1481            editor.selections.display_ranges(cx),
 1482            &[empty_range(2, "αβγ".len())]
 1483        );
 1484
 1485        editor.move_down(&MoveDown, window, cx);
 1486        assert_eq!(
 1487            editor.selections.display_ranges(cx),
 1488            &[empty_range(3, "abcd".len())]
 1489        );
 1490
 1491        editor.move_down(&MoveDown, window, cx);
 1492        assert_eq!(
 1493            editor.selections.display_ranges(cx),
 1494            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1495        );
 1496
 1497        // moving past end of document should not change goal_x
 1498        editor.move_down(&MoveDown, window, cx);
 1499        assert_eq!(
 1500            editor.selections.display_ranges(cx),
 1501            &[empty_range(5, "".len())]
 1502        );
 1503
 1504        editor.move_down(&MoveDown, window, cx);
 1505        assert_eq!(
 1506            editor.selections.display_ranges(cx),
 1507            &[empty_range(5, "".len())]
 1508        );
 1509
 1510        editor.move_up(&MoveUp, window, cx);
 1511        assert_eq!(
 1512            editor.selections.display_ranges(cx),
 1513            &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]
 1514        );
 1515
 1516        editor.move_up(&MoveUp, window, cx);
 1517        assert_eq!(
 1518            editor.selections.display_ranges(cx),
 1519            &[empty_range(3, "abcd".len())]
 1520        );
 1521
 1522        editor.move_up(&MoveUp, window, cx);
 1523        assert_eq!(
 1524            editor.selections.display_ranges(cx),
 1525            &[empty_range(2, "αβγ".len())]
 1526        );
 1527    });
 1528}
 1529
 1530#[gpui::test]
 1531fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1532    init_test(cx, |_| {});
 1533    let move_to_beg = MoveToBeginningOfLine {
 1534        stop_at_soft_wraps: true,
 1535        stop_at_indent: true,
 1536    };
 1537
 1538    let delete_to_beg = DeleteToBeginningOfLine {
 1539        stop_at_indent: false,
 1540    };
 1541
 1542    let move_to_end = MoveToEndOfLine {
 1543        stop_at_soft_wraps: true,
 1544    };
 1545
 1546    let editor = cx.add_window(|window, cx| {
 1547        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1548        build_editor(buffer, window, cx)
 1549    });
 1550    _ = editor.update(cx, |editor, window, cx| {
 1551        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1552            s.select_display_ranges([
 1553                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1554                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1555            ]);
 1556        });
 1557    });
 1558
 1559    _ = editor.update(cx, |editor, window, cx| {
 1560        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1561        assert_eq!(
 1562            editor.selections.display_ranges(cx),
 1563            &[
 1564                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1565                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1566            ]
 1567        );
 1568    });
 1569
 1570    _ = editor.update(cx, |editor, window, cx| {
 1571        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1572        assert_eq!(
 1573            editor.selections.display_ranges(cx),
 1574            &[
 1575                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1576                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1577            ]
 1578        );
 1579    });
 1580
 1581    _ = editor.update(cx, |editor, window, cx| {
 1582        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1583        assert_eq!(
 1584            editor.selections.display_ranges(cx),
 1585            &[
 1586                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1587                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1588            ]
 1589        );
 1590    });
 1591
 1592    _ = editor.update(cx, |editor, window, cx| {
 1593        editor.move_to_end_of_line(&move_to_end, window, cx);
 1594        assert_eq!(
 1595            editor.selections.display_ranges(cx),
 1596            &[
 1597                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1598                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1599            ]
 1600        );
 1601    });
 1602
 1603    // Moving to the end of line again is a no-op.
 1604    _ = editor.update(cx, |editor, window, cx| {
 1605        editor.move_to_end_of_line(&move_to_end, window, cx);
 1606        assert_eq!(
 1607            editor.selections.display_ranges(cx),
 1608            &[
 1609                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1610                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1611            ]
 1612        );
 1613    });
 1614
 1615    _ = editor.update(cx, |editor, window, cx| {
 1616        editor.move_left(&MoveLeft, window, cx);
 1617        editor.select_to_beginning_of_line(
 1618            &SelectToBeginningOfLine {
 1619                stop_at_soft_wraps: true,
 1620                stop_at_indent: true,
 1621            },
 1622            window,
 1623            cx,
 1624        );
 1625        assert_eq!(
 1626            editor.selections.display_ranges(cx),
 1627            &[
 1628                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1629                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1630            ]
 1631        );
 1632    });
 1633
 1634    _ = editor.update(cx, |editor, window, cx| {
 1635        editor.select_to_beginning_of_line(
 1636            &SelectToBeginningOfLine {
 1637                stop_at_soft_wraps: true,
 1638                stop_at_indent: true,
 1639            },
 1640            window,
 1641            cx,
 1642        );
 1643        assert_eq!(
 1644            editor.selections.display_ranges(cx),
 1645            &[
 1646                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1647                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1648            ]
 1649        );
 1650    });
 1651
 1652    _ = editor.update(cx, |editor, window, cx| {
 1653        editor.select_to_beginning_of_line(
 1654            &SelectToBeginningOfLine {
 1655                stop_at_soft_wraps: true,
 1656                stop_at_indent: true,
 1657            },
 1658            window,
 1659            cx,
 1660        );
 1661        assert_eq!(
 1662            editor.selections.display_ranges(cx),
 1663            &[
 1664                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1665                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1666            ]
 1667        );
 1668    });
 1669
 1670    _ = editor.update(cx, |editor, window, cx| {
 1671        editor.select_to_end_of_line(
 1672            &SelectToEndOfLine {
 1673                stop_at_soft_wraps: true,
 1674            },
 1675            window,
 1676            cx,
 1677        );
 1678        assert_eq!(
 1679            editor.selections.display_ranges(cx),
 1680            &[
 1681                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1682                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1683            ]
 1684        );
 1685    });
 1686
 1687    _ = editor.update(cx, |editor, window, cx| {
 1688        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1689        assert_eq!(editor.display_text(cx), "ab\n  de");
 1690        assert_eq!(
 1691            editor.selections.display_ranges(cx),
 1692            &[
 1693                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1694                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1695            ]
 1696        );
 1697    });
 1698
 1699    _ = editor.update(cx, |editor, window, cx| {
 1700        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1701        assert_eq!(editor.display_text(cx), "\n");
 1702        assert_eq!(
 1703            editor.selections.display_ranges(cx),
 1704            &[
 1705                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1706                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1707            ]
 1708        );
 1709    });
 1710}
 1711
 1712#[gpui::test]
 1713fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1714    init_test(cx, |_| {});
 1715    let move_to_beg = MoveToBeginningOfLine {
 1716        stop_at_soft_wraps: false,
 1717        stop_at_indent: false,
 1718    };
 1719
 1720    let move_to_end = MoveToEndOfLine {
 1721        stop_at_soft_wraps: false,
 1722    };
 1723
 1724    let editor = cx.add_window(|window, cx| {
 1725        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1726        build_editor(buffer, window, cx)
 1727    });
 1728
 1729    _ = editor.update(cx, |editor, window, cx| {
 1730        editor.set_wrap_width(Some(140.0.into()), cx);
 1731
 1732        // We expect the following lines after wrapping
 1733        // ```
 1734        // thequickbrownfox
 1735        // jumpedoverthelazydo
 1736        // gs
 1737        // ```
 1738        // The final `gs` was soft-wrapped onto a new line.
 1739        assert_eq!(
 1740            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1741            editor.display_text(cx),
 1742        );
 1743
 1744        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1745        // Start the cursor at the `k` on the first line
 1746        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1747            s.select_display_ranges([
 1748                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1749            ]);
 1750        });
 1751
 1752        // Moving to the beginning of the line should put us at the beginning of the line.
 1753        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1754        assert_eq!(
 1755            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1756            editor.selections.display_ranges(cx)
 1757        );
 1758
 1759        // Moving to the end of the line should put us at the end of the line.
 1760        editor.move_to_end_of_line(&move_to_end, window, cx);
 1761        assert_eq!(
 1762            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1763            editor.selections.display_ranges(cx)
 1764        );
 1765
 1766        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1767        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1768        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1769            s.select_display_ranges([
 1770                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1771            ]);
 1772        });
 1773
 1774        // Moving to the beginning of the line should put us at the start of the second line of
 1775        // display text, i.e., the `j`.
 1776        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1777        assert_eq!(
 1778            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1779            editor.selections.display_ranges(cx)
 1780        );
 1781
 1782        // Moving to the beginning of the line again should be a no-op.
 1783        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1784        assert_eq!(
 1785            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1786            editor.selections.display_ranges(cx)
 1787        );
 1788
 1789        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1790        // next display line.
 1791        editor.move_to_end_of_line(&move_to_end, window, cx);
 1792        assert_eq!(
 1793            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1794            editor.selections.display_ranges(cx)
 1795        );
 1796
 1797        // Moving to the end of the line again should be a no-op.
 1798        editor.move_to_end_of_line(&move_to_end, window, cx);
 1799        assert_eq!(
 1800            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1801            editor.selections.display_ranges(cx)
 1802        );
 1803    });
 1804}
 1805
 1806#[gpui::test]
 1807fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1808    init_test(cx, |_| {});
 1809
 1810    let move_to_beg = MoveToBeginningOfLine {
 1811        stop_at_soft_wraps: true,
 1812        stop_at_indent: true,
 1813    };
 1814
 1815    let select_to_beg = SelectToBeginningOfLine {
 1816        stop_at_soft_wraps: true,
 1817        stop_at_indent: true,
 1818    };
 1819
 1820    let delete_to_beg = DeleteToBeginningOfLine {
 1821        stop_at_indent: true,
 1822    };
 1823
 1824    let move_to_end = MoveToEndOfLine {
 1825        stop_at_soft_wraps: false,
 1826    };
 1827
 1828    let editor = cx.add_window(|window, cx| {
 1829        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1830        build_editor(buffer, window, cx)
 1831    });
 1832
 1833    _ = editor.update(cx, |editor, window, cx| {
 1834        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1835            s.select_display_ranges([
 1836                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1837                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1838            ]);
 1839        });
 1840
 1841        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1842        // and the second cursor at the first non-whitespace character in the line.
 1843        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1844        assert_eq!(
 1845            editor.selections.display_ranges(cx),
 1846            &[
 1847                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1848                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1849            ]
 1850        );
 1851
 1852        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1853        // and should move the second cursor to the beginning of the line.
 1854        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1855        assert_eq!(
 1856            editor.selections.display_ranges(cx),
 1857            &[
 1858                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1859                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1860            ]
 1861        );
 1862
 1863        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 1864        // and should move the second cursor back to the first non-whitespace character in the line.
 1865        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1866        assert_eq!(
 1867            editor.selections.display_ranges(cx),
 1868            &[
 1869                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1870                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1871            ]
 1872        );
 1873
 1874        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 1875        // and to the first non-whitespace character in the line for the second cursor.
 1876        editor.move_to_end_of_line(&move_to_end, window, cx);
 1877        editor.move_left(&MoveLeft, window, cx);
 1878        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1879        assert_eq!(
 1880            editor.selections.display_ranges(cx),
 1881            &[
 1882                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1883                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1884            ]
 1885        );
 1886
 1887        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 1888        // and should select to the beginning of the line for the second cursor.
 1889        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 1890        assert_eq!(
 1891            editor.selections.display_ranges(cx),
 1892            &[
 1893                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1894                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1895            ]
 1896        );
 1897
 1898        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 1899        // and should delete to the first non-whitespace character in the line for the second cursor.
 1900        editor.move_to_end_of_line(&move_to_end, window, cx);
 1901        editor.move_left(&MoveLeft, window, cx);
 1902        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1903        assert_eq!(editor.text(cx), "c\n  f");
 1904    });
 1905}
 1906
 1907#[gpui::test]
 1908fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 1909    init_test(cx, |_| {});
 1910
 1911    let move_to_beg = MoveToBeginningOfLine {
 1912        stop_at_soft_wraps: true,
 1913        stop_at_indent: true,
 1914    };
 1915
 1916    let editor = cx.add_window(|window, cx| {
 1917        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 1918        build_editor(buffer, window, cx)
 1919    });
 1920
 1921    _ = editor.update(cx, |editor, window, cx| {
 1922        // test cursor between line_start and indent_start
 1923        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1924            s.select_display_ranges([
 1925                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 1926            ]);
 1927        });
 1928
 1929        // cursor should move to line_start
 1930        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1931        assert_eq!(
 1932            editor.selections.display_ranges(cx),
 1933            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1934        );
 1935
 1936        // cursor should move to indent_start
 1937        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1938        assert_eq!(
 1939            editor.selections.display_ranges(cx),
 1940            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 1941        );
 1942
 1943        // cursor should move to back to line_start
 1944        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1945        assert_eq!(
 1946            editor.selections.display_ranges(cx),
 1947            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1948        );
 1949    });
 1950}
 1951
 1952#[gpui::test]
 1953fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 1954    init_test(cx, |_| {});
 1955
 1956    let editor = cx.add_window(|window, cx| {
 1957        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 1958        build_editor(buffer, window, cx)
 1959    });
 1960    _ = editor.update(cx, |editor, window, cx| {
 1961        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1962            s.select_display_ranges([
 1963                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 1964                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 1965            ])
 1966        });
 1967        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1968        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 1969
 1970        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1971        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 1972
 1973        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1974        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1975
 1976        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1977        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1978
 1979        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 1980        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 1981
 1982        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1983        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 1984
 1985        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1986        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 1987
 1988        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 1989        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 1990
 1991        editor.move_right(&MoveRight, window, cx);
 1992        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 1993        assert_selection_ranges(
 1994            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 1995            editor,
 1996            cx,
 1997        );
 1998
 1999        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2000        assert_selection_ranges(
 2001            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2002            editor,
 2003            cx,
 2004        );
 2005
 2006        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2007        assert_selection_ranges(
 2008            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2009            editor,
 2010            cx,
 2011        );
 2012    });
 2013}
 2014
 2015#[gpui::test]
 2016fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2017    init_test(cx, |_| {});
 2018
 2019    let editor = cx.add_window(|window, cx| {
 2020        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2021        build_editor(buffer, window, cx)
 2022    });
 2023
 2024    _ = editor.update(cx, |editor, window, cx| {
 2025        editor.set_wrap_width(Some(140.0.into()), cx);
 2026        assert_eq!(
 2027            editor.display_text(cx),
 2028            "use one::{\n    two::three::\n    four::five\n};"
 2029        );
 2030
 2031        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2032            s.select_display_ranges([
 2033                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2034            ]);
 2035        });
 2036
 2037        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2038        assert_eq!(
 2039            editor.selections.display_ranges(cx),
 2040            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2041        );
 2042
 2043        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2044        assert_eq!(
 2045            editor.selections.display_ranges(cx),
 2046            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2047        );
 2048
 2049        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2050        assert_eq!(
 2051            editor.selections.display_ranges(cx),
 2052            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2053        );
 2054
 2055        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2056        assert_eq!(
 2057            editor.selections.display_ranges(cx),
 2058            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2059        );
 2060
 2061        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2062        assert_eq!(
 2063            editor.selections.display_ranges(cx),
 2064            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2065        );
 2066
 2067        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2068        assert_eq!(
 2069            editor.selections.display_ranges(cx),
 2070            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2071        );
 2072    });
 2073}
 2074
 2075#[gpui::test]
 2076async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2077    init_test(cx, |_| {});
 2078    let mut cx = EditorTestContext::new(cx).await;
 2079
 2080    let line_height = cx.editor(|editor, window, _| {
 2081        editor
 2082            .style()
 2083            .unwrap()
 2084            .text
 2085            .line_height_in_pixels(window.rem_size())
 2086    });
 2087    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2088
 2089    cx.set_state(
 2090        &r#"ˇone
 2091        two
 2092
 2093        three
 2094        fourˇ
 2095        five
 2096
 2097        six"#
 2098            .unindent(),
 2099    );
 2100
 2101    cx.update_editor(|editor, window, cx| {
 2102        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2103    });
 2104    cx.assert_editor_state(
 2105        &r#"one
 2106        two
 2107        ˇ
 2108        three
 2109        four
 2110        five
 2111        ˇ
 2112        six"#
 2113            .unindent(),
 2114    );
 2115
 2116    cx.update_editor(|editor, window, cx| {
 2117        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2118    });
 2119    cx.assert_editor_state(
 2120        &r#"one
 2121        two
 2122
 2123        three
 2124        four
 2125        five
 2126        ˇ
 2127        sixˇ"#
 2128            .unindent(),
 2129    );
 2130
 2131    cx.update_editor(|editor, window, cx| {
 2132        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2133    });
 2134    cx.assert_editor_state(
 2135        &r#"one
 2136        two
 2137
 2138        three
 2139        four
 2140        five
 2141
 2142        sixˇ"#
 2143            .unindent(),
 2144    );
 2145
 2146    cx.update_editor(|editor, window, cx| {
 2147        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2148    });
 2149    cx.assert_editor_state(
 2150        &r#"one
 2151        two
 2152
 2153        three
 2154        four
 2155        five
 2156        ˇ
 2157        six"#
 2158            .unindent(),
 2159    );
 2160
 2161    cx.update_editor(|editor, window, cx| {
 2162        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2163    });
 2164    cx.assert_editor_state(
 2165        &r#"one
 2166        two
 2167        ˇ
 2168        three
 2169        four
 2170        five
 2171
 2172        six"#
 2173            .unindent(),
 2174    );
 2175
 2176    cx.update_editor(|editor, window, cx| {
 2177        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2178    });
 2179    cx.assert_editor_state(
 2180        &r#"ˇone
 2181        two
 2182
 2183        three
 2184        four
 2185        five
 2186
 2187        six"#
 2188            .unindent(),
 2189    );
 2190}
 2191
 2192#[gpui::test]
 2193async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2194    init_test(cx, |_| {});
 2195    let mut cx = EditorTestContext::new(cx).await;
 2196    let line_height = cx.editor(|editor, window, _| {
 2197        editor
 2198            .style()
 2199            .unwrap()
 2200            .text
 2201            .line_height_in_pixels(window.rem_size())
 2202    });
 2203    let window = cx.window;
 2204    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2205
 2206    cx.set_state(
 2207        r#"ˇone
 2208        two
 2209        three
 2210        four
 2211        five
 2212        six
 2213        seven
 2214        eight
 2215        nine
 2216        ten
 2217        "#,
 2218    );
 2219
 2220    cx.update_editor(|editor, window, cx| {
 2221        assert_eq!(
 2222            editor.snapshot(window, cx).scroll_position(),
 2223            gpui::Point::new(0., 0.)
 2224        );
 2225        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2226        assert_eq!(
 2227            editor.snapshot(window, cx).scroll_position(),
 2228            gpui::Point::new(0., 3.)
 2229        );
 2230        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2231        assert_eq!(
 2232            editor.snapshot(window, cx).scroll_position(),
 2233            gpui::Point::new(0., 6.)
 2234        );
 2235        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2236        assert_eq!(
 2237            editor.snapshot(window, cx).scroll_position(),
 2238            gpui::Point::new(0., 3.)
 2239        );
 2240
 2241        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2242        assert_eq!(
 2243            editor.snapshot(window, cx).scroll_position(),
 2244            gpui::Point::new(0., 1.)
 2245        );
 2246        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2247        assert_eq!(
 2248            editor.snapshot(window, cx).scroll_position(),
 2249            gpui::Point::new(0., 3.)
 2250        );
 2251    });
 2252}
 2253
 2254#[gpui::test]
 2255async fn test_autoscroll(cx: &mut TestAppContext) {
 2256    init_test(cx, |_| {});
 2257    let mut cx = EditorTestContext::new(cx).await;
 2258
 2259    let line_height = cx.update_editor(|editor, window, cx| {
 2260        editor.set_vertical_scroll_margin(2, cx);
 2261        editor
 2262            .style()
 2263            .unwrap()
 2264            .text
 2265            .line_height_in_pixels(window.rem_size())
 2266    });
 2267    let window = cx.window;
 2268    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2269
 2270    cx.set_state(
 2271        r#"ˇone
 2272            two
 2273            three
 2274            four
 2275            five
 2276            six
 2277            seven
 2278            eight
 2279            nine
 2280            ten
 2281        "#,
 2282    );
 2283    cx.update_editor(|editor, window, cx| {
 2284        assert_eq!(
 2285            editor.snapshot(window, cx).scroll_position(),
 2286            gpui::Point::new(0., 0.0)
 2287        );
 2288    });
 2289
 2290    // Add a cursor below the visible area. Since both cursors cannot fit
 2291    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2292    // allows the vertical scroll margin below that cursor.
 2293    cx.update_editor(|editor, window, cx| {
 2294        editor.change_selections(Default::default(), window, cx, |selections| {
 2295            selections.select_ranges([
 2296                Point::new(0, 0)..Point::new(0, 0),
 2297                Point::new(6, 0)..Point::new(6, 0),
 2298            ]);
 2299        })
 2300    });
 2301    cx.update_editor(|editor, window, cx| {
 2302        assert_eq!(
 2303            editor.snapshot(window, cx).scroll_position(),
 2304            gpui::Point::new(0., 3.0)
 2305        );
 2306    });
 2307
 2308    // Move down. The editor cursor scrolls down to track the newest cursor.
 2309    cx.update_editor(|editor, window, cx| {
 2310        editor.move_down(&Default::default(), window, cx);
 2311    });
 2312    cx.update_editor(|editor, window, cx| {
 2313        assert_eq!(
 2314            editor.snapshot(window, cx).scroll_position(),
 2315            gpui::Point::new(0., 4.0)
 2316        );
 2317    });
 2318
 2319    // Add a cursor above the visible area. Since both cursors fit on screen,
 2320    // the editor scrolls to show both.
 2321    cx.update_editor(|editor, window, cx| {
 2322        editor.change_selections(Default::default(), window, cx, |selections| {
 2323            selections.select_ranges([
 2324                Point::new(1, 0)..Point::new(1, 0),
 2325                Point::new(6, 0)..Point::new(6, 0),
 2326            ]);
 2327        })
 2328    });
 2329    cx.update_editor(|editor, window, cx| {
 2330        assert_eq!(
 2331            editor.snapshot(window, cx).scroll_position(),
 2332            gpui::Point::new(0., 1.0)
 2333        );
 2334    });
 2335}
 2336
 2337#[gpui::test]
 2338async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2339    init_test(cx, |_| {});
 2340    let mut cx = EditorTestContext::new(cx).await;
 2341
 2342    let line_height = cx.editor(|editor, window, _cx| {
 2343        editor
 2344            .style()
 2345            .unwrap()
 2346            .text
 2347            .line_height_in_pixels(window.rem_size())
 2348    });
 2349    let window = cx.window;
 2350    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2351    cx.set_state(
 2352        &r#"
 2353        ˇone
 2354        two
 2355        threeˇ
 2356        four
 2357        five
 2358        six
 2359        seven
 2360        eight
 2361        nine
 2362        ten
 2363        "#
 2364        .unindent(),
 2365    );
 2366
 2367    cx.update_editor(|editor, window, cx| {
 2368        editor.move_page_down(&MovePageDown::default(), window, cx)
 2369    });
 2370    cx.assert_editor_state(
 2371        &r#"
 2372        one
 2373        two
 2374        three
 2375        ˇfour
 2376        five
 2377        sixˇ
 2378        seven
 2379        eight
 2380        nine
 2381        ten
 2382        "#
 2383        .unindent(),
 2384    );
 2385
 2386    cx.update_editor(|editor, window, cx| {
 2387        editor.move_page_down(&MovePageDown::default(), window, cx)
 2388    });
 2389    cx.assert_editor_state(
 2390        &r#"
 2391        one
 2392        two
 2393        three
 2394        four
 2395        five
 2396        six
 2397        ˇseven
 2398        eight
 2399        nineˇ
 2400        ten
 2401        "#
 2402        .unindent(),
 2403    );
 2404
 2405    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2406    cx.assert_editor_state(
 2407        &r#"
 2408        one
 2409        two
 2410        three
 2411        ˇfour
 2412        five
 2413        sixˇ
 2414        seven
 2415        eight
 2416        nine
 2417        ten
 2418        "#
 2419        .unindent(),
 2420    );
 2421
 2422    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2423    cx.assert_editor_state(
 2424        &r#"
 2425        ˇone
 2426        two
 2427        threeˇ
 2428        four
 2429        five
 2430        six
 2431        seven
 2432        eight
 2433        nine
 2434        ten
 2435        "#
 2436        .unindent(),
 2437    );
 2438
 2439    // Test select collapsing
 2440    cx.update_editor(|editor, window, cx| {
 2441        editor.move_page_down(&MovePageDown::default(), window, cx);
 2442        editor.move_page_down(&MovePageDown::default(), window, cx);
 2443        editor.move_page_down(&MovePageDown::default(), window, cx);
 2444    });
 2445    cx.assert_editor_state(
 2446        &r#"
 2447        one
 2448        two
 2449        three
 2450        four
 2451        five
 2452        six
 2453        seven
 2454        eight
 2455        nine
 2456        ˇten
 2457        ˇ"#
 2458        .unindent(),
 2459    );
 2460}
 2461
 2462#[gpui::test]
 2463async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2464    init_test(cx, |_| {});
 2465    let mut cx = EditorTestContext::new(cx).await;
 2466    cx.set_state("one «two threeˇ» four");
 2467    cx.update_editor(|editor, window, cx| {
 2468        editor.delete_to_beginning_of_line(
 2469            &DeleteToBeginningOfLine {
 2470                stop_at_indent: false,
 2471            },
 2472            window,
 2473            cx,
 2474        );
 2475        assert_eq!(editor.text(cx), " four");
 2476    });
 2477}
 2478
 2479#[gpui::test]
 2480async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2481    init_test(cx, |_| {});
 2482
 2483    let mut cx = EditorTestContext::new(cx).await;
 2484
 2485    // For an empty selection, the preceding word fragment is deleted.
 2486    // For non-empty selections, only selected characters are deleted.
 2487    cx.set_state("onˇe two t«hreˇ»e four");
 2488    cx.update_editor(|editor, window, cx| {
 2489        editor.delete_to_previous_word_start(
 2490            &DeleteToPreviousWordStart {
 2491                ignore_newlines: false,
 2492                ignore_brackets: false,
 2493            },
 2494            window,
 2495            cx,
 2496        );
 2497    });
 2498    cx.assert_editor_state("ˇe two tˇe four");
 2499
 2500    cx.set_state("e tˇwo te «fˇ»our");
 2501    cx.update_editor(|editor, window, cx| {
 2502        editor.delete_to_next_word_end(
 2503            &DeleteToNextWordEnd {
 2504                ignore_newlines: false,
 2505                ignore_brackets: false,
 2506            },
 2507            window,
 2508            cx,
 2509        );
 2510    });
 2511    cx.assert_editor_state("e tˇ te ˇour");
 2512}
 2513
 2514#[gpui::test]
 2515async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2516    init_test(cx, |_| {});
 2517
 2518    let mut cx = EditorTestContext::new(cx).await;
 2519
 2520    cx.set_state("here is some text    ˇwith a space");
 2521    cx.update_editor(|editor, window, cx| {
 2522        editor.delete_to_previous_word_start(
 2523            &DeleteToPreviousWordStart {
 2524                ignore_newlines: false,
 2525                ignore_brackets: true,
 2526            },
 2527            window,
 2528            cx,
 2529        );
 2530    });
 2531    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2532    cx.assert_editor_state("here is some textˇwith a space");
 2533
 2534    cx.set_state("here is some text    ˇwith a space");
 2535    cx.update_editor(|editor, window, cx| {
 2536        editor.delete_to_previous_word_start(
 2537            &DeleteToPreviousWordStart {
 2538                ignore_newlines: false,
 2539                ignore_brackets: false,
 2540            },
 2541            window,
 2542            cx,
 2543        );
 2544    });
 2545    cx.assert_editor_state("here is some textˇwith a space");
 2546
 2547    cx.set_state("here is some textˇ    with a space");
 2548    cx.update_editor(|editor, window, cx| {
 2549        editor.delete_to_next_word_end(
 2550            &DeleteToNextWordEnd {
 2551                ignore_newlines: false,
 2552                ignore_brackets: true,
 2553            },
 2554            window,
 2555            cx,
 2556        );
 2557    });
 2558    // Same happens in the other direction.
 2559    cx.assert_editor_state("here is some textˇwith a space");
 2560
 2561    cx.set_state("here is some textˇ    with a space");
 2562    cx.update_editor(|editor, window, cx| {
 2563        editor.delete_to_next_word_end(
 2564            &DeleteToNextWordEnd {
 2565                ignore_newlines: false,
 2566                ignore_brackets: false,
 2567            },
 2568            window,
 2569            cx,
 2570        );
 2571    });
 2572    cx.assert_editor_state("here is some textˇwith a space");
 2573
 2574    cx.set_state("here is some textˇ    with a space");
 2575    cx.update_editor(|editor, window, cx| {
 2576        editor.delete_to_next_word_end(
 2577            &DeleteToNextWordEnd {
 2578                ignore_newlines: true,
 2579                ignore_brackets: false,
 2580            },
 2581            window,
 2582            cx,
 2583        );
 2584    });
 2585    cx.assert_editor_state("here is some textˇwith a space");
 2586    cx.update_editor(|editor, window, cx| {
 2587        editor.delete_to_previous_word_start(
 2588            &DeleteToPreviousWordStart {
 2589                ignore_newlines: true,
 2590                ignore_brackets: false,
 2591            },
 2592            window,
 2593            cx,
 2594        );
 2595    });
 2596    cx.assert_editor_state("here is some ˇwith a space");
 2597    cx.update_editor(|editor, window, cx| {
 2598        editor.delete_to_previous_word_start(
 2599            &DeleteToPreviousWordStart {
 2600                ignore_newlines: true,
 2601                ignore_brackets: false,
 2602            },
 2603            window,
 2604            cx,
 2605        );
 2606    });
 2607    // Single whitespaces are removed with the word behind them.
 2608    cx.assert_editor_state("here is ˇwith a space");
 2609    cx.update_editor(|editor, window, cx| {
 2610        editor.delete_to_previous_word_start(
 2611            &DeleteToPreviousWordStart {
 2612                ignore_newlines: true,
 2613                ignore_brackets: false,
 2614            },
 2615            window,
 2616            cx,
 2617        );
 2618    });
 2619    cx.assert_editor_state("here ˇwith a space");
 2620    cx.update_editor(|editor, window, cx| {
 2621        editor.delete_to_previous_word_start(
 2622            &DeleteToPreviousWordStart {
 2623                ignore_newlines: true,
 2624                ignore_brackets: false,
 2625            },
 2626            window,
 2627            cx,
 2628        );
 2629    });
 2630    cx.assert_editor_state("ˇwith a space");
 2631    cx.update_editor(|editor, window, cx| {
 2632        editor.delete_to_previous_word_start(
 2633            &DeleteToPreviousWordStart {
 2634                ignore_newlines: true,
 2635                ignore_brackets: false,
 2636            },
 2637            window,
 2638            cx,
 2639        );
 2640    });
 2641    cx.assert_editor_state("ˇwith a space");
 2642    cx.update_editor(|editor, window, cx| {
 2643        editor.delete_to_next_word_end(
 2644            &DeleteToNextWordEnd {
 2645                ignore_newlines: true,
 2646                ignore_brackets: false,
 2647            },
 2648            window,
 2649            cx,
 2650        );
 2651    });
 2652    // Same happens in the other direction.
 2653    cx.assert_editor_state("ˇ a space");
 2654    cx.update_editor(|editor, window, cx| {
 2655        editor.delete_to_next_word_end(
 2656            &DeleteToNextWordEnd {
 2657                ignore_newlines: true,
 2658                ignore_brackets: false,
 2659            },
 2660            window,
 2661            cx,
 2662        );
 2663    });
 2664    cx.assert_editor_state("ˇ space");
 2665    cx.update_editor(|editor, window, cx| {
 2666        editor.delete_to_next_word_end(
 2667            &DeleteToNextWordEnd {
 2668                ignore_newlines: true,
 2669                ignore_brackets: false,
 2670            },
 2671            window,
 2672            cx,
 2673        );
 2674    });
 2675    cx.assert_editor_state("ˇ");
 2676    cx.update_editor(|editor, window, cx| {
 2677        editor.delete_to_next_word_end(
 2678            &DeleteToNextWordEnd {
 2679                ignore_newlines: true,
 2680                ignore_brackets: false,
 2681            },
 2682            window,
 2683            cx,
 2684        );
 2685    });
 2686    cx.assert_editor_state("ˇ");
 2687    cx.update_editor(|editor, window, cx| {
 2688        editor.delete_to_previous_word_start(
 2689            &DeleteToPreviousWordStart {
 2690                ignore_newlines: true,
 2691                ignore_brackets: false,
 2692            },
 2693            window,
 2694            cx,
 2695        );
 2696    });
 2697    cx.assert_editor_state("ˇ");
 2698}
 2699
 2700#[gpui::test]
 2701async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2702    init_test(cx, |_| {});
 2703
 2704    let language = Arc::new(
 2705        Language::new(
 2706            LanguageConfig {
 2707                brackets: BracketPairConfig {
 2708                    pairs: vec![
 2709                        BracketPair {
 2710                            start: "\"".to_string(),
 2711                            end: "\"".to_string(),
 2712                            close: true,
 2713                            surround: true,
 2714                            newline: false,
 2715                        },
 2716                        BracketPair {
 2717                            start: "(".to_string(),
 2718                            end: ")".to_string(),
 2719                            close: true,
 2720                            surround: true,
 2721                            newline: true,
 2722                        },
 2723                    ],
 2724                    ..BracketPairConfig::default()
 2725                },
 2726                ..LanguageConfig::default()
 2727            },
 2728            Some(tree_sitter_rust::LANGUAGE.into()),
 2729        )
 2730        .with_brackets_query(
 2731            r#"
 2732                ("(" @open ")" @close)
 2733                ("\"" @open "\"" @close)
 2734            "#,
 2735        )
 2736        .unwrap(),
 2737    );
 2738
 2739    let mut cx = EditorTestContext::new(cx).await;
 2740    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2741
 2742    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2743    cx.update_editor(|editor, window, cx| {
 2744        editor.delete_to_previous_word_start(
 2745            &DeleteToPreviousWordStart {
 2746                ignore_newlines: true,
 2747                ignore_brackets: false,
 2748            },
 2749            window,
 2750            cx,
 2751        );
 2752    });
 2753    // Deletion stops before brackets if asked to not ignore them.
 2754    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2755    cx.update_editor(|editor, window, cx| {
 2756        editor.delete_to_previous_word_start(
 2757            &DeleteToPreviousWordStart {
 2758                ignore_newlines: true,
 2759                ignore_brackets: false,
 2760            },
 2761            window,
 2762            cx,
 2763        );
 2764    });
 2765    // Deletion has to remove a single bracket and then stop again.
 2766    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2767
 2768    cx.update_editor(|editor, window, cx| {
 2769        editor.delete_to_previous_word_start(
 2770            &DeleteToPreviousWordStart {
 2771                ignore_newlines: true,
 2772                ignore_brackets: false,
 2773            },
 2774            window,
 2775            cx,
 2776        );
 2777    });
 2778    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2779
 2780    cx.update_editor(|editor, window, cx| {
 2781        editor.delete_to_previous_word_start(
 2782            &DeleteToPreviousWordStart {
 2783                ignore_newlines: true,
 2784                ignore_brackets: false,
 2785            },
 2786            window,
 2787            cx,
 2788        );
 2789    });
 2790    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2791
 2792    cx.update_editor(|editor, window, cx| {
 2793        editor.delete_to_previous_word_start(
 2794            &DeleteToPreviousWordStart {
 2795                ignore_newlines: true,
 2796                ignore_brackets: false,
 2797            },
 2798            window,
 2799            cx,
 2800        );
 2801    });
 2802    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2803
 2804    cx.update_editor(|editor, window, cx| {
 2805        editor.delete_to_next_word_end(
 2806            &DeleteToNextWordEnd {
 2807                ignore_newlines: true,
 2808                ignore_brackets: false,
 2809            },
 2810            window,
 2811            cx,
 2812        );
 2813    });
 2814    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2815    cx.assert_editor_state(r#"ˇ");"#);
 2816
 2817    cx.update_editor(|editor, window, cx| {
 2818        editor.delete_to_next_word_end(
 2819            &DeleteToNextWordEnd {
 2820                ignore_newlines: true,
 2821                ignore_brackets: false,
 2822            },
 2823            window,
 2824            cx,
 2825        );
 2826    });
 2827    cx.assert_editor_state(r#"ˇ"#);
 2828
 2829    cx.update_editor(|editor, window, cx| {
 2830        editor.delete_to_next_word_end(
 2831            &DeleteToNextWordEnd {
 2832                ignore_newlines: true,
 2833                ignore_brackets: false,
 2834            },
 2835            window,
 2836            cx,
 2837        );
 2838    });
 2839    cx.assert_editor_state(r#"ˇ"#);
 2840
 2841    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2842    cx.update_editor(|editor, window, cx| {
 2843        editor.delete_to_previous_word_start(
 2844            &DeleteToPreviousWordStart {
 2845                ignore_newlines: true,
 2846                ignore_brackets: true,
 2847            },
 2848            window,
 2849            cx,
 2850        );
 2851    });
 2852    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2853}
 2854
 2855#[gpui::test]
 2856fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2857    init_test(cx, |_| {});
 2858
 2859    let editor = cx.add_window(|window, cx| {
 2860        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2861        build_editor(buffer, window, cx)
 2862    });
 2863    let del_to_prev_word_start = DeleteToPreviousWordStart {
 2864        ignore_newlines: false,
 2865        ignore_brackets: false,
 2866    };
 2867    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 2868        ignore_newlines: true,
 2869        ignore_brackets: false,
 2870    };
 2871
 2872    _ = editor.update(cx, |editor, window, cx| {
 2873        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2874            s.select_display_ranges([
 2875                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 2876            ])
 2877        });
 2878        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2879        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 2880        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2881        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 2882        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2883        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 2884        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 2885        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 2886        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2887        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 2888        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 2889        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2890    });
 2891}
 2892
 2893#[gpui::test]
 2894fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 2895    init_test(cx, |_| {});
 2896
 2897    let editor = cx.add_window(|window, cx| {
 2898        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 2899        build_editor(buffer, window, cx)
 2900    });
 2901    let del_to_next_word_end = DeleteToNextWordEnd {
 2902        ignore_newlines: false,
 2903        ignore_brackets: false,
 2904    };
 2905    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 2906        ignore_newlines: true,
 2907        ignore_brackets: false,
 2908    };
 2909
 2910    _ = editor.update(cx, |editor, window, cx| {
 2911        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2912            s.select_display_ranges([
 2913                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 2914            ])
 2915        });
 2916        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2917        assert_eq!(
 2918            editor.buffer.read(cx).read(cx).text(),
 2919            "one\n   two\nthree\n   four"
 2920        );
 2921        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2922        assert_eq!(
 2923            editor.buffer.read(cx).read(cx).text(),
 2924            "\n   two\nthree\n   four"
 2925        );
 2926        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2927        assert_eq!(
 2928            editor.buffer.read(cx).read(cx).text(),
 2929            "two\nthree\n   four"
 2930        );
 2931        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 2932        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 2933        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2934        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 2935        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2936        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 2937        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 2938        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 2939    });
 2940}
 2941
 2942#[gpui::test]
 2943fn test_newline(cx: &mut TestAppContext) {
 2944    init_test(cx, |_| {});
 2945
 2946    let editor = cx.add_window(|window, cx| {
 2947        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 2948        build_editor(buffer, window, cx)
 2949    });
 2950
 2951    _ = editor.update(cx, |editor, window, cx| {
 2952        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2953            s.select_display_ranges([
 2954                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 2955                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2956                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 2957            ])
 2958        });
 2959
 2960        editor.newline(&Newline, window, cx);
 2961        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 2962    });
 2963}
 2964
 2965#[gpui::test]
 2966fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 2967    init_test(cx, |_| {});
 2968
 2969    let editor = cx.add_window(|window, cx| {
 2970        let buffer = MultiBuffer::build_simple(
 2971            "
 2972                a
 2973                b(
 2974                    X
 2975                )
 2976                c(
 2977                    X
 2978                )
 2979            "
 2980            .unindent()
 2981            .as_str(),
 2982            cx,
 2983        );
 2984        let mut editor = build_editor(buffer, window, cx);
 2985        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2986            s.select_ranges([
 2987                Point::new(2, 4)..Point::new(2, 5),
 2988                Point::new(5, 4)..Point::new(5, 5),
 2989            ])
 2990        });
 2991        editor
 2992    });
 2993
 2994    _ = editor.update(cx, |editor, window, cx| {
 2995        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 2996        editor.buffer.update(cx, |buffer, cx| {
 2997            buffer.edit(
 2998                [
 2999                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3000                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3001                ],
 3002                None,
 3003                cx,
 3004            );
 3005            assert_eq!(
 3006                buffer.read(cx).text(),
 3007                "
 3008                    a
 3009                    b()
 3010                    c()
 3011                "
 3012                .unindent()
 3013            );
 3014        });
 3015        assert_eq!(
 3016            editor.selections.ranges(cx),
 3017            &[
 3018                Point::new(1, 2)..Point::new(1, 2),
 3019                Point::new(2, 2)..Point::new(2, 2),
 3020            ],
 3021        );
 3022
 3023        editor.newline(&Newline, window, cx);
 3024        assert_eq!(
 3025            editor.text(cx),
 3026            "
 3027                a
 3028                b(
 3029                )
 3030                c(
 3031                )
 3032            "
 3033            .unindent()
 3034        );
 3035
 3036        // The selections are moved after the inserted newlines
 3037        assert_eq!(
 3038            editor.selections.ranges(cx),
 3039            &[
 3040                Point::new(2, 0)..Point::new(2, 0),
 3041                Point::new(4, 0)..Point::new(4, 0),
 3042            ],
 3043        );
 3044    });
 3045}
 3046
 3047#[gpui::test]
 3048async fn test_newline_above(cx: &mut TestAppContext) {
 3049    init_test(cx, |settings| {
 3050        settings.defaults.tab_size = NonZeroU32::new(4)
 3051    });
 3052
 3053    let language = Arc::new(
 3054        Language::new(
 3055            LanguageConfig::default(),
 3056            Some(tree_sitter_rust::LANGUAGE.into()),
 3057        )
 3058        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3059        .unwrap(),
 3060    );
 3061
 3062    let mut cx = EditorTestContext::new(cx).await;
 3063    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3064    cx.set_state(indoc! {"
 3065        const a: ˇA = (
 3066 3067                «const_functionˇ»(ˇ),
 3068                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3069 3070        ˇ);ˇ
 3071    "});
 3072
 3073    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3074    cx.assert_editor_state(indoc! {"
 3075        ˇ
 3076        const a: A = (
 3077            ˇ
 3078            (
 3079                ˇ
 3080                ˇ
 3081                const_function(),
 3082                ˇ
 3083                ˇ
 3084                ˇ
 3085                ˇ
 3086                something_else,
 3087                ˇ
 3088            )
 3089            ˇ
 3090            ˇ
 3091        );
 3092    "});
 3093}
 3094
 3095#[gpui::test]
 3096async fn test_newline_below(cx: &mut TestAppContext) {
 3097    init_test(cx, |settings| {
 3098        settings.defaults.tab_size = NonZeroU32::new(4)
 3099    });
 3100
 3101    let language = Arc::new(
 3102        Language::new(
 3103            LanguageConfig::default(),
 3104            Some(tree_sitter_rust::LANGUAGE.into()),
 3105        )
 3106        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3107        .unwrap(),
 3108    );
 3109
 3110    let mut cx = EditorTestContext::new(cx).await;
 3111    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3112    cx.set_state(indoc! {"
 3113        const a: ˇA = (
 3114 3115                «const_functionˇ»(ˇ),
 3116                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3117 3118        ˇ);ˇ
 3119    "});
 3120
 3121    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3122    cx.assert_editor_state(indoc! {"
 3123        const a: A = (
 3124            ˇ
 3125            (
 3126                ˇ
 3127                const_function(),
 3128                ˇ
 3129                ˇ
 3130                something_else,
 3131                ˇ
 3132                ˇ
 3133                ˇ
 3134                ˇ
 3135            )
 3136            ˇ
 3137        );
 3138        ˇ
 3139        ˇ
 3140    "});
 3141}
 3142
 3143#[gpui::test]
 3144async fn test_newline_comments(cx: &mut TestAppContext) {
 3145    init_test(cx, |settings| {
 3146        settings.defaults.tab_size = NonZeroU32::new(4)
 3147    });
 3148
 3149    let language = Arc::new(Language::new(
 3150        LanguageConfig {
 3151            line_comments: vec!["// ".into()],
 3152            ..LanguageConfig::default()
 3153        },
 3154        None,
 3155    ));
 3156    {
 3157        let mut cx = EditorTestContext::new(cx).await;
 3158        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3159        cx.set_state(indoc! {"
 3160        // Fooˇ
 3161    "});
 3162
 3163        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3164        cx.assert_editor_state(indoc! {"
 3165        // Foo
 3166        // ˇ
 3167    "});
 3168        // Ensure that we add comment prefix when existing line contains space
 3169        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3170        cx.assert_editor_state(
 3171            indoc! {"
 3172        // Foo
 3173        //s
 3174        // ˇ
 3175    "}
 3176            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3177            .as_str(),
 3178        );
 3179        // Ensure that we add comment prefix when existing line does not contain space
 3180        cx.set_state(indoc! {"
 3181        // Foo
 3182        //ˇ
 3183    "});
 3184        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3185        cx.assert_editor_state(indoc! {"
 3186        // Foo
 3187        //
 3188        // ˇ
 3189    "});
 3190        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3191        cx.set_state(indoc! {"
 3192        ˇ// Foo
 3193    "});
 3194        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3195        cx.assert_editor_state(indoc! {"
 3196
 3197        ˇ// Foo
 3198    "});
 3199    }
 3200    // Ensure that comment continuations can be disabled.
 3201    update_test_language_settings(cx, |settings| {
 3202        settings.defaults.extend_comment_on_newline = Some(false);
 3203    });
 3204    let mut cx = EditorTestContext::new(cx).await;
 3205    cx.set_state(indoc! {"
 3206        // Fooˇ
 3207    "});
 3208    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3209    cx.assert_editor_state(indoc! {"
 3210        // Foo
 3211        ˇ
 3212    "});
 3213}
 3214
 3215#[gpui::test]
 3216async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3217    init_test(cx, |settings| {
 3218        settings.defaults.tab_size = NonZeroU32::new(4)
 3219    });
 3220
 3221    let language = Arc::new(Language::new(
 3222        LanguageConfig {
 3223            line_comments: vec!["// ".into(), "/// ".into()],
 3224            ..LanguageConfig::default()
 3225        },
 3226        None,
 3227    ));
 3228    {
 3229        let mut cx = EditorTestContext::new(cx).await;
 3230        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3231        cx.set_state(indoc! {"
 3232        //ˇ
 3233    "});
 3234        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3235        cx.assert_editor_state(indoc! {"
 3236        //
 3237        // ˇ
 3238    "});
 3239
 3240        cx.set_state(indoc! {"
 3241        ///ˇ
 3242    "});
 3243        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3244        cx.assert_editor_state(indoc! {"
 3245        ///
 3246        /// ˇ
 3247    "});
 3248    }
 3249}
 3250
 3251#[gpui::test]
 3252async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3253    init_test(cx, |settings| {
 3254        settings.defaults.tab_size = NonZeroU32::new(4)
 3255    });
 3256
 3257    let language = Arc::new(
 3258        Language::new(
 3259            LanguageConfig {
 3260                documentation_comment: Some(language::BlockCommentConfig {
 3261                    start: "/**".into(),
 3262                    end: "*/".into(),
 3263                    prefix: "* ".into(),
 3264                    tab_size: 1,
 3265                }),
 3266
 3267                ..LanguageConfig::default()
 3268            },
 3269            Some(tree_sitter_rust::LANGUAGE.into()),
 3270        )
 3271        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3272        .unwrap(),
 3273    );
 3274
 3275    {
 3276        let mut cx = EditorTestContext::new(cx).await;
 3277        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3278        cx.set_state(indoc! {"
 3279        /**ˇ
 3280    "});
 3281
 3282        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3283        cx.assert_editor_state(indoc! {"
 3284        /**
 3285         * ˇ
 3286    "});
 3287        // Ensure that if cursor is before the comment start,
 3288        // we do not actually insert a comment prefix.
 3289        cx.set_state(indoc! {"
 3290        ˇ/**
 3291    "});
 3292        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3293        cx.assert_editor_state(indoc! {"
 3294
 3295        ˇ/**
 3296    "});
 3297        // Ensure that if cursor is between it doesn't add comment prefix.
 3298        cx.set_state(indoc! {"
 3299        /*ˇ*
 3300    "});
 3301        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3302        cx.assert_editor_state(indoc! {"
 3303        /*
 3304        ˇ*
 3305    "});
 3306        // Ensure that if suffix exists on same line after cursor it adds new line.
 3307        cx.set_state(indoc! {"
 3308        /**ˇ*/
 3309    "});
 3310        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3311        cx.assert_editor_state(indoc! {"
 3312        /**
 3313         * ˇ
 3314         */
 3315    "});
 3316        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3317        cx.set_state(indoc! {"
 3318        /**ˇ */
 3319    "});
 3320        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3321        cx.assert_editor_state(indoc! {"
 3322        /**
 3323         * ˇ
 3324         */
 3325    "});
 3326        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3327        cx.set_state(indoc! {"
 3328        /** ˇ*/
 3329    "});
 3330        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3331        cx.assert_editor_state(
 3332            indoc! {"
 3333        /**s
 3334         * ˇ
 3335         */
 3336    "}
 3337            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3338            .as_str(),
 3339        );
 3340        // Ensure that delimiter space is preserved when newline on already
 3341        // spaced delimiter.
 3342        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3343        cx.assert_editor_state(
 3344            indoc! {"
 3345        /**s
 3346         *s
 3347         * ˇ
 3348         */
 3349    "}
 3350            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3351            .as_str(),
 3352        );
 3353        // Ensure that delimiter space is preserved when space is not
 3354        // on existing delimiter.
 3355        cx.set_state(indoc! {"
 3356        /**
 3357 3358         */
 3359    "});
 3360        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3361        cx.assert_editor_state(indoc! {"
 3362        /**
 3363         *
 3364         * ˇ
 3365         */
 3366    "});
 3367        // Ensure that if suffix exists on same line after cursor it
 3368        // doesn't add extra new line if prefix is not on same line.
 3369        cx.set_state(indoc! {"
 3370        /**
 3371        ˇ*/
 3372    "});
 3373        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3374        cx.assert_editor_state(indoc! {"
 3375        /**
 3376
 3377        ˇ*/
 3378    "});
 3379        // Ensure that it detects suffix after existing prefix.
 3380        cx.set_state(indoc! {"
 3381        /**ˇ/
 3382    "});
 3383        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3384        cx.assert_editor_state(indoc! {"
 3385        /**
 3386        ˇ/
 3387    "});
 3388        // Ensure that if suffix exists on same line before
 3389        // cursor it does not add comment prefix.
 3390        cx.set_state(indoc! {"
 3391        /** */ˇ
 3392    "});
 3393        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3394        cx.assert_editor_state(indoc! {"
 3395        /** */
 3396        ˇ
 3397    "});
 3398        // Ensure that if suffix exists on same line before
 3399        // cursor it does not add comment prefix.
 3400        cx.set_state(indoc! {"
 3401        /**
 3402         *
 3403         */ˇ
 3404    "});
 3405        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3406        cx.assert_editor_state(indoc! {"
 3407        /**
 3408         *
 3409         */
 3410         ˇ
 3411    "});
 3412
 3413        // Ensure that inline comment followed by code
 3414        // doesn't add comment prefix on newline
 3415        cx.set_state(indoc! {"
 3416        /** */ textˇ
 3417    "});
 3418        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3419        cx.assert_editor_state(indoc! {"
 3420        /** */ text
 3421        ˇ
 3422    "});
 3423
 3424        // Ensure that text after comment end tag
 3425        // doesn't add comment prefix on newline
 3426        cx.set_state(indoc! {"
 3427        /**
 3428         *
 3429         */ˇtext
 3430    "});
 3431        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3432        cx.assert_editor_state(indoc! {"
 3433        /**
 3434         *
 3435         */
 3436         ˇtext
 3437    "});
 3438
 3439        // Ensure if not comment block it doesn't
 3440        // add comment prefix on newline
 3441        cx.set_state(indoc! {"
 3442        * textˇ
 3443    "});
 3444        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3445        cx.assert_editor_state(indoc! {"
 3446        * text
 3447        ˇ
 3448    "});
 3449    }
 3450    // Ensure that comment continuations can be disabled.
 3451    update_test_language_settings(cx, |settings| {
 3452        settings.defaults.extend_comment_on_newline = Some(false);
 3453    });
 3454    let mut cx = EditorTestContext::new(cx).await;
 3455    cx.set_state(indoc! {"
 3456        /**ˇ
 3457    "});
 3458    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3459    cx.assert_editor_state(indoc! {"
 3460        /**
 3461        ˇ
 3462    "});
 3463}
 3464
 3465#[gpui::test]
 3466async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3467    init_test(cx, |settings| {
 3468        settings.defaults.tab_size = NonZeroU32::new(4)
 3469    });
 3470
 3471    let lua_language = Arc::new(Language::new(
 3472        LanguageConfig {
 3473            line_comments: vec!["--".into()],
 3474            block_comment: Some(language::BlockCommentConfig {
 3475                start: "--[[".into(),
 3476                prefix: "".into(),
 3477                end: "]]".into(),
 3478                tab_size: 0,
 3479            }),
 3480            ..LanguageConfig::default()
 3481        },
 3482        None,
 3483    ));
 3484
 3485    let mut cx = EditorTestContext::new(cx).await;
 3486    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3487
 3488    // Line with line comment should extend
 3489    cx.set_state(indoc! {"
 3490        --ˇ
 3491    "});
 3492    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3493    cx.assert_editor_state(indoc! {"
 3494        --
 3495        --ˇ
 3496    "});
 3497
 3498    // Line with block comment that matches line comment should not extend
 3499    cx.set_state(indoc! {"
 3500        --[[ˇ
 3501    "});
 3502    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3503    cx.assert_editor_state(indoc! {"
 3504        --[[
 3505        ˇ
 3506    "});
 3507}
 3508
 3509#[gpui::test]
 3510fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3511    init_test(cx, |_| {});
 3512
 3513    let editor = cx.add_window(|window, cx| {
 3514        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3515        let mut editor = build_editor(buffer, window, cx);
 3516        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3517            s.select_ranges([3..4, 11..12, 19..20])
 3518        });
 3519        editor
 3520    });
 3521
 3522    _ = editor.update(cx, |editor, window, cx| {
 3523        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3524        editor.buffer.update(cx, |buffer, cx| {
 3525            buffer.edit([(2..5, ""), (10..13, ""), (18..21, "")], None, cx);
 3526            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3527        });
 3528        assert_eq!(editor.selections.ranges(cx), &[2..2, 7..7, 12..12],);
 3529
 3530        editor.insert("Z", window, cx);
 3531        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3532
 3533        // The selections are moved after the inserted characters
 3534        assert_eq!(editor.selections.ranges(cx), &[3..3, 9..9, 15..15],);
 3535    });
 3536}
 3537
 3538#[gpui::test]
 3539async fn test_tab(cx: &mut TestAppContext) {
 3540    init_test(cx, |settings| {
 3541        settings.defaults.tab_size = NonZeroU32::new(3)
 3542    });
 3543
 3544    let mut cx = EditorTestContext::new(cx).await;
 3545    cx.set_state(indoc! {"
 3546        ˇabˇc
 3547        ˇ🏀ˇ🏀ˇefg
 3548 3549    "});
 3550    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3551    cx.assert_editor_state(indoc! {"
 3552           ˇab ˇc
 3553           ˇ🏀  ˇ🏀  ˇefg
 3554        d  ˇ
 3555    "});
 3556
 3557    cx.set_state(indoc! {"
 3558        a
 3559        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3560    "});
 3561    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3562    cx.assert_editor_state(indoc! {"
 3563        a
 3564           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3565    "});
 3566}
 3567
 3568#[gpui::test]
 3569async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3570    init_test(cx, |_| {});
 3571
 3572    let mut cx = EditorTestContext::new(cx).await;
 3573    let language = Arc::new(
 3574        Language::new(
 3575            LanguageConfig::default(),
 3576            Some(tree_sitter_rust::LANGUAGE.into()),
 3577        )
 3578        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3579        .unwrap(),
 3580    );
 3581    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3582
 3583    // test when all cursors are not at suggested indent
 3584    // then simply move to their suggested indent location
 3585    cx.set_state(indoc! {"
 3586        const a: B = (
 3587            c(
 3588        ˇ
 3589        ˇ    )
 3590        );
 3591    "});
 3592    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3593    cx.assert_editor_state(indoc! {"
 3594        const a: B = (
 3595            c(
 3596                ˇ
 3597            ˇ)
 3598        );
 3599    "});
 3600
 3601    // test cursor already at suggested indent not moving when
 3602    // other cursors are yet to reach their suggested indents
 3603    cx.set_state(indoc! {"
 3604        ˇ
 3605        const a: B = (
 3606            c(
 3607                d(
 3608        ˇ
 3609                )
 3610        ˇ
 3611        ˇ    )
 3612        );
 3613    "});
 3614    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3615    cx.assert_editor_state(indoc! {"
 3616        ˇ
 3617        const a: B = (
 3618            c(
 3619                d(
 3620                    ˇ
 3621                )
 3622                ˇ
 3623            ˇ)
 3624        );
 3625    "});
 3626    // test when all cursors are at suggested indent then tab is inserted
 3627    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3628    cx.assert_editor_state(indoc! {"
 3629            ˇ
 3630        const a: B = (
 3631            c(
 3632                d(
 3633                        ˇ
 3634                )
 3635                    ˇ
 3636                ˇ)
 3637        );
 3638    "});
 3639
 3640    // test when current indent is less than suggested indent,
 3641    // we adjust line to match suggested indent and move cursor to it
 3642    //
 3643    // when no other cursor is at word boundary, all of them should move
 3644    cx.set_state(indoc! {"
 3645        const a: B = (
 3646            c(
 3647                d(
 3648        ˇ
 3649        ˇ   )
 3650        ˇ   )
 3651        );
 3652    "});
 3653    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3654    cx.assert_editor_state(indoc! {"
 3655        const a: B = (
 3656            c(
 3657                d(
 3658                    ˇ
 3659                ˇ)
 3660            ˇ)
 3661        );
 3662    "});
 3663
 3664    // test when current indent is less than suggested indent,
 3665    // we adjust line to match suggested indent and move cursor to it
 3666    //
 3667    // when some other cursor is at word boundary, it should not move
 3668    cx.set_state(indoc! {"
 3669        const a: B = (
 3670            c(
 3671                d(
 3672        ˇ
 3673        ˇ   )
 3674           ˇ)
 3675        );
 3676    "});
 3677    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3678    cx.assert_editor_state(indoc! {"
 3679        const a: B = (
 3680            c(
 3681                d(
 3682                    ˇ
 3683                ˇ)
 3684            ˇ)
 3685        );
 3686    "});
 3687
 3688    // test when current indent is more than suggested indent,
 3689    // we just move cursor to current indent instead of suggested indent
 3690    //
 3691    // when no other cursor is at word boundary, all of them should move
 3692    cx.set_state(indoc! {"
 3693        const a: B = (
 3694            c(
 3695                d(
 3696        ˇ
 3697        ˇ                )
 3698        ˇ   )
 3699        );
 3700    "});
 3701    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3702    cx.assert_editor_state(indoc! {"
 3703        const a: B = (
 3704            c(
 3705                d(
 3706                    ˇ
 3707                        ˇ)
 3708            ˇ)
 3709        );
 3710    "});
 3711    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3712    cx.assert_editor_state(indoc! {"
 3713        const a: B = (
 3714            c(
 3715                d(
 3716                        ˇ
 3717                            ˇ)
 3718                ˇ)
 3719        );
 3720    "});
 3721
 3722    // test when current indent is more than suggested indent,
 3723    // we just move cursor to current indent instead of suggested indent
 3724    //
 3725    // when some other cursor is at word boundary, it doesn't move
 3726    cx.set_state(indoc! {"
 3727        const a: B = (
 3728            c(
 3729                d(
 3730        ˇ
 3731        ˇ                )
 3732            ˇ)
 3733        );
 3734    "});
 3735    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3736    cx.assert_editor_state(indoc! {"
 3737        const a: B = (
 3738            c(
 3739                d(
 3740                    ˇ
 3741                        ˇ)
 3742            ˇ)
 3743        );
 3744    "});
 3745
 3746    // handle auto-indent when there are multiple cursors on the same line
 3747    cx.set_state(indoc! {"
 3748        const a: B = (
 3749            c(
 3750        ˇ    ˇ
 3751        ˇ    )
 3752        );
 3753    "});
 3754    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3755    cx.assert_editor_state(indoc! {"
 3756        const a: B = (
 3757            c(
 3758                ˇ
 3759            ˇ)
 3760        );
 3761    "});
 3762}
 3763
 3764#[gpui::test]
 3765async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 3766    init_test(cx, |settings| {
 3767        settings.defaults.tab_size = NonZeroU32::new(3)
 3768    });
 3769
 3770    let mut cx = EditorTestContext::new(cx).await;
 3771    cx.set_state(indoc! {"
 3772         ˇ
 3773        \t ˇ
 3774        \t  ˇ
 3775        \t   ˇ
 3776         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 3777    "});
 3778
 3779    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3780    cx.assert_editor_state(indoc! {"
 3781           ˇ
 3782        \t   ˇ
 3783        \t   ˇ
 3784        \t      ˇ
 3785         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 3786    "});
 3787}
 3788
 3789#[gpui::test]
 3790async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 3791    init_test(cx, |settings| {
 3792        settings.defaults.tab_size = NonZeroU32::new(4)
 3793    });
 3794
 3795    let language = Arc::new(
 3796        Language::new(
 3797            LanguageConfig::default(),
 3798            Some(tree_sitter_rust::LANGUAGE.into()),
 3799        )
 3800        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 3801        .unwrap(),
 3802    );
 3803
 3804    let mut cx = EditorTestContext::new(cx).await;
 3805    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3806    cx.set_state(indoc! {"
 3807        fn a() {
 3808            if b {
 3809        \t ˇc
 3810            }
 3811        }
 3812    "});
 3813
 3814    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3815    cx.assert_editor_state(indoc! {"
 3816        fn a() {
 3817            if b {
 3818                ˇc
 3819            }
 3820        }
 3821    "});
 3822}
 3823
 3824#[gpui::test]
 3825async fn test_indent_outdent(cx: &mut TestAppContext) {
 3826    init_test(cx, |settings| {
 3827        settings.defaults.tab_size = NonZeroU32::new(4);
 3828    });
 3829
 3830    let mut cx = EditorTestContext::new(cx).await;
 3831
 3832    cx.set_state(indoc! {"
 3833          «oneˇ» «twoˇ»
 3834        three
 3835         four
 3836    "});
 3837    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3838    cx.assert_editor_state(indoc! {"
 3839            «oneˇ» «twoˇ»
 3840        three
 3841         four
 3842    "});
 3843
 3844    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3845    cx.assert_editor_state(indoc! {"
 3846        «oneˇ» «twoˇ»
 3847        three
 3848         four
 3849    "});
 3850
 3851    // select across line ending
 3852    cx.set_state(indoc! {"
 3853        one two
 3854        t«hree
 3855        ˇ» four
 3856    "});
 3857    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3858    cx.assert_editor_state(indoc! {"
 3859        one two
 3860            t«hree
 3861        ˇ» four
 3862    "});
 3863
 3864    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3865    cx.assert_editor_state(indoc! {"
 3866        one two
 3867        t«hree
 3868        ˇ» four
 3869    "});
 3870
 3871    // Ensure that indenting/outdenting works when the cursor is at column 0.
 3872    cx.set_state(indoc! {"
 3873        one two
 3874        ˇthree
 3875            four
 3876    "});
 3877    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3878    cx.assert_editor_state(indoc! {"
 3879        one two
 3880            ˇthree
 3881            four
 3882    "});
 3883
 3884    cx.set_state(indoc! {"
 3885        one two
 3886        ˇ    three
 3887            four
 3888    "});
 3889    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3890    cx.assert_editor_state(indoc! {"
 3891        one two
 3892        ˇthree
 3893            four
 3894    "});
 3895}
 3896
 3897#[gpui::test]
 3898async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3899    // This is a regression test for issue #33761
 3900    init_test(cx, |_| {});
 3901
 3902    let mut cx = EditorTestContext::new(cx).await;
 3903    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3904    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3905
 3906    cx.set_state(
 3907        r#"ˇ#     ingress:
 3908ˇ#         api:
 3909ˇ#             enabled: false
 3910ˇ#             pathType: Prefix
 3911ˇ#           console:
 3912ˇ#               enabled: false
 3913ˇ#               pathType: Prefix
 3914"#,
 3915    );
 3916
 3917    // Press tab to indent all lines
 3918    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3919
 3920    cx.assert_editor_state(
 3921        r#"    ˇ#     ingress:
 3922    ˇ#         api:
 3923    ˇ#             enabled: false
 3924    ˇ#             pathType: Prefix
 3925    ˇ#           console:
 3926    ˇ#               enabled: false
 3927    ˇ#               pathType: Prefix
 3928"#,
 3929    );
 3930}
 3931
 3932#[gpui::test]
 3933async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 3934    // This is a test to make sure our fix for issue #33761 didn't break anything
 3935    init_test(cx, |_| {});
 3936
 3937    let mut cx = EditorTestContext::new(cx).await;
 3938    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3939    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3940
 3941    cx.set_state(
 3942        r#"ˇingress:
 3943ˇ  api:
 3944ˇ    enabled: false
 3945ˇ    pathType: Prefix
 3946"#,
 3947    );
 3948
 3949    // Press tab to indent all lines
 3950    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3951
 3952    cx.assert_editor_state(
 3953        r#"ˇingress:
 3954    ˇapi:
 3955        ˇenabled: false
 3956        ˇpathType: Prefix
 3957"#,
 3958    );
 3959}
 3960
 3961#[gpui::test]
 3962async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 3963    init_test(cx, |settings| {
 3964        settings.defaults.hard_tabs = Some(true);
 3965    });
 3966
 3967    let mut cx = EditorTestContext::new(cx).await;
 3968
 3969    // select two ranges on one line
 3970    cx.set_state(indoc! {"
 3971        «oneˇ» «twoˇ»
 3972        three
 3973        four
 3974    "});
 3975    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3976    cx.assert_editor_state(indoc! {"
 3977        \t«oneˇ» «twoˇ»
 3978        three
 3979        four
 3980    "});
 3981    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3982    cx.assert_editor_state(indoc! {"
 3983        \t\t«oneˇ» «twoˇ»
 3984        three
 3985        four
 3986    "});
 3987    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3988    cx.assert_editor_state(indoc! {"
 3989        \t«oneˇ» «twoˇ»
 3990        three
 3991        four
 3992    "});
 3993    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 3994    cx.assert_editor_state(indoc! {"
 3995        «oneˇ» «twoˇ»
 3996        three
 3997        four
 3998    "});
 3999
 4000    // select across a line ending
 4001    cx.set_state(indoc! {"
 4002        one two
 4003        t«hree
 4004        ˇ»four
 4005    "});
 4006    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4007    cx.assert_editor_state(indoc! {"
 4008        one two
 4009        \tt«hree
 4010        ˇ»four
 4011    "});
 4012    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4013    cx.assert_editor_state(indoc! {"
 4014        one two
 4015        \t\tt«hree
 4016        ˇ»four
 4017    "});
 4018    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4019    cx.assert_editor_state(indoc! {"
 4020        one two
 4021        \tt«hree
 4022        ˇ»four
 4023    "});
 4024    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4025    cx.assert_editor_state(indoc! {"
 4026        one two
 4027        t«hree
 4028        ˇ»four
 4029    "});
 4030
 4031    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4032    cx.set_state(indoc! {"
 4033        one two
 4034        ˇthree
 4035        four
 4036    "});
 4037    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4038    cx.assert_editor_state(indoc! {"
 4039        one two
 4040        ˇthree
 4041        four
 4042    "});
 4043    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4044    cx.assert_editor_state(indoc! {"
 4045        one two
 4046        \tˇthree
 4047        four
 4048    "});
 4049    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4050    cx.assert_editor_state(indoc! {"
 4051        one two
 4052        ˇthree
 4053        four
 4054    "});
 4055}
 4056
 4057#[gpui::test]
 4058fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4059    init_test(cx, |settings| {
 4060        settings.languages.0.extend([
 4061            (
 4062                "TOML".into(),
 4063                LanguageSettingsContent {
 4064                    tab_size: NonZeroU32::new(2),
 4065                    ..Default::default()
 4066                },
 4067            ),
 4068            (
 4069                "Rust".into(),
 4070                LanguageSettingsContent {
 4071                    tab_size: NonZeroU32::new(4),
 4072                    ..Default::default()
 4073                },
 4074            ),
 4075        ]);
 4076    });
 4077
 4078    let toml_language = Arc::new(Language::new(
 4079        LanguageConfig {
 4080            name: "TOML".into(),
 4081            ..Default::default()
 4082        },
 4083        None,
 4084    ));
 4085    let rust_language = Arc::new(Language::new(
 4086        LanguageConfig {
 4087            name: "Rust".into(),
 4088            ..Default::default()
 4089        },
 4090        None,
 4091    ));
 4092
 4093    let toml_buffer =
 4094        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4095    let rust_buffer =
 4096        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4097    let multibuffer = cx.new(|cx| {
 4098        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4099        multibuffer.push_excerpts(
 4100            toml_buffer.clone(),
 4101            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4102            cx,
 4103        );
 4104        multibuffer.push_excerpts(
 4105            rust_buffer.clone(),
 4106            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4107            cx,
 4108        );
 4109        multibuffer
 4110    });
 4111
 4112    cx.add_window(|window, cx| {
 4113        let mut editor = build_editor(multibuffer, window, cx);
 4114
 4115        assert_eq!(
 4116            editor.text(cx),
 4117            indoc! {"
 4118                a = 1
 4119                b = 2
 4120
 4121                const c: usize = 3;
 4122            "}
 4123        );
 4124
 4125        select_ranges(
 4126            &mut editor,
 4127            indoc! {"
 4128                «aˇ» = 1
 4129                b = 2
 4130
 4131                «const c:ˇ» usize = 3;
 4132            "},
 4133            window,
 4134            cx,
 4135        );
 4136
 4137        editor.tab(&Tab, window, cx);
 4138        assert_text_with_selections(
 4139            &mut editor,
 4140            indoc! {"
 4141                  «aˇ» = 1
 4142                b = 2
 4143
 4144                    «const c:ˇ» usize = 3;
 4145            "},
 4146            cx,
 4147        );
 4148        editor.backtab(&Backtab, window, cx);
 4149        assert_text_with_selections(
 4150            &mut editor,
 4151            indoc! {"
 4152                «aˇ» = 1
 4153                b = 2
 4154
 4155                «const c:ˇ» usize = 3;
 4156            "},
 4157            cx,
 4158        );
 4159
 4160        editor
 4161    });
 4162}
 4163
 4164#[gpui::test]
 4165async fn test_backspace(cx: &mut TestAppContext) {
 4166    init_test(cx, |_| {});
 4167
 4168    let mut cx = EditorTestContext::new(cx).await;
 4169
 4170    // Basic backspace
 4171    cx.set_state(indoc! {"
 4172        onˇe two three
 4173        fou«rˇ» five six
 4174        seven «ˇeight nine
 4175        »ten
 4176    "});
 4177    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4178    cx.assert_editor_state(indoc! {"
 4179        oˇe two three
 4180        fouˇ five six
 4181        seven ˇten
 4182    "});
 4183
 4184    // Test backspace inside and around indents
 4185    cx.set_state(indoc! {"
 4186        zero
 4187            ˇone
 4188                ˇtwo
 4189            ˇ ˇ ˇ  three
 4190        ˇ  ˇ  four
 4191    "});
 4192    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4193    cx.assert_editor_state(indoc! {"
 4194        zero
 4195        ˇone
 4196            ˇtwo
 4197        ˇ  threeˇ  four
 4198    "});
 4199}
 4200
 4201#[gpui::test]
 4202async fn test_delete(cx: &mut TestAppContext) {
 4203    init_test(cx, |_| {});
 4204
 4205    let mut cx = EditorTestContext::new(cx).await;
 4206    cx.set_state(indoc! {"
 4207        onˇe two three
 4208        fou«rˇ» five six
 4209        seven «ˇeight nine
 4210        »ten
 4211    "});
 4212    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4213    cx.assert_editor_state(indoc! {"
 4214        onˇ two three
 4215        fouˇ five six
 4216        seven ˇten
 4217    "});
 4218}
 4219
 4220#[gpui::test]
 4221fn test_delete_line(cx: &mut TestAppContext) {
 4222    init_test(cx, |_| {});
 4223
 4224    let editor = cx.add_window(|window, cx| {
 4225        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4226        build_editor(buffer, window, cx)
 4227    });
 4228    _ = editor.update(cx, |editor, window, cx| {
 4229        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4230            s.select_display_ranges([
 4231                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4232                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4233                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4234            ])
 4235        });
 4236        editor.delete_line(&DeleteLine, window, cx);
 4237        assert_eq!(editor.display_text(cx), "ghi");
 4238        assert_eq!(
 4239            editor.selections.display_ranges(cx),
 4240            vec![
 4241                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4242                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)
 4243            ]
 4244        );
 4245    });
 4246
 4247    let editor = cx.add_window(|window, cx| {
 4248        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4249        build_editor(buffer, window, cx)
 4250    });
 4251    _ = editor.update(cx, |editor, window, cx| {
 4252        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4253            s.select_display_ranges([
 4254                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4255            ])
 4256        });
 4257        editor.delete_line(&DeleteLine, window, cx);
 4258        assert_eq!(editor.display_text(cx), "ghi\n");
 4259        assert_eq!(
 4260            editor.selections.display_ranges(cx),
 4261            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4262        );
 4263    });
 4264}
 4265
 4266#[gpui::test]
 4267fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4268    init_test(cx, |_| {});
 4269
 4270    cx.add_window(|window, cx| {
 4271        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4272        let mut editor = build_editor(buffer.clone(), window, cx);
 4273        let buffer = buffer.read(cx).as_singleton().unwrap();
 4274
 4275        assert_eq!(
 4276            editor.selections.ranges::<Point>(cx),
 4277            &[Point::new(0, 0)..Point::new(0, 0)]
 4278        );
 4279
 4280        // When on single line, replace newline at end by space
 4281        editor.join_lines(&JoinLines, window, cx);
 4282        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4283        assert_eq!(
 4284            editor.selections.ranges::<Point>(cx),
 4285            &[Point::new(0, 3)..Point::new(0, 3)]
 4286        );
 4287
 4288        // When multiple lines are selected, remove newlines that are spanned by the selection
 4289        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4290            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4291        });
 4292        editor.join_lines(&JoinLines, window, cx);
 4293        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4294        assert_eq!(
 4295            editor.selections.ranges::<Point>(cx),
 4296            &[Point::new(0, 11)..Point::new(0, 11)]
 4297        );
 4298
 4299        // Undo should be transactional
 4300        editor.undo(&Undo, window, cx);
 4301        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4302        assert_eq!(
 4303            editor.selections.ranges::<Point>(cx),
 4304            &[Point::new(0, 5)..Point::new(2, 2)]
 4305        );
 4306
 4307        // When joining an empty line don't insert a space
 4308        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4309            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4310        });
 4311        editor.join_lines(&JoinLines, window, cx);
 4312        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4313        assert_eq!(
 4314            editor.selections.ranges::<Point>(cx),
 4315            [Point::new(2, 3)..Point::new(2, 3)]
 4316        );
 4317
 4318        // We can remove trailing newlines
 4319        editor.join_lines(&JoinLines, window, cx);
 4320        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4321        assert_eq!(
 4322            editor.selections.ranges::<Point>(cx),
 4323            [Point::new(2, 3)..Point::new(2, 3)]
 4324        );
 4325
 4326        // We don't blow up on the last line
 4327        editor.join_lines(&JoinLines, window, cx);
 4328        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4329        assert_eq!(
 4330            editor.selections.ranges::<Point>(cx),
 4331            [Point::new(2, 3)..Point::new(2, 3)]
 4332        );
 4333
 4334        // reset to test indentation
 4335        editor.buffer.update(cx, |buffer, cx| {
 4336            buffer.edit(
 4337                [
 4338                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4339                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4340                ],
 4341                None,
 4342                cx,
 4343            )
 4344        });
 4345
 4346        // We remove any leading spaces
 4347        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4348        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4349            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4350        });
 4351        editor.join_lines(&JoinLines, window, cx);
 4352        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4353
 4354        // We don't insert a space for a line containing only spaces
 4355        editor.join_lines(&JoinLines, window, cx);
 4356        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4357
 4358        // We ignore any leading tabs
 4359        editor.join_lines(&JoinLines, window, cx);
 4360        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4361
 4362        editor
 4363    });
 4364}
 4365
 4366#[gpui::test]
 4367fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4368    init_test(cx, |_| {});
 4369
 4370    cx.add_window(|window, cx| {
 4371        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4372        let mut editor = build_editor(buffer.clone(), window, cx);
 4373        let buffer = buffer.read(cx).as_singleton().unwrap();
 4374
 4375        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4376            s.select_ranges([
 4377                Point::new(0, 2)..Point::new(1, 1),
 4378                Point::new(1, 2)..Point::new(1, 2),
 4379                Point::new(3, 1)..Point::new(3, 2),
 4380            ])
 4381        });
 4382
 4383        editor.join_lines(&JoinLines, window, cx);
 4384        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4385
 4386        assert_eq!(
 4387            editor.selections.ranges::<Point>(cx),
 4388            [
 4389                Point::new(0, 7)..Point::new(0, 7),
 4390                Point::new(1, 3)..Point::new(1, 3)
 4391            ]
 4392        );
 4393        editor
 4394    });
 4395}
 4396
 4397#[gpui::test]
 4398async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4399    init_test(cx, |_| {});
 4400
 4401    let mut cx = EditorTestContext::new(cx).await;
 4402
 4403    let diff_base = r#"
 4404        Line 0
 4405        Line 1
 4406        Line 2
 4407        Line 3
 4408        "#
 4409    .unindent();
 4410
 4411    cx.set_state(
 4412        &r#"
 4413        ˇLine 0
 4414        Line 1
 4415        Line 2
 4416        Line 3
 4417        "#
 4418        .unindent(),
 4419    );
 4420
 4421    cx.set_head_text(&diff_base);
 4422    executor.run_until_parked();
 4423
 4424    // Join lines
 4425    cx.update_editor(|editor, window, cx| {
 4426        editor.join_lines(&JoinLines, window, cx);
 4427    });
 4428    executor.run_until_parked();
 4429
 4430    cx.assert_editor_state(
 4431        &r#"
 4432        Line 0ˇ Line 1
 4433        Line 2
 4434        Line 3
 4435        "#
 4436        .unindent(),
 4437    );
 4438    // Join again
 4439    cx.update_editor(|editor, window, cx| {
 4440        editor.join_lines(&JoinLines, window, cx);
 4441    });
 4442    executor.run_until_parked();
 4443
 4444    cx.assert_editor_state(
 4445        &r#"
 4446        Line 0 Line 1ˇ Line 2
 4447        Line 3
 4448        "#
 4449        .unindent(),
 4450    );
 4451}
 4452
 4453#[gpui::test]
 4454async fn test_custom_newlines_cause_no_false_positive_diffs(
 4455    executor: BackgroundExecutor,
 4456    cx: &mut TestAppContext,
 4457) {
 4458    init_test(cx, |_| {});
 4459    let mut cx = EditorTestContext::new(cx).await;
 4460    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4461    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4462    executor.run_until_parked();
 4463
 4464    cx.update_editor(|editor, window, cx| {
 4465        let snapshot = editor.snapshot(window, cx);
 4466        assert_eq!(
 4467            snapshot
 4468                .buffer_snapshot
 4469                .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
 4470                .collect::<Vec<_>>(),
 4471            Vec::new(),
 4472            "Should not have any diffs for files with custom newlines"
 4473        );
 4474    });
 4475}
 4476
 4477#[gpui::test]
 4478async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4479    init_test(cx, |_| {});
 4480
 4481    let mut cx = EditorTestContext::new(cx).await;
 4482
 4483    // Test sort_lines_case_insensitive()
 4484    cx.set_state(indoc! {"
 4485        «z
 4486        y
 4487        x
 4488        Z
 4489        Y
 4490        Xˇ»
 4491    "});
 4492    cx.update_editor(|e, window, cx| {
 4493        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4494    });
 4495    cx.assert_editor_state(indoc! {"
 4496        «x
 4497        X
 4498        y
 4499        Y
 4500        z
 4501        Zˇ»
 4502    "});
 4503
 4504    // Test sort_lines_by_length()
 4505    //
 4506    // Demonstrates:
 4507    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4508    // - sort is stable
 4509    cx.set_state(indoc! {"
 4510        «123
 4511        æ
 4512        12
 4513 4514        1
 4515        æˇ»
 4516    "});
 4517    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4518    cx.assert_editor_state(indoc! {"
 4519        «æ
 4520 4521        1
 4522        æ
 4523        12
 4524        123ˇ»
 4525    "});
 4526
 4527    // Test reverse_lines()
 4528    cx.set_state(indoc! {"
 4529        «5
 4530        4
 4531        3
 4532        2
 4533        1ˇ»
 4534    "});
 4535    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4536    cx.assert_editor_state(indoc! {"
 4537        «1
 4538        2
 4539        3
 4540        4
 4541        5ˇ»
 4542    "});
 4543
 4544    // Skip testing shuffle_line()
 4545
 4546    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4547    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4548
 4549    // Don't manipulate when cursor is on single line, but expand the selection
 4550    cx.set_state(indoc! {"
 4551        ddˇdd
 4552        ccc
 4553        bb
 4554        a
 4555    "});
 4556    cx.update_editor(|e, window, cx| {
 4557        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4558    });
 4559    cx.assert_editor_state(indoc! {"
 4560        «ddddˇ»
 4561        ccc
 4562        bb
 4563        a
 4564    "});
 4565
 4566    // Basic manipulate case
 4567    // Start selection moves to column 0
 4568    // End of selection shrinks to fit shorter line
 4569    cx.set_state(indoc! {"
 4570        dd«d
 4571        ccc
 4572        bb
 4573        aaaaaˇ»
 4574    "});
 4575    cx.update_editor(|e, window, cx| {
 4576        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4577    });
 4578    cx.assert_editor_state(indoc! {"
 4579        «aaaaa
 4580        bb
 4581        ccc
 4582        dddˇ»
 4583    "});
 4584
 4585    // Manipulate case with newlines
 4586    cx.set_state(indoc! {"
 4587        dd«d
 4588        ccc
 4589
 4590        bb
 4591        aaaaa
 4592
 4593        ˇ»
 4594    "});
 4595    cx.update_editor(|e, window, cx| {
 4596        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4597    });
 4598    cx.assert_editor_state(indoc! {"
 4599        «
 4600
 4601        aaaaa
 4602        bb
 4603        ccc
 4604        dddˇ»
 4605
 4606    "});
 4607
 4608    // Adding new line
 4609    cx.set_state(indoc! {"
 4610        aa«a
 4611        bbˇ»b
 4612    "});
 4613    cx.update_editor(|e, window, cx| {
 4614        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4615    });
 4616    cx.assert_editor_state(indoc! {"
 4617        «aaa
 4618        bbb
 4619        added_lineˇ»
 4620    "});
 4621
 4622    // Removing line
 4623    cx.set_state(indoc! {"
 4624        aa«a
 4625        bbbˇ»
 4626    "});
 4627    cx.update_editor(|e, window, cx| {
 4628        e.manipulate_immutable_lines(window, cx, |lines| {
 4629            lines.pop();
 4630        })
 4631    });
 4632    cx.assert_editor_state(indoc! {"
 4633        «aaaˇ»
 4634    "});
 4635
 4636    // Removing all lines
 4637    cx.set_state(indoc! {"
 4638        aa«a
 4639        bbbˇ»
 4640    "});
 4641    cx.update_editor(|e, window, cx| {
 4642        e.manipulate_immutable_lines(window, cx, |lines| {
 4643            lines.drain(..);
 4644        })
 4645    });
 4646    cx.assert_editor_state(indoc! {"
 4647        ˇ
 4648    "});
 4649}
 4650
 4651#[gpui::test]
 4652async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4653    init_test(cx, |_| {});
 4654
 4655    let mut cx = EditorTestContext::new(cx).await;
 4656
 4657    // Consider continuous selection as single selection
 4658    cx.set_state(indoc! {"
 4659        Aaa«aa
 4660        cˇ»c«c
 4661        bb
 4662        aaaˇ»aa
 4663    "});
 4664    cx.update_editor(|e, window, cx| {
 4665        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4666    });
 4667    cx.assert_editor_state(indoc! {"
 4668        «Aaaaa
 4669        ccc
 4670        bb
 4671        aaaaaˇ»
 4672    "});
 4673
 4674    cx.set_state(indoc! {"
 4675        Aaa«aa
 4676        cˇ»c«c
 4677        bb
 4678        aaaˇ»aa
 4679    "});
 4680    cx.update_editor(|e, window, cx| {
 4681        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4682    });
 4683    cx.assert_editor_state(indoc! {"
 4684        «Aaaaa
 4685        ccc
 4686        bbˇ»
 4687    "});
 4688
 4689    // Consider non continuous selection as distinct dedup operations
 4690    cx.set_state(indoc! {"
 4691        «aaaaa
 4692        bb
 4693        aaaaa
 4694        aaaaaˇ»
 4695
 4696        aaa«aaˇ»
 4697    "});
 4698    cx.update_editor(|e, window, cx| {
 4699        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4700    });
 4701    cx.assert_editor_state(indoc! {"
 4702        «aaaaa
 4703        bbˇ»
 4704
 4705        «aaaaaˇ»
 4706    "});
 4707}
 4708
 4709#[gpui::test]
 4710async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4711    init_test(cx, |_| {});
 4712
 4713    let mut cx = EditorTestContext::new(cx).await;
 4714
 4715    cx.set_state(indoc! {"
 4716        «Aaa
 4717        aAa
 4718        Aaaˇ»
 4719    "});
 4720    cx.update_editor(|e, window, cx| {
 4721        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4722    });
 4723    cx.assert_editor_state(indoc! {"
 4724        «Aaa
 4725        aAaˇ»
 4726    "});
 4727
 4728    cx.set_state(indoc! {"
 4729        «Aaa
 4730        aAa
 4731        aaAˇ»
 4732    "});
 4733    cx.update_editor(|e, window, cx| {
 4734        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4735    });
 4736    cx.assert_editor_state(indoc! {"
 4737        «Aaaˇ»
 4738    "});
 4739}
 4740
 4741#[gpui::test]
 4742async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 4743    init_test(cx, |_| {});
 4744
 4745    let mut cx = EditorTestContext::new(cx).await;
 4746
 4747    let js_language = Arc::new(Language::new(
 4748        LanguageConfig {
 4749            name: "JavaScript".into(),
 4750            wrap_characters: Some(language::WrapCharactersConfig {
 4751                start_prefix: "<".into(),
 4752                start_suffix: ">".into(),
 4753                end_prefix: "</".into(),
 4754                end_suffix: ">".into(),
 4755            }),
 4756            ..LanguageConfig::default()
 4757        },
 4758        None,
 4759    ));
 4760
 4761    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4762
 4763    cx.set_state(indoc! {"
 4764        «testˇ»
 4765    "});
 4766    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4767    cx.assert_editor_state(indoc! {"
 4768        <«ˇ»>test</«ˇ»>
 4769    "});
 4770
 4771    cx.set_state(indoc! {"
 4772        «test
 4773         testˇ»
 4774    "});
 4775    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4776    cx.assert_editor_state(indoc! {"
 4777        <«ˇ»>test
 4778         test</«ˇ»>
 4779    "});
 4780
 4781    cx.set_state(indoc! {"
 4782        teˇst
 4783    "});
 4784    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4785    cx.assert_editor_state(indoc! {"
 4786        te<«ˇ»></«ˇ»>st
 4787    "});
 4788}
 4789
 4790#[gpui::test]
 4791async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 4792    init_test(cx, |_| {});
 4793
 4794    let mut cx = EditorTestContext::new(cx).await;
 4795
 4796    let js_language = Arc::new(Language::new(
 4797        LanguageConfig {
 4798            name: "JavaScript".into(),
 4799            wrap_characters: Some(language::WrapCharactersConfig {
 4800                start_prefix: "<".into(),
 4801                start_suffix: ">".into(),
 4802                end_prefix: "</".into(),
 4803                end_suffix: ">".into(),
 4804            }),
 4805            ..LanguageConfig::default()
 4806        },
 4807        None,
 4808    ));
 4809
 4810    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 4811
 4812    cx.set_state(indoc! {"
 4813        «testˇ»
 4814        «testˇ» «testˇ»
 4815        «testˇ»
 4816    "});
 4817    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4818    cx.assert_editor_state(indoc! {"
 4819        <«ˇ»>test</«ˇ»>
 4820        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 4821        <«ˇ»>test</«ˇ»>
 4822    "});
 4823
 4824    cx.set_state(indoc! {"
 4825        «test
 4826         testˇ»
 4827        «test
 4828         testˇ»
 4829    "});
 4830    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4831    cx.assert_editor_state(indoc! {"
 4832        <«ˇ»>test
 4833         test</«ˇ»>
 4834        <«ˇ»>test
 4835         test</«ˇ»>
 4836    "});
 4837}
 4838
 4839#[gpui::test]
 4840async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 4841    init_test(cx, |_| {});
 4842
 4843    let mut cx = EditorTestContext::new(cx).await;
 4844
 4845    let plaintext_language = Arc::new(Language::new(
 4846        LanguageConfig {
 4847            name: "Plain Text".into(),
 4848            ..LanguageConfig::default()
 4849        },
 4850        None,
 4851    ));
 4852
 4853    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 4854
 4855    cx.set_state(indoc! {"
 4856        «testˇ»
 4857    "});
 4858    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 4859    cx.assert_editor_state(indoc! {"
 4860      «testˇ»
 4861    "});
 4862}
 4863
 4864#[gpui::test]
 4865async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 4866    init_test(cx, |_| {});
 4867
 4868    let mut cx = EditorTestContext::new(cx).await;
 4869
 4870    // Manipulate with multiple selections on a single line
 4871    cx.set_state(indoc! {"
 4872        dd«dd
 4873        cˇ»c«c
 4874        bb
 4875        aaaˇ»aa
 4876    "});
 4877    cx.update_editor(|e, window, cx| {
 4878        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4879    });
 4880    cx.assert_editor_state(indoc! {"
 4881        «aaaaa
 4882        bb
 4883        ccc
 4884        ddddˇ»
 4885    "});
 4886
 4887    // Manipulate with multiple disjoin selections
 4888    cx.set_state(indoc! {"
 4889 4890        4
 4891        3
 4892        2
 4893        1ˇ»
 4894
 4895        dd«dd
 4896        ccc
 4897        bb
 4898        aaaˇ»aa
 4899    "});
 4900    cx.update_editor(|e, window, cx| {
 4901        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4902    });
 4903    cx.assert_editor_state(indoc! {"
 4904        «1
 4905        2
 4906        3
 4907        4
 4908        5ˇ»
 4909
 4910        «aaaaa
 4911        bb
 4912        ccc
 4913        ddddˇ»
 4914    "});
 4915
 4916    // Adding lines on each selection
 4917    cx.set_state(indoc! {"
 4918 4919        1ˇ»
 4920
 4921        bb«bb
 4922        aaaˇ»aa
 4923    "});
 4924    cx.update_editor(|e, window, cx| {
 4925        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 4926    });
 4927    cx.assert_editor_state(indoc! {"
 4928        «2
 4929        1
 4930        added lineˇ»
 4931
 4932        «bbbb
 4933        aaaaa
 4934        added lineˇ»
 4935    "});
 4936
 4937    // Removing lines on each selection
 4938    cx.set_state(indoc! {"
 4939 4940        1ˇ»
 4941
 4942        bb«bb
 4943        aaaˇ»aa
 4944    "});
 4945    cx.update_editor(|e, window, cx| {
 4946        e.manipulate_immutable_lines(window, cx, |lines| {
 4947            lines.pop();
 4948        })
 4949    });
 4950    cx.assert_editor_state(indoc! {"
 4951        «2ˇ»
 4952
 4953        «bbbbˇ»
 4954    "});
 4955}
 4956
 4957#[gpui::test]
 4958async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 4959    init_test(cx, |settings| {
 4960        settings.defaults.tab_size = NonZeroU32::new(3)
 4961    });
 4962
 4963    let mut cx = EditorTestContext::new(cx).await;
 4964
 4965    // MULTI SELECTION
 4966    // Ln.1 "«" tests empty lines
 4967    // Ln.9 tests just leading whitespace
 4968    cx.set_state(indoc! {"
 4969        «
 4970        abc                 // No indentationˇ»
 4971        «\tabc              // 1 tabˇ»
 4972        \t\tabc «      ˇ»   // 2 tabs
 4973        \t ab«c             // Tab followed by space
 4974         \tabc              // Space followed by tab (3 spaces should be the result)
 4975        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 4976           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 4977        \t
 4978        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 4979    "});
 4980    cx.update_editor(|e, window, cx| {
 4981        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 4982    });
 4983    cx.assert_editor_state(
 4984        indoc! {"
 4985            «
 4986            abc                 // No indentation
 4987               abc              // 1 tab
 4988                  abc          // 2 tabs
 4989                abc             // Tab followed by space
 4990               abc              // Space followed by tab (3 spaces should be the result)
 4991                           abc   // Mixed indentation (tab conversion depends on the column)
 4992               abc         // Already space indented
 4993               ·
 4994               abc\tdef          // Only the leading tab is manipulatedˇ»
 4995        "}
 4996        .replace("·", "")
 4997        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 4998    );
 4999
 5000    // Test on just a few lines, the others should remain unchanged
 5001    // Only lines (3, 5, 10, 11) should change
 5002    cx.set_state(
 5003        indoc! {"
 5004            ·
 5005            abc                 // No indentation
 5006            \tabcˇ               // 1 tab
 5007            \t\tabc             // 2 tabs
 5008            \t abcˇ              // Tab followed by space
 5009             \tabc              // Space followed by tab (3 spaces should be the result)
 5010            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5011               abc              // Already space indented
 5012            «\t
 5013            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5014        "}
 5015        .replace("·", "")
 5016        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5017    );
 5018    cx.update_editor(|e, window, cx| {
 5019        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5020    });
 5021    cx.assert_editor_state(
 5022        indoc! {"
 5023            ·
 5024            abc                 // No indentation
 5025            «   abc               // 1 tabˇ»
 5026            \t\tabc             // 2 tabs
 5027            «    abc              // Tab followed by spaceˇ»
 5028             \tabc              // Space followed by tab (3 spaces should be the result)
 5029            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5030               abc              // Already space indented
 5031            «   ·
 5032               abc\tdef          // Only the leading tab is manipulatedˇ»
 5033        "}
 5034        .replace("·", "")
 5035        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5036    );
 5037
 5038    // SINGLE SELECTION
 5039    // Ln.1 "«" tests empty lines
 5040    // Ln.9 tests just leading whitespace
 5041    cx.set_state(indoc! {"
 5042        «
 5043        abc                 // No indentation
 5044        \tabc               // 1 tab
 5045        \t\tabc             // 2 tabs
 5046        \t abc              // Tab followed by space
 5047         \tabc              // Space followed by tab (3 spaces should be the result)
 5048        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5049           abc              // Already space indented
 5050        \t
 5051        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5052    "});
 5053    cx.update_editor(|e, window, cx| {
 5054        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5055    });
 5056    cx.assert_editor_state(
 5057        indoc! {"
 5058            «
 5059            abc                 // No indentation
 5060               abc               // 1 tab
 5061                  abc             // 2 tabs
 5062                abc              // Tab followed by space
 5063               abc              // Space followed by tab (3 spaces should be the result)
 5064                           abc   // Mixed indentation (tab conversion depends on the column)
 5065               abc              // Already space indented
 5066               ·
 5067               abc\tdef          // Only the leading tab is manipulatedˇ»
 5068        "}
 5069        .replace("·", "")
 5070        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5071    );
 5072}
 5073
 5074#[gpui::test]
 5075async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5076    init_test(cx, |settings| {
 5077        settings.defaults.tab_size = NonZeroU32::new(3)
 5078    });
 5079
 5080    let mut cx = EditorTestContext::new(cx).await;
 5081
 5082    // MULTI SELECTION
 5083    // Ln.1 "«" tests empty lines
 5084    // Ln.11 tests just leading whitespace
 5085    cx.set_state(indoc! {"
 5086        «
 5087        abˇ»ˇc                 // No indentation
 5088         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5089          abc  «             // 2 spaces (< 3 so dont convert)
 5090           abc              // 3 spaces (convert)
 5091             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5092        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5093        «\t abc              // Tab followed by space
 5094         \tabc              // Space followed by tab (should be consumed due to tab)
 5095        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5096           \tˇ»  «\t
 5097           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5098    "});
 5099    cx.update_editor(|e, window, cx| {
 5100        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5101    });
 5102    cx.assert_editor_state(indoc! {"
 5103        «
 5104        abc                 // No indentation
 5105         abc                // 1 space (< 3 so dont convert)
 5106          abc               // 2 spaces (< 3 so dont convert)
 5107        \tabc              // 3 spaces (convert)
 5108        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5109        \t\t\tabc           // Already tab indented
 5110        \t abc              // Tab followed by space
 5111        \tabc              // Space followed by tab (should be consumed due to tab)
 5112        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5113        \t\t\t
 5114        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5115    "});
 5116
 5117    // Test on just a few lines, the other should remain unchanged
 5118    // Only lines (4, 8, 11, 12) should change
 5119    cx.set_state(
 5120        indoc! {"
 5121            ·
 5122            abc                 // No indentation
 5123             abc                // 1 space (< 3 so dont convert)
 5124              abc               // 2 spaces (< 3 so dont convert)
 5125            «   abc              // 3 spaces (convert)ˇ»
 5126                 abc            // 5 spaces (1 tab + 2 spaces)
 5127            \t\t\tabc           // Already tab indented
 5128            \t abc              // Tab followed by space
 5129             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5130               \t\t  \tabc      // Mixed indentation
 5131            \t \t  \t   \tabc   // Mixed indentation
 5132               \t  \tˇ
 5133            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5134        "}
 5135        .replace("·", "")
 5136        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5137    );
 5138    cx.update_editor(|e, window, cx| {
 5139        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5140    });
 5141    cx.assert_editor_state(
 5142        indoc! {"
 5143            ·
 5144            abc                 // No indentation
 5145             abc                // 1 space (< 3 so dont convert)
 5146              abc               // 2 spaces (< 3 so dont convert)
 5147            «\tabc              // 3 spaces (convert)ˇ»
 5148                 abc            // 5 spaces (1 tab + 2 spaces)
 5149            \t\t\tabc           // Already tab indented
 5150            \t abc              // Tab followed by space
 5151            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5152               \t\t  \tabc      // Mixed indentation
 5153            \t \t  \t   \tabc   // Mixed indentation
 5154            «\t\t\t
 5155            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5156        "}
 5157        .replace("·", "")
 5158        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5159    );
 5160
 5161    // SINGLE SELECTION
 5162    // Ln.1 "«" tests empty lines
 5163    // Ln.11 tests just leading whitespace
 5164    cx.set_state(indoc! {"
 5165        «
 5166        abc                 // No indentation
 5167         abc                // 1 space (< 3 so dont convert)
 5168          abc               // 2 spaces (< 3 so dont convert)
 5169           abc              // 3 spaces (convert)
 5170             abc            // 5 spaces (1 tab + 2 spaces)
 5171        \t\t\tabc           // Already tab indented
 5172        \t abc              // Tab followed by space
 5173         \tabc              // Space followed by tab (should be consumed due to tab)
 5174        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5175           \t  \t
 5176           abc   \t         // Only the leading spaces should be convertedˇ»
 5177    "});
 5178    cx.update_editor(|e, window, cx| {
 5179        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5180    });
 5181    cx.assert_editor_state(indoc! {"
 5182        «
 5183        abc                 // No indentation
 5184         abc                // 1 space (< 3 so dont convert)
 5185          abc               // 2 spaces (< 3 so dont convert)
 5186        \tabc              // 3 spaces (convert)
 5187        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5188        \t\t\tabc           // Already tab indented
 5189        \t abc              // Tab followed by space
 5190        \tabc              // Space followed by tab (should be consumed due to tab)
 5191        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5192        \t\t\t
 5193        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5194    "});
 5195}
 5196
 5197#[gpui::test]
 5198async fn test_toggle_case(cx: &mut TestAppContext) {
 5199    init_test(cx, |_| {});
 5200
 5201    let mut cx = EditorTestContext::new(cx).await;
 5202
 5203    // If all lower case -> upper case
 5204    cx.set_state(indoc! {"
 5205        «hello worldˇ»
 5206    "});
 5207    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5208    cx.assert_editor_state(indoc! {"
 5209        «HELLO WORLDˇ»
 5210    "});
 5211
 5212    // If all upper case -> lower case
 5213    cx.set_state(indoc! {"
 5214        «HELLO WORLDˇ»
 5215    "});
 5216    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5217    cx.assert_editor_state(indoc! {"
 5218        «hello worldˇ»
 5219    "});
 5220
 5221    // If any upper case characters are identified -> lower case
 5222    // This matches JetBrains IDEs
 5223    cx.set_state(indoc! {"
 5224        «hEllo worldˇ»
 5225    "});
 5226    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5227    cx.assert_editor_state(indoc! {"
 5228        «hello worldˇ»
 5229    "});
 5230}
 5231
 5232#[gpui::test]
 5233async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5234    init_test(cx, |_| {});
 5235
 5236    let mut cx = EditorTestContext::new(cx).await;
 5237
 5238    cx.set_state(indoc! {"
 5239        «implement-windows-supportˇ»
 5240    "});
 5241    cx.update_editor(|e, window, cx| {
 5242        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5243    });
 5244    cx.assert_editor_state(indoc! {"
 5245        «Implement windows supportˇ»
 5246    "});
 5247}
 5248
 5249#[gpui::test]
 5250async fn test_manipulate_text(cx: &mut TestAppContext) {
 5251    init_test(cx, |_| {});
 5252
 5253    let mut cx = EditorTestContext::new(cx).await;
 5254
 5255    // Test convert_to_upper_case()
 5256    cx.set_state(indoc! {"
 5257        «hello worldˇ»
 5258    "});
 5259    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5260    cx.assert_editor_state(indoc! {"
 5261        «HELLO WORLDˇ»
 5262    "});
 5263
 5264    // Test convert_to_lower_case()
 5265    cx.set_state(indoc! {"
 5266        «HELLO WORLDˇ»
 5267    "});
 5268    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5269    cx.assert_editor_state(indoc! {"
 5270        «hello worldˇ»
 5271    "});
 5272
 5273    // Test multiple line, single selection case
 5274    cx.set_state(indoc! {"
 5275        «The quick brown
 5276        fox jumps over
 5277        the lazy dogˇ»
 5278    "});
 5279    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5280    cx.assert_editor_state(indoc! {"
 5281        «The Quick Brown
 5282        Fox Jumps Over
 5283        The Lazy Dogˇ»
 5284    "});
 5285
 5286    // Test multiple line, single selection case
 5287    cx.set_state(indoc! {"
 5288        «The quick brown
 5289        fox jumps over
 5290        the lazy dogˇ»
 5291    "});
 5292    cx.update_editor(|e, window, cx| {
 5293        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5294    });
 5295    cx.assert_editor_state(indoc! {"
 5296        «TheQuickBrown
 5297        FoxJumpsOver
 5298        TheLazyDogˇ»
 5299    "});
 5300
 5301    // From here on out, test more complex cases of manipulate_text()
 5302
 5303    // Test no selection case - should affect words cursors are in
 5304    // Cursor at beginning, middle, and end of word
 5305    cx.set_state(indoc! {"
 5306        ˇhello big beauˇtiful worldˇ
 5307    "});
 5308    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5309    cx.assert_editor_state(indoc! {"
 5310        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5311    "});
 5312
 5313    // Test multiple selections on a single line and across multiple lines
 5314    cx.set_state(indoc! {"
 5315        «Theˇ» quick «brown
 5316        foxˇ» jumps «overˇ»
 5317        the «lazyˇ» dog
 5318    "});
 5319    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5320    cx.assert_editor_state(indoc! {"
 5321        «THEˇ» quick «BROWN
 5322        FOXˇ» jumps «OVERˇ»
 5323        the «LAZYˇ» dog
 5324    "});
 5325
 5326    // Test case where text length grows
 5327    cx.set_state(indoc! {"
 5328        «tschüߡ»
 5329    "});
 5330    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5331    cx.assert_editor_state(indoc! {"
 5332        «TSCHÜSSˇ»
 5333    "});
 5334
 5335    // Test to make sure we don't crash when text shrinks
 5336    cx.set_state(indoc! {"
 5337        aaa_bbbˇ
 5338    "});
 5339    cx.update_editor(|e, window, cx| {
 5340        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5341    });
 5342    cx.assert_editor_state(indoc! {"
 5343        «aaaBbbˇ»
 5344    "});
 5345
 5346    // Test to make sure we all aware of the fact that each word can grow and shrink
 5347    // Final selections should be aware of this fact
 5348    cx.set_state(indoc! {"
 5349        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5350    "});
 5351    cx.update_editor(|e, window, cx| {
 5352        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5353    });
 5354    cx.assert_editor_state(indoc! {"
 5355        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5356    "});
 5357
 5358    cx.set_state(indoc! {"
 5359        «hElLo, WoRld!ˇ»
 5360    "});
 5361    cx.update_editor(|e, window, cx| {
 5362        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5363    });
 5364    cx.assert_editor_state(indoc! {"
 5365        «HeLlO, wOrLD!ˇ»
 5366    "});
 5367
 5368    // Test selections with `line_mode = true`.
 5369    cx.update_editor(|editor, _window, _cx| editor.selections.line_mode = true);
 5370    cx.set_state(indoc! {"
 5371        «The quick brown
 5372        fox jumps over
 5373        tˇ»he lazy dog
 5374    "});
 5375    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5376    cx.assert_editor_state(indoc! {"
 5377        «THE QUICK BROWN
 5378        FOX JUMPS OVER
 5379        THE LAZY DOGˇ»
 5380    "});
 5381}
 5382
 5383#[gpui::test]
 5384fn test_duplicate_line(cx: &mut TestAppContext) {
 5385    init_test(cx, |_| {});
 5386
 5387    let editor = cx.add_window(|window, cx| {
 5388        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5389        build_editor(buffer, window, cx)
 5390    });
 5391    _ = editor.update(cx, |editor, window, cx| {
 5392        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5393            s.select_display_ranges([
 5394                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5395                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5396                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5397                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5398            ])
 5399        });
 5400        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5401        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5402        assert_eq!(
 5403            editor.selections.display_ranges(cx),
 5404            vec![
 5405                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5406                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5407                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5408                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5409            ]
 5410        );
 5411    });
 5412
 5413    let editor = cx.add_window(|window, cx| {
 5414        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5415        build_editor(buffer, window, cx)
 5416    });
 5417    _ = editor.update(cx, |editor, window, cx| {
 5418        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5419            s.select_display_ranges([
 5420                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5421                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5422            ])
 5423        });
 5424        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5425        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5426        assert_eq!(
 5427            editor.selections.display_ranges(cx),
 5428            vec![
 5429                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5430                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5431            ]
 5432        );
 5433    });
 5434
 5435    // With `move_upwards` the selections stay in place, except for
 5436    // the lines inserted above them
 5437    let editor = cx.add_window(|window, cx| {
 5438        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5439        build_editor(buffer, window, cx)
 5440    });
 5441    _ = editor.update(cx, |editor, window, cx| {
 5442        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5443            s.select_display_ranges([
 5444                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5445                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5446                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5447                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5448            ])
 5449        });
 5450        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5451        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5452        assert_eq!(
 5453            editor.selections.display_ranges(cx),
 5454            vec![
 5455                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5456                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5457                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5458                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5459            ]
 5460        );
 5461    });
 5462
 5463    let editor = cx.add_window(|window, cx| {
 5464        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5465        build_editor(buffer, window, cx)
 5466    });
 5467    _ = editor.update(cx, |editor, window, cx| {
 5468        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5469            s.select_display_ranges([
 5470                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5471                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5472            ])
 5473        });
 5474        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5475        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5476        assert_eq!(
 5477            editor.selections.display_ranges(cx),
 5478            vec![
 5479                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5480                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5481            ]
 5482        );
 5483    });
 5484
 5485    let editor = cx.add_window(|window, cx| {
 5486        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5487        build_editor(buffer, window, cx)
 5488    });
 5489    _ = editor.update(cx, |editor, window, cx| {
 5490        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5491            s.select_display_ranges([
 5492                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5493                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5494            ])
 5495        });
 5496        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5497        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5498        assert_eq!(
 5499            editor.selections.display_ranges(cx),
 5500            vec![
 5501                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5502                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5503            ]
 5504        );
 5505    });
 5506}
 5507
 5508#[gpui::test]
 5509fn test_move_line_up_down(cx: &mut TestAppContext) {
 5510    init_test(cx, |_| {});
 5511
 5512    let editor = cx.add_window(|window, cx| {
 5513        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5514        build_editor(buffer, window, cx)
 5515    });
 5516    _ = editor.update(cx, |editor, window, cx| {
 5517        editor.fold_creases(
 5518            vec![
 5519                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5520                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5521                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5522            ],
 5523            true,
 5524            window,
 5525            cx,
 5526        );
 5527        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5528            s.select_display_ranges([
 5529                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5530                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5531                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5532                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5533            ])
 5534        });
 5535        assert_eq!(
 5536            editor.display_text(cx),
 5537            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5538        );
 5539
 5540        editor.move_line_up(&MoveLineUp, window, cx);
 5541        assert_eq!(
 5542            editor.display_text(cx),
 5543            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5544        );
 5545        assert_eq!(
 5546            editor.selections.display_ranges(cx),
 5547            vec![
 5548                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5549                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5550                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5551                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5552            ]
 5553        );
 5554    });
 5555
 5556    _ = editor.update(cx, |editor, window, cx| {
 5557        editor.move_line_down(&MoveLineDown, window, cx);
 5558        assert_eq!(
 5559            editor.display_text(cx),
 5560            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5561        );
 5562        assert_eq!(
 5563            editor.selections.display_ranges(cx),
 5564            vec![
 5565                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5566                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5567                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5568                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5569            ]
 5570        );
 5571    });
 5572
 5573    _ = editor.update(cx, |editor, window, cx| {
 5574        editor.move_line_down(&MoveLineDown, window, cx);
 5575        assert_eq!(
 5576            editor.display_text(cx),
 5577            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5578        );
 5579        assert_eq!(
 5580            editor.selections.display_ranges(cx),
 5581            vec![
 5582                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5583                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5584                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5585                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5586            ]
 5587        );
 5588    });
 5589
 5590    _ = editor.update(cx, |editor, window, cx| {
 5591        editor.move_line_up(&MoveLineUp, window, cx);
 5592        assert_eq!(
 5593            editor.display_text(cx),
 5594            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5595        );
 5596        assert_eq!(
 5597            editor.selections.display_ranges(cx),
 5598            vec![
 5599                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5600                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5601                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5602                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5603            ]
 5604        );
 5605    });
 5606}
 5607
 5608#[gpui::test]
 5609fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5610    init_test(cx, |_| {});
 5611    let editor = cx.add_window(|window, cx| {
 5612        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5613        build_editor(buffer, window, cx)
 5614    });
 5615    _ = editor.update(cx, |editor, window, cx| {
 5616        editor.fold_creases(
 5617            vec![Crease::simple(
 5618                Point::new(6, 4)..Point::new(7, 4),
 5619                FoldPlaceholder::test(),
 5620            )],
 5621            true,
 5622            window,
 5623            cx,
 5624        );
 5625        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5626            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5627        });
 5628        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5629        editor.move_line_up(&MoveLineUp, window, cx);
 5630        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5631        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5632    });
 5633}
 5634
 5635#[gpui::test]
 5636fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5637    init_test(cx, |_| {});
 5638
 5639    let editor = cx.add_window(|window, cx| {
 5640        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5641        build_editor(buffer, window, cx)
 5642    });
 5643    _ = editor.update(cx, |editor, window, cx| {
 5644        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5645        editor.insert_blocks(
 5646            [BlockProperties {
 5647                style: BlockStyle::Fixed,
 5648                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5649                height: Some(1),
 5650                render: Arc::new(|_| div().into_any()),
 5651                priority: 0,
 5652            }],
 5653            Some(Autoscroll::fit()),
 5654            cx,
 5655        );
 5656        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5657            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5658        });
 5659        editor.move_line_down(&MoveLineDown, window, cx);
 5660    });
 5661}
 5662
 5663#[gpui::test]
 5664async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5665    init_test(cx, |_| {});
 5666
 5667    let mut cx = EditorTestContext::new(cx).await;
 5668    cx.set_state(
 5669        &"
 5670            ˇzero
 5671            one
 5672            two
 5673            three
 5674            four
 5675            five
 5676        "
 5677        .unindent(),
 5678    );
 5679
 5680    // Create a four-line block that replaces three lines of text.
 5681    cx.update_editor(|editor, window, cx| {
 5682        let snapshot = editor.snapshot(window, cx);
 5683        let snapshot = &snapshot.buffer_snapshot;
 5684        let placement = BlockPlacement::Replace(
 5685            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5686        );
 5687        editor.insert_blocks(
 5688            [BlockProperties {
 5689                placement,
 5690                height: Some(4),
 5691                style: BlockStyle::Sticky,
 5692                render: Arc::new(|_| gpui::div().into_any_element()),
 5693                priority: 0,
 5694            }],
 5695            None,
 5696            cx,
 5697        );
 5698    });
 5699
 5700    // Move down so that the cursor touches the block.
 5701    cx.update_editor(|editor, window, cx| {
 5702        editor.move_down(&Default::default(), window, cx);
 5703    });
 5704    cx.assert_editor_state(
 5705        &"
 5706            zero
 5707            «one
 5708            two
 5709            threeˇ»
 5710            four
 5711            five
 5712        "
 5713        .unindent(),
 5714    );
 5715
 5716    // Move down past the block.
 5717    cx.update_editor(|editor, window, cx| {
 5718        editor.move_down(&Default::default(), window, cx);
 5719    });
 5720    cx.assert_editor_state(
 5721        &"
 5722            zero
 5723            one
 5724            two
 5725            three
 5726            ˇfour
 5727            five
 5728        "
 5729        .unindent(),
 5730    );
 5731}
 5732
 5733#[gpui::test]
 5734fn test_transpose(cx: &mut TestAppContext) {
 5735    init_test(cx, |_| {});
 5736
 5737    _ = cx.add_window(|window, cx| {
 5738        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 5739        editor.set_style(EditorStyle::default(), window, cx);
 5740        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5741            s.select_ranges([1..1])
 5742        });
 5743        editor.transpose(&Default::default(), window, cx);
 5744        assert_eq!(editor.text(cx), "bac");
 5745        assert_eq!(editor.selections.ranges(cx), [2..2]);
 5746
 5747        editor.transpose(&Default::default(), window, cx);
 5748        assert_eq!(editor.text(cx), "bca");
 5749        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5750
 5751        editor.transpose(&Default::default(), window, cx);
 5752        assert_eq!(editor.text(cx), "bac");
 5753        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5754
 5755        editor
 5756    });
 5757
 5758    _ = cx.add_window(|window, cx| {
 5759        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5760        editor.set_style(EditorStyle::default(), window, cx);
 5761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5762            s.select_ranges([3..3])
 5763        });
 5764        editor.transpose(&Default::default(), window, cx);
 5765        assert_eq!(editor.text(cx), "acb\nde");
 5766        assert_eq!(editor.selections.ranges(cx), [3..3]);
 5767
 5768        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5769            s.select_ranges([4..4])
 5770        });
 5771        editor.transpose(&Default::default(), window, cx);
 5772        assert_eq!(editor.text(cx), "acbd\ne");
 5773        assert_eq!(editor.selections.ranges(cx), [5..5]);
 5774
 5775        editor.transpose(&Default::default(), window, cx);
 5776        assert_eq!(editor.text(cx), "acbde\n");
 5777        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5778
 5779        editor.transpose(&Default::default(), window, cx);
 5780        assert_eq!(editor.text(cx), "acbd\ne");
 5781        assert_eq!(editor.selections.ranges(cx), [6..6]);
 5782
 5783        editor
 5784    });
 5785
 5786    _ = cx.add_window(|window, cx| {
 5787        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 5788        editor.set_style(EditorStyle::default(), window, cx);
 5789        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5790            s.select_ranges([1..1, 2..2, 4..4])
 5791        });
 5792        editor.transpose(&Default::default(), window, cx);
 5793        assert_eq!(editor.text(cx), "bacd\ne");
 5794        assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
 5795
 5796        editor.transpose(&Default::default(), window, cx);
 5797        assert_eq!(editor.text(cx), "bcade\n");
 5798        assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
 5799
 5800        editor.transpose(&Default::default(), window, cx);
 5801        assert_eq!(editor.text(cx), "bcda\ne");
 5802        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5803
 5804        editor.transpose(&Default::default(), window, cx);
 5805        assert_eq!(editor.text(cx), "bcade\n");
 5806        assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
 5807
 5808        editor.transpose(&Default::default(), window, cx);
 5809        assert_eq!(editor.text(cx), "bcaed\n");
 5810        assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
 5811
 5812        editor
 5813    });
 5814
 5815    _ = cx.add_window(|window, cx| {
 5816        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 5817        editor.set_style(EditorStyle::default(), window, cx);
 5818        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5819            s.select_ranges([4..4])
 5820        });
 5821        editor.transpose(&Default::default(), window, cx);
 5822        assert_eq!(editor.text(cx), "🏀🍐✋");
 5823        assert_eq!(editor.selections.ranges(cx), [8..8]);
 5824
 5825        editor.transpose(&Default::default(), window, cx);
 5826        assert_eq!(editor.text(cx), "🏀✋🍐");
 5827        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5828
 5829        editor.transpose(&Default::default(), window, cx);
 5830        assert_eq!(editor.text(cx), "🏀🍐✋");
 5831        assert_eq!(editor.selections.ranges(cx), [11..11]);
 5832
 5833        editor
 5834    });
 5835}
 5836
 5837#[gpui::test]
 5838async fn test_rewrap(cx: &mut TestAppContext) {
 5839    init_test(cx, |settings| {
 5840        settings.languages.0.extend([
 5841            (
 5842                "Markdown".into(),
 5843                LanguageSettingsContent {
 5844                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5845                    preferred_line_length: Some(40),
 5846                    ..Default::default()
 5847                },
 5848            ),
 5849            (
 5850                "Plain Text".into(),
 5851                LanguageSettingsContent {
 5852                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 5853                    preferred_line_length: Some(40),
 5854                    ..Default::default()
 5855                },
 5856            ),
 5857            (
 5858                "C++".into(),
 5859                LanguageSettingsContent {
 5860                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5861                    preferred_line_length: Some(40),
 5862                    ..Default::default()
 5863                },
 5864            ),
 5865            (
 5866                "Python".into(),
 5867                LanguageSettingsContent {
 5868                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5869                    preferred_line_length: Some(40),
 5870                    ..Default::default()
 5871                },
 5872            ),
 5873            (
 5874                "Rust".into(),
 5875                LanguageSettingsContent {
 5876                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 5877                    preferred_line_length: Some(40),
 5878                    ..Default::default()
 5879                },
 5880            ),
 5881        ])
 5882    });
 5883
 5884    let mut cx = EditorTestContext::new(cx).await;
 5885
 5886    let cpp_language = Arc::new(Language::new(
 5887        LanguageConfig {
 5888            name: "C++".into(),
 5889            line_comments: vec!["// ".into()],
 5890            ..LanguageConfig::default()
 5891        },
 5892        None,
 5893    ));
 5894    let python_language = Arc::new(Language::new(
 5895        LanguageConfig {
 5896            name: "Python".into(),
 5897            line_comments: vec!["# ".into()],
 5898            ..LanguageConfig::default()
 5899        },
 5900        None,
 5901    ));
 5902    let markdown_language = Arc::new(Language::new(
 5903        LanguageConfig {
 5904            name: "Markdown".into(),
 5905            rewrap_prefixes: vec![
 5906                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 5907                regex::Regex::new("[-*+]\\s+").unwrap(),
 5908            ],
 5909            ..LanguageConfig::default()
 5910        },
 5911        None,
 5912    ));
 5913    let rust_language = Arc::new(
 5914        Language::new(
 5915            LanguageConfig {
 5916                name: "Rust".into(),
 5917                line_comments: vec!["// ".into(), "/// ".into()],
 5918                ..LanguageConfig::default()
 5919            },
 5920            Some(tree_sitter_rust::LANGUAGE.into()),
 5921        )
 5922        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 5923        .unwrap(),
 5924    );
 5925
 5926    let plaintext_language = Arc::new(Language::new(
 5927        LanguageConfig {
 5928            name: "Plain Text".into(),
 5929            ..LanguageConfig::default()
 5930        },
 5931        None,
 5932    ));
 5933
 5934    // Test basic rewrapping of a long line with a cursor
 5935    assert_rewrap(
 5936        indoc! {"
 5937            // ˇThis is a long comment that needs to be wrapped.
 5938        "},
 5939        indoc! {"
 5940            // ˇThis is a long comment that needs to
 5941            // be wrapped.
 5942        "},
 5943        cpp_language.clone(),
 5944        &mut cx,
 5945    );
 5946
 5947    // Test rewrapping a full selection
 5948    assert_rewrap(
 5949        indoc! {"
 5950            «// This selected long comment needs to be wrapped.ˇ»"
 5951        },
 5952        indoc! {"
 5953            «// This selected long comment needs to
 5954            // be wrapped.ˇ»"
 5955        },
 5956        cpp_language.clone(),
 5957        &mut cx,
 5958    );
 5959
 5960    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 5961    assert_rewrap(
 5962        indoc! {"
 5963            // ˇThis is the first line.
 5964            // Thisˇ is the second line.
 5965            // This is the thirdˇ line, all part of one paragraph.
 5966         "},
 5967        indoc! {"
 5968            // ˇThis is the first line. Thisˇ is the
 5969            // second line. This is the thirdˇ line,
 5970            // all part of one paragraph.
 5971         "},
 5972        cpp_language.clone(),
 5973        &mut cx,
 5974    );
 5975
 5976    // Test multiple cursors in different paragraphs trigger separate rewraps
 5977    assert_rewrap(
 5978        indoc! {"
 5979            // ˇThis is the first paragraph, first line.
 5980            // ˇThis is the first paragraph, second line.
 5981
 5982            // ˇThis is the second paragraph, first line.
 5983            // ˇThis is the second paragraph, second line.
 5984        "},
 5985        indoc! {"
 5986            // ˇThis is the first paragraph, first
 5987            // line. ˇThis is the first paragraph,
 5988            // second line.
 5989
 5990            // ˇThis is the second paragraph, first
 5991            // line. ˇThis is the second paragraph,
 5992            // second line.
 5993        "},
 5994        cpp_language.clone(),
 5995        &mut cx,
 5996    );
 5997
 5998    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 5999    assert_rewrap(
 6000        indoc! {"
 6001            «// A regular long long comment to be wrapped.
 6002            /// A documentation long comment to be wrapped.ˇ»
 6003          "},
 6004        indoc! {"
 6005            «// A regular long long comment to be
 6006            // wrapped.
 6007            /// A documentation long comment to be
 6008            /// wrapped.ˇ»
 6009          "},
 6010        rust_language.clone(),
 6011        &mut cx,
 6012    );
 6013
 6014    // Test that change in indentation level trigger seperate rewraps
 6015    assert_rewrap(
 6016        indoc! {"
 6017            fn foo() {
 6018                «// This is a long comment at the base indent.
 6019                    // This is a long comment at the next indent.ˇ»
 6020            }
 6021        "},
 6022        indoc! {"
 6023            fn foo() {
 6024                «// This is a long comment at the
 6025                // base indent.
 6026                    // This is a long comment at the
 6027                    // next indent.ˇ»
 6028            }
 6029        "},
 6030        rust_language.clone(),
 6031        &mut cx,
 6032    );
 6033
 6034    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6035    assert_rewrap(
 6036        indoc! {"
 6037            # ˇThis is a long comment using a pound sign.
 6038        "},
 6039        indoc! {"
 6040            # ˇThis is a long comment using a pound
 6041            # sign.
 6042        "},
 6043        python_language,
 6044        &mut cx,
 6045    );
 6046
 6047    // Test rewrapping only affects comments, not code even when selected
 6048    assert_rewrap(
 6049        indoc! {"
 6050            «/// This doc comment is long and should be wrapped.
 6051            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6052        "},
 6053        indoc! {"
 6054            «/// This doc comment is long and should
 6055            /// be wrapped.
 6056            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6057        "},
 6058        rust_language.clone(),
 6059        &mut cx,
 6060    );
 6061
 6062    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6063    assert_rewrap(
 6064        indoc! {"
 6065            # Header
 6066
 6067            A long long long line of markdown text to wrap.ˇ
 6068         "},
 6069        indoc! {"
 6070            # Header
 6071
 6072            A long long long line of markdown text
 6073            to wrap.ˇ
 6074         "},
 6075        markdown_language.clone(),
 6076        &mut cx,
 6077    );
 6078
 6079    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6080    assert_rewrap(
 6081        indoc! {"
 6082            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6083            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6084            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6085        "},
 6086        indoc! {"
 6087            «1. This is a numbered list item that is
 6088               very long and needs to be wrapped
 6089               properly.
 6090            2. This is a numbered list item that is
 6091               very long and needs to be wrapped
 6092               properly.
 6093            - This is an unordered list item that is
 6094              also very long and should not merge
 6095              with the numbered item.ˇ»
 6096        "},
 6097        markdown_language.clone(),
 6098        &mut cx,
 6099    );
 6100
 6101    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6102    assert_rewrap(
 6103        indoc! {"
 6104            «1. This is a numbered list item that is
 6105            very long and needs to be wrapped
 6106            properly.
 6107            2. This is a numbered list item that is
 6108            very long and needs to be wrapped
 6109            properly.
 6110            - This is an unordered list item that is
 6111            also very long and should not merge with
 6112            the numbered item.ˇ»
 6113        "},
 6114        indoc! {"
 6115            «1. This is a numbered list item that is
 6116               very long and needs to be wrapped
 6117               properly.
 6118            2. This is a numbered list item that is
 6119               very long and needs to be wrapped
 6120               properly.
 6121            - This is an unordered list item that is
 6122              also very long and should not merge
 6123              with the numbered item.ˇ»
 6124        "},
 6125        markdown_language.clone(),
 6126        &mut cx,
 6127    );
 6128
 6129    // Test that rewrapping maintain indents even when they already exists.
 6130    assert_rewrap(
 6131        indoc! {"
 6132            «1. This is a numbered list
 6133               item that is very long and needs to be wrapped properly.
 6134            2. This is a numbered list
 6135               item that is very long and needs to be wrapped properly.
 6136            - This is an unordered list item that is also very long and
 6137              should not merge with the numbered item.ˇ»
 6138        "},
 6139        indoc! {"
 6140            «1. This is a numbered list item that is
 6141               very long and needs to be wrapped
 6142               properly.
 6143            2. This is a numbered list item that is
 6144               very long and needs to be wrapped
 6145               properly.
 6146            - This is an unordered list item that is
 6147              also very long and should not merge
 6148              with the numbered item.ˇ»
 6149        "},
 6150        markdown_language,
 6151        &mut cx,
 6152    );
 6153
 6154    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6155    assert_rewrap(
 6156        indoc! {"
 6157            ˇThis is a very long line of plain text that will be wrapped.
 6158        "},
 6159        indoc! {"
 6160            ˇThis is a very long line of plain text
 6161            that will be wrapped.
 6162        "},
 6163        plaintext_language.clone(),
 6164        &mut cx,
 6165    );
 6166
 6167    // Test that non-commented code acts as a paragraph boundary within a selection
 6168    assert_rewrap(
 6169        indoc! {"
 6170               «// This is the first long comment block to be wrapped.
 6171               fn my_func(a: u32);
 6172               // This is the second long comment block to be wrapped.ˇ»
 6173           "},
 6174        indoc! {"
 6175               «// This is the first long comment block
 6176               // to be wrapped.
 6177               fn my_func(a: u32);
 6178               // This is the second long comment block
 6179               // to be wrapped.ˇ»
 6180           "},
 6181        rust_language,
 6182        &mut cx,
 6183    );
 6184
 6185    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6186    assert_rewrap(
 6187        indoc! {"
 6188            «ˇThis is a very long line that will be wrapped.
 6189
 6190            This is another paragraph in the same selection.»
 6191
 6192            «\tThis is a very long indented line that will be wrapped.ˇ»
 6193         "},
 6194        indoc! {"
 6195            «ˇThis is a very long line that will be
 6196            wrapped.
 6197
 6198            This is another paragraph in the same
 6199            selection.»
 6200
 6201            «\tThis is a very long indented line
 6202            \tthat will be wrapped.ˇ»
 6203         "},
 6204        plaintext_language,
 6205        &mut cx,
 6206    );
 6207
 6208    // Test that an empty comment line acts as a paragraph boundary
 6209    assert_rewrap(
 6210        indoc! {"
 6211            // ˇThis is a long comment that will be wrapped.
 6212            //
 6213            // And this is another long comment that will also be wrapped.ˇ
 6214         "},
 6215        indoc! {"
 6216            // ˇThis is a long comment that will be
 6217            // wrapped.
 6218            //
 6219            // And this is another long comment that
 6220            // will also be wrapped.ˇ
 6221         "},
 6222        cpp_language,
 6223        &mut cx,
 6224    );
 6225
 6226    #[track_caller]
 6227    fn assert_rewrap(
 6228        unwrapped_text: &str,
 6229        wrapped_text: &str,
 6230        language: Arc<Language>,
 6231        cx: &mut EditorTestContext,
 6232    ) {
 6233        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6234        cx.set_state(unwrapped_text);
 6235        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6236        cx.assert_editor_state(wrapped_text);
 6237    }
 6238}
 6239
 6240#[gpui::test]
 6241async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6242    init_test(cx, |settings| {
 6243        settings.languages.0.extend([(
 6244            "Rust".into(),
 6245            LanguageSettingsContent {
 6246                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6247                preferred_line_length: Some(40),
 6248                ..Default::default()
 6249            },
 6250        )])
 6251    });
 6252
 6253    let mut cx = EditorTestContext::new(cx).await;
 6254
 6255    let rust_lang = Arc::new(
 6256        Language::new(
 6257            LanguageConfig {
 6258                name: "Rust".into(),
 6259                line_comments: vec!["// ".into()],
 6260                block_comment: Some(BlockCommentConfig {
 6261                    start: "/*".into(),
 6262                    end: "*/".into(),
 6263                    prefix: "* ".into(),
 6264                    tab_size: 1,
 6265                }),
 6266                documentation_comment: Some(BlockCommentConfig {
 6267                    start: "/**".into(),
 6268                    end: "*/".into(),
 6269                    prefix: "* ".into(),
 6270                    tab_size: 1,
 6271                }),
 6272
 6273                ..LanguageConfig::default()
 6274            },
 6275            Some(tree_sitter_rust::LANGUAGE.into()),
 6276        )
 6277        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6278        .unwrap(),
 6279    );
 6280
 6281    // regular block comment
 6282    assert_rewrap(
 6283        indoc! {"
 6284            /*
 6285             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6286             */
 6287            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6288        "},
 6289        indoc! {"
 6290            /*
 6291             *ˇ Lorem ipsum dolor sit amet,
 6292             * consectetur adipiscing elit.
 6293             */
 6294            /*
 6295             *ˇ Lorem ipsum dolor sit amet,
 6296             * consectetur adipiscing elit.
 6297             */
 6298        "},
 6299        rust_lang.clone(),
 6300        &mut cx,
 6301    );
 6302
 6303    // indent is respected
 6304    assert_rewrap(
 6305        indoc! {"
 6306            {}
 6307                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6308        "},
 6309        indoc! {"
 6310            {}
 6311                /*
 6312                 *ˇ Lorem ipsum dolor sit amet,
 6313                 * consectetur adipiscing elit.
 6314                 */
 6315        "},
 6316        rust_lang.clone(),
 6317        &mut cx,
 6318    );
 6319
 6320    // short block comments with inline delimiters
 6321    assert_rewrap(
 6322        indoc! {"
 6323            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6324            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6325             */
 6326            /*
 6327             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6328        "},
 6329        indoc! {"
 6330            /*
 6331             *ˇ Lorem ipsum dolor sit amet,
 6332             * consectetur adipiscing elit.
 6333             */
 6334            /*
 6335             *ˇ Lorem ipsum dolor sit amet,
 6336             * consectetur adipiscing elit.
 6337             */
 6338            /*
 6339             *ˇ Lorem ipsum dolor sit amet,
 6340             * consectetur adipiscing elit.
 6341             */
 6342        "},
 6343        rust_lang.clone(),
 6344        &mut cx,
 6345    );
 6346
 6347    // multiline block comment with inline start/end delimiters
 6348    assert_rewrap(
 6349        indoc! {"
 6350            /*ˇ Lorem ipsum dolor sit amet,
 6351             * consectetur adipiscing elit. */
 6352        "},
 6353        indoc! {"
 6354            /*
 6355             *ˇ Lorem ipsum dolor sit amet,
 6356             * consectetur adipiscing elit.
 6357             */
 6358        "},
 6359        rust_lang.clone(),
 6360        &mut cx,
 6361    );
 6362
 6363    // block comment rewrap still respects paragraph bounds
 6364    assert_rewrap(
 6365        indoc! {"
 6366            /*
 6367             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6368             *
 6369             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6370             */
 6371        "},
 6372        indoc! {"
 6373            /*
 6374             *ˇ Lorem ipsum dolor sit amet,
 6375             * consectetur adipiscing elit.
 6376             *
 6377             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6378             */
 6379        "},
 6380        rust_lang.clone(),
 6381        &mut cx,
 6382    );
 6383
 6384    // documentation comments
 6385    assert_rewrap(
 6386        indoc! {"
 6387            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6388            /**
 6389             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6390             */
 6391        "},
 6392        indoc! {"
 6393            /**
 6394             *ˇ Lorem ipsum dolor sit amet,
 6395             * consectetur adipiscing elit.
 6396             */
 6397            /**
 6398             *ˇ Lorem ipsum dolor sit amet,
 6399             * consectetur adipiscing elit.
 6400             */
 6401        "},
 6402        rust_lang.clone(),
 6403        &mut cx,
 6404    );
 6405
 6406    // different, adjacent comments
 6407    assert_rewrap(
 6408        indoc! {"
 6409            /**
 6410             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6411             */
 6412            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6413            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6414        "},
 6415        indoc! {"
 6416            /**
 6417             *ˇ Lorem ipsum dolor sit amet,
 6418             * consectetur adipiscing elit.
 6419             */
 6420            /*
 6421             *ˇ Lorem ipsum dolor sit amet,
 6422             * consectetur adipiscing elit.
 6423             */
 6424            //ˇ Lorem ipsum dolor sit amet,
 6425            // consectetur adipiscing elit.
 6426        "},
 6427        rust_lang.clone(),
 6428        &mut cx,
 6429    );
 6430
 6431    // selection w/ single short block comment
 6432    assert_rewrap(
 6433        indoc! {"
 6434            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6435        "},
 6436        indoc! {"
 6437            «/*
 6438             * Lorem ipsum dolor sit amet,
 6439             * consectetur adipiscing elit.
 6440             */ˇ»
 6441        "},
 6442        rust_lang.clone(),
 6443        &mut cx,
 6444    );
 6445
 6446    // rewrapping a single comment w/ abutting comments
 6447    assert_rewrap(
 6448        indoc! {"
 6449            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6450            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6451        "},
 6452        indoc! {"
 6453            /*
 6454             * ˇLorem ipsum dolor sit amet,
 6455             * consectetur adipiscing elit.
 6456             */
 6457            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6458        "},
 6459        rust_lang.clone(),
 6460        &mut cx,
 6461    );
 6462
 6463    // selection w/ non-abutting short block comments
 6464    assert_rewrap(
 6465        indoc! {"
 6466            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6467
 6468            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6469        "},
 6470        indoc! {"
 6471            «/*
 6472             * Lorem ipsum dolor sit amet,
 6473             * consectetur adipiscing elit.
 6474             */
 6475
 6476            /*
 6477             * Lorem ipsum dolor sit amet,
 6478             * consectetur adipiscing elit.
 6479             */ˇ»
 6480        "},
 6481        rust_lang.clone(),
 6482        &mut cx,
 6483    );
 6484
 6485    // selection of multiline block comments
 6486    assert_rewrap(
 6487        indoc! {"
 6488            «/* Lorem ipsum dolor sit amet,
 6489             * consectetur adipiscing elit. */ˇ»
 6490        "},
 6491        indoc! {"
 6492            «/*
 6493             * Lorem ipsum dolor sit amet,
 6494             * consectetur adipiscing elit.
 6495             */ˇ»
 6496        "},
 6497        rust_lang.clone(),
 6498        &mut cx,
 6499    );
 6500
 6501    // partial selection of multiline block comments
 6502    assert_rewrap(
 6503        indoc! {"
 6504            «/* Lorem ipsum dolor sit amet,ˇ»
 6505             * consectetur adipiscing elit. */
 6506            /* Lorem ipsum dolor sit amet,
 6507             «* consectetur adipiscing elit. */ˇ»
 6508        "},
 6509        indoc! {"
 6510            «/*
 6511             * Lorem ipsum dolor sit amet,ˇ»
 6512             * consectetur adipiscing elit. */
 6513            /* Lorem ipsum dolor sit amet,
 6514             «* consectetur adipiscing elit.
 6515             */ˇ»
 6516        "},
 6517        rust_lang.clone(),
 6518        &mut cx,
 6519    );
 6520
 6521    // selection w/ abutting short block comments
 6522    // TODO: should not be combined; should rewrap as 2 comments
 6523    assert_rewrap(
 6524        indoc! {"
 6525            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6526            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6527        "},
 6528        // desired behavior:
 6529        // indoc! {"
 6530        //     «/*
 6531        //      * Lorem ipsum dolor sit amet,
 6532        //      * consectetur adipiscing elit.
 6533        //      */
 6534        //     /*
 6535        //      * Lorem ipsum dolor sit amet,
 6536        //      * consectetur adipiscing elit.
 6537        //      */ˇ»
 6538        // "},
 6539        // actual behaviour:
 6540        indoc! {"
 6541            «/*
 6542             * Lorem ipsum dolor sit amet,
 6543             * consectetur adipiscing elit. Lorem
 6544             * ipsum dolor sit amet, consectetur
 6545             * adipiscing elit.
 6546             */ˇ»
 6547        "},
 6548        rust_lang.clone(),
 6549        &mut cx,
 6550    );
 6551
 6552    // TODO: same as above, but with delimiters on separate line
 6553    // assert_rewrap(
 6554    //     indoc! {"
 6555    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6556    //          */
 6557    //         /*
 6558    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6559    //     "},
 6560    //     // desired:
 6561    //     // indoc! {"
 6562    //     //     «/*
 6563    //     //      * Lorem ipsum dolor sit amet,
 6564    //     //      * consectetur adipiscing elit.
 6565    //     //      */
 6566    //     //     /*
 6567    //     //      * Lorem ipsum dolor sit amet,
 6568    //     //      * consectetur adipiscing elit.
 6569    //     //      */ˇ»
 6570    //     // "},
 6571    //     // actual: (but with trailing w/s on the empty lines)
 6572    //     indoc! {"
 6573    //         «/*
 6574    //          * Lorem ipsum dolor sit amet,
 6575    //          * consectetur adipiscing elit.
 6576    //          *
 6577    //          */
 6578    //         /*
 6579    //          *
 6580    //          * Lorem ipsum dolor sit amet,
 6581    //          * consectetur adipiscing elit.
 6582    //          */ˇ»
 6583    //     "},
 6584    //     rust_lang.clone(),
 6585    //     &mut cx,
 6586    // );
 6587
 6588    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6589    assert_rewrap(
 6590        indoc! {"
 6591            /*
 6592             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6593             */
 6594            /*
 6595             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6596            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6597        "},
 6598        // desired:
 6599        // indoc! {"
 6600        //     /*
 6601        //      *ˇ Lorem ipsum dolor sit amet,
 6602        //      * consectetur adipiscing elit.
 6603        //      */
 6604        //     /*
 6605        //      *ˇ Lorem ipsum dolor sit amet,
 6606        //      * consectetur adipiscing elit.
 6607        //      */
 6608        //     /*
 6609        //      *ˇ Lorem ipsum dolor sit amet
 6610        //      */ /* consectetur adipiscing elit. */
 6611        // "},
 6612        // actual:
 6613        indoc! {"
 6614            /*
 6615             //ˇ Lorem ipsum dolor sit amet,
 6616             // consectetur adipiscing elit.
 6617             */
 6618            /*
 6619             * //ˇ Lorem ipsum dolor sit amet,
 6620             * consectetur adipiscing elit.
 6621             */
 6622            /*
 6623             *ˇ Lorem ipsum dolor sit amet */ /*
 6624             * consectetur adipiscing elit.
 6625             */
 6626        "},
 6627        rust_lang,
 6628        &mut cx,
 6629    );
 6630
 6631    #[track_caller]
 6632    fn assert_rewrap(
 6633        unwrapped_text: &str,
 6634        wrapped_text: &str,
 6635        language: Arc<Language>,
 6636        cx: &mut EditorTestContext,
 6637    ) {
 6638        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6639        cx.set_state(unwrapped_text);
 6640        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6641        cx.assert_editor_state(wrapped_text);
 6642    }
 6643}
 6644
 6645#[gpui::test]
 6646async fn test_hard_wrap(cx: &mut TestAppContext) {
 6647    init_test(cx, |_| {});
 6648    let mut cx = EditorTestContext::new(cx).await;
 6649
 6650    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6651    cx.update_editor(|editor, _, cx| {
 6652        editor.set_hard_wrap(Some(14), cx);
 6653    });
 6654
 6655    cx.set_state(indoc!(
 6656        "
 6657        one two three ˇ
 6658        "
 6659    ));
 6660    cx.simulate_input("four");
 6661    cx.run_until_parked();
 6662
 6663    cx.assert_editor_state(indoc!(
 6664        "
 6665        one two three
 6666        fourˇ
 6667        "
 6668    ));
 6669
 6670    cx.update_editor(|editor, window, cx| {
 6671        editor.newline(&Default::default(), window, cx);
 6672    });
 6673    cx.run_until_parked();
 6674    cx.assert_editor_state(indoc!(
 6675        "
 6676        one two three
 6677        four
 6678        ˇ
 6679        "
 6680    ));
 6681
 6682    cx.simulate_input("five");
 6683    cx.run_until_parked();
 6684    cx.assert_editor_state(indoc!(
 6685        "
 6686        one two three
 6687        four
 6688        fiveˇ
 6689        "
 6690    ));
 6691
 6692    cx.update_editor(|editor, window, cx| {
 6693        editor.newline(&Default::default(), window, cx);
 6694    });
 6695    cx.run_until_parked();
 6696    cx.simulate_input("# ");
 6697    cx.run_until_parked();
 6698    cx.assert_editor_state(indoc!(
 6699        "
 6700        one two three
 6701        four
 6702        five
 6703        # ˇ
 6704        "
 6705    ));
 6706
 6707    cx.update_editor(|editor, window, cx| {
 6708        editor.newline(&Default::default(), window, cx);
 6709    });
 6710    cx.run_until_parked();
 6711    cx.assert_editor_state(indoc!(
 6712        "
 6713        one two three
 6714        four
 6715        five
 6716        #\x20
 6717 6718        "
 6719    ));
 6720
 6721    cx.simulate_input(" 6");
 6722    cx.run_until_parked();
 6723    cx.assert_editor_state(indoc!(
 6724        "
 6725        one two three
 6726        four
 6727        five
 6728        #
 6729        # 6ˇ
 6730        "
 6731    ));
 6732}
 6733
 6734#[gpui::test]
 6735async fn test_cut_line_ends(cx: &mut TestAppContext) {
 6736    init_test(cx, |_| {});
 6737
 6738    let mut cx = EditorTestContext::new(cx).await;
 6739
 6740    cx.set_state(indoc! {"
 6741        The quick« brownˇ»
 6742        fox jumps overˇ
 6743        the lazy dog"});
 6744    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6745    cx.assert_editor_state(indoc! {"
 6746        The quickˇ
 6747        ˇthe lazy dog"});
 6748
 6749    cx.set_state(indoc! {"
 6750        The quick« brownˇ»
 6751        fox jumps overˇ
 6752        the lazy dog"});
 6753    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 6754    cx.assert_editor_state(indoc! {"
 6755        The quickˇ
 6756        fox jumps overˇthe lazy dog"});
 6757
 6758    cx.set_state(indoc! {"
 6759        The quick« brownˇ»
 6760        fox jumps overˇ
 6761        the lazy dog"});
 6762    cx.update_editor(|e, window, cx| {
 6763        e.cut_to_end_of_line(
 6764            &CutToEndOfLine {
 6765                stop_at_newlines: true,
 6766            },
 6767            window,
 6768            cx,
 6769        )
 6770    });
 6771    cx.assert_editor_state(indoc! {"
 6772        The quickˇ
 6773        fox jumps overˇ
 6774        the lazy dog"});
 6775
 6776    cx.set_state(indoc! {"
 6777        The quick« brownˇ»
 6778        fox jumps overˇ
 6779        the lazy dog"});
 6780    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 6781    cx.assert_editor_state(indoc! {"
 6782        The quickˇ
 6783        fox jumps overˇthe lazy dog"});
 6784}
 6785
 6786#[gpui::test]
 6787async fn test_clipboard(cx: &mut TestAppContext) {
 6788    init_test(cx, |_| {});
 6789
 6790    let mut cx = EditorTestContext::new(cx).await;
 6791
 6792    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 6793    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6794    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 6795
 6796    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 6797    cx.set_state("two ˇfour ˇsix ˇ");
 6798    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6799    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 6800
 6801    // Paste again but with only two cursors. Since the number of cursors doesn't
 6802    // match the number of slices in the clipboard, the entire clipboard text
 6803    // is pasted at each cursor.
 6804    cx.set_state("ˇtwo one✅ four three six five ˇ");
 6805    cx.update_editor(|e, window, cx| {
 6806        e.handle_input("( ", window, cx);
 6807        e.paste(&Paste, window, cx);
 6808        e.handle_input(") ", window, cx);
 6809    });
 6810    cx.assert_editor_state(
 6811        &([
 6812            "( one✅ ",
 6813            "three ",
 6814            "five ) ˇtwo one✅ four three six five ( one✅ ",
 6815            "three ",
 6816            "five ) ˇ",
 6817        ]
 6818        .join("\n")),
 6819    );
 6820
 6821    // Cut with three selections, one of which is full-line.
 6822    cx.set_state(indoc! {"
 6823        1«2ˇ»3
 6824        4ˇ567
 6825        «8ˇ»9"});
 6826    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 6827    cx.assert_editor_state(indoc! {"
 6828        1ˇ3
 6829        ˇ9"});
 6830
 6831    // Paste with three selections, noticing how the copied selection that was full-line
 6832    // gets inserted before the second cursor.
 6833    cx.set_state(indoc! {"
 6834        1ˇ3
 6835 6836        «oˇ»ne"});
 6837    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6838    cx.assert_editor_state(indoc! {"
 6839        12ˇ3
 6840        4567
 6841 6842        8ˇne"});
 6843
 6844    // Copy with a single cursor only, which writes the whole line into the clipboard.
 6845    cx.set_state(indoc! {"
 6846        The quick brown
 6847        fox juˇmps over
 6848        the lazy dog"});
 6849    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6850    assert_eq!(
 6851        cx.read_from_clipboard()
 6852            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6853        Some("fox jumps over\n".to_string())
 6854    );
 6855
 6856    // Paste with three selections, noticing how the copied full-line selection is inserted
 6857    // before the empty selections but replaces the selection that is non-empty.
 6858    cx.set_state(indoc! {"
 6859        Tˇhe quick brown
 6860        «foˇ»x jumps over
 6861        tˇhe lazy dog"});
 6862    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 6863    cx.assert_editor_state(indoc! {"
 6864        fox jumps over
 6865        Tˇhe quick brown
 6866        fox jumps over
 6867        ˇx jumps over
 6868        fox jumps over
 6869        tˇhe lazy dog"});
 6870}
 6871
 6872#[gpui::test]
 6873async fn test_copy_trim(cx: &mut TestAppContext) {
 6874    init_test(cx, |_| {});
 6875
 6876    let mut cx = EditorTestContext::new(cx).await;
 6877    cx.set_state(
 6878        r#"            «for selection in selections.iter() {
 6879            let mut start = selection.start;
 6880            let mut end = selection.end;
 6881            let is_entire_line = selection.is_empty();
 6882            if is_entire_line {
 6883                start = Point::new(start.row, 0);ˇ»
 6884                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6885            }
 6886        "#,
 6887    );
 6888    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6889    assert_eq!(
 6890        cx.read_from_clipboard()
 6891            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6892        Some(
 6893            "for selection in selections.iter() {
 6894            let mut start = selection.start;
 6895            let mut end = selection.end;
 6896            let is_entire_line = selection.is_empty();
 6897            if is_entire_line {
 6898                start = Point::new(start.row, 0);"
 6899                .to_string()
 6900        ),
 6901        "Regular copying preserves all indentation selected",
 6902    );
 6903    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6904    assert_eq!(
 6905        cx.read_from_clipboard()
 6906            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6907        Some(
 6908            "for selection in selections.iter() {
 6909let mut start = selection.start;
 6910let mut end = selection.end;
 6911let is_entire_line = selection.is_empty();
 6912if is_entire_line {
 6913    start = Point::new(start.row, 0);"
 6914                .to_string()
 6915        ),
 6916        "Copying with stripping should strip all leading whitespaces"
 6917    );
 6918
 6919    cx.set_state(
 6920        r#"       «     for selection in selections.iter() {
 6921            let mut start = selection.start;
 6922            let mut end = selection.end;
 6923            let is_entire_line = selection.is_empty();
 6924            if is_entire_line {
 6925                start = Point::new(start.row, 0);ˇ»
 6926                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6927            }
 6928        "#,
 6929    );
 6930    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6931    assert_eq!(
 6932        cx.read_from_clipboard()
 6933            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6934        Some(
 6935            "     for selection in selections.iter() {
 6936            let mut start = selection.start;
 6937            let mut end = selection.end;
 6938            let is_entire_line = selection.is_empty();
 6939            if is_entire_line {
 6940                start = Point::new(start.row, 0);"
 6941                .to_string()
 6942        ),
 6943        "Regular copying preserves all indentation selected",
 6944    );
 6945    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6946    assert_eq!(
 6947        cx.read_from_clipboard()
 6948            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6949        Some(
 6950            "for selection in selections.iter() {
 6951let mut start = selection.start;
 6952let mut end = selection.end;
 6953let is_entire_line = selection.is_empty();
 6954if is_entire_line {
 6955    start = Point::new(start.row, 0);"
 6956                .to_string()
 6957        ),
 6958        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 6959    );
 6960
 6961    cx.set_state(
 6962        r#"       «ˇ     for selection in selections.iter() {
 6963            let mut start = selection.start;
 6964            let mut end = selection.end;
 6965            let is_entire_line = selection.is_empty();
 6966            if is_entire_line {
 6967                start = Point::new(start.row, 0);»
 6968                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 6969            }
 6970        "#,
 6971    );
 6972    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 6973    assert_eq!(
 6974        cx.read_from_clipboard()
 6975            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6976        Some(
 6977            "     for selection in selections.iter() {
 6978            let mut start = selection.start;
 6979            let mut end = selection.end;
 6980            let is_entire_line = selection.is_empty();
 6981            if is_entire_line {
 6982                start = Point::new(start.row, 0);"
 6983                .to_string()
 6984        ),
 6985        "Regular copying for reverse selection works the same",
 6986    );
 6987    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 6988    assert_eq!(
 6989        cx.read_from_clipboard()
 6990            .and_then(|item| item.text().as_deref().map(str::to_string)),
 6991        Some(
 6992            "for selection in selections.iter() {
 6993let mut start = selection.start;
 6994let mut end = selection.end;
 6995let is_entire_line = selection.is_empty();
 6996if is_entire_line {
 6997    start = Point::new(start.row, 0);"
 6998                .to_string()
 6999        ),
 7000        "Copying with stripping for reverse selection works the same"
 7001    );
 7002
 7003    cx.set_state(
 7004        r#"            for selection «in selections.iter() {
 7005            let mut start = selection.start;
 7006            let mut end = selection.end;
 7007            let is_entire_line = selection.is_empty();
 7008            if is_entire_line {
 7009                start = Point::new(start.row, 0);ˇ»
 7010                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7011            }
 7012        "#,
 7013    );
 7014    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7015    assert_eq!(
 7016        cx.read_from_clipboard()
 7017            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7018        Some(
 7019            "in selections.iter() {
 7020            let mut start = selection.start;
 7021            let mut end = selection.end;
 7022            let is_entire_line = selection.is_empty();
 7023            if is_entire_line {
 7024                start = Point::new(start.row, 0);"
 7025                .to_string()
 7026        ),
 7027        "When selecting past the indent, the copying works as usual",
 7028    );
 7029    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7030    assert_eq!(
 7031        cx.read_from_clipboard()
 7032            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7033        Some(
 7034            "in selections.iter() {
 7035            let mut start = selection.start;
 7036            let mut end = selection.end;
 7037            let is_entire_line = selection.is_empty();
 7038            if is_entire_line {
 7039                start = Point::new(start.row, 0);"
 7040                .to_string()
 7041        ),
 7042        "When selecting past the indent, nothing is trimmed"
 7043    );
 7044
 7045    cx.set_state(
 7046        r#"            «for selection in selections.iter() {
 7047            let mut start = selection.start;
 7048
 7049            let mut end = selection.end;
 7050            let is_entire_line = selection.is_empty();
 7051            if is_entire_line {
 7052                start = Point::new(start.row, 0);
 7053ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7054            }
 7055        "#,
 7056    );
 7057    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7058    assert_eq!(
 7059        cx.read_from_clipboard()
 7060            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7061        Some(
 7062            "for selection in selections.iter() {
 7063let mut start = selection.start;
 7064
 7065let mut end = selection.end;
 7066let is_entire_line = selection.is_empty();
 7067if is_entire_line {
 7068    start = Point::new(start.row, 0);
 7069"
 7070            .to_string()
 7071        ),
 7072        "Copying with stripping should ignore empty lines"
 7073    );
 7074}
 7075
 7076#[gpui::test]
 7077async fn test_paste_multiline(cx: &mut TestAppContext) {
 7078    init_test(cx, |_| {});
 7079
 7080    let mut cx = EditorTestContext::new(cx).await;
 7081    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7082
 7083    // Cut an indented block, without the leading whitespace.
 7084    cx.set_state(indoc! {"
 7085        const a: B = (
 7086            c(),
 7087            «d(
 7088                e,
 7089                f
 7090            )ˇ»
 7091        );
 7092    "});
 7093    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7094    cx.assert_editor_state(indoc! {"
 7095        const a: B = (
 7096            c(),
 7097            ˇ
 7098        );
 7099    "});
 7100
 7101    // Paste it at the same position.
 7102    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7103    cx.assert_editor_state(indoc! {"
 7104        const a: B = (
 7105            c(),
 7106            d(
 7107                e,
 7108                f
 7109 7110        );
 7111    "});
 7112
 7113    // Paste it at a line with a lower indent level.
 7114    cx.set_state(indoc! {"
 7115        ˇ
 7116        const a: B = (
 7117            c(),
 7118        );
 7119    "});
 7120    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7121    cx.assert_editor_state(indoc! {"
 7122        d(
 7123            e,
 7124            f
 7125 7126        const a: B = (
 7127            c(),
 7128        );
 7129    "});
 7130
 7131    // Cut an indented block, with the leading whitespace.
 7132    cx.set_state(indoc! {"
 7133        const a: B = (
 7134            c(),
 7135        «    d(
 7136                e,
 7137                f
 7138            )
 7139        ˇ»);
 7140    "});
 7141    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7142    cx.assert_editor_state(indoc! {"
 7143        const a: B = (
 7144            c(),
 7145        ˇ);
 7146    "});
 7147
 7148    // Paste it at the same position.
 7149    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7150    cx.assert_editor_state(indoc! {"
 7151        const a: B = (
 7152            c(),
 7153            d(
 7154                e,
 7155                f
 7156            )
 7157        ˇ);
 7158    "});
 7159
 7160    // Paste it at a line with a higher indent level.
 7161    cx.set_state(indoc! {"
 7162        const a: B = (
 7163            c(),
 7164            d(
 7165                e,
 7166 7167            )
 7168        );
 7169    "});
 7170    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7171    cx.assert_editor_state(indoc! {"
 7172        const a: B = (
 7173            c(),
 7174            d(
 7175                e,
 7176                f    d(
 7177                    e,
 7178                    f
 7179                )
 7180        ˇ
 7181            )
 7182        );
 7183    "});
 7184
 7185    // Copy an indented block, starting mid-line
 7186    cx.set_state(indoc! {"
 7187        const a: B = (
 7188            c(),
 7189            somethin«g(
 7190                e,
 7191                f
 7192            )ˇ»
 7193        );
 7194    "});
 7195    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7196
 7197    // Paste it on a line with a lower indent level
 7198    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7199    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7200    cx.assert_editor_state(indoc! {"
 7201        const a: B = (
 7202            c(),
 7203            something(
 7204                e,
 7205                f
 7206            )
 7207        );
 7208        g(
 7209            e,
 7210            f
 7211"});
 7212}
 7213
 7214#[gpui::test]
 7215async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7216    init_test(cx, |_| {});
 7217
 7218    cx.write_to_clipboard(ClipboardItem::new_string(
 7219        "    d(\n        e\n    );\n".into(),
 7220    ));
 7221
 7222    let mut cx = EditorTestContext::new(cx).await;
 7223    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7224
 7225    cx.set_state(indoc! {"
 7226        fn a() {
 7227            b();
 7228            if c() {
 7229                ˇ
 7230            }
 7231        }
 7232    "});
 7233
 7234    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7235    cx.assert_editor_state(indoc! {"
 7236        fn a() {
 7237            b();
 7238            if c() {
 7239                d(
 7240                    e
 7241                );
 7242        ˇ
 7243            }
 7244        }
 7245    "});
 7246
 7247    cx.set_state(indoc! {"
 7248        fn a() {
 7249            b();
 7250            ˇ
 7251        }
 7252    "});
 7253
 7254    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7255    cx.assert_editor_state(indoc! {"
 7256        fn a() {
 7257            b();
 7258            d(
 7259                e
 7260            );
 7261        ˇ
 7262        }
 7263    "});
 7264}
 7265
 7266#[gpui::test]
 7267fn test_select_all(cx: &mut TestAppContext) {
 7268    init_test(cx, |_| {});
 7269
 7270    let editor = cx.add_window(|window, cx| {
 7271        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7272        build_editor(buffer, window, cx)
 7273    });
 7274    _ = editor.update(cx, |editor, window, cx| {
 7275        editor.select_all(&SelectAll, window, cx);
 7276        assert_eq!(
 7277            editor.selections.display_ranges(cx),
 7278            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7279        );
 7280    });
 7281}
 7282
 7283#[gpui::test]
 7284fn test_select_line(cx: &mut TestAppContext) {
 7285    init_test(cx, |_| {});
 7286
 7287    let editor = cx.add_window(|window, cx| {
 7288        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7289        build_editor(buffer, window, cx)
 7290    });
 7291    _ = editor.update(cx, |editor, window, cx| {
 7292        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7293            s.select_display_ranges([
 7294                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7295                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7296                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7297                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7298            ])
 7299        });
 7300        editor.select_line(&SelectLine, window, cx);
 7301        assert_eq!(
 7302            editor.selections.display_ranges(cx),
 7303            vec![
 7304                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7305                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7306            ]
 7307        );
 7308    });
 7309
 7310    _ = editor.update(cx, |editor, window, cx| {
 7311        editor.select_line(&SelectLine, window, cx);
 7312        assert_eq!(
 7313            editor.selections.display_ranges(cx),
 7314            vec![
 7315                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7316                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7317            ]
 7318        );
 7319    });
 7320
 7321    _ = editor.update(cx, |editor, window, cx| {
 7322        editor.select_line(&SelectLine, window, cx);
 7323        assert_eq!(
 7324            editor.selections.display_ranges(cx),
 7325            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7326        );
 7327    });
 7328}
 7329
 7330#[gpui::test]
 7331async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7332    init_test(cx, |_| {});
 7333    let mut cx = EditorTestContext::new(cx).await;
 7334
 7335    #[track_caller]
 7336    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7337        cx.set_state(initial_state);
 7338        cx.update_editor(|e, window, cx| {
 7339            e.split_selection_into_lines(&Default::default(), window, cx)
 7340        });
 7341        cx.assert_editor_state(expected_state);
 7342    }
 7343
 7344    // Selection starts and ends at the middle of lines, left-to-right
 7345    test(
 7346        &mut cx,
 7347        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7348        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7349    );
 7350    // Same thing, right-to-left
 7351    test(
 7352        &mut cx,
 7353        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7354        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7355    );
 7356
 7357    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7358    test(
 7359        &mut cx,
 7360        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7361        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7362    );
 7363    // Same thing, right-to-left
 7364    test(
 7365        &mut cx,
 7366        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7367        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7368    );
 7369
 7370    // Whole buffer, left-to-right, last line ends with newline
 7371    test(
 7372        &mut cx,
 7373        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7374        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7375    );
 7376    // Same thing, right-to-left
 7377    test(
 7378        &mut cx,
 7379        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7380        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7381    );
 7382
 7383    // Starts at the end of a line, ends at the start of another
 7384    test(
 7385        &mut cx,
 7386        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7387        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7388    );
 7389}
 7390
 7391#[gpui::test]
 7392async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7393    init_test(cx, |_| {});
 7394
 7395    let editor = cx.add_window(|window, cx| {
 7396        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7397        build_editor(buffer, window, cx)
 7398    });
 7399
 7400    // setup
 7401    _ = editor.update(cx, |editor, window, cx| {
 7402        editor.fold_creases(
 7403            vec![
 7404                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7405                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7406                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7407            ],
 7408            true,
 7409            window,
 7410            cx,
 7411        );
 7412        assert_eq!(
 7413            editor.display_text(cx),
 7414            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7415        );
 7416    });
 7417
 7418    _ = editor.update(cx, |editor, window, cx| {
 7419        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7420            s.select_display_ranges([
 7421                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7422                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7423                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7424                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7425            ])
 7426        });
 7427        editor.split_selection_into_lines(&Default::default(), window, cx);
 7428        assert_eq!(
 7429            editor.display_text(cx),
 7430            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7431        );
 7432    });
 7433    EditorTestContext::for_editor(editor, cx)
 7434        .await
 7435        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7436
 7437    _ = editor.update(cx, |editor, window, cx| {
 7438        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7439            s.select_display_ranges([
 7440                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7441            ])
 7442        });
 7443        editor.split_selection_into_lines(&Default::default(), window, cx);
 7444        assert_eq!(
 7445            editor.display_text(cx),
 7446            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7447        );
 7448        assert_eq!(
 7449            editor.selections.display_ranges(cx),
 7450            [
 7451                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7452                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7453                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7454                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7455                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7456                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7457                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7458            ]
 7459        );
 7460    });
 7461    EditorTestContext::for_editor(editor, cx)
 7462        .await
 7463        .assert_editor_state(
 7464            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7465        );
 7466}
 7467
 7468#[gpui::test]
 7469async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7470    init_test(cx, |_| {});
 7471
 7472    let mut cx = EditorTestContext::new(cx).await;
 7473
 7474    cx.set_state(indoc!(
 7475        r#"abc
 7476           defˇghi
 7477
 7478           jk
 7479           nlmo
 7480           "#
 7481    ));
 7482
 7483    cx.update_editor(|editor, window, cx| {
 7484        editor.add_selection_above(&Default::default(), window, cx);
 7485    });
 7486
 7487    cx.assert_editor_state(indoc!(
 7488        r#"abcˇ
 7489           defˇghi
 7490
 7491           jk
 7492           nlmo
 7493           "#
 7494    ));
 7495
 7496    cx.update_editor(|editor, window, cx| {
 7497        editor.add_selection_above(&Default::default(), window, cx);
 7498    });
 7499
 7500    cx.assert_editor_state(indoc!(
 7501        r#"abcˇ
 7502            defˇghi
 7503
 7504            jk
 7505            nlmo
 7506            "#
 7507    ));
 7508
 7509    cx.update_editor(|editor, window, cx| {
 7510        editor.add_selection_below(&Default::default(), window, cx);
 7511    });
 7512
 7513    cx.assert_editor_state(indoc!(
 7514        r#"abc
 7515           defˇghi
 7516
 7517           jk
 7518           nlmo
 7519           "#
 7520    ));
 7521
 7522    cx.update_editor(|editor, window, cx| {
 7523        editor.undo_selection(&Default::default(), window, cx);
 7524    });
 7525
 7526    cx.assert_editor_state(indoc!(
 7527        r#"abcˇ
 7528           defˇghi
 7529
 7530           jk
 7531           nlmo
 7532           "#
 7533    ));
 7534
 7535    cx.update_editor(|editor, window, cx| {
 7536        editor.redo_selection(&Default::default(), window, cx);
 7537    });
 7538
 7539    cx.assert_editor_state(indoc!(
 7540        r#"abc
 7541           defˇghi
 7542
 7543           jk
 7544           nlmo
 7545           "#
 7546    ));
 7547
 7548    cx.update_editor(|editor, window, cx| {
 7549        editor.add_selection_below(&Default::default(), window, cx);
 7550    });
 7551
 7552    cx.assert_editor_state(indoc!(
 7553        r#"abc
 7554           defˇghi
 7555           ˇ
 7556           jk
 7557           nlmo
 7558           "#
 7559    ));
 7560
 7561    cx.update_editor(|editor, window, cx| {
 7562        editor.add_selection_below(&Default::default(), window, cx);
 7563    });
 7564
 7565    cx.assert_editor_state(indoc!(
 7566        r#"abc
 7567           defˇghi
 7568           ˇ
 7569           jkˇ
 7570           nlmo
 7571           "#
 7572    ));
 7573
 7574    cx.update_editor(|editor, window, cx| {
 7575        editor.add_selection_below(&Default::default(), window, cx);
 7576    });
 7577
 7578    cx.assert_editor_state(indoc!(
 7579        r#"abc
 7580           defˇghi
 7581           ˇ
 7582           jkˇ
 7583           nlmˇo
 7584           "#
 7585    ));
 7586
 7587    cx.update_editor(|editor, window, cx| {
 7588        editor.add_selection_below(&Default::default(), window, cx);
 7589    });
 7590
 7591    cx.assert_editor_state(indoc!(
 7592        r#"abc
 7593           defˇghi
 7594           ˇ
 7595           jkˇ
 7596           nlmˇo
 7597           ˇ"#
 7598    ));
 7599
 7600    // change selections
 7601    cx.set_state(indoc!(
 7602        r#"abc
 7603           def«ˇg»hi
 7604
 7605           jk
 7606           nlmo
 7607           "#
 7608    ));
 7609
 7610    cx.update_editor(|editor, window, cx| {
 7611        editor.add_selection_below(&Default::default(), window, cx);
 7612    });
 7613
 7614    cx.assert_editor_state(indoc!(
 7615        r#"abc
 7616           def«ˇg»hi
 7617
 7618           jk
 7619           nlm«ˇo»
 7620           "#
 7621    ));
 7622
 7623    cx.update_editor(|editor, window, cx| {
 7624        editor.add_selection_below(&Default::default(), window, cx);
 7625    });
 7626
 7627    cx.assert_editor_state(indoc!(
 7628        r#"abc
 7629           def«ˇg»hi
 7630
 7631           jk
 7632           nlm«ˇo»
 7633           "#
 7634    ));
 7635
 7636    cx.update_editor(|editor, window, cx| {
 7637        editor.add_selection_above(&Default::default(), window, cx);
 7638    });
 7639
 7640    cx.assert_editor_state(indoc!(
 7641        r#"abc
 7642           def«ˇg»hi
 7643
 7644           jk
 7645           nlmo
 7646           "#
 7647    ));
 7648
 7649    cx.update_editor(|editor, window, cx| {
 7650        editor.add_selection_above(&Default::default(), window, cx);
 7651    });
 7652
 7653    cx.assert_editor_state(indoc!(
 7654        r#"abc
 7655           def«ˇg»hi
 7656
 7657           jk
 7658           nlmo
 7659           "#
 7660    ));
 7661
 7662    // Change selections again
 7663    cx.set_state(indoc!(
 7664        r#"a«bc
 7665           defgˇ»hi
 7666
 7667           jk
 7668           nlmo
 7669           "#
 7670    ));
 7671
 7672    cx.update_editor(|editor, window, cx| {
 7673        editor.add_selection_below(&Default::default(), window, cx);
 7674    });
 7675
 7676    cx.assert_editor_state(indoc!(
 7677        r#"a«bcˇ»
 7678           d«efgˇ»hi
 7679
 7680           j«kˇ»
 7681           nlmo
 7682           "#
 7683    ));
 7684
 7685    cx.update_editor(|editor, window, cx| {
 7686        editor.add_selection_below(&Default::default(), window, cx);
 7687    });
 7688    cx.assert_editor_state(indoc!(
 7689        r#"a«bcˇ»
 7690           d«efgˇ»hi
 7691
 7692           j«kˇ»
 7693           n«lmoˇ»
 7694           "#
 7695    ));
 7696    cx.update_editor(|editor, window, cx| {
 7697        editor.add_selection_above(&Default::default(), window, cx);
 7698    });
 7699
 7700    cx.assert_editor_state(indoc!(
 7701        r#"a«bcˇ»
 7702           d«efgˇ»hi
 7703
 7704           j«kˇ»
 7705           nlmo
 7706           "#
 7707    ));
 7708
 7709    // Change selections again
 7710    cx.set_state(indoc!(
 7711        r#"abc
 7712           d«ˇefghi
 7713
 7714           jk
 7715           nlm»o
 7716           "#
 7717    ));
 7718
 7719    cx.update_editor(|editor, window, cx| {
 7720        editor.add_selection_above(&Default::default(), window, cx);
 7721    });
 7722
 7723    cx.assert_editor_state(indoc!(
 7724        r#"a«ˇbc»
 7725           d«ˇef»ghi
 7726
 7727           j«ˇk»
 7728           n«ˇlm»o
 7729           "#
 7730    ));
 7731
 7732    cx.update_editor(|editor, window, cx| {
 7733        editor.add_selection_below(&Default::default(), window, cx);
 7734    });
 7735
 7736    cx.assert_editor_state(indoc!(
 7737        r#"abc
 7738           d«ˇef»ghi
 7739
 7740           j«ˇk»
 7741           n«ˇlm»o
 7742           "#
 7743    ));
 7744}
 7745
 7746#[gpui::test]
 7747async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 7748    init_test(cx, |_| {});
 7749    let mut cx = EditorTestContext::new(cx).await;
 7750
 7751    cx.set_state(indoc!(
 7752        r#"line onˇe
 7753           liˇne two
 7754           line three
 7755           line four"#
 7756    ));
 7757
 7758    cx.update_editor(|editor, window, cx| {
 7759        editor.add_selection_below(&Default::default(), window, cx);
 7760    });
 7761
 7762    // test multiple cursors expand in the same direction
 7763    cx.assert_editor_state(indoc!(
 7764        r#"line onˇe
 7765           liˇne twˇo
 7766           liˇne three
 7767           line four"#
 7768    ));
 7769
 7770    cx.update_editor(|editor, window, cx| {
 7771        editor.add_selection_below(&Default::default(), window, cx);
 7772    });
 7773
 7774    cx.update_editor(|editor, window, cx| {
 7775        editor.add_selection_below(&Default::default(), window, cx);
 7776    });
 7777
 7778    // test multiple cursors expand below overflow
 7779    cx.assert_editor_state(indoc!(
 7780        r#"line onˇe
 7781           liˇne twˇo
 7782           liˇne thˇree
 7783           liˇne foˇur"#
 7784    ));
 7785
 7786    cx.update_editor(|editor, window, cx| {
 7787        editor.add_selection_above(&Default::default(), window, cx);
 7788    });
 7789
 7790    // test multiple cursors retrieves back correctly
 7791    cx.assert_editor_state(indoc!(
 7792        r#"line onˇe
 7793           liˇne twˇo
 7794           liˇne thˇree
 7795           line four"#
 7796    ));
 7797
 7798    cx.update_editor(|editor, window, cx| {
 7799        editor.add_selection_above(&Default::default(), window, cx);
 7800    });
 7801
 7802    cx.update_editor(|editor, window, cx| {
 7803        editor.add_selection_above(&Default::default(), window, cx);
 7804    });
 7805
 7806    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 7807    cx.assert_editor_state(indoc!(
 7808        r#"liˇne onˇe
 7809           liˇne two
 7810           line three
 7811           line four"#
 7812    ));
 7813
 7814    cx.update_editor(|editor, window, cx| {
 7815        editor.undo_selection(&Default::default(), window, cx);
 7816    });
 7817
 7818    // test undo
 7819    cx.assert_editor_state(indoc!(
 7820        r#"line onˇe
 7821           liˇne twˇo
 7822           line three
 7823           line four"#
 7824    ));
 7825
 7826    cx.update_editor(|editor, window, cx| {
 7827        editor.redo_selection(&Default::default(), window, cx);
 7828    });
 7829
 7830    // test redo
 7831    cx.assert_editor_state(indoc!(
 7832        r#"liˇne onˇe
 7833           liˇne two
 7834           line three
 7835           line four"#
 7836    ));
 7837
 7838    cx.set_state(indoc!(
 7839        r#"abcd
 7840           ef«ghˇ»
 7841           ijkl
 7842           «mˇ»nop"#
 7843    ));
 7844
 7845    cx.update_editor(|editor, window, cx| {
 7846        editor.add_selection_above(&Default::default(), window, cx);
 7847    });
 7848
 7849    // test multiple selections expand in the same direction
 7850    cx.assert_editor_state(indoc!(
 7851        r#"ab«cdˇ»
 7852           ef«ghˇ»
 7853           «iˇ»jkl
 7854           «mˇ»nop"#
 7855    ));
 7856
 7857    cx.update_editor(|editor, window, cx| {
 7858        editor.add_selection_above(&Default::default(), window, cx);
 7859    });
 7860
 7861    // test multiple selection upward overflow
 7862    cx.assert_editor_state(indoc!(
 7863        r#"ab«cdˇ»
 7864           «eˇ»f«ghˇ»
 7865           «iˇ»jkl
 7866           «mˇ»nop"#
 7867    ));
 7868
 7869    cx.update_editor(|editor, window, cx| {
 7870        editor.add_selection_below(&Default::default(), window, cx);
 7871    });
 7872
 7873    // test multiple selection retrieves back correctly
 7874    cx.assert_editor_state(indoc!(
 7875        r#"abcd
 7876           ef«ghˇ»
 7877           «iˇ»jkl
 7878           «mˇ»nop"#
 7879    ));
 7880
 7881    cx.update_editor(|editor, window, cx| {
 7882        editor.add_selection_below(&Default::default(), window, cx);
 7883    });
 7884
 7885    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 7886    cx.assert_editor_state(indoc!(
 7887        r#"abcd
 7888           ef«ghˇ»
 7889           ij«klˇ»
 7890           «mˇ»nop"#
 7891    ));
 7892
 7893    cx.update_editor(|editor, window, cx| {
 7894        editor.undo_selection(&Default::default(), window, cx);
 7895    });
 7896
 7897    // test undo
 7898    cx.assert_editor_state(indoc!(
 7899        r#"abcd
 7900           ef«ghˇ»
 7901           «iˇ»jkl
 7902           «mˇ»nop"#
 7903    ));
 7904
 7905    cx.update_editor(|editor, window, cx| {
 7906        editor.redo_selection(&Default::default(), window, cx);
 7907    });
 7908
 7909    // test redo
 7910    cx.assert_editor_state(indoc!(
 7911        r#"abcd
 7912           ef«ghˇ»
 7913           ij«klˇ»
 7914           «mˇ»nop"#
 7915    ));
 7916}
 7917
 7918#[gpui::test]
 7919async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 7920    init_test(cx, |_| {});
 7921    let mut cx = EditorTestContext::new(cx).await;
 7922
 7923    cx.set_state(indoc!(
 7924        r#"line onˇe
 7925           liˇne two
 7926           line three
 7927           line four"#
 7928    ));
 7929
 7930    cx.update_editor(|editor, window, cx| {
 7931        editor.add_selection_below(&Default::default(), window, cx);
 7932        editor.add_selection_below(&Default::default(), window, cx);
 7933        editor.add_selection_below(&Default::default(), window, cx);
 7934    });
 7935
 7936    // initial state with two multi cursor groups
 7937    cx.assert_editor_state(indoc!(
 7938        r#"line onˇe
 7939           liˇne twˇo
 7940           liˇne thˇree
 7941           liˇne foˇur"#
 7942    ));
 7943
 7944    // add single cursor in middle - simulate opt click
 7945    cx.update_editor(|editor, window, cx| {
 7946        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 7947        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 7948        editor.end_selection(window, cx);
 7949    });
 7950
 7951    cx.assert_editor_state(indoc!(
 7952        r#"line onˇe
 7953           liˇne twˇo
 7954           liˇneˇ thˇree
 7955           liˇne foˇur"#
 7956    ));
 7957
 7958    cx.update_editor(|editor, window, cx| {
 7959        editor.add_selection_above(&Default::default(), window, cx);
 7960    });
 7961
 7962    // test new added selection expands above and existing selection shrinks
 7963    cx.assert_editor_state(indoc!(
 7964        r#"line onˇe
 7965           liˇneˇ twˇo
 7966           liˇneˇ thˇree
 7967           line four"#
 7968    ));
 7969
 7970    cx.update_editor(|editor, window, cx| {
 7971        editor.add_selection_above(&Default::default(), window, cx);
 7972    });
 7973
 7974    // test new added selection expands above and existing selection shrinks
 7975    cx.assert_editor_state(indoc!(
 7976        r#"lineˇ onˇe
 7977           liˇneˇ twˇo
 7978           lineˇ three
 7979           line four"#
 7980    ));
 7981
 7982    // intial state with two selection groups
 7983    cx.set_state(indoc!(
 7984        r#"abcd
 7985           ef«ghˇ»
 7986           ijkl
 7987           «mˇ»nop"#
 7988    ));
 7989
 7990    cx.update_editor(|editor, window, cx| {
 7991        editor.add_selection_above(&Default::default(), window, cx);
 7992        editor.add_selection_above(&Default::default(), window, cx);
 7993    });
 7994
 7995    cx.assert_editor_state(indoc!(
 7996        r#"ab«cdˇ»
 7997           «eˇ»f«ghˇ»
 7998           «iˇ»jkl
 7999           «mˇ»nop"#
 8000    ));
 8001
 8002    // add single selection in middle - simulate opt drag
 8003    cx.update_editor(|editor, window, cx| {
 8004        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8005        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8006        editor.update_selection(
 8007            DisplayPoint::new(DisplayRow(2), 4),
 8008            0,
 8009            gpui::Point::<f32>::default(),
 8010            window,
 8011            cx,
 8012        );
 8013        editor.end_selection(window, cx);
 8014    });
 8015
 8016    cx.assert_editor_state(indoc!(
 8017        r#"ab«cdˇ»
 8018           «eˇ»f«ghˇ»
 8019           «iˇ»jk«lˇ»
 8020           «mˇ»nop"#
 8021    ));
 8022
 8023    cx.update_editor(|editor, window, cx| {
 8024        editor.add_selection_below(&Default::default(), window, cx);
 8025    });
 8026
 8027    // test new added selection expands below, others shrinks from above
 8028    cx.assert_editor_state(indoc!(
 8029        r#"abcd
 8030           ef«ghˇ»
 8031           «iˇ»jk«lˇ»
 8032           «mˇ»no«pˇ»"#
 8033    ));
 8034}
 8035
 8036#[gpui::test]
 8037async fn test_select_next(cx: &mut TestAppContext) {
 8038    init_test(cx, |_| {});
 8039
 8040    let mut cx = EditorTestContext::new(cx).await;
 8041    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8042
 8043    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8044        .unwrap();
 8045    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8046
 8047    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8048        .unwrap();
 8049    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8050
 8051    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8052    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8053
 8054    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8055    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8056
 8057    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8058        .unwrap();
 8059    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8060
 8061    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8062        .unwrap();
 8063    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8064
 8065    // Test selection direction should be preserved
 8066    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8067
 8068    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8069        .unwrap();
 8070    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8071}
 8072
 8073#[gpui::test]
 8074async fn test_select_all_matches(cx: &mut TestAppContext) {
 8075    init_test(cx, |_| {});
 8076
 8077    let mut cx = EditorTestContext::new(cx).await;
 8078
 8079    // Test caret-only selections
 8080    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8081    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8082        .unwrap();
 8083    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8084
 8085    // Test left-to-right selections
 8086    cx.set_state("abc\n«abcˇ»\nabc");
 8087    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8088        .unwrap();
 8089    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8090
 8091    // Test right-to-left selections
 8092    cx.set_state("abc\n«ˇabc»\nabc");
 8093    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8094        .unwrap();
 8095    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8096
 8097    // Test selecting whitespace with caret selection
 8098    cx.set_state("abc\nˇ   abc\nabc");
 8099    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8100        .unwrap();
 8101    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8102
 8103    // Test selecting whitespace with left-to-right selection
 8104    cx.set_state("abc\n«ˇ  »abc\nabc");
 8105    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8106        .unwrap();
 8107    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8108
 8109    // Test no matches with right-to-left selection
 8110    cx.set_state("abc\n«  ˇ»abc\nabc");
 8111    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8112        .unwrap();
 8113    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8114
 8115    // Test with a single word and clip_at_line_ends=true (#29823)
 8116    cx.set_state("aˇbc");
 8117    cx.update_editor(|e, window, cx| {
 8118        e.set_clip_at_line_ends(true, cx);
 8119        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8120        e.set_clip_at_line_ends(false, cx);
 8121    });
 8122    cx.assert_editor_state("«abcˇ»");
 8123}
 8124
 8125#[gpui::test]
 8126async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8127    init_test(cx, |_| {});
 8128
 8129    let mut cx = EditorTestContext::new(cx).await;
 8130
 8131    let large_body_1 = "\nd".repeat(200);
 8132    let large_body_2 = "\ne".repeat(200);
 8133
 8134    cx.set_state(&format!(
 8135        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8136    ));
 8137    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8138        let scroll_position = editor.scroll_position(cx);
 8139        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8140        scroll_position
 8141    });
 8142
 8143    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8144        .unwrap();
 8145    cx.assert_editor_state(&format!(
 8146        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8147    ));
 8148    let scroll_position_after_selection =
 8149        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8150    assert_eq!(
 8151        initial_scroll_position, scroll_position_after_selection,
 8152        "Scroll position should not change after selecting all matches"
 8153    );
 8154}
 8155
 8156#[gpui::test]
 8157async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8158    init_test(cx, |_| {});
 8159
 8160    let mut cx = EditorLspTestContext::new_rust(
 8161        lsp::ServerCapabilities {
 8162            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8163            ..Default::default()
 8164        },
 8165        cx,
 8166    )
 8167    .await;
 8168
 8169    cx.set_state(indoc! {"
 8170        line 1
 8171        line 2
 8172        linˇe 3
 8173        line 4
 8174        line 5
 8175    "});
 8176
 8177    // Make an edit
 8178    cx.update_editor(|editor, window, cx| {
 8179        editor.handle_input("X", window, cx);
 8180    });
 8181
 8182    // Move cursor to a different position
 8183    cx.update_editor(|editor, window, cx| {
 8184        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8185            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8186        });
 8187    });
 8188
 8189    cx.assert_editor_state(indoc! {"
 8190        line 1
 8191        line 2
 8192        linXe 3
 8193        line 4
 8194        liˇne 5
 8195    "});
 8196
 8197    cx.lsp
 8198        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8199            Ok(Some(vec![lsp::TextEdit::new(
 8200                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8201                "PREFIX ".to_string(),
 8202            )]))
 8203        });
 8204
 8205    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8206        .unwrap()
 8207        .await
 8208        .unwrap();
 8209
 8210    cx.assert_editor_state(indoc! {"
 8211        PREFIX line 1
 8212        line 2
 8213        linXe 3
 8214        line 4
 8215        liˇne 5
 8216    "});
 8217
 8218    // Undo formatting
 8219    cx.update_editor(|editor, window, cx| {
 8220        editor.undo(&Default::default(), window, cx);
 8221    });
 8222
 8223    // Verify cursor moved back to position after edit
 8224    cx.assert_editor_state(indoc! {"
 8225        line 1
 8226        line 2
 8227        linXˇe 3
 8228        line 4
 8229        line 5
 8230    "});
 8231}
 8232
 8233#[gpui::test]
 8234async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8235    init_test(cx, |_| {});
 8236
 8237    let mut cx = EditorTestContext::new(cx).await;
 8238
 8239    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8240    cx.update_editor(|editor, window, cx| {
 8241        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8242    });
 8243
 8244    cx.set_state(indoc! {"
 8245        line 1
 8246        line 2
 8247        linˇe 3
 8248        line 4
 8249        line 5
 8250        line 6
 8251        line 7
 8252        line 8
 8253        line 9
 8254        line 10
 8255    "});
 8256
 8257    let snapshot = cx.buffer_snapshot();
 8258    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8259
 8260    cx.update(|_, cx| {
 8261        provider.update(cx, |provider, _| {
 8262            provider.set_edit_prediction(Some(edit_prediction::EditPrediction {
 8263                id: None,
 8264                edits: vec![(edit_position..edit_position, "X".into())],
 8265                edit_preview: None,
 8266            }))
 8267        })
 8268    });
 8269
 8270    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8271    cx.update_editor(|editor, window, cx| {
 8272        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8273    });
 8274
 8275    cx.assert_editor_state(indoc! {"
 8276        line 1
 8277        line 2
 8278        lineXˇ 3
 8279        line 4
 8280        line 5
 8281        line 6
 8282        line 7
 8283        line 8
 8284        line 9
 8285        line 10
 8286    "});
 8287
 8288    cx.update_editor(|editor, window, cx| {
 8289        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8290            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8291        });
 8292    });
 8293
 8294    cx.assert_editor_state(indoc! {"
 8295        line 1
 8296        line 2
 8297        lineX 3
 8298        line 4
 8299        line 5
 8300        line 6
 8301        line 7
 8302        line 8
 8303        line 9
 8304        liˇne 10
 8305    "});
 8306
 8307    cx.update_editor(|editor, window, cx| {
 8308        editor.undo(&Default::default(), window, cx);
 8309    });
 8310
 8311    cx.assert_editor_state(indoc! {"
 8312        line 1
 8313        line 2
 8314        lineˇ 3
 8315        line 4
 8316        line 5
 8317        line 6
 8318        line 7
 8319        line 8
 8320        line 9
 8321        line 10
 8322    "});
 8323}
 8324
 8325#[gpui::test]
 8326async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8327    init_test(cx, |_| {});
 8328
 8329    let mut cx = EditorTestContext::new(cx).await;
 8330    cx.set_state(
 8331        r#"let foo = 2;
 8332lˇet foo = 2;
 8333let fooˇ = 2;
 8334let foo = 2;
 8335let foo = ˇ2;"#,
 8336    );
 8337
 8338    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8339        .unwrap();
 8340    cx.assert_editor_state(
 8341        r#"let foo = 2;
 8342«letˇ» foo = 2;
 8343let «fooˇ» = 2;
 8344let foo = 2;
 8345let foo = «2ˇ»;"#,
 8346    );
 8347
 8348    // noop for multiple selections with different contents
 8349    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8350        .unwrap();
 8351    cx.assert_editor_state(
 8352        r#"let foo = 2;
 8353«letˇ» foo = 2;
 8354let «fooˇ» = 2;
 8355let foo = 2;
 8356let foo = «2ˇ»;"#,
 8357    );
 8358
 8359    // Test last selection direction should be preserved
 8360    cx.set_state(
 8361        r#"let foo = 2;
 8362let foo = 2;
 8363let «fooˇ» = 2;
 8364let «ˇfoo» = 2;
 8365let foo = 2;"#,
 8366    );
 8367
 8368    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8369        .unwrap();
 8370    cx.assert_editor_state(
 8371        r#"let foo = 2;
 8372let foo = 2;
 8373let «fooˇ» = 2;
 8374let «ˇfoo» = 2;
 8375let «ˇfoo» = 2;"#,
 8376    );
 8377}
 8378
 8379#[gpui::test]
 8380async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8381    init_test(cx, |_| {});
 8382
 8383    let mut cx =
 8384        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8385
 8386    cx.assert_editor_state(indoc! {"
 8387        ˇbbb
 8388        ccc
 8389
 8390        bbb
 8391        ccc
 8392        "});
 8393    cx.dispatch_action(SelectPrevious::default());
 8394    cx.assert_editor_state(indoc! {"
 8395                «bbbˇ»
 8396                ccc
 8397
 8398                bbb
 8399                ccc
 8400                "});
 8401    cx.dispatch_action(SelectPrevious::default());
 8402    cx.assert_editor_state(indoc! {"
 8403                «bbbˇ»
 8404                ccc
 8405
 8406                «bbbˇ»
 8407                ccc
 8408                "});
 8409}
 8410
 8411#[gpui::test]
 8412async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8413    init_test(cx, |_| {});
 8414
 8415    let mut cx = EditorTestContext::new(cx).await;
 8416    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8417
 8418    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8419        .unwrap();
 8420    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8421
 8422    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8423        .unwrap();
 8424    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8425
 8426    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8427    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8428
 8429    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8430    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8431
 8432    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8433        .unwrap();
 8434    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8435
 8436    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8437        .unwrap();
 8438    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8439}
 8440
 8441#[gpui::test]
 8442async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8443    init_test(cx, |_| {});
 8444
 8445    let mut cx = EditorTestContext::new(cx).await;
 8446    cx.set_state("");
 8447
 8448    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8449        .unwrap();
 8450    cx.assert_editor_state("«aˇ»");
 8451    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8452        .unwrap();
 8453    cx.assert_editor_state("«aˇ»");
 8454}
 8455
 8456#[gpui::test]
 8457async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8458    init_test(cx, |_| {});
 8459
 8460    let mut cx = EditorTestContext::new(cx).await;
 8461    cx.set_state(
 8462        r#"let foo = 2;
 8463lˇet foo = 2;
 8464let fooˇ = 2;
 8465let foo = 2;
 8466let foo = ˇ2;"#,
 8467    );
 8468
 8469    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8470        .unwrap();
 8471    cx.assert_editor_state(
 8472        r#"let foo = 2;
 8473«letˇ» foo = 2;
 8474let «fooˇ» = 2;
 8475let foo = 2;
 8476let foo = «2ˇ»;"#,
 8477    );
 8478
 8479    // noop for multiple selections with different contents
 8480    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8481        .unwrap();
 8482    cx.assert_editor_state(
 8483        r#"let foo = 2;
 8484«letˇ» foo = 2;
 8485let «fooˇ» = 2;
 8486let foo = 2;
 8487let foo = «2ˇ»;"#,
 8488    );
 8489}
 8490
 8491#[gpui::test]
 8492async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8493    init_test(cx, |_| {});
 8494
 8495    let mut cx = EditorTestContext::new(cx).await;
 8496    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8497
 8498    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8499        .unwrap();
 8500    // selection direction is preserved
 8501    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8502
 8503    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8504        .unwrap();
 8505    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8506
 8507    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8508    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8509
 8510    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8511    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8512
 8513    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8514        .unwrap();
 8515    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8516
 8517    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8518        .unwrap();
 8519    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8520}
 8521
 8522#[gpui::test]
 8523async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8524    init_test(cx, |_| {});
 8525
 8526    let language = Arc::new(Language::new(
 8527        LanguageConfig::default(),
 8528        Some(tree_sitter_rust::LANGUAGE.into()),
 8529    ));
 8530
 8531    let text = r#"
 8532        use mod1::mod2::{mod3, mod4};
 8533
 8534        fn fn_1(param1: bool, param2: &str) {
 8535            let var1 = "text";
 8536        }
 8537    "#
 8538    .unindent();
 8539
 8540    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8541    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8542    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8543
 8544    editor
 8545        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8546        .await;
 8547
 8548    editor.update_in(cx, |editor, window, cx| {
 8549        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8550            s.select_display_ranges([
 8551                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8552                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8553                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8554            ]);
 8555        });
 8556        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8557    });
 8558    editor.update(cx, |editor, cx| {
 8559        assert_text_with_selections(
 8560            editor,
 8561            indoc! {r#"
 8562                use mod1::mod2::{mod3, «mod4ˇ»};
 8563
 8564                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8565                    let var1 = "«ˇtext»";
 8566                }
 8567            "#},
 8568            cx,
 8569        );
 8570    });
 8571
 8572    editor.update_in(cx, |editor, window, cx| {
 8573        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8574    });
 8575    editor.update(cx, |editor, cx| {
 8576        assert_text_with_selections(
 8577            editor,
 8578            indoc! {r#"
 8579                use mod1::mod2::«{mod3, mod4}ˇ»;
 8580
 8581                «ˇfn fn_1(param1: bool, param2: &str) {
 8582                    let var1 = "text";
 8583 8584            "#},
 8585            cx,
 8586        );
 8587    });
 8588
 8589    editor.update_in(cx, |editor, window, cx| {
 8590        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8591    });
 8592    assert_eq!(
 8593        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8594        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8595    );
 8596
 8597    // Trying to expand the selected syntax node one more time has no effect.
 8598    editor.update_in(cx, |editor, window, cx| {
 8599        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8600    });
 8601    assert_eq!(
 8602        editor.update(cx, |editor, cx| editor.selections.display_ranges(cx)),
 8603        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 8604    );
 8605
 8606    editor.update_in(cx, |editor, window, cx| {
 8607        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8608    });
 8609    editor.update(cx, |editor, cx| {
 8610        assert_text_with_selections(
 8611            editor,
 8612            indoc! {r#"
 8613                use mod1::mod2::«{mod3, mod4}ˇ»;
 8614
 8615                «ˇfn fn_1(param1: bool, param2: &str) {
 8616                    let var1 = "text";
 8617 8618            "#},
 8619            cx,
 8620        );
 8621    });
 8622
 8623    editor.update_in(cx, |editor, window, cx| {
 8624        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8625    });
 8626    editor.update(cx, |editor, cx| {
 8627        assert_text_with_selections(
 8628            editor,
 8629            indoc! {r#"
 8630                use mod1::mod2::{mod3, «mod4ˇ»};
 8631
 8632                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8633                    let var1 = "«ˇtext»";
 8634                }
 8635            "#},
 8636            cx,
 8637        );
 8638    });
 8639
 8640    editor.update_in(cx, |editor, window, cx| {
 8641        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8642    });
 8643    editor.update(cx, |editor, cx| {
 8644        assert_text_with_selections(
 8645            editor,
 8646            indoc! {r#"
 8647                use mod1::mod2::{mod3, moˇd4};
 8648
 8649                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8650                    let var1 = "teˇxt";
 8651                }
 8652            "#},
 8653            cx,
 8654        );
 8655    });
 8656
 8657    // Trying to shrink the selected syntax node one more time has no effect.
 8658    editor.update_in(cx, |editor, window, cx| {
 8659        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 8660    });
 8661    editor.update_in(cx, |editor, _, cx| {
 8662        assert_text_with_selections(
 8663            editor,
 8664            indoc! {r#"
 8665                use mod1::mod2::{mod3, moˇd4};
 8666
 8667                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 8668                    let var1 = "teˇxt";
 8669                }
 8670            "#},
 8671            cx,
 8672        );
 8673    });
 8674
 8675    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 8676    // a fold.
 8677    editor.update_in(cx, |editor, window, cx| {
 8678        editor.fold_creases(
 8679            vec![
 8680                Crease::simple(
 8681                    Point::new(0, 21)..Point::new(0, 24),
 8682                    FoldPlaceholder::test(),
 8683                ),
 8684                Crease::simple(
 8685                    Point::new(3, 20)..Point::new(3, 22),
 8686                    FoldPlaceholder::test(),
 8687                ),
 8688            ],
 8689            true,
 8690            window,
 8691            cx,
 8692        );
 8693        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8694    });
 8695    editor.update(cx, |editor, cx| {
 8696        assert_text_with_selections(
 8697            editor,
 8698            indoc! {r#"
 8699                use mod1::mod2::«{mod3, mod4}ˇ»;
 8700
 8701                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8702                    let var1 = "«ˇtext»";
 8703                }
 8704            "#},
 8705            cx,
 8706        );
 8707    });
 8708}
 8709
 8710#[gpui::test]
 8711async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 8712    init_test(cx, |_| {});
 8713
 8714    let language = Arc::new(Language::new(
 8715        LanguageConfig::default(),
 8716        Some(tree_sitter_rust::LANGUAGE.into()),
 8717    ));
 8718
 8719    let text = "let a = 2;";
 8720
 8721    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8722    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8723    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8724
 8725    editor
 8726        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8727        .await;
 8728
 8729    // Test case 1: Cursor at end of word
 8730    editor.update_in(cx, |editor, window, cx| {
 8731        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8732            s.select_display_ranges([
 8733                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 8734            ]);
 8735        });
 8736    });
 8737    editor.update(cx, |editor, cx| {
 8738        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 8739    });
 8740    editor.update_in(cx, |editor, window, cx| {
 8741        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8742    });
 8743    editor.update(cx, |editor, cx| {
 8744        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 8745    });
 8746    editor.update_in(cx, |editor, window, cx| {
 8747        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8748    });
 8749    editor.update(cx, |editor, cx| {
 8750        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8751    });
 8752
 8753    // Test case 2: Cursor at end of statement
 8754    editor.update_in(cx, |editor, window, cx| {
 8755        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8756            s.select_display_ranges([
 8757                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 8758            ]);
 8759        });
 8760    });
 8761    editor.update(cx, |editor, cx| {
 8762        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 8763    });
 8764    editor.update_in(cx, |editor, window, cx| {
 8765        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8766    });
 8767    editor.update(cx, |editor, cx| {
 8768        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 8769    });
 8770}
 8771
 8772#[gpui::test]
 8773async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 8774    init_test(cx, |_| {});
 8775
 8776    let language = Arc::new(Language::new(
 8777        LanguageConfig {
 8778            name: "JavaScript".into(),
 8779            ..Default::default()
 8780        },
 8781        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 8782    ));
 8783
 8784    let text = r#"
 8785        let a = {
 8786            key: "value",
 8787        };
 8788    "#
 8789    .unindent();
 8790
 8791    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8792    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8793    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8794
 8795    editor
 8796        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8797        .await;
 8798
 8799    // Test case 1: Cursor after '{'
 8800    editor.update_in(cx, |editor, window, cx| {
 8801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8802            s.select_display_ranges([
 8803                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 8804            ]);
 8805        });
 8806    });
 8807    editor.update(cx, |editor, cx| {
 8808        assert_text_with_selections(
 8809            editor,
 8810            indoc! {r#"
 8811                let a = {ˇ
 8812                    key: "value",
 8813                };
 8814            "#},
 8815            cx,
 8816        );
 8817    });
 8818    editor.update_in(cx, |editor, window, cx| {
 8819        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8820    });
 8821    editor.update(cx, |editor, cx| {
 8822        assert_text_with_selections(
 8823            editor,
 8824            indoc! {r#"
 8825                let a = «ˇ{
 8826                    key: "value",
 8827                }»;
 8828            "#},
 8829            cx,
 8830        );
 8831    });
 8832
 8833    // Test case 2: Cursor after ':'
 8834    editor.update_in(cx, |editor, window, cx| {
 8835        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8836            s.select_display_ranges([
 8837                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 8838            ]);
 8839        });
 8840    });
 8841    editor.update(cx, |editor, cx| {
 8842        assert_text_with_selections(
 8843            editor,
 8844            indoc! {r#"
 8845                let a = {
 8846                    key:ˇ "value",
 8847                };
 8848            "#},
 8849            cx,
 8850        );
 8851    });
 8852    editor.update_in(cx, |editor, window, cx| {
 8853        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8854    });
 8855    editor.update(cx, |editor, cx| {
 8856        assert_text_with_selections(
 8857            editor,
 8858            indoc! {r#"
 8859                let a = {
 8860                    «ˇkey: "value"»,
 8861                };
 8862            "#},
 8863            cx,
 8864        );
 8865    });
 8866    editor.update_in(cx, |editor, window, cx| {
 8867        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8868    });
 8869    editor.update(cx, |editor, cx| {
 8870        assert_text_with_selections(
 8871            editor,
 8872            indoc! {r#"
 8873                let a = «ˇ{
 8874                    key: "value",
 8875                }»;
 8876            "#},
 8877            cx,
 8878        );
 8879    });
 8880
 8881    // Test case 3: Cursor after ','
 8882    editor.update_in(cx, |editor, window, cx| {
 8883        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8884            s.select_display_ranges([
 8885                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 8886            ]);
 8887        });
 8888    });
 8889    editor.update(cx, |editor, cx| {
 8890        assert_text_with_selections(
 8891            editor,
 8892            indoc! {r#"
 8893                let a = {
 8894                    key: "value",ˇ
 8895                };
 8896            "#},
 8897            cx,
 8898        );
 8899    });
 8900    editor.update_in(cx, |editor, window, cx| {
 8901        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8902    });
 8903    editor.update(cx, |editor, cx| {
 8904        assert_text_with_selections(
 8905            editor,
 8906            indoc! {r#"
 8907                let a = «ˇ{
 8908                    key: "value",
 8909                }»;
 8910            "#},
 8911            cx,
 8912        );
 8913    });
 8914
 8915    // Test case 4: Cursor after ';'
 8916    editor.update_in(cx, |editor, window, cx| {
 8917        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8918            s.select_display_ranges([
 8919                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 8920            ]);
 8921        });
 8922    });
 8923    editor.update(cx, |editor, cx| {
 8924        assert_text_with_selections(
 8925            editor,
 8926            indoc! {r#"
 8927                let a = {
 8928                    key: "value",
 8929                };ˇ
 8930            "#},
 8931            cx,
 8932        );
 8933    });
 8934    editor.update_in(cx, |editor, window, cx| {
 8935        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8936    });
 8937    editor.update(cx, |editor, cx| {
 8938        assert_text_with_selections(
 8939            editor,
 8940            indoc! {r#"
 8941                «ˇlet a = {
 8942                    key: "value",
 8943                };
 8944                »"#},
 8945            cx,
 8946        );
 8947    });
 8948}
 8949
 8950#[gpui::test]
 8951async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 8952    init_test(cx, |_| {});
 8953
 8954    let language = Arc::new(Language::new(
 8955        LanguageConfig::default(),
 8956        Some(tree_sitter_rust::LANGUAGE.into()),
 8957    ));
 8958
 8959    let text = r#"
 8960        use mod1::mod2::{mod3, mod4};
 8961
 8962        fn fn_1(param1: bool, param2: &str) {
 8963            let var1 = "hello world";
 8964        }
 8965    "#
 8966    .unindent();
 8967
 8968    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8969    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8970    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8971
 8972    editor
 8973        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8974        .await;
 8975
 8976    // Test 1: Cursor on a letter of a string word
 8977    editor.update_in(cx, |editor, window, cx| {
 8978        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8979            s.select_display_ranges([
 8980                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 8981            ]);
 8982        });
 8983    });
 8984    editor.update_in(cx, |editor, window, cx| {
 8985        assert_text_with_selections(
 8986            editor,
 8987            indoc! {r#"
 8988                use mod1::mod2::{mod3, mod4};
 8989
 8990                fn fn_1(param1: bool, param2: &str) {
 8991                    let var1 = "hˇello world";
 8992                }
 8993            "#},
 8994            cx,
 8995        );
 8996        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8997        assert_text_with_selections(
 8998            editor,
 8999            indoc! {r#"
 9000                use mod1::mod2::{mod3, mod4};
 9001
 9002                fn fn_1(param1: bool, param2: &str) {
 9003                    let var1 = "«ˇhello» world";
 9004                }
 9005            "#},
 9006            cx,
 9007        );
 9008    });
 9009
 9010    // Test 2: Partial selection within a word
 9011    editor.update_in(cx, |editor, window, cx| {
 9012        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9013            s.select_display_ranges([
 9014                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9015            ]);
 9016        });
 9017    });
 9018    editor.update_in(cx, |editor, window, cx| {
 9019        assert_text_with_selections(
 9020            editor,
 9021            indoc! {r#"
 9022                use mod1::mod2::{mod3, mod4};
 9023
 9024                fn fn_1(param1: bool, param2: &str) {
 9025                    let var1 = "h«elˇ»lo world";
 9026                }
 9027            "#},
 9028            cx,
 9029        );
 9030        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9031        assert_text_with_selections(
 9032            editor,
 9033            indoc! {r#"
 9034                use mod1::mod2::{mod3, mod4};
 9035
 9036                fn fn_1(param1: bool, param2: &str) {
 9037                    let var1 = "«ˇhello» world";
 9038                }
 9039            "#},
 9040            cx,
 9041        );
 9042    });
 9043
 9044    // Test 3: Complete word already selected
 9045    editor.update_in(cx, |editor, window, cx| {
 9046        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9047            s.select_display_ranges([
 9048                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9049            ]);
 9050        });
 9051    });
 9052    editor.update_in(cx, |editor, window, cx| {
 9053        assert_text_with_selections(
 9054            editor,
 9055            indoc! {r#"
 9056                use mod1::mod2::{mod3, mod4};
 9057
 9058                fn fn_1(param1: bool, param2: &str) {
 9059                    let var1 = "«helloˇ» world";
 9060                }
 9061            "#},
 9062            cx,
 9063        );
 9064        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9065        assert_text_with_selections(
 9066            editor,
 9067            indoc! {r#"
 9068                use mod1::mod2::{mod3, mod4};
 9069
 9070                fn fn_1(param1: bool, param2: &str) {
 9071                    let var1 = "«hello worldˇ»";
 9072                }
 9073            "#},
 9074            cx,
 9075        );
 9076    });
 9077
 9078    // Test 4: Selection spanning across words
 9079    editor.update_in(cx, |editor, window, cx| {
 9080        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9081            s.select_display_ranges([
 9082                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9083            ]);
 9084        });
 9085    });
 9086    editor.update_in(cx, |editor, window, cx| {
 9087        assert_text_with_selections(
 9088            editor,
 9089            indoc! {r#"
 9090                use mod1::mod2::{mod3, mod4};
 9091
 9092                fn fn_1(param1: bool, param2: &str) {
 9093                    let var1 = "hel«lo woˇ»rld";
 9094                }
 9095            "#},
 9096            cx,
 9097        );
 9098        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9099        assert_text_with_selections(
 9100            editor,
 9101            indoc! {r#"
 9102                use mod1::mod2::{mod3, mod4};
 9103
 9104                fn fn_1(param1: bool, param2: &str) {
 9105                    let var1 = "«ˇhello world»";
 9106                }
 9107            "#},
 9108            cx,
 9109        );
 9110    });
 9111
 9112    // Test 5: Expansion beyond string
 9113    editor.update_in(cx, |editor, window, cx| {
 9114        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9115        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9116        assert_text_with_selections(
 9117            editor,
 9118            indoc! {r#"
 9119                use mod1::mod2::{mod3, mod4};
 9120
 9121                fn fn_1(param1: bool, param2: &str) {
 9122                    «ˇlet var1 = "hello world";»
 9123                }
 9124            "#},
 9125            cx,
 9126        );
 9127    });
 9128}
 9129
 9130#[gpui::test]
 9131async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9132    init_test(cx, |_| {});
 9133
 9134    let mut cx = EditorTestContext::new(cx).await;
 9135
 9136    let language = Arc::new(Language::new(
 9137        LanguageConfig::default(),
 9138        Some(tree_sitter_rust::LANGUAGE.into()),
 9139    ));
 9140
 9141    cx.update_buffer(|buffer, cx| {
 9142        buffer.set_language(Some(language), cx);
 9143    });
 9144
 9145    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9146    cx.update_editor(|editor, window, cx| {
 9147        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9148    });
 9149
 9150    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9151}
 9152
 9153#[gpui::test]
 9154async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9155    init_test(cx, |_| {});
 9156
 9157    let base_text = r#"
 9158        impl A {
 9159            // this is an uncommitted comment
 9160
 9161            fn b() {
 9162                c();
 9163            }
 9164
 9165            // this is another uncommitted comment
 9166
 9167            fn d() {
 9168                // e
 9169                // f
 9170            }
 9171        }
 9172
 9173        fn g() {
 9174            // h
 9175        }
 9176    "#
 9177    .unindent();
 9178
 9179    let text = r#"
 9180        ˇimpl A {
 9181
 9182            fn b() {
 9183                c();
 9184            }
 9185
 9186            fn d() {
 9187                // e
 9188                // f
 9189            }
 9190        }
 9191
 9192        fn g() {
 9193            // h
 9194        }
 9195    "#
 9196    .unindent();
 9197
 9198    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9199    cx.set_state(&text);
 9200    cx.set_head_text(&base_text);
 9201    cx.update_editor(|editor, window, cx| {
 9202        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9203    });
 9204
 9205    cx.assert_state_with_diff(
 9206        "
 9207        ˇimpl A {
 9208      -     // this is an uncommitted comment
 9209
 9210            fn b() {
 9211                c();
 9212            }
 9213
 9214      -     // this is another uncommitted comment
 9215      -
 9216            fn d() {
 9217                // e
 9218                // f
 9219            }
 9220        }
 9221
 9222        fn g() {
 9223            // h
 9224        }
 9225    "
 9226        .unindent(),
 9227    );
 9228
 9229    let expected_display_text = "
 9230        impl A {
 9231            // this is an uncommitted comment
 9232
 9233            fn b() {
 9234 9235            }
 9236
 9237            // this is another uncommitted comment
 9238
 9239            fn d() {
 9240 9241            }
 9242        }
 9243
 9244        fn g() {
 9245 9246        }
 9247        "
 9248    .unindent();
 9249
 9250    cx.update_editor(|editor, window, cx| {
 9251        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9252        assert_eq!(editor.display_text(cx), expected_display_text);
 9253    });
 9254}
 9255
 9256#[gpui::test]
 9257async fn test_autoindent(cx: &mut TestAppContext) {
 9258    init_test(cx, |_| {});
 9259
 9260    let language = Arc::new(
 9261        Language::new(
 9262            LanguageConfig {
 9263                brackets: BracketPairConfig {
 9264                    pairs: vec![
 9265                        BracketPair {
 9266                            start: "{".to_string(),
 9267                            end: "}".to_string(),
 9268                            close: false,
 9269                            surround: false,
 9270                            newline: true,
 9271                        },
 9272                        BracketPair {
 9273                            start: "(".to_string(),
 9274                            end: ")".to_string(),
 9275                            close: false,
 9276                            surround: false,
 9277                            newline: true,
 9278                        },
 9279                    ],
 9280                    ..Default::default()
 9281                },
 9282                ..Default::default()
 9283            },
 9284            Some(tree_sitter_rust::LANGUAGE.into()),
 9285        )
 9286        .with_indents_query(
 9287            r#"
 9288                (_ "(" ")" @end) @indent
 9289                (_ "{" "}" @end) @indent
 9290            "#,
 9291        )
 9292        .unwrap(),
 9293    );
 9294
 9295    let text = "fn a() {}";
 9296
 9297    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9298    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9299    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9300    editor
 9301        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9302        .await;
 9303
 9304    editor.update_in(cx, |editor, window, cx| {
 9305        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9306            s.select_ranges([5..5, 8..8, 9..9])
 9307        });
 9308        editor.newline(&Newline, window, cx);
 9309        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9310        assert_eq!(
 9311            editor.selections.ranges(cx),
 9312            &[
 9313                Point::new(1, 4)..Point::new(1, 4),
 9314                Point::new(3, 4)..Point::new(3, 4),
 9315                Point::new(5, 0)..Point::new(5, 0)
 9316            ]
 9317        );
 9318    });
 9319}
 9320
 9321#[gpui::test]
 9322async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9323    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9324
 9325    let language = Arc::new(
 9326        Language::new(
 9327            LanguageConfig {
 9328                brackets: BracketPairConfig {
 9329                    pairs: vec![
 9330                        BracketPair {
 9331                            start: "{".to_string(),
 9332                            end: "}".to_string(),
 9333                            close: false,
 9334                            surround: false,
 9335                            newline: true,
 9336                        },
 9337                        BracketPair {
 9338                            start: "(".to_string(),
 9339                            end: ")".to_string(),
 9340                            close: false,
 9341                            surround: false,
 9342                            newline: true,
 9343                        },
 9344                    ],
 9345                    ..Default::default()
 9346                },
 9347                ..Default::default()
 9348            },
 9349            Some(tree_sitter_rust::LANGUAGE.into()),
 9350        )
 9351        .with_indents_query(
 9352            r#"
 9353                (_ "(" ")" @end) @indent
 9354                (_ "{" "}" @end) @indent
 9355            "#,
 9356        )
 9357        .unwrap(),
 9358    );
 9359
 9360    let text = "fn a() {}";
 9361
 9362    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9363    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9364    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9365    editor
 9366        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9367        .await;
 9368
 9369    editor.update_in(cx, |editor, window, cx| {
 9370        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9371            s.select_ranges([5..5, 8..8, 9..9])
 9372        });
 9373        editor.newline(&Newline, window, cx);
 9374        assert_eq!(
 9375            editor.text(cx),
 9376            indoc!(
 9377                "
 9378                fn a(
 9379
 9380                ) {
 9381
 9382                }
 9383                "
 9384            )
 9385        );
 9386        assert_eq!(
 9387            editor.selections.ranges(cx),
 9388            &[
 9389                Point::new(1, 0)..Point::new(1, 0),
 9390                Point::new(3, 0)..Point::new(3, 0),
 9391                Point::new(5, 0)..Point::new(5, 0)
 9392            ]
 9393        );
 9394    });
 9395}
 9396
 9397#[gpui::test]
 9398async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9399    init_test(cx, |settings| {
 9400        settings.defaults.auto_indent = Some(true);
 9401        settings.languages.0.insert(
 9402            "python".into(),
 9403            LanguageSettingsContent {
 9404                auto_indent: Some(false),
 9405                ..Default::default()
 9406            },
 9407        );
 9408    });
 9409
 9410    let mut cx = EditorTestContext::new(cx).await;
 9411
 9412    let injected_language = Arc::new(
 9413        Language::new(
 9414            LanguageConfig {
 9415                brackets: BracketPairConfig {
 9416                    pairs: vec![
 9417                        BracketPair {
 9418                            start: "{".to_string(),
 9419                            end: "}".to_string(),
 9420                            close: false,
 9421                            surround: false,
 9422                            newline: true,
 9423                        },
 9424                        BracketPair {
 9425                            start: "(".to_string(),
 9426                            end: ")".to_string(),
 9427                            close: true,
 9428                            surround: false,
 9429                            newline: true,
 9430                        },
 9431                    ],
 9432                    ..Default::default()
 9433                },
 9434                name: "python".into(),
 9435                ..Default::default()
 9436            },
 9437            Some(tree_sitter_python::LANGUAGE.into()),
 9438        )
 9439        .with_indents_query(
 9440            r#"
 9441                (_ "(" ")" @end) @indent
 9442                (_ "{" "}" @end) @indent
 9443            "#,
 9444        )
 9445        .unwrap(),
 9446    );
 9447
 9448    let language = Arc::new(
 9449        Language::new(
 9450            LanguageConfig {
 9451                brackets: BracketPairConfig {
 9452                    pairs: vec![
 9453                        BracketPair {
 9454                            start: "{".to_string(),
 9455                            end: "}".to_string(),
 9456                            close: false,
 9457                            surround: false,
 9458                            newline: true,
 9459                        },
 9460                        BracketPair {
 9461                            start: "(".to_string(),
 9462                            end: ")".to_string(),
 9463                            close: true,
 9464                            surround: false,
 9465                            newline: true,
 9466                        },
 9467                    ],
 9468                    ..Default::default()
 9469                },
 9470                name: LanguageName::new("rust"),
 9471                ..Default::default()
 9472            },
 9473            Some(tree_sitter_rust::LANGUAGE.into()),
 9474        )
 9475        .with_indents_query(
 9476            r#"
 9477                (_ "(" ")" @end) @indent
 9478                (_ "{" "}" @end) @indent
 9479            "#,
 9480        )
 9481        .unwrap()
 9482        .with_injection_query(
 9483            r#"
 9484            (macro_invocation
 9485                macro: (identifier) @_macro_name
 9486                (token_tree) @injection.content
 9487                (#set! injection.language "python"))
 9488           "#,
 9489        )
 9490        .unwrap(),
 9491    );
 9492
 9493    cx.language_registry().add(injected_language);
 9494    cx.language_registry().add(language.clone());
 9495
 9496    cx.update_buffer(|buffer, cx| {
 9497        buffer.set_language(Some(language), cx);
 9498    });
 9499
 9500    cx.set_state(r#"struct A {ˇ}"#);
 9501
 9502    cx.update_editor(|editor, window, cx| {
 9503        editor.newline(&Default::default(), window, cx);
 9504    });
 9505
 9506    cx.assert_editor_state(indoc!(
 9507        "struct A {
 9508            ˇ
 9509        }"
 9510    ));
 9511
 9512    cx.set_state(r#"select_biased!(ˇ)"#);
 9513
 9514    cx.update_editor(|editor, window, cx| {
 9515        editor.newline(&Default::default(), window, cx);
 9516        editor.handle_input("def ", window, cx);
 9517        editor.handle_input("(", window, cx);
 9518        editor.newline(&Default::default(), window, cx);
 9519        editor.handle_input("a", window, cx);
 9520    });
 9521
 9522    cx.assert_editor_state(indoc!(
 9523        "select_biased!(
 9524        def (
 9525 9526        )
 9527        )"
 9528    ));
 9529}
 9530
 9531#[gpui::test]
 9532async fn test_autoindent_selections(cx: &mut TestAppContext) {
 9533    init_test(cx, |_| {});
 9534
 9535    {
 9536        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9537        cx.set_state(indoc! {"
 9538            impl A {
 9539
 9540                fn b() {}
 9541
 9542            «fn c() {
 9543
 9544            }ˇ»
 9545            }
 9546        "});
 9547
 9548        cx.update_editor(|editor, window, cx| {
 9549            editor.autoindent(&Default::default(), window, cx);
 9550        });
 9551
 9552        cx.assert_editor_state(indoc! {"
 9553            impl A {
 9554
 9555                fn b() {}
 9556
 9557                «fn c() {
 9558
 9559                }ˇ»
 9560            }
 9561        "});
 9562    }
 9563
 9564    {
 9565        let mut cx = EditorTestContext::new_multibuffer(
 9566            cx,
 9567            [indoc! { "
 9568                impl A {
 9569                «
 9570                // a
 9571                fn b(){}
 9572                »
 9573                «
 9574                    }
 9575                    fn c(){}
 9576                »
 9577            "}],
 9578        );
 9579
 9580        let buffer = cx.update_editor(|editor, _, cx| {
 9581            let buffer = editor.buffer().update(cx, |buffer, _| {
 9582                buffer.all_buffers().iter().next().unwrap().clone()
 9583            });
 9584            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 9585            buffer
 9586        });
 9587
 9588        cx.run_until_parked();
 9589        cx.update_editor(|editor, window, cx| {
 9590            editor.select_all(&Default::default(), window, cx);
 9591            editor.autoindent(&Default::default(), window, cx)
 9592        });
 9593        cx.run_until_parked();
 9594
 9595        cx.update(|_, cx| {
 9596            assert_eq!(
 9597                buffer.read(cx).text(),
 9598                indoc! { "
 9599                    impl A {
 9600
 9601                        // a
 9602                        fn b(){}
 9603
 9604
 9605                    }
 9606                    fn c(){}
 9607
 9608                " }
 9609            )
 9610        });
 9611    }
 9612}
 9613
 9614#[gpui::test]
 9615async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
 9616    init_test(cx, |_| {});
 9617
 9618    let mut cx = EditorTestContext::new(cx).await;
 9619
 9620    let language = Arc::new(Language::new(
 9621        LanguageConfig {
 9622            brackets: BracketPairConfig {
 9623                pairs: vec![
 9624                    BracketPair {
 9625                        start: "{".to_string(),
 9626                        end: "}".to_string(),
 9627                        close: true,
 9628                        surround: true,
 9629                        newline: true,
 9630                    },
 9631                    BracketPair {
 9632                        start: "(".to_string(),
 9633                        end: ")".to_string(),
 9634                        close: true,
 9635                        surround: true,
 9636                        newline: true,
 9637                    },
 9638                    BracketPair {
 9639                        start: "/*".to_string(),
 9640                        end: " */".to_string(),
 9641                        close: true,
 9642                        surround: true,
 9643                        newline: true,
 9644                    },
 9645                    BracketPair {
 9646                        start: "[".to_string(),
 9647                        end: "]".to_string(),
 9648                        close: false,
 9649                        surround: false,
 9650                        newline: true,
 9651                    },
 9652                    BracketPair {
 9653                        start: "\"".to_string(),
 9654                        end: "\"".to_string(),
 9655                        close: true,
 9656                        surround: true,
 9657                        newline: false,
 9658                    },
 9659                    BracketPair {
 9660                        start: "<".to_string(),
 9661                        end: ">".to_string(),
 9662                        close: false,
 9663                        surround: true,
 9664                        newline: true,
 9665                    },
 9666                ],
 9667                ..Default::default()
 9668            },
 9669            autoclose_before: "})]".to_string(),
 9670            ..Default::default()
 9671        },
 9672        Some(tree_sitter_rust::LANGUAGE.into()),
 9673    ));
 9674
 9675    cx.language_registry().add(language.clone());
 9676    cx.update_buffer(|buffer, cx| {
 9677        buffer.set_language(Some(language), cx);
 9678    });
 9679
 9680    cx.set_state(
 9681        &r#"
 9682            🏀ˇ
 9683            εˇ
 9684            ❤️ˇ
 9685        "#
 9686        .unindent(),
 9687    );
 9688
 9689    // autoclose multiple nested brackets at multiple cursors
 9690    cx.update_editor(|editor, window, cx| {
 9691        editor.handle_input("{", window, cx);
 9692        editor.handle_input("{", window, cx);
 9693        editor.handle_input("{", window, cx);
 9694    });
 9695    cx.assert_editor_state(
 9696        &"
 9697            🏀{{{ˇ}}}
 9698            ε{{{ˇ}}}
 9699            ❤️{{{ˇ}}}
 9700        "
 9701        .unindent(),
 9702    );
 9703
 9704    // insert a different closing bracket
 9705    cx.update_editor(|editor, window, cx| {
 9706        editor.handle_input(")", window, cx);
 9707    });
 9708    cx.assert_editor_state(
 9709        &"
 9710            🏀{{{)ˇ}}}
 9711            ε{{{)ˇ}}}
 9712            ❤️{{{)ˇ}}}
 9713        "
 9714        .unindent(),
 9715    );
 9716
 9717    // skip over the auto-closed brackets when typing a closing bracket
 9718    cx.update_editor(|editor, window, cx| {
 9719        editor.move_right(&MoveRight, window, cx);
 9720        editor.handle_input("}", window, cx);
 9721        editor.handle_input("}", window, cx);
 9722        editor.handle_input("}", window, cx);
 9723    });
 9724    cx.assert_editor_state(
 9725        &"
 9726            🏀{{{)}}}}ˇ
 9727            ε{{{)}}}}ˇ
 9728            ❤️{{{)}}}}ˇ
 9729        "
 9730        .unindent(),
 9731    );
 9732
 9733    // autoclose multi-character pairs
 9734    cx.set_state(
 9735        &"
 9736            ˇ
 9737            ˇ
 9738        "
 9739        .unindent(),
 9740    );
 9741    cx.update_editor(|editor, window, cx| {
 9742        editor.handle_input("/", window, cx);
 9743        editor.handle_input("*", window, cx);
 9744    });
 9745    cx.assert_editor_state(
 9746        &"
 9747            /*ˇ */
 9748            /*ˇ */
 9749        "
 9750        .unindent(),
 9751    );
 9752
 9753    // one cursor autocloses a multi-character pair, one cursor
 9754    // does not autoclose.
 9755    cx.set_state(
 9756        &"
 9757 9758            ˇ
 9759        "
 9760        .unindent(),
 9761    );
 9762    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
 9763    cx.assert_editor_state(
 9764        &"
 9765            /*ˇ */
 9766 9767        "
 9768        .unindent(),
 9769    );
 9770
 9771    // Don't autoclose if the next character isn't whitespace and isn't
 9772    // listed in the language's "autoclose_before" section.
 9773    cx.set_state("ˇa b");
 9774    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9775    cx.assert_editor_state("{ˇa b");
 9776
 9777    // Don't autoclose if `close` is false for the bracket pair
 9778    cx.set_state("ˇ");
 9779    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
 9780    cx.assert_editor_state("");
 9781
 9782    // Surround with brackets if text is selected
 9783    cx.set_state("«aˇ» b");
 9784    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9785    cx.assert_editor_state("{«aˇ»} b");
 9786
 9787    // Autoclose when not immediately after a word character
 9788    cx.set_state("a ˇ");
 9789    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9790    cx.assert_editor_state("a \"ˇ\"");
 9791
 9792    // Autoclose pair where the start and end characters are the same
 9793    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9794    cx.assert_editor_state("a \"\"ˇ");
 9795
 9796    // Don't autoclose when immediately after a word character
 9797    cx.set_state("");
 9798    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9799    cx.assert_editor_state("a\"ˇ");
 9800
 9801    // Do autoclose when after a non-word character
 9802    cx.set_state("");
 9803    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
 9804    cx.assert_editor_state("{\"ˇ\"");
 9805
 9806    // Non identical pairs autoclose regardless of preceding character
 9807    cx.set_state("");
 9808    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
 9809    cx.assert_editor_state("a{ˇ}");
 9810
 9811    // Don't autoclose pair if autoclose is disabled
 9812    cx.set_state("ˇ");
 9813    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9814    cx.assert_editor_state("");
 9815
 9816    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
 9817    cx.set_state("«aˇ» b");
 9818    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
 9819    cx.assert_editor_state("<«aˇ»> b");
 9820}
 9821
 9822#[gpui::test]
 9823async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
 9824    init_test(cx, |settings| {
 9825        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
 9826    });
 9827
 9828    let mut cx = EditorTestContext::new(cx).await;
 9829
 9830    let language = Arc::new(Language::new(
 9831        LanguageConfig {
 9832            brackets: BracketPairConfig {
 9833                pairs: vec![
 9834                    BracketPair {
 9835                        start: "{".to_string(),
 9836                        end: "}".to_string(),
 9837                        close: true,
 9838                        surround: true,
 9839                        newline: true,
 9840                    },
 9841                    BracketPair {
 9842                        start: "(".to_string(),
 9843                        end: ")".to_string(),
 9844                        close: true,
 9845                        surround: true,
 9846                        newline: true,
 9847                    },
 9848                    BracketPair {
 9849                        start: "[".to_string(),
 9850                        end: "]".to_string(),
 9851                        close: false,
 9852                        surround: false,
 9853                        newline: true,
 9854                    },
 9855                ],
 9856                ..Default::default()
 9857            },
 9858            autoclose_before: "})]".to_string(),
 9859            ..Default::default()
 9860        },
 9861        Some(tree_sitter_rust::LANGUAGE.into()),
 9862    ));
 9863
 9864    cx.language_registry().add(language.clone());
 9865    cx.update_buffer(|buffer, cx| {
 9866        buffer.set_language(Some(language), cx);
 9867    });
 9868
 9869    cx.set_state(
 9870        &"
 9871            ˇ
 9872            ˇ
 9873            ˇ
 9874        "
 9875        .unindent(),
 9876    );
 9877
 9878    // ensure only matching closing brackets are skipped over
 9879    cx.update_editor(|editor, window, cx| {
 9880        editor.handle_input("}", window, cx);
 9881        editor.move_left(&MoveLeft, window, cx);
 9882        editor.handle_input(")", window, cx);
 9883        editor.move_left(&MoveLeft, window, cx);
 9884    });
 9885    cx.assert_editor_state(
 9886        &"
 9887            ˇ)}
 9888            ˇ)}
 9889            ˇ)}
 9890        "
 9891        .unindent(),
 9892    );
 9893
 9894    // skip-over closing brackets at multiple cursors
 9895    cx.update_editor(|editor, window, cx| {
 9896        editor.handle_input(")", window, cx);
 9897        editor.handle_input("}", window, cx);
 9898    });
 9899    cx.assert_editor_state(
 9900        &"
 9901            )}ˇ
 9902            )}ˇ
 9903            )}ˇ
 9904        "
 9905        .unindent(),
 9906    );
 9907
 9908    // ignore non-close brackets
 9909    cx.update_editor(|editor, window, cx| {
 9910        editor.handle_input("]", window, cx);
 9911        editor.move_left(&MoveLeft, window, cx);
 9912        editor.handle_input("]", window, cx);
 9913    });
 9914    cx.assert_editor_state(
 9915        &"
 9916            )}]ˇ]
 9917            )}]ˇ]
 9918            )}]ˇ]
 9919        "
 9920        .unindent(),
 9921    );
 9922}
 9923
 9924#[gpui::test]
 9925async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
 9926    init_test(cx, |_| {});
 9927
 9928    let mut cx = EditorTestContext::new(cx).await;
 9929
 9930    let html_language = Arc::new(
 9931        Language::new(
 9932            LanguageConfig {
 9933                name: "HTML".into(),
 9934                brackets: BracketPairConfig {
 9935                    pairs: vec![
 9936                        BracketPair {
 9937                            start: "<".into(),
 9938                            end: ">".into(),
 9939                            close: true,
 9940                            ..Default::default()
 9941                        },
 9942                        BracketPair {
 9943                            start: "{".into(),
 9944                            end: "}".into(),
 9945                            close: true,
 9946                            ..Default::default()
 9947                        },
 9948                        BracketPair {
 9949                            start: "(".into(),
 9950                            end: ")".into(),
 9951                            close: true,
 9952                            ..Default::default()
 9953                        },
 9954                    ],
 9955                    ..Default::default()
 9956                },
 9957                autoclose_before: "})]>".into(),
 9958                ..Default::default()
 9959            },
 9960            Some(tree_sitter_html::LANGUAGE.into()),
 9961        )
 9962        .with_injection_query(
 9963            r#"
 9964            (script_element
 9965                (raw_text) @injection.content
 9966                (#set! injection.language "javascript"))
 9967            "#,
 9968        )
 9969        .unwrap(),
 9970    );
 9971
 9972    let javascript_language = Arc::new(Language::new(
 9973        LanguageConfig {
 9974            name: "JavaScript".into(),
 9975            brackets: BracketPairConfig {
 9976                pairs: vec![
 9977                    BracketPair {
 9978                        start: "/*".into(),
 9979                        end: " */".into(),
 9980                        close: true,
 9981                        ..Default::default()
 9982                    },
 9983                    BracketPair {
 9984                        start: "{".into(),
 9985                        end: "}".into(),
 9986                        close: true,
 9987                        ..Default::default()
 9988                    },
 9989                    BracketPair {
 9990                        start: "(".into(),
 9991                        end: ")".into(),
 9992                        close: true,
 9993                        ..Default::default()
 9994                    },
 9995                ],
 9996                ..Default::default()
 9997            },
 9998            autoclose_before: "})]>".into(),
 9999            ..Default::default()
10000        },
10001        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10002    ));
10003
10004    cx.language_registry().add(html_language.clone());
10005    cx.language_registry().add(javascript_language);
10006    cx.executor().run_until_parked();
10007
10008    cx.update_buffer(|buffer, cx| {
10009        buffer.set_language(Some(html_language), cx);
10010    });
10011
10012    cx.set_state(
10013        &r#"
10014            <body>ˇ
10015                <script>
10016                    var x = 1;ˇ
10017                </script>
10018            </body>ˇ
10019        "#
10020        .unindent(),
10021    );
10022
10023    // Precondition: different languages are active at different locations.
10024    cx.update_editor(|editor, window, cx| {
10025        let snapshot = editor.snapshot(window, cx);
10026        let cursors = editor.selections.ranges::<usize>(cx);
10027        let languages = cursors
10028            .iter()
10029            .map(|c| snapshot.language_at(c.start).unwrap().name())
10030            .collect::<Vec<_>>();
10031        assert_eq!(
10032            languages,
10033            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10034        );
10035    });
10036
10037    // Angle brackets autoclose in HTML, but not JavaScript.
10038    cx.update_editor(|editor, window, cx| {
10039        editor.handle_input("<", window, cx);
10040        editor.handle_input("a", window, cx);
10041    });
10042    cx.assert_editor_state(
10043        &r#"
10044            <body><aˇ>
10045                <script>
10046                    var x = 1;<aˇ
10047                </script>
10048            </body><aˇ>
10049        "#
10050        .unindent(),
10051    );
10052
10053    // Curly braces and parens autoclose in both HTML and JavaScript.
10054    cx.update_editor(|editor, window, cx| {
10055        editor.handle_input(" b=", window, cx);
10056        editor.handle_input("{", window, cx);
10057        editor.handle_input("c", window, cx);
10058        editor.handle_input("(", window, cx);
10059    });
10060    cx.assert_editor_state(
10061        &r#"
10062            <body><a b={c(ˇ)}>
10063                <script>
10064                    var x = 1;<a b={c(ˇ)}
10065                </script>
10066            </body><a b={c(ˇ)}>
10067        "#
10068        .unindent(),
10069    );
10070
10071    // Brackets that were already autoclosed are skipped.
10072    cx.update_editor(|editor, window, cx| {
10073        editor.handle_input(")", window, cx);
10074        editor.handle_input("d", window, cx);
10075        editor.handle_input("}", window, cx);
10076    });
10077    cx.assert_editor_state(
10078        &r#"
10079            <body><a b={c()d}ˇ>
10080                <script>
10081                    var x = 1;<a b={c()d}ˇ
10082                </script>
10083            </body><a b={c()d}ˇ>
10084        "#
10085        .unindent(),
10086    );
10087    cx.update_editor(|editor, window, cx| {
10088        editor.handle_input(">", window, cx);
10089    });
10090    cx.assert_editor_state(
10091        &r#"
10092            <body><a b={c()d}>ˇ
10093                <script>
10094                    var x = 1;<a b={c()d}>ˇ
10095                </script>
10096            </body><a b={c()d}>ˇ
10097        "#
10098        .unindent(),
10099    );
10100
10101    // Reset
10102    cx.set_state(
10103        &r#"
10104            <body>ˇ
10105                <script>
10106                    var x = 1;ˇ
10107                </script>
10108            </body>ˇ
10109        "#
10110        .unindent(),
10111    );
10112
10113    cx.update_editor(|editor, window, cx| {
10114        editor.handle_input("<", window, cx);
10115    });
10116    cx.assert_editor_state(
10117        &r#"
10118            <body><ˇ>
10119                <script>
10120                    var x = 1;<ˇ
10121                </script>
10122            </body><ˇ>
10123        "#
10124        .unindent(),
10125    );
10126
10127    // When backspacing, the closing angle brackets are removed.
10128    cx.update_editor(|editor, window, cx| {
10129        editor.backspace(&Backspace, window, cx);
10130    });
10131    cx.assert_editor_state(
10132        &r#"
10133            <body>ˇ
10134                <script>
10135                    var x = 1;ˇ
10136                </script>
10137            </body>ˇ
10138        "#
10139        .unindent(),
10140    );
10141
10142    // Block comments autoclose in JavaScript, but not HTML.
10143    cx.update_editor(|editor, window, cx| {
10144        editor.handle_input("/", window, cx);
10145        editor.handle_input("*", window, cx);
10146    });
10147    cx.assert_editor_state(
10148        &r#"
10149            <body>/*ˇ
10150                <script>
10151                    var x = 1;/*ˇ */
10152                </script>
10153            </body>/*ˇ
10154        "#
10155        .unindent(),
10156    );
10157}
10158
10159#[gpui::test]
10160async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10161    init_test(cx, |_| {});
10162
10163    let mut cx = EditorTestContext::new(cx).await;
10164
10165    let rust_language = Arc::new(
10166        Language::new(
10167            LanguageConfig {
10168                name: "Rust".into(),
10169                brackets: serde_json::from_value(json!([
10170                    { "start": "{", "end": "}", "close": true, "newline": true },
10171                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10172                ]))
10173                .unwrap(),
10174                autoclose_before: "})]>".into(),
10175                ..Default::default()
10176            },
10177            Some(tree_sitter_rust::LANGUAGE.into()),
10178        )
10179        .with_override_query("(string_literal) @string")
10180        .unwrap(),
10181    );
10182
10183    cx.language_registry().add(rust_language.clone());
10184    cx.update_buffer(|buffer, cx| {
10185        buffer.set_language(Some(rust_language), cx);
10186    });
10187
10188    cx.set_state(
10189        &r#"
10190            let x = ˇ
10191        "#
10192        .unindent(),
10193    );
10194
10195    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10196    cx.update_editor(|editor, window, cx| {
10197        editor.handle_input("\"", window, cx);
10198    });
10199    cx.assert_editor_state(
10200        &r#"
10201            let x = "ˇ"
10202        "#
10203        .unindent(),
10204    );
10205
10206    // Inserting another quotation mark. The cursor moves across the existing
10207    // automatically-inserted quotation mark.
10208    cx.update_editor(|editor, window, cx| {
10209        editor.handle_input("\"", window, cx);
10210    });
10211    cx.assert_editor_state(
10212        &r#"
10213            let x = ""ˇ
10214        "#
10215        .unindent(),
10216    );
10217
10218    // Reset
10219    cx.set_state(
10220        &r#"
10221            let x = ˇ
10222        "#
10223        .unindent(),
10224    );
10225
10226    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10227    cx.update_editor(|editor, window, cx| {
10228        editor.handle_input("\"", window, cx);
10229        editor.handle_input(" ", window, cx);
10230        editor.move_left(&Default::default(), window, cx);
10231        editor.handle_input("\\", window, cx);
10232        editor.handle_input("\"", window, cx);
10233    });
10234    cx.assert_editor_state(
10235        &r#"
10236            let x = "\"ˇ "
10237        "#
10238        .unindent(),
10239    );
10240
10241    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10242    // mark. Nothing is inserted.
10243    cx.update_editor(|editor, window, cx| {
10244        editor.move_right(&Default::default(), window, cx);
10245        editor.handle_input("\"", window, cx);
10246    });
10247    cx.assert_editor_state(
10248        &r#"
10249            let x = "\" "ˇ
10250        "#
10251        .unindent(),
10252    );
10253}
10254
10255#[gpui::test]
10256async fn test_surround_with_pair(cx: &mut TestAppContext) {
10257    init_test(cx, |_| {});
10258
10259    let language = Arc::new(Language::new(
10260        LanguageConfig {
10261            brackets: BracketPairConfig {
10262                pairs: vec![
10263                    BracketPair {
10264                        start: "{".to_string(),
10265                        end: "}".to_string(),
10266                        close: true,
10267                        surround: true,
10268                        newline: true,
10269                    },
10270                    BracketPair {
10271                        start: "/* ".to_string(),
10272                        end: "*/".to_string(),
10273                        close: true,
10274                        surround: true,
10275                        ..Default::default()
10276                    },
10277                ],
10278                ..Default::default()
10279            },
10280            ..Default::default()
10281        },
10282        Some(tree_sitter_rust::LANGUAGE.into()),
10283    ));
10284
10285    let text = r#"
10286        a
10287        b
10288        c
10289    "#
10290    .unindent();
10291
10292    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10293    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10294    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10295    editor
10296        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10297        .await;
10298
10299    editor.update_in(cx, |editor, window, cx| {
10300        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10301            s.select_display_ranges([
10302                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10303                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10304                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10305            ])
10306        });
10307
10308        editor.handle_input("{", window, cx);
10309        editor.handle_input("{", window, cx);
10310        editor.handle_input("{", window, cx);
10311        assert_eq!(
10312            editor.text(cx),
10313            "
10314                {{{a}}}
10315                {{{b}}}
10316                {{{c}}}
10317            "
10318            .unindent()
10319        );
10320        assert_eq!(
10321            editor.selections.display_ranges(cx),
10322            [
10323                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10324                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10325                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10326            ]
10327        );
10328
10329        editor.undo(&Undo, window, cx);
10330        editor.undo(&Undo, window, cx);
10331        editor.undo(&Undo, window, cx);
10332        assert_eq!(
10333            editor.text(cx),
10334            "
10335                a
10336                b
10337                c
10338            "
10339            .unindent()
10340        );
10341        assert_eq!(
10342            editor.selections.display_ranges(cx),
10343            [
10344                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10345                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10346                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10347            ]
10348        );
10349
10350        // Ensure inserting the first character of a multi-byte bracket pair
10351        // doesn't surround the selections with the bracket.
10352        editor.handle_input("/", window, cx);
10353        assert_eq!(
10354            editor.text(cx),
10355            "
10356                /
10357                /
10358                /
10359            "
10360            .unindent()
10361        );
10362        assert_eq!(
10363            editor.selections.display_ranges(cx),
10364            [
10365                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10366                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10367                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10368            ]
10369        );
10370
10371        editor.undo(&Undo, window, cx);
10372        assert_eq!(
10373            editor.text(cx),
10374            "
10375                a
10376                b
10377                c
10378            "
10379            .unindent()
10380        );
10381        assert_eq!(
10382            editor.selections.display_ranges(cx),
10383            [
10384                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10385                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10386                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10387            ]
10388        );
10389
10390        // Ensure inserting the last character of a multi-byte bracket pair
10391        // doesn't surround the selections with the bracket.
10392        editor.handle_input("*", window, cx);
10393        assert_eq!(
10394            editor.text(cx),
10395            "
10396                *
10397                *
10398                *
10399            "
10400            .unindent()
10401        );
10402        assert_eq!(
10403            editor.selections.display_ranges(cx),
10404            [
10405                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10406                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10407                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10408            ]
10409        );
10410    });
10411}
10412
10413#[gpui::test]
10414async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10415    init_test(cx, |_| {});
10416
10417    let language = Arc::new(Language::new(
10418        LanguageConfig {
10419            brackets: BracketPairConfig {
10420                pairs: vec![BracketPair {
10421                    start: "{".to_string(),
10422                    end: "}".to_string(),
10423                    close: true,
10424                    surround: true,
10425                    newline: true,
10426                }],
10427                ..Default::default()
10428            },
10429            autoclose_before: "}".to_string(),
10430            ..Default::default()
10431        },
10432        Some(tree_sitter_rust::LANGUAGE.into()),
10433    ));
10434
10435    let text = r#"
10436        a
10437        b
10438        c
10439    "#
10440    .unindent();
10441
10442    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10443    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10444    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10445    editor
10446        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10447        .await;
10448
10449    editor.update_in(cx, |editor, window, cx| {
10450        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10451            s.select_ranges([
10452                Point::new(0, 1)..Point::new(0, 1),
10453                Point::new(1, 1)..Point::new(1, 1),
10454                Point::new(2, 1)..Point::new(2, 1),
10455            ])
10456        });
10457
10458        editor.handle_input("{", window, cx);
10459        editor.handle_input("{", window, cx);
10460        editor.handle_input("_", window, cx);
10461        assert_eq!(
10462            editor.text(cx),
10463            "
10464                a{{_}}
10465                b{{_}}
10466                c{{_}}
10467            "
10468            .unindent()
10469        );
10470        assert_eq!(
10471            editor.selections.ranges::<Point>(cx),
10472            [
10473                Point::new(0, 4)..Point::new(0, 4),
10474                Point::new(1, 4)..Point::new(1, 4),
10475                Point::new(2, 4)..Point::new(2, 4)
10476            ]
10477        );
10478
10479        editor.backspace(&Default::default(), window, cx);
10480        editor.backspace(&Default::default(), window, cx);
10481        assert_eq!(
10482            editor.text(cx),
10483            "
10484                a{}
10485                b{}
10486                c{}
10487            "
10488            .unindent()
10489        );
10490        assert_eq!(
10491            editor.selections.ranges::<Point>(cx),
10492            [
10493                Point::new(0, 2)..Point::new(0, 2),
10494                Point::new(1, 2)..Point::new(1, 2),
10495                Point::new(2, 2)..Point::new(2, 2)
10496            ]
10497        );
10498
10499        editor.delete_to_previous_word_start(&Default::default(), window, cx);
10500        assert_eq!(
10501            editor.text(cx),
10502            "
10503                a
10504                b
10505                c
10506            "
10507            .unindent()
10508        );
10509        assert_eq!(
10510            editor.selections.ranges::<Point>(cx),
10511            [
10512                Point::new(0, 1)..Point::new(0, 1),
10513                Point::new(1, 1)..Point::new(1, 1),
10514                Point::new(2, 1)..Point::new(2, 1)
10515            ]
10516        );
10517    });
10518}
10519
10520#[gpui::test]
10521async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
10522    init_test(cx, |settings| {
10523        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10524    });
10525
10526    let mut cx = EditorTestContext::new(cx).await;
10527
10528    let language = Arc::new(Language::new(
10529        LanguageConfig {
10530            brackets: BracketPairConfig {
10531                pairs: vec![
10532                    BracketPair {
10533                        start: "{".to_string(),
10534                        end: "}".to_string(),
10535                        close: true,
10536                        surround: true,
10537                        newline: true,
10538                    },
10539                    BracketPair {
10540                        start: "(".to_string(),
10541                        end: ")".to_string(),
10542                        close: true,
10543                        surround: true,
10544                        newline: true,
10545                    },
10546                    BracketPair {
10547                        start: "[".to_string(),
10548                        end: "]".to_string(),
10549                        close: false,
10550                        surround: true,
10551                        newline: true,
10552                    },
10553                ],
10554                ..Default::default()
10555            },
10556            autoclose_before: "})]".to_string(),
10557            ..Default::default()
10558        },
10559        Some(tree_sitter_rust::LANGUAGE.into()),
10560    ));
10561
10562    cx.language_registry().add(language.clone());
10563    cx.update_buffer(|buffer, cx| {
10564        buffer.set_language(Some(language), cx);
10565    });
10566
10567    cx.set_state(
10568        &"
10569            {(ˇ)}
10570            [[ˇ]]
10571            {(ˇ)}
10572        "
10573        .unindent(),
10574    );
10575
10576    cx.update_editor(|editor, window, cx| {
10577        editor.backspace(&Default::default(), window, cx);
10578        editor.backspace(&Default::default(), window, cx);
10579    });
10580
10581    cx.assert_editor_state(
10582        &"
10583            ˇ
10584            ˇ]]
10585            ˇ
10586        "
10587        .unindent(),
10588    );
10589
10590    cx.update_editor(|editor, window, cx| {
10591        editor.handle_input("{", window, cx);
10592        editor.handle_input("{", window, cx);
10593        editor.move_right(&MoveRight, window, cx);
10594        editor.move_right(&MoveRight, window, cx);
10595        editor.move_left(&MoveLeft, window, cx);
10596        editor.move_left(&MoveLeft, window, cx);
10597        editor.backspace(&Default::default(), window, cx);
10598    });
10599
10600    cx.assert_editor_state(
10601        &"
10602            {ˇ}
10603            {ˇ}]]
10604            {ˇ}
10605        "
10606        .unindent(),
10607    );
10608
10609    cx.update_editor(|editor, window, cx| {
10610        editor.backspace(&Default::default(), window, cx);
10611    });
10612
10613    cx.assert_editor_state(
10614        &"
10615            ˇ
10616            ˇ]]
10617            ˇ
10618        "
10619        .unindent(),
10620    );
10621}
10622
10623#[gpui::test]
10624async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
10625    init_test(cx, |_| {});
10626
10627    let language = Arc::new(Language::new(
10628        LanguageConfig::default(),
10629        Some(tree_sitter_rust::LANGUAGE.into()),
10630    ));
10631
10632    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
10633    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10634    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10635    editor
10636        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10637        .await;
10638
10639    editor.update_in(cx, |editor, window, cx| {
10640        editor.set_auto_replace_emoji_shortcode(true);
10641
10642        editor.handle_input("Hello ", window, cx);
10643        editor.handle_input(":wave", window, cx);
10644        assert_eq!(editor.text(cx), "Hello :wave".unindent());
10645
10646        editor.handle_input(":", window, cx);
10647        assert_eq!(editor.text(cx), "Hello 👋".unindent());
10648
10649        editor.handle_input(" :smile", window, cx);
10650        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
10651
10652        editor.handle_input(":", window, cx);
10653        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
10654
10655        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
10656        editor.handle_input(":wave", window, cx);
10657        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
10658
10659        editor.handle_input(":", window, cx);
10660        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
10661
10662        editor.handle_input(":1", window, cx);
10663        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
10664
10665        editor.handle_input(":", window, cx);
10666        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
10667
10668        // Ensure shortcode does not get replaced when it is part of a word
10669        editor.handle_input(" Test:wave", window, cx);
10670        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
10671
10672        editor.handle_input(":", window, cx);
10673        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
10674
10675        editor.set_auto_replace_emoji_shortcode(false);
10676
10677        // Ensure shortcode does not get replaced when auto replace is off
10678        editor.handle_input(" :wave", window, cx);
10679        assert_eq!(
10680            editor.text(cx),
10681            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
10682        );
10683
10684        editor.handle_input(":", window, cx);
10685        assert_eq!(
10686            editor.text(cx),
10687            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
10688        );
10689    });
10690}
10691
10692#[gpui::test]
10693async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
10694    init_test(cx, |_| {});
10695
10696    let (text, insertion_ranges) = marked_text_ranges(
10697        indoc! {"
10698            ˇ
10699        "},
10700        false,
10701    );
10702
10703    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
10704    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10705
10706    _ = editor.update_in(cx, |editor, window, cx| {
10707        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
10708
10709        editor
10710            .insert_snippet(&insertion_ranges, snippet, window, cx)
10711            .unwrap();
10712
10713        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
10714            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
10715            assert_eq!(editor.text(cx), expected_text);
10716            assert_eq!(editor.selections.ranges::<usize>(cx), selection_ranges);
10717        }
10718
10719        assert(
10720            editor,
10721            cx,
10722            indoc! {"
10723            type «» =•
10724            "},
10725        );
10726
10727        assert!(editor.context_menu_visible(), "There should be a matches");
10728    });
10729}
10730
10731#[gpui::test]
10732async fn test_snippets(cx: &mut TestAppContext) {
10733    init_test(cx, |_| {});
10734
10735    let mut cx = EditorTestContext::new(cx).await;
10736
10737    cx.set_state(indoc! {"
10738        a.ˇ b
10739        a.ˇ b
10740        a.ˇ b
10741    "});
10742
10743    cx.update_editor(|editor, window, cx| {
10744        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
10745        let insertion_ranges = editor
10746            .selections
10747            .all(cx)
10748            .iter()
10749            .map(|s| s.range())
10750            .collect::<Vec<_>>();
10751        editor
10752            .insert_snippet(&insertion_ranges, snippet, window, cx)
10753            .unwrap();
10754    });
10755
10756    cx.assert_editor_state(indoc! {"
10757        a.f(«oneˇ», two, «threeˇ») b
10758        a.f(«oneˇ», two, «threeˇ») b
10759        a.f(«oneˇ», two, «threeˇ») b
10760    "});
10761
10762    // Can't move earlier than the first tab stop
10763    cx.update_editor(|editor, window, cx| {
10764        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10765    });
10766    cx.assert_editor_state(indoc! {"
10767        a.f(«oneˇ», two, «threeˇ») b
10768        a.f(«oneˇ», two, «threeˇ») b
10769        a.f(«oneˇ», two, «threeˇ») b
10770    "});
10771
10772    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10773    cx.assert_editor_state(indoc! {"
10774        a.f(one, «twoˇ», three) b
10775        a.f(one, «twoˇ», three) b
10776        a.f(one, «twoˇ», three) b
10777    "});
10778
10779    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
10780    cx.assert_editor_state(indoc! {"
10781        a.f(«oneˇ», two, «threeˇ») b
10782        a.f(«oneˇ», two, «threeˇ») b
10783        a.f(«oneˇ», two, «threeˇ») b
10784    "});
10785
10786    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10787    cx.assert_editor_state(indoc! {"
10788        a.f(one, «twoˇ», three) b
10789        a.f(one, «twoˇ», three) b
10790        a.f(one, «twoˇ», three) b
10791    "});
10792    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10793    cx.assert_editor_state(indoc! {"
10794        a.f(one, two, three)ˇ b
10795        a.f(one, two, three)ˇ b
10796        a.f(one, two, three)ˇ b
10797    "});
10798
10799    // As soon as the last tab stop is reached, snippet state is gone
10800    cx.update_editor(|editor, window, cx| {
10801        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
10802    });
10803    cx.assert_editor_state(indoc! {"
10804        a.f(one, two, three)ˇ b
10805        a.f(one, two, three)ˇ b
10806        a.f(one, two, three)ˇ b
10807    "});
10808}
10809
10810#[gpui::test]
10811async fn test_snippet_indentation(cx: &mut TestAppContext) {
10812    init_test(cx, |_| {});
10813
10814    let mut cx = EditorTestContext::new(cx).await;
10815
10816    cx.update_editor(|editor, window, cx| {
10817        let snippet = Snippet::parse(indoc! {"
10818            /*
10819             * Multiline comment with leading indentation
10820             *
10821             * $1
10822             */
10823            $0"})
10824        .unwrap();
10825        let insertion_ranges = editor
10826            .selections
10827            .all(cx)
10828            .iter()
10829            .map(|s| s.range())
10830            .collect::<Vec<_>>();
10831        editor
10832            .insert_snippet(&insertion_ranges, snippet, window, cx)
10833            .unwrap();
10834    });
10835
10836    cx.assert_editor_state(indoc! {"
10837        /*
10838         * Multiline comment with leading indentation
10839         *
10840         * ˇ
10841         */
10842    "});
10843
10844    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
10845    cx.assert_editor_state(indoc! {"
10846        /*
10847         * Multiline comment with leading indentation
10848         *
10849         *•
10850         */
10851        ˇ"});
10852}
10853
10854#[gpui::test]
10855async fn test_document_format_during_save(cx: &mut TestAppContext) {
10856    init_test(cx, |_| {});
10857
10858    let fs = FakeFs::new(cx.executor());
10859    fs.insert_file(path!("/file.rs"), Default::default()).await;
10860
10861    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
10862
10863    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
10864    language_registry.add(rust_lang());
10865    let mut fake_servers = language_registry.register_fake_lsp(
10866        "Rust",
10867        FakeLspAdapter {
10868            capabilities: lsp::ServerCapabilities {
10869                document_formatting_provider: Some(lsp::OneOf::Left(true)),
10870                ..Default::default()
10871            },
10872            ..Default::default()
10873        },
10874    );
10875
10876    let buffer = project
10877        .update(cx, |project, cx| {
10878            project.open_local_buffer(path!("/file.rs"), cx)
10879        })
10880        .await
10881        .unwrap();
10882
10883    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10884    let (editor, cx) = cx.add_window_view(|window, cx| {
10885        build_editor_with_project(project.clone(), buffer, window, cx)
10886    });
10887    editor.update_in(cx, |editor, window, cx| {
10888        editor.set_text("one\ntwo\nthree\n", window, cx)
10889    });
10890    assert!(cx.read(|cx| editor.is_dirty(cx)));
10891
10892    cx.executor().start_waiting();
10893    let fake_server = fake_servers.next().await.unwrap();
10894
10895    {
10896        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10897            move |params, _| async move {
10898                assert_eq!(
10899                    params.text_document.uri,
10900                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10901                );
10902                assert_eq!(params.options.tab_size, 4);
10903                Ok(Some(vec![lsp::TextEdit::new(
10904                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
10905                    ", ".to_string(),
10906                )]))
10907            },
10908        );
10909        let save = editor
10910            .update_in(cx, |editor, window, cx| {
10911                editor.save(
10912                    SaveOptions {
10913                        format: true,
10914                        autosave: false,
10915                    },
10916                    project.clone(),
10917                    window,
10918                    cx,
10919                )
10920            })
10921            .unwrap();
10922        cx.executor().start_waiting();
10923        save.await;
10924
10925        assert_eq!(
10926            editor.update(cx, |editor, cx| editor.text(cx)),
10927            "one, two\nthree\n"
10928        );
10929        assert!(!cx.read(|cx| editor.is_dirty(cx)));
10930    }
10931
10932    {
10933        editor.update_in(cx, |editor, window, cx| {
10934            editor.set_text("one\ntwo\nthree\n", window, cx)
10935        });
10936        assert!(cx.read(|cx| editor.is_dirty(cx)));
10937
10938        // Ensure we can still save even if formatting hangs.
10939        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
10940            move |params, _| async move {
10941                assert_eq!(
10942                    params.text_document.uri,
10943                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10944                );
10945                futures::future::pending::<()>().await;
10946                unreachable!()
10947            },
10948        );
10949        let save = editor
10950            .update_in(cx, |editor, window, cx| {
10951                editor.save(
10952                    SaveOptions {
10953                        format: true,
10954                        autosave: false,
10955                    },
10956                    project.clone(),
10957                    window,
10958                    cx,
10959                )
10960            })
10961            .unwrap();
10962        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
10963        cx.executor().start_waiting();
10964        save.await;
10965        assert_eq!(
10966            editor.update(cx, |editor, cx| editor.text(cx)),
10967            "one\ntwo\nthree\n"
10968        );
10969    }
10970
10971    // Set rust language override and assert overridden tabsize is sent to language server
10972    update_test_language_settings(cx, |settings| {
10973        settings.languages.0.insert(
10974            "Rust".into(),
10975            LanguageSettingsContent {
10976                tab_size: NonZeroU32::new(8),
10977                ..Default::default()
10978            },
10979        );
10980    });
10981
10982    {
10983        editor.update_in(cx, |editor, window, cx| {
10984            editor.set_text("somehting_new\n", window, cx)
10985        });
10986        assert!(cx.read(|cx| editor.is_dirty(cx)));
10987        let _formatting_request_signal = fake_server
10988            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
10989                assert_eq!(
10990                    params.text_document.uri,
10991                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
10992                );
10993                assert_eq!(params.options.tab_size, 8);
10994                Ok(Some(vec![]))
10995            });
10996        let save = editor
10997            .update_in(cx, |editor, window, cx| {
10998                editor.save(
10999                    SaveOptions {
11000                        format: true,
11001                        autosave: false,
11002                    },
11003                    project.clone(),
11004                    window,
11005                    cx,
11006                )
11007            })
11008            .unwrap();
11009        cx.executor().start_waiting();
11010        save.await;
11011    }
11012}
11013
11014#[gpui::test]
11015async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11016    init_test(cx, |settings| {
11017        settings.defaults.ensure_final_newline_on_save = Some(false);
11018    });
11019
11020    let fs = FakeFs::new(cx.executor());
11021    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11022
11023    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11024
11025    let buffer = project
11026        .update(cx, |project, cx| {
11027            project.open_local_buffer(path!("/file.txt"), cx)
11028        })
11029        .await
11030        .unwrap();
11031
11032    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11033    let (editor, cx) = cx.add_window_view(|window, cx| {
11034        build_editor_with_project(project.clone(), buffer, window, cx)
11035    });
11036    editor.update_in(cx, |editor, window, cx| {
11037        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11038            s.select_ranges([0..0])
11039        });
11040    });
11041    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11042
11043    editor.update_in(cx, |editor, window, cx| {
11044        editor.handle_input("\n", window, cx)
11045    });
11046    cx.run_until_parked();
11047    save(&editor, &project, cx).await;
11048    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11049
11050    editor.update_in(cx, |editor, window, cx| {
11051        editor.undo(&Default::default(), window, cx);
11052    });
11053    save(&editor, &project, cx).await;
11054    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11055
11056    editor.update_in(cx, |editor, window, cx| {
11057        editor.redo(&Default::default(), window, cx);
11058    });
11059    cx.run_until_parked();
11060    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11061
11062    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11063        let save = editor
11064            .update_in(cx, |editor, window, cx| {
11065                editor.save(
11066                    SaveOptions {
11067                        format: true,
11068                        autosave: false,
11069                    },
11070                    project.clone(),
11071                    window,
11072                    cx,
11073                )
11074            })
11075            .unwrap();
11076        cx.executor().start_waiting();
11077        save.await;
11078        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11079    }
11080}
11081
11082#[gpui::test]
11083async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11084    init_test(cx, |_| {});
11085
11086    let cols = 4;
11087    let rows = 10;
11088    let sample_text_1 = sample_text(rows, cols, 'a');
11089    assert_eq!(
11090        sample_text_1,
11091        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11092    );
11093    let sample_text_2 = sample_text(rows, cols, 'l');
11094    assert_eq!(
11095        sample_text_2,
11096        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11097    );
11098    let sample_text_3 = sample_text(rows, cols, 'v');
11099    assert_eq!(
11100        sample_text_3,
11101        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11102    );
11103
11104    let fs = FakeFs::new(cx.executor());
11105    fs.insert_tree(
11106        path!("/a"),
11107        json!({
11108            "main.rs": sample_text_1,
11109            "other.rs": sample_text_2,
11110            "lib.rs": sample_text_3,
11111        }),
11112    )
11113    .await;
11114
11115    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11116    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11117    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11118
11119    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11120    language_registry.add(rust_lang());
11121    let mut fake_servers = language_registry.register_fake_lsp(
11122        "Rust",
11123        FakeLspAdapter {
11124            capabilities: lsp::ServerCapabilities {
11125                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11126                ..Default::default()
11127            },
11128            ..Default::default()
11129        },
11130    );
11131
11132    let worktree = project.update(cx, |project, cx| {
11133        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11134        assert_eq!(worktrees.len(), 1);
11135        worktrees.pop().unwrap()
11136    });
11137    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11138
11139    let buffer_1 = project
11140        .update(cx, |project, cx| {
11141            project.open_buffer((worktree_id, "main.rs"), cx)
11142        })
11143        .await
11144        .unwrap();
11145    let buffer_2 = project
11146        .update(cx, |project, cx| {
11147            project.open_buffer((worktree_id, "other.rs"), cx)
11148        })
11149        .await
11150        .unwrap();
11151    let buffer_3 = project
11152        .update(cx, |project, cx| {
11153            project.open_buffer((worktree_id, "lib.rs"), cx)
11154        })
11155        .await
11156        .unwrap();
11157
11158    let multi_buffer = cx.new(|cx| {
11159        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11160        multi_buffer.push_excerpts(
11161            buffer_1.clone(),
11162            [
11163                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11164                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11165                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11166            ],
11167            cx,
11168        );
11169        multi_buffer.push_excerpts(
11170            buffer_2.clone(),
11171            [
11172                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11173                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11174                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11175            ],
11176            cx,
11177        );
11178        multi_buffer.push_excerpts(
11179            buffer_3.clone(),
11180            [
11181                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11182                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11183                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11184            ],
11185            cx,
11186        );
11187        multi_buffer
11188    });
11189    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11190        Editor::new(
11191            EditorMode::full(),
11192            multi_buffer,
11193            Some(project.clone()),
11194            window,
11195            cx,
11196        )
11197    });
11198
11199    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11200        editor.change_selections(
11201            SelectionEffects::scroll(Autoscroll::Next),
11202            window,
11203            cx,
11204            |s| s.select_ranges(Some(1..2)),
11205        );
11206        editor.insert("|one|two|three|", window, cx);
11207    });
11208    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11209    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11210        editor.change_selections(
11211            SelectionEffects::scroll(Autoscroll::Next),
11212            window,
11213            cx,
11214            |s| s.select_ranges(Some(60..70)),
11215        );
11216        editor.insert("|four|five|six|", window, cx);
11217    });
11218    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11219
11220    // First two buffers should be edited, but not the third one.
11221    assert_eq!(
11222        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11223        "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}",
11224    );
11225    buffer_1.update(cx, |buffer, _| {
11226        assert!(buffer.is_dirty());
11227        assert_eq!(
11228            buffer.text(),
11229            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11230        )
11231    });
11232    buffer_2.update(cx, |buffer, _| {
11233        assert!(buffer.is_dirty());
11234        assert_eq!(
11235            buffer.text(),
11236            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11237        )
11238    });
11239    buffer_3.update(cx, |buffer, _| {
11240        assert!(!buffer.is_dirty());
11241        assert_eq!(buffer.text(), sample_text_3,)
11242    });
11243    cx.executor().run_until_parked();
11244
11245    cx.executor().start_waiting();
11246    let save = multi_buffer_editor
11247        .update_in(cx, |editor, window, cx| {
11248            editor.save(
11249                SaveOptions {
11250                    format: true,
11251                    autosave: false,
11252                },
11253                project.clone(),
11254                window,
11255                cx,
11256            )
11257        })
11258        .unwrap();
11259
11260    let fake_server = fake_servers.next().await.unwrap();
11261    fake_server
11262        .server
11263        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11264            Ok(Some(vec![lsp::TextEdit::new(
11265                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11266                format!("[{} formatted]", params.text_document.uri),
11267            )]))
11268        })
11269        .detach();
11270    save.await;
11271
11272    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11273    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11274    assert_eq!(
11275        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11276        uri!(
11277            "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}"
11278        ),
11279    );
11280    buffer_1.update(cx, |buffer, _| {
11281        assert!(!buffer.is_dirty());
11282        assert_eq!(
11283            buffer.text(),
11284            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11285        )
11286    });
11287    buffer_2.update(cx, |buffer, _| {
11288        assert!(!buffer.is_dirty());
11289        assert_eq!(
11290            buffer.text(),
11291            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11292        )
11293    });
11294    buffer_3.update(cx, |buffer, _| {
11295        assert!(!buffer.is_dirty());
11296        assert_eq!(buffer.text(), sample_text_3,)
11297    });
11298}
11299
11300#[gpui::test]
11301async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
11302    init_test(cx, |_| {});
11303
11304    let fs = FakeFs::new(cx.executor());
11305    fs.insert_tree(
11306        path!("/dir"),
11307        json!({
11308            "file1.rs": "fn main() { println!(\"hello\"); }",
11309            "file2.rs": "fn test() { println!(\"test\"); }",
11310            "file3.rs": "fn other() { println!(\"other\"); }\n",
11311        }),
11312    )
11313    .await;
11314
11315    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
11316    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11317    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11318
11319    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11320    language_registry.add(rust_lang());
11321
11322    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
11323    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11324
11325    // Open three buffers
11326    let buffer_1 = project
11327        .update(cx, |project, cx| {
11328            project.open_buffer((worktree_id, "file1.rs"), cx)
11329        })
11330        .await
11331        .unwrap();
11332    let buffer_2 = project
11333        .update(cx, |project, cx| {
11334            project.open_buffer((worktree_id, "file2.rs"), cx)
11335        })
11336        .await
11337        .unwrap();
11338    let buffer_3 = project
11339        .update(cx, |project, cx| {
11340            project.open_buffer((worktree_id, "file3.rs"), cx)
11341        })
11342        .await
11343        .unwrap();
11344
11345    // Create a multi-buffer with all three buffers
11346    let multi_buffer = cx.new(|cx| {
11347        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11348        multi_buffer.push_excerpts(
11349            buffer_1.clone(),
11350            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11351            cx,
11352        );
11353        multi_buffer.push_excerpts(
11354            buffer_2.clone(),
11355            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11356            cx,
11357        );
11358        multi_buffer.push_excerpts(
11359            buffer_3.clone(),
11360            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
11361            cx,
11362        );
11363        multi_buffer
11364    });
11365
11366    let editor = cx.new_window_entity(|window, cx| {
11367        Editor::new(
11368            EditorMode::full(),
11369            multi_buffer,
11370            Some(project.clone()),
11371            window,
11372            cx,
11373        )
11374    });
11375
11376    // Edit only the first buffer
11377    editor.update_in(cx, |editor, window, cx| {
11378        editor.change_selections(
11379            SelectionEffects::scroll(Autoscroll::Next),
11380            window,
11381            cx,
11382            |s| s.select_ranges(Some(10..10)),
11383        );
11384        editor.insert("// edited", window, cx);
11385    });
11386
11387    // Verify that only buffer 1 is dirty
11388    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
11389    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11390    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11391
11392    // Get write counts after file creation (files were created with initial content)
11393    // We expect each file to have been written once during creation
11394    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
11395    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
11396    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
11397
11398    // Perform autosave
11399    let save_task = editor.update_in(cx, |editor, window, cx| {
11400        editor.save(
11401            SaveOptions {
11402                format: true,
11403                autosave: true,
11404            },
11405            project.clone(),
11406            window,
11407            cx,
11408        )
11409    });
11410    save_task.await.unwrap();
11411
11412    // Only the dirty buffer should have been saved
11413    assert_eq!(
11414        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11415        1,
11416        "Buffer 1 was dirty, so it should have been written once during autosave"
11417    );
11418    assert_eq!(
11419        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11420        0,
11421        "Buffer 2 was clean, so it should not have been written during autosave"
11422    );
11423    assert_eq!(
11424        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11425        0,
11426        "Buffer 3 was clean, so it should not have been written during autosave"
11427    );
11428
11429    // Verify buffer states after autosave
11430    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11431    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11432    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
11433
11434    // Now perform a manual save (format = true)
11435    let save_task = editor.update_in(cx, |editor, window, cx| {
11436        editor.save(
11437            SaveOptions {
11438                format: true,
11439                autosave: false,
11440            },
11441            project.clone(),
11442            window,
11443            cx,
11444        )
11445    });
11446    save_task.await.unwrap();
11447
11448    // During manual save, clean buffers don't get written to disk
11449    // They just get did_save called for language server notifications
11450    assert_eq!(
11451        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
11452        1,
11453        "Buffer 1 should only have been written once total (during autosave, not manual save)"
11454    );
11455    assert_eq!(
11456        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
11457        0,
11458        "Buffer 2 should not have been written at all"
11459    );
11460    assert_eq!(
11461        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
11462        0,
11463        "Buffer 3 should not have been written at all"
11464    );
11465}
11466
11467async fn setup_range_format_test(
11468    cx: &mut TestAppContext,
11469) -> (
11470    Entity<Project>,
11471    Entity<Editor>,
11472    &mut gpui::VisualTestContext,
11473    lsp::FakeLanguageServer,
11474) {
11475    init_test(cx, |_| {});
11476
11477    let fs = FakeFs::new(cx.executor());
11478    fs.insert_file(path!("/file.rs"), Default::default()).await;
11479
11480    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11481
11482    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11483    language_registry.add(rust_lang());
11484    let mut fake_servers = language_registry.register_fake_lsp(
11485        "Rust",
11486        FakeLspAdapter {
11487            capabilities: lsp::ServerCapabilities {
11488                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
11489                ..lsp::ServerCapabilities::default()
11490            },
11491            ..FakeLspAdapter::default()
11492        },
11493    );
11494
11495    let buffer = project
11496        .update(cx, |project, cx| {
11497            project.open_local_buffer(path!("/file.rs"), cx)
11498        })
11499        .await
11500        .unwrap();
11501
11502    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11503    let (editor, cx) = cx.add_window_view(|window, cx| {
11504        build_editor_with_project(project.clone(), buffer, window, cx)
11505    });
11506
11507    cx.executor().start_waiting();
11508    let fake_server = fake_servers.next().await.unwrap();
11509
11510    (project, editor, cx, fake_server)
11511}
11512
11513#[gpui::test]
11514async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
11515    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11516
11517    editor.update_in(cx, |editor, window, cx| {
11518        editor.set_text("one\ntwo\nthree\n", window, cx)
11519    });
11520    assert!(cx.read(|cx| editor.is_dirty(cx)));
11521
11522    let save = editor
11523        .update_in(cx, |editor, window, cx| {
11524            editor.save(
11525                SaveOptions {
11526                    format: true,
11527                    autosave: false,
11528                },
11529                project.clone(),
11530                window,
11531                cx,
11532            )
11533        })
11534        .unwrap();
11535    fake_server
11536        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11537            assert_eq!(
11538                params.text_document.uri,
11539                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11540            );
11541            assert_eq!(params.options.tab_size, 4);
11542            Ok(Some(vec![lsp::TextEdit::new(
11543                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11544                ", ".to_string(),
11545            )]))
11546        })
11547        .next()
11548        .await;
11549    cx.executor().start_waiting();
11550    save.await;
11551    assert_eq!(
11552        editor.update(cx, |editor, cx| editor.text(cx)),
11553        "one, two\nthree\n"
11554    );
11555    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11556}
11557
11558#[gpui::test]
11559async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
11560    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11561
11562    editor.update_in(cx, |editor, window, cx| {
11563        editor.set_text("one\ntwo\nthree\n", window, cx)
11564    });
11565    assert!(cx.read(|cx| editor.is_dirty(cx)));
11566
11567    // Test that save still works when formatting hangs
11568    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
11569        move |params, _| async move {
11570            assert_eq!(
11571                params.text_document.uri,
11572                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11573            );
11574            futures::future::pending::<()>().await;
11575            unreachable!()
11576        },
11577    );
11578    let save = editor
11579        .update_in(cx, |editor, window, cx| {
11580            editor.save(
11581                SaveOptions {
11582                    format: true,
11583                    autosave: false,
11584                },
11585                project.clone(),
11586                window,
11587                cx,
11588            )
11589        })
11590        .unwrap();
11591    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11592    cx.executor().start_waiting();
11593    save.await;
11594    assert_eq!(
11595        editor.update(cx, |editor, cx| editor.text(cx)),
11596        "one\ntwo\nthree\n"
11597    );
11598    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11599}
11600
11601#[gpui::test]
11602async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
11603    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11604
11605    // Buffer starts clean, no formatting should be requested
11606    let save = editor
11607        .update_in(cx, |editor, window, cx| {
11608            editor.save(
11609                SaveOptions {
11610                    format: false,
11611                    autosave: false,
11612                },
11613                project.clone(),
11614                window,
11615                cx,
11616            )
11617        })
11618        .unwrap();
11619    let _pending_format_request = fake_server
11620        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
11621            panic!("Should not be invoked");
11622        })
11623        .next();
11624    cx.executor().start_waiting();
11625    save.await;
11626    cx.run_until_parked();
11627}
11628
11629#[gpui::test]
11630async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
11631    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
11632
11633    // Set Rust language override and assert overridden tabsize is sent to language server
11634    update_test_language_settings(cx, |settings| {
11635        settings.languages.0.insert(
11636            "Rust".into(),
11637            LanguageSettingsContent {
11638                tab_size: NonZeroU32::new(8),
11639                ..Default::default()
11640            },
11641        );
11642    });
11643
11644    editor.update_in(cx, |editor, window, cx| {
11645        editor.set_text("something_new\n", window, cx)
11646    });
11647    assert!(cx.read(|cx| editor.is_dirty(cx)));
11648    let save = editor
11649        .update_in(cx, |editor, window, cx| {
11650            editor.save(
11651                SaveOptions {
11652                    format: true,
11653                    autosave: false,
11654                },
11655                project.clone(),
11656                window,
11657                cx,
11658            )
11659        })
11660        .unwrap();
11661    fake_server
11662        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
11663            assert_eq!(
11664                params.text_document.uri,
11665                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11666            );
11667            assert_eq!(params.options.tab_size, 8);
11668            Ok(Some(Vec::new()))
11669        })
11670        .next()
11671        .await;
11672    save.await;
11673}
11674
11675#[gpui::test]
11676async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
11677    init_test(cx, |settings| {
11678        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
11679            Formatter::LanguageServer { name: None },
11680        )))
11681    });
11682
11683    let fs = FakeFs::new(cx.executor());
11684    fs.insert_file(path!("/file.rs"), Default::default()).await;
11685
11686    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11687
11688    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11689    language_registry.add(Arc::new(Language::new(
11690        LanguageConfig {
11691            name: "Rust".into(),
11692            matcher: LanguageMatcher {
11693                path_suffixes: vec!["rs".to_string()],
11694                ..Default::default()
11695            },
11696            ..LanguageConfig::default()
11697        },
11698        Some(tree_sitter_rust::LANGUAGE.into()),
11699    )));
11700    update_test_language_settings(cx, |settings| {
11701        // Enable Prettier formatting for the same buffer, and ensure
11702        // LSP is called instead of Prettier.
11703        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
11704    });
11705    let mut fake_servers = language_registry.register_fake_lsp(
11706        "Rust",
11707        FakeLspAdapter {
11708            capabilities: lsp::ServerCapabilities {
11709                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11710                ..Default::default()
11711            },
11712            ..Default::default()
11713        },
11714    );
11715
11716    let buffer = project
11717        .update(cx, |project, cx| {
11718            project.open_local_buffer(path!("/file.rs"), cx)
11719        })
11720        .await
11721        .unwrap();
11722
11723    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11724    let (editor, cx) = cx.add_window_view(|window, cx| {
11725        build_editor_with_project(project.clone(), buffer, window, cx)
11726    });
11727    editor.update_in(cx, |editor, window, cx| {
11728        editor.set_text("one\ntwo\nthree\n", window, cx)
11729    });
11730
11731    cx.executor().start_waiting();
11732    let fake_server = fake_servers.next().await.unwrap();
11733
11734    let format = editor
11735        .update_in(cx, |editor, window, cx| {
11736            editor.perform_format(
11737                project.clone(),
11738                FormatTrigger::Manual,
11739                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11740                window,
11741                cx,
11742            )
11743        })
11744        .unwrap();
11745    fake_server
11746        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11747            assert_eq!(
11748                params.text_document.uri,
11749                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11750            );
11751            assert_eq!(params.options.tab_size, 4);
11752            Ok(Some(vec![lsp::TextEdit::new(
11753                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11754                ", ".to_string(),
11755            )]))
11756        })
11757        .next()
11758        .await;
11759    cx.executor().start_waiting();
11760    format.await;
11761    assert_eq!(
11762        editor.update(cx, |editor, cx| editor.text(cx)),
11763        "one, two\nthree\n"
11764    );
11765
11766    editor.update_in(cx, |editor, window, cx| {
11767        editor.set_text("one\ntwo\nthree\n", window, cx)
11768    });
11769    // Ensure we don't lock if formatting hangs.
11770    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11771        move |params, _| async move {
11772            assert_eq!(
11773                params.text_document.uri,
11774                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11775            );
11776            futures::future::pending::<()>().await;
11777            unreachable!()
11778        },
11779    );
11780    let format = editor
11781        .update_in(cx, |editor, window, cx| {
11782            editor.perform_format(
11783                project,
11784                FormatTrigger::Manual,
11785                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11786                window,
11787                cx,
11788            )
11789        })
11790        .unwrap();
11791    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11792    cx.executor().start_waiting();
11793    format.await;
11794    assert_eq!(
11795        editor.update(cx, |editor, cx| editor.text(cx)),
11796        "one\ntwo\nthree\n"
11797    );
11798}
11799
11800#[gpui::test]
11801async fn test_multiple_formatters(cx: &mut TestAppContext) {
11802    init_test(cx, |settings| {
11803        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
11804        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
11805            Formatter::LanguageServer { name: None },
11806            Formatter::CodeActions(
11807                [
11808                    ("code-action-1".into(), true),
11809                    ("code-action-2".into(), true),
11810                ]
11811                .into_iter()
11812                .collect(),
11813            ),
11814        ])))
11815    });
11816
11817    let fs = FakeFs::new(cx.executor());
11818    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
11819        .await;
11820
11821    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
11822    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11823    language_registry.add(rust_lang());
11824
11825    let mut fake_servers = language_registry.register_fake_lsp(
11826        "Rust",
11827        FakeLspAdapter {
11828            capabilities: lsp::ServerCapabilities {
11829                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11830                execute_command_provider: Some(lsp::ExecuteCommandOptions {
11831                    commands: vec!["the-command-for-code-action-1".into()],
11832                    ..Default::default()
11833                }),
11834                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
11835                ..Default::default()
11836            },
11837            ..Default::default()
11838        },
11839    );
11840
11841    let buffer = project
11842        .update(cx, |project, cx| {
11843            project.open_local_buffer(path!("/file.rs"), cx)
11844        })
11845        .await
11846        .unwrap();
11847
11848    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11849    let (editor, cx) = cx.add_window_view(|window, cx| {
11850        build_editor_with_project(project.clone(), buffer, window, cx)
11851    });
11852
11853    cx.executor().start_waiting();
11854
11855    let fake_server = fake_servers.next().await.unwrap();
11856    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11857        move |_params, _| async move {
11858            Ok(Some(vec![lsp::TextEdit::new(
11859                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11860                "applied-formatting\n".to_string(),
11861            )]))
11862        },
11863    );
11864    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
11865        move |params, _| async move {
11866            assert_eq!(
11867                params.context.only,
11868                Some(vec!["code-action-1".into(), "code-action-2".into()])
11869            );
11870            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
11871            Ok(Some(vec![
11872                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11873                    kind: Some("code-action-1".into()),
11874                    edit: Some(lsp::WorkspaceEdit::new(
11875                        [(
11876                            uri.clone(),
11877                            vec![lsp::TextEdit::new(
11878                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11879                                "applied-code-action-1-edit\n".to_string(),
11880                            )],
11881                        )]
11882                        .into_iter()
11883                        .collect(),
11884                    )),
11885                    command: Some(lsp::Command {
11886                        command: "the-command-for-code-action-1".into(),
11887                        ..Default::default()
11888                    }),
11889                    ..Default::default()
11890                }),
11891                lsp::CodeActionOrCommand::CodeAction(lsp::CodeAction {
11892                    kind: Some("code-action-2".into()),
11893                    edit: Some(lsp::WorkspaceEdit::new(
11894                        [(
11895                            uri,
11896                            vec![lsp::TextEdit::new(
11897                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
11898                                "applied-code-action-2-edit\n".to_string(),
11899                            )],
11900                        )]
11901                        .into_iter()
11902                        .collect(),
11903                    )),
11904                    ..Default::default()
11905                }),
11906            ]))
11907        },
11908    );
11909
11910    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
11911        move |params, _| async move { Ok(params) }
11912    });
11913
11914    let command_lock = Arc::new(futures::lock::Mutex::new(()));
11915    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
11916        let fake = fake_server.clone();
11917        let lock = command_lock.clone();
11918        move |params, _| {
11919            assert_eq!(params.command, "the-command-for-code-action-1");
11920            let fake = fake.clone();
11921            let lock = lock.clone();
11922            async move {
11923                lock.lock().await;
11924                fake.server
11925                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
11926                        label: None,
11927                        edit: lsp::WorkspaceEdit {
11928                            changes: Some(
11929                                [(
11930                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
11931                                    vec![lsp::TextEdit {
11932                                        range: lsp::Range::new(
11933                                            lsp::Position::new(0, 0),
11934                                            lsp::Position::new(0, 0),
11935                                        ),
11936                                        new_text: "applied-code-action-1-command\n".into(),
11937                                    }],
11938                                )]
11939                                .into_iter()
11940                                .collect(),
11941                            ),
11942                            ..Default::default()
11943                        },
11944                    })
11945                    .await
11946                    .into_response()
11947                    .unwrap();
11948                Ok(Some(json!(null)))
11949            }
11950        }
11951    });
11952
11953    cx.executor().start_waiting();
11954    editor
11955        .update_in(cx, |editor, window, cx| {
11956            editor.perform_format(
11957                project.clone(),
11958                FormatTrigger::Manual,
11959                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11960                window,
11961                cx,
11962            )
11963        })
11964        .unwrap()
11965        .await;
11966    editor.update(cx, |editor, cx| {
11967        assert_eq!(
11968            editor.text(cx),
11969            r#"
11970                applied-code-action-2-edit
11971                applied-code-action-1-command
11972                applied-code-action-1-edit
11973                applied-formatting
11974                one
11975                two
11976                three
11977            "#
11978            .unindent()
11979        );
11980    });
11981
11982    editor.update_in(cx, |editor, window, cx| {
11983        editor.undo(&Default::default(), window, cx);
11984        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
11985    });
11986
11987    // Perform a manual edit while waiting for an LSP command
11988    // that's being run as part of a formatting code action.
11989    let lock_guard = command_lock.lock().await;
11990    let format = editor
11991        .update_in(cx, |editor, window, cx| {
11992            editor.perform_format(
11993                project.clone(),
11994                FormatTrigger::Manual,
11995                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
11996                window,
11997                cx,
11998            )
11999        })
12000        .unwrap();
12001    cx.run_until_parked();
12002    editor.update(cx, |editor, cx| {
12003        assert_eq!(
12004            editor.text(cx),
12005            r#"
12006                applied-code-action-1-edit
12007                applied-formatting
12008                one
12009                two
12010                three
12011            "#
12012            .unindent()
12013        );
12014
12015        editor.buffer.update(cx, |buffer, cx| {
12016            let ix = buffer.len(cx);
12017            buffer.edit([(ix..ix, "edited\n")], None, cx);
12018        });
12019    });
12020
12021    // Allow the LSP command to proceed. Because the buffer was edited,
12022    // the second code action will not be run.
12023    drop(lock_guard);
12024    format.await;
12025    editor.update_in(cx, |editor, window, cx| {
12026        assert_eq!(
12027            editor.text(cx),
12028            r#"
12029                applied-code-action-1-command
12030                applied-code-action-1-edit
12031                applied-formatting
12032                one
12033                two
12034                three
12035                edited
12036            "#
12037            .unindent()
12038        );
12039
12040        // The manual edit is undone first, because it is the last thing the user did
12041        // (even though the command completed afterwards).
12042        editor.undo(&Default::default(), window, cx);
12043        assert_eq!(
12044            editor.text(cx),
12045            r#"
12046                applied-code-action-1-command
12047                applied-code-action-1-edit
12048                applied-formatting
12049                one
12050                two
12051                three
12052            "#
12053            .unindent()
12054        );
12055
12056        // All the formatting (including the command, which completed after the manual edit)
12057        // is undone together.
12058        editor.undo(&Default::default(), window, cx);
12059        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12060    });
12061}
12062
12063#[gpui::test]
12064async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12065    init_test(cx, |settings| {
12066        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Vec(vec![
12067            Formatter::LanguageServer { name: None },
12068        ])))
12069    });
12070
12071    let fs = FakeFs::new(cx.executor());
12072    fs.insert_file(path!("/file.ts"), Default::default()).await;
12073
12074    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12075
12076    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12077    language_registry.add(Arc::new(Language::new(
12078        LanguageConfig {
12079            name: "TypeScript".into(),
12080            matcher: LanguageMatcher {
12081                path_suffixes: vec!["ts".to_string()],
12082                ..Default::default()
12083            },
12084            ..LanguageConfig::default()
12085        },
12086        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12087    )));
12088    update_test_language_settings(cx, |settings| {
12089        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12090    });
12091    let mut fake_servers = language_registry.register_fake_lsp(
12092        "TypeScript",
12093        FakeLspAdapter {
12094            capabilities: lsp::ServerCapabilities {
12095                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12096                ..Default::default()
12097            },
12098            ..Default::default()
12099        },
12100    );
12101
12102    let buffer = project
12103        .update(cx, |project, cx| {
12104            project.open_local_buffer(path!("/file.ts"), cx)
12105        })
12106        .await
12107        .unwrap();
12108
12109    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12110    let (editor, cx) = cx.add_window_view(|window, cx| {
12111        build_editor_with_project(project.clone(), buffer, window, cx)
12112    });
12113    editor.update_in(cx, |editor, window, cx| {
12114        editor.set_text(
12115            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12116            window,
12117            cx,
12118        )
12119    });
12120
12121    cx.executor().start_waiting();
12122    let fake_server = fake_servers.next().await.unwrap();
12123
12124    let format = editor
12125        .update_in(cx, |editor, window, cx| {
12126            editor.perform_code_action_kind(
12127                project.clone(),
12128                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12129                window,
12130                cx,
12131            )
12132        })
12133        .unwrap();
12134    fake_server
12135        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12136            assert_eq!(
12137                params.text_document.uri,
12138                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12139            );
12140            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12141                lsp::CodeAction {
12142                    title: "Organize Imports".to_string(),
12143                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12144                    edit: Some(lsp::WorkspaceEdit {
12145                        changes: Some(
12146                            [(
12147                                params.text_document.uri.clone(),
12148                                vec![lsp::TextEdit::new(
12149                                    lsp::Range::new(
12150                                        lsp::Position::new(1, 0),
12151                                        lsp::Position::new(2, 0),
12152                                    ),
12153                                    "".to_string(),
12154                                )],
12155                            )]
12156                            .into_iter()
12157                            .collect(),
12158                        ),
12159                        ..Default::default()
12160                    }),
12161                    ..Default::default()
12162                },
12163            )]))
12164        })
12165        .next()
12166        .await;
12167    cx.executor().start_waiting();
12168    format.await;
12169    assert_eq!(
12170        editor.update(cx, |editor, cx| editor.text(cx)),
12171        "import { a } from 'module';\n\nconst x = a;\n"
12172    );
12173
12174    editor.update_in(cx, |editor, window, cx| {
12175        editor.set_text(
12176            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12177            window,
12178            cx,
12179        )
12180    });
12181    // Ensure we don't lock if code action hangs.
12182    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12183        move |params, _| async move {
12184            assert_eq!(
12185                params.text_document.uri,
12186                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12187            );
12188            futures::future::pending::<()>().await;
12189            unreachable!()
12190        },
12191    );
12192    let format = editor
12193        .update_in(cx, |editor, window, cx| {
12194            editor.perform_code_action_kind(
12195                project,
12196                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12197                window,
12198                cx,
12199            )
12200        })
12201        .unwrap();
12202    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12203    cx.executor().start_waiting();
12204    format.await;
12205    assert_eq!(
12206        editor.update(cx, |editor, cx| editor.text(cx)),
12207        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12208    );
12209}
12210
12211#[gpui::test]
12212async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12213    init_test(cx, |_| {});
12214
12215    let mut cx = EditorLspTestContext::new_rust(
12216        lsp::ServerCapabilities {
12217            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12218            ..Default::default()
12219        },
12220        cx,
12221    )
12222    .await;
12223
12224    cx.set_state(indoc! {"
12225        one.twoˇ
12226    "});
12227
12228    // The format request takes a long time. When it completes, it inserts
12229    // a newline and an indent before the `.`
12230    cx.lsp
12231        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12232            let executor = cx.background_executor().clone();
12233            async move {
12234                executor.timer(Duration::from_millis(100)).await;
12235                Ok(Some(vec![lsp::TextEdit {
12236                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12237                    new_text: "\n    ".into(),
12238                }]))
12239            }
12240        });
12241
12242    // Submit a format request.
12243    let format_1 = cx
12244        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12245        .unwrap();
12246    cx.executor().run_until_parked();
12247
12248    // Submit a second format request.
12249    let format_2 = cx
12250        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12251        .unwrap();
12252    cx.executor().run_until_parked();
12253
12254    // Wait for both format requests to complete
12255    cx.executor().advance_clock(Duration::from_millis(200));
12256    cx.executor().start_waiting();
12257    format_1.await.unwrap();
12258    cx.executor().start_waiting();
12259    format_2.await.unwrap();
12260
12261    // The formatting edits only happens once.
12262    cx.assert_editor_state(indoc! {"
12263        one
12264            .twoˇ
12265    "});
12266}
12267
12268#[gpui::test]
12269async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12270    init_test(cx, |settings| {
12271        settings.defaults.formatter = Some(SelectedFormatter::Auto)
12272    });
12273
12274    let mut cx = EditorLspTestContext::new_rust(
12275        lsp::ServerCapabilities {
12276            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12277            ..Default::default()
12278        },
12279        cx,
12280    )
12281    .await;
12282
12283    // Set up a buffer white some trailing whitespace and no trailing newline.
12284    cx.set_state(
12285        &[
12286            "one ",   //
12287            "twoˇ",   //
12288            "three ", //
12289            "four",   //
12290        ]
12291        .join("\n"),
12292    );
12293
12294    // Submit a format request.
12295    let format = cx
12296        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12297        .unwrap();
12298
12299    // Record which buffer changes have been sent to the language server
12300    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12301    cx.lsp
12302        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12303            let buffer_changes = buffer_changes.clone();
12304            move |params, _| {
12305                buffer_changes.lock().extend(
12306                    params
12307                        .content_changes
12308                        .into_iter()
12309                        .map(|e| (e.range.unwrap(), e.text)),
12310                );
12311            }
12312        });
12313
12314    // Handle formatting requests to the language server.
12315    cx.lsp
12316        .set_request_handler::<lsp::request::Formatting, _, _>({
12317            let buffer_changes = buffer_changes.clone();
12318            move |_, _| {
12319                // When formatting is requested, trailing whitespace has already been stripped,
12320                // and the trailing newline has already been added.
12321                assert_eq!(
12322                    &buffer_changes.lock()[1..],
12323                    &[
12324                        (
12325                            lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
12326                            "".into()
12327                        ),
12328                        (
12329                            lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
12330                            "".into()
12331                        ),
12332                        (
12333                            lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
12334                            "\n".into()
12335                        ),
12336                    ]
12337                );
12338
12339                // Insert blank lines between each line of the buffer.
12340                async move {
12341                    Ok(Some(vec![
12342                        lsp::TextEdit {
12343                            range: lsp::Range::new(
12344                                lsp::Position::new(1, 0),
12345                                lsp::Position::new(1, 0),
12346                            ),
12347                            new_text: "\n".into(),
12348                        },
12349                        lsp::TextEdit {
12350                            range: lsp::Range::new(
12351                                lsp::Position::new(2, 0),
12352                                lsp::Position::new(2, 0),
12353                            ),
12354                            new_text: "\n".into(),
12355                        },
12356                    ]))
12357                }
12358            }
12359        });
12360
12361    // After formatting the buffer, the trailing whitespace is stripped,
12362    // a newline is appended, and the edits provided by the language server
12363    // have been applied.
12364    format.await.unwrap();
12365    cx.assert_editor_state(
12366        &[
12367            "one",   //
12368            "",      //
12369            "twoˇ",  //
12370            "",      //
12371            "three", //
12372            "four",  //
12373            "",      //
12374        ]
12375        .join("\n"),
12376    );
12377
12378    // Undoing the formatting undoes the trailing whitespace removal, the
12379    // trailing newline, and the LSP edits.
12380    cx.update_buffer(|buffer, cx| buffer.undo(cx));
12381    cx.assert_editor_state(
12382        &[
12383            "one ",   //
12384            "twoˇ",   //
12385            "three ", //
12386            "four",   //
12387        ]
12388        .join("\n"),
12389    );
12390}
12391
12392#[gpui::test]
12393async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
12394    cx: &mut TestAppContext,
12395) {
12396    init_test(cx, |_| {});
12397
12398    cx.update(|cx| {
12399        cx.update_global::<SettingsStore, _>(|settings, cx| {
12400            settings.update_user_settings(cx, |settings| {
12401                settings.editor.auto_signature_help = Some(true);
12402            });
12403        });
12404    });
12405
12406    let mut cx = EditorLspTestContext::new_rust(
12407        lsp::ServerCapabilities {
12408            signature_help_provider: Some(lsp::SignatureHelpOptions {
12409                ..Default::default()
12410            }),
12411            ..Default::default()
12412        },
12413        cx,
12414    )
12415    .await;
12416
12417    let language = Language::new(
12418        LanguageConfig {
12419            name: "Rust".into(),
12420            brackets: BracketPairConfig {
12421                pairs: vec![
12422                    BracketPair {
12423                        start: "{".to_string(),
12424                        end: "}".to_string(),
12425                        close: true,
12426                        surround: true,
12427                        newline: true,
12428                    },
12429                    BracketPair {
12430                        start: "(".to_string(),
12431                        end: ")".to_string(),
12432                        close: true,
12433                        surround: true,
12434                        newline: true,
12435                    },
12436                    BracketPair {
12437                        start: "/*".to_string(),
12438                        end: " */".to_string(),
12439                        close: true,
12440                        surround: true,
12441                        newline: true,
12442                    },
12443                    BracketPair {
12444                        start: "[".to_string(),
12445                        end: "]".to_string(),
12446                        close: false,
12447                        surround: false,
12448                        newline: true,
12449                    },
12450                    BracketPair {
12451                        start: "\"".to_string(),
12452                        end: "\"".to_string(),
12453                        close: true,
12454                        surround: true,
12455                        newline: false,
12456                    },
12457                    BracketPair {
12458                        start: "<".to_string(),
12459                        end: ">".to_string(),
12460                        close: false,
12461                        surround: true,
12462                        newline: true,
12463                    },
12464                ],
12465                ..Default::default()
12466            },
12467            autoclose_before: "})]".to_string(),
12468            ..Default::default()
12469        },
12470        Some(tree_sitter_rust::LANGUAGE.into()),
12471    );
12472    let language = Arc::new(language);
12473
12474    cx.language_registry().add(language.clone());
12475    cx.update_buffer(|buffer, cx| {
12476        buffer.set_language(Some(language), cx);
12477    });
12478
12479    cx.set_state(
12480        &r#"
12481            fn main() {
12482                sampleˇ
12483            }
12484        "#
12485        .unindent(),
12486    );
12487
12488    cx.update_editor(|editor, window, cx| {
12489        editor.handle_input("(", window, cx);
12490    });
12491    cx.assert_editor_state(
12492        &"
12493            fn main() {
12494                sample(ˇ)
12495            }
12496        "
12497        .unindent(),
12498    );
12499
12500    let mocked_response = lsp::SignatureHelp {
12501        signatures: vec![lsp::SignatureInformation {
12502            label: "fn sample(param1: u8, param2: u8)".to_string(),
12503            documentation: None,
12504            parameters: Some(vec![
12505                lsp::ParameterInformation {
12506                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12507                    documentation: None,
12508                },
12509                lsp::ParameterInformation {
12510                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12511                    documentation: None,
12512                },
12513            ]),
12514            active_parameter: None,
12515        }],
12516        active_signature: Some(0),
12517        active_parameter: Some(0),
12518    };
12519    handle_signature_help_request(&mut cx, mocked_response).await;
12520
12521    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12522        .await;
12523
12524    cx.editor(|editor, _, _| {
12525        let signature_help_state = editor.signature_help_state.popover().cloned();
12526        let signature = signature_help_state.unwrap();
12527        assert_eq!(
12528            signature.signatures[signature.current_signature].label,
12529            "fn sample(param1: u8, param2: u8)"
12530        );
12531    });
12532}
12533
12534#[gpui::test]
12535async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
12536    init_test(cx, |_| {});
12537
12538    cx.update(|cx| {
12539        cx.update_global::<SettingsStore, _>(|settings, cx| {
12540            settings.update_user_settings(cx, |settings| {
12541                settings.editor.auto_signature_help = Some(false);
12542                settings.editor.show_signature_help_after_edits = Some(false);
12543            });
12544        });
12545    });
12546
12547    let mut cx = EditorLspTestContext::new_rust(
12548        lsp::ServerCapabilities {
12549            signature_help_provider: Some(lsp::SignatureHelpOptions {
12550                ..Default::default()
12551            }),
12552            ..Default::default()
12553        },
12554        cx,
12555    )
12556    .await;
12557
12558    let language = Language::new(
12559        LanguageConfig {
12560            name: "Rust".into(),
12561            brackets: BracketPairConfig {
12562                pairs: vec![
12563                    BracketPair {
12564                        start: "{".to_string(),
12565                        end: "}".to_string(),
12566                        close: true,
12567                        surround: true,
12568                        newline: true,
12569                    },
12570                    BracketPair {
12571                        start: "(".to_string(),
12572                        end: ")".to_string(),
12573                        close: true,
12574                        surround: true,
12575                        newline: true,
12576                    },
12577                    BracketPair {
12578                        start: "/*".to_string(),
12579                        end: " */".to_string(),
12580                        close: true,
12581                        surround: true,
12582                        newline: true,
12583                    },
12584                    BracketPair {
12585                        start: "[".to_string(),
12586                        end: "]".to_string(),
12587                        close: false,
12588                        surround: false,
12589                        newline: true,
12590                    },
12591                    BracketPair {
12592                        start: "\"".to_string(),
12593                        end: "\"".to_string(),
12594                        close: true,
12595                        surround: true,
12596                        newline: false,
12597                    },
12598                    BracketPair {
12599                        start: "<".to_string(),
12600                        end: ">".to_string(),
12601                        close: false,
12602                        surround: true,
12603                        newline: true,
12604                    },
12605                ],
12606                ..Default::default()
12607            },
12608            autoclose_before: "})]".to_string(),
12609            ..Default::default()
12610        },
12611        Some(tree_sitter_rust::LANGUAGE.into()),
12612    );
12613    let language = Arc::new(language);
12614
12615    cx.language_registry().add(language.clone());
12616    cx.update_buffer(|buffer, cx| {
12617        buffer.set_language(Some(language), cx);
12618    });
12619
12620    // Ensure that signature_help is not called when no signature help is enabled.
12621    cx.set_state(
12622        &r#"
12623            fn main() {
12624                sampleˇ
12625            }
12626        "#
12627        .unindent(),
12628    );
12629    cx.update_editor(|editor, window, cx| {
12630        editor.handle_input("(", window, cx);
12631    });
12632    cx.assert_editor_state(
12633        &"
12634            fn main() {
12635                sample(ˇ)
12636            }
12637        "
12638        .unindent(),
12639    );
12640    cx.editor(|editor, _, _| {
12641        assert!(editor.signature_help_state.task().is_none());
12642    });
12643
12644    let mocked_response = lsp::SignatureHelp {
12645        signatures: vec![lsp::SignatureInformation {
12646            label: "fn sample(param1: u8, param2: u8)".to_string(),
12647            documentation: None,
12648            parameters: Some(vec![
12649                lsp::ParameterInformation {
12650                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12651                    documentation: None,
12652                },
12653                lsp::ParameterInformation {
12654                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12655                    documentation: None,
12656                },
12657            ]),
12658            active_parameter: None,
12659        }],
12660        active_signature: Some(0),
12661        active_parameter: Some(0),
12662    };
12663
12664    // Ensure that signature_help is called when enabled afte edits
12665    cx.update(|_, cx| {
12666        cx.update_global::<SettingsStore, _>(|settings, cx| {
12667            settings.update_user_settings(cx, |settings| {
12668                settings.editor.auto_signature_help = Some(false);
12669                settings.editor.show_signature_help_after_edits = Some(true);
12670            });
12671        });
12672    });
12673    cx.set_state(
12674        &r#"
12675            fn main() {
12676                sampleˇ
12677            }
12678        "#
12679        .unindent(),
12680    );
12681    cx.update_editor(|editor, window, cx| {
12682        editor.handle_input("(", window, cx);
12683    });
12684    cx.assert_editor_state(
12685        &"
12686            fn main() {
12687                sample(ˇ)
12688            }
12689        "
12690        .unindent(),
12691    );
12692    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12693    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12694        .await;
12695    cx.update_editor(|editor, _, _| {
12696        let signature_help_state = editor.signature_help_state.popover().cloned();
12697        assert!(signature_help_state.is_some());
12698        let signature = signature_help_state.unwrap();
12699        assert_eq!(
12700            signature.signatures[signature.current_signature].label,
12701            "fn sample(param1: u8, param2: u8)"
12702        );
12703        editor.signature_help_state = SignatureHelpState::default();
12704    });
12705
12706    // Ensure that signature_help is called when auto signature help override is enabled
12707    cx.update(|_, cx| {
12708        cx.update_global::<SettingsStore, _>(|settings, cx| {
12709            settings.update_user_settings(cx, |settings| {
12710                settings.editor.auto_signature_help = Some(true);
12711                settings.editor.show_signature_help_after_edits = Some(false);
12712            });
12713        });
12714    });
12715    cx.set_state(
12716        &r#"
12717            fn main() {
12718                sampleˇ
12719            }
12720        "#
12721        .unindent(),
12722    );
12723    cx.update_editor(|editor, window, cx| {
12724        editor.handle_input("(", window, cx);
12725    });
12726    cx.assert_editor_state(
12727        &"
12728            fn main() {
12729                sample(ˇ)
12730            }
12731        "
12732        .unindent(),
12733    );
12734    handle_signature_help_request(&mut cx, mocked_response).await;
12735    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12736        .await;
12737    cx.editor(|editor, _, _| {
12738        let signature_help_state = editor.signature_help_state.popover().cloned();
12739        assert!(signature_help_state.is_some());
12740        let signature = signature_help_state.unwrap();
12741        assert_eq!(
12742            signature.signatures[signature.current_signature].label,
12743            "fn sample(param1: u8, param2: u8)"
12744        );
12745    });
12746}
12747
12748#[gpui::test]
12749async fn test_signature_help(cx: &mut TestAppContext) {
12750    init_test(cx, |_| {});
12751    cx.update(|cx| {
12752        cx.update_global::<SettingsStore, _>(|settings, cx| {
12753            settings.update_user_settings(cx, |settings| {
12754                settings.editor.auto_signature_help = Some(true);
12755            });
12756        });
12757    });
12758
12759    let mut cx = EditorLspTestContext::new_rust(
12760        lsp::ServerCapabilities {
12761            signature_help_provider: Some(lsp::SignatureHelpOptions {
12762                ..Default::default()
12763            }),
12764            ..Default::default()
12765        },
12766        cx,
12767    )
12768    .await;
12769
12770    // A test that directly calls `show_signature_help`
12771    cx.update_editor(|editor, window, cx| {
12772        editor.show_signature_help(&ShowSignatureHelp, window, cx);
12773    });
12774
12775    let mocked_response = lsp::SignatureHelp {
12776        signatures: vec![lsp::SignatureInformation {
12777            label: "fn sample(param1: u8, param2: u8)".to_string(),
12778            documentation: None,
12779            parameters: Some(vec![
12780                lsp::ParameterInformation {
12781                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12782                    documentation: None,
12783                },
12784                lsp::ParameterInformation {
12785                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12786                    documentation: None,
12787                },
12788            ]),
12789            active_parameter: None,
12790        }],
12791        active_signature: Some(0),
12792        active_parameter: Some(0),
12793    };
12794    handle_signature_help_request(&mut cx, mocked_response).await;
12795
12796    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12797        .await;
12798
12799    cx.editor(|editor, _, _| {
12800        let signature_help_state = editor.signature_help_state.popover().cloned();
12801        assert!(signature_help_state.is_some());
12802        let signature = signature_help_state.unwrap();
12803        assert_eq!(
12804            signature.signatures[signature.current_signature].label,
12805            "fn sample(param1: u8, param2: u8)"
12806        );
12807    });
12808
12809    // When exiting outside from inside the brackets, `signature_help` is closed.
12810    cx.set_state(indoc! {"
12811        fn main() {
12812            sample(ˇ);
12813        }
12814
12815        fn sample(param1: u8, param2: u8) {}
12816    "});
12817
12818    cx.update_editor(|editor, window, cx| {
12819        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12820            s.select_ranges([0..0])
12821        });
12822    });
12823
12824    let mocked_response = lsp::SignatureHelp {
12825        signatures: Vec::new(),
12826        active_signature: None,
12827        active_parameter: None,
12828    };
12829    handle_signature_help_request(&mut cx, mocked_response).await;
12830
12831    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12832        .await;
12833
12834    cx.editor(|editor, _, _| {
12835        assert!(!editor.signature_help_state.is_shown());
12836    });
12837
12838    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
12839    cx.set_state(indoc! {"
12840        fn main() {
12841            sample(ˇ);
12842        }
12843
12844        fn sample(param1: u8, param2: u8) {}
12845    "});
12846
12847    let mocked_response = lsp::SignatureHelp {
12848        signatures: vec![lsp::SignatureInformation {
12849            label: "fn sample(param1: u8, param2: u8)".to_string(),
12850            documentation: None,
12851            parameters: Some(vec![
12852                lsp::ParameterInformation {
12853                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12854                    documentation: None,
12855                },
12856                lsp::ParameterInformation {
12857                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12858                    documentation: None,
12859                },
12860            ]),
12861            active_parameter: None,
12862        }],
12863        active_signature: Some(0),
12864        active_parameter: Some(0),
12865    };
12866    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12867    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12868        .await;
12869    cx.editor(|editor, _, _| {
12870        assert!(editor.signature_help_state.is_shown());
12871    });
12872
12873    // Restore the popover with more parameter input
12874    cx.set_state(indoc! {"
12875        fn main() {
12876            sample(param1, param2ˇ);
12877        }
12878
12879        fn sample(param1: u8, param2: u8) {}
12880    "});
12881
12882    let mocked_response = lsp::SignatureHelp {
12883        signatures: vec![lsp::SignatureInformation {
12884            label: "fn sample(param1: u8, param2: u8)".to_string(),
12885            documentation: None,
12886            parameters: Some(vec![
12887                lsp::ParameterInformation {
12888                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12889                    documentation: None,
12890                },
12891                lsp::ParameterInformation {
12892                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12893                    documentation: None,
12894                },
12895            ]),
12896            active_parameter: None,
12897        }],
12898        active_signature: Some(0),
12899        active_parameter: Some(1),
12900    };
12901    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12902    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12903        .await;
12904
12905    // When selecting a range, the popover is gone.
12906    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
12907    cx.update_editor(|editor, window, cx| {
12908        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12909            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12910        })
12911    });
12912    cx.assert_editor_state(indoc! {"
12913        fn main() {
12914            sample(param1, «ˇparam2»);
12915        }
12916
12917        fn sample(param1: u8, param2: u8) {}
12918    "});
12919    cx.editor(|editor, _, _| {
12920        assert!(!editor.signature_help_state.is_shown());
12921    });
12922
12923    // When unselecting again, the popover is back if within the brackets.
12924    cx.update_editor(|editor, window, cx| {
12925        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12926            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12927        })
12928    });
12929    cx.assert_editor_state(indoc! {"
12930        fn main() {
12931            sample(param1, ˇparam2);
12932        }
12933
12934        fn sample(param1: u8, param2: u8) {}
12935    "});
12936    handle_signature_help_request(&mut cx, mocked_response).await;
12937    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12938        .await;
12939    cx.editor(|editor, _, _| {
12940        assert!(editor.signature_help_state.is_shown());
12941    });
12942
12943    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
12944    cx.update_editor(|editor, window, cx| {
12945        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12946            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
12947            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
12948        })
12949    });
12950    cx.assert_editor_state(indoc! {"
12951        fn main() {
12952            sample(param1, ˇparam2);
12953        }
12954
12955        fn sample(param1: u8, param2: u8) {}
12956    "});
12957
12958    let mocked_response = lsp::SignatureHelp {
12959        signatures: vec![lsp::SignatureInformation {
12960            label: "fn sample(param1: u8, param2: u8)".to_string(),
12961            documentation: None,
12962            parameters: Some(vec![
12963                lsp::ParameterInformation {
12964                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
12965                    documentation: None,
12966                },
12967                lsp::ParameterInformation {
12968                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
12969                    documentation: None,
12970                },
12971            ]),
12972            active_parameter: None,
12973        }],
12974        active_signature: Some(0),
12975        active_parameter: Some(1),
12976    };
12977    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
12978    cx.condition(|editor, _| editor.signature_help_state.is_shown())
12979        .await;
12980    cx.update_editor(|editor, _, cx| {
12981        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
12982    });
12983    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
12984        .await;
12985    cx.update_editor(|editor, window, cx| {
12986        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12987            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
12988        })
12989    });
12990    cx.assert_editor_state(indoc! {"
12991        fn main() {
12992            sample(param1, «ˇparam2»);
12993        }
12994
12995        fn sample(param1: u8, param2: u8) {}
12996    "});
12997    cx.update_editor(|editor, window, cx| {
12998        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12999            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13000        })
13001    });
13002    cx.assert_editor_state(indoc! {"
13003        fn main() {
13004            sample(param1, ˇparam2);
13005        }
13006
13007        fn sample(param1: u8, param2: u8) {}
13008    "});
13009    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13010        .await;
13011}
13012
13013#[gpui::test]
13014async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13015    init_test(cx, |_| {});
13016
13017    let mut cx = EditorLspTestContext::new_rust(
13018        lsp::ServerCapabilities {
13019            signature_help_provider: Some(lsp::SignatureHelpOptions {
13020                ..Default::default()
13021            }),
13022            ..Default::default()
13023        },
13024        cx,
13025    )
13026    .await;
13027
13028    cx.set_state(indoc! {"
13029        fn main() {
13030            overloadedˇ
13031        }
13032    "});
13033
13034    cx.update_editor(|editor, window, cx| {
13035        editor.handle_input("(", window, cx);
13036        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13037    });
13038
13039    // Mock response with 3 signatures
13040    let mocked_response = lsp::SignatureHelp {
13041        signatures: vec![
13042            lsp::SignatureInformation {
13043                label: "fn overloaded(x: i32)".to_string(),
13044                documentation: None,
13045                parameters: Some(vec![lsp::ParameterInformation {
13046                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13047                    documentation: None,
13048                }]),
13049                active_parameter: None,
13050            },
13051            lsp::SignatureInformation {
13052                label: "fn overloaded(x: i32, y: i32)".to_string(),
13053                documentation: None,
13054                parameters: Some(vec![
13055                    lsp::ParameterInformation {
13056                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13057                        documentation: None,
13058                    },
13059                    lsp::ParameterInformation {
13060                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13061                        documentation: None,
13062                    },
13063                ]),
13064                active_parameter: None,
13065            },
13066            lsp::SignatureInformation {
13067                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13068                documentation: None,
13069                parameters: Some(vec![
13070                    lsp::ParameterInformation {
13071                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13072                        documentation: None,
13073                    },
13074                    lsp::ParameterInformation {
13075                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13076                        documentation: None,
13077                    },
13078                    lsp::ParameterInformation {
13079                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13080                        documentation: None,
13081                    },
13082                ]),
13083                active_parameter: None,
13084            },
13085        ],
13086        active_signature: Some(1),
13087        active_parameter: Some(0),
13088    };
13089    handle_signature_help_request(&mut cx, mocked_response).await;
13090
13091    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13092        .await;
13093
13094    // Verify we have multiple signatures and the right one is selected
13095    cx.editor(|editor, _, _| {
13096        let popover = editor.signature_help_state.popover().cloned().unwrap();
13097        assert_eq!(popover.signatures.len(), 3);
13098        // active_signature was 1, so that should be the current
13099        assert_eq!(popover.current_signature, 1);
13100        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13101        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13102        assert_eq!(
13103            popover.signatures[2].label,
13104            "fn overloaded(x: i32, y: i32, z: i32)"
13105        );
13106    });
13107
13108    // Test navigation functionality
13109    cx.update_editor(|editor, window, cx| {
13110        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13111    });
13112
13113    cx.editor(|editor, _, _| {
13114        let popover = editor.signature_help_state.popover().cloned().unwrap();
13115        assert_eq!(popover.current_signature, 2);
13116    });
13117
13118    // Test wrap around
13119    cx.update_editor(|editor, window, cx| {
13120        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13121    });
13122
13123    cx.editor(|editor, _, _| {
13124        let popover = editor.signature_help_state.popover().cloned().unwrap();
13125        assert_eq!(popover.current_signature, 0);
13126    });
13127
13128    // Test previous navigation
13129    cx.update_editor(|editor, window, cx| {
13130        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13131    });
13132
13133    cx.editor(|editor, _, _| {
13134        let popover = editor.signature_help_state.popover().cloned().unwrap();
13135        assert_eq!(popover.current_signature, 2);
13136    });
13137}
13138
13139#[gpui::test]
13140async fn test_completion_mode(cx: &mut TestAppContext) {
13141    init_test(cx, |_| {});
13142    let mut cx = EditorLspTestContext::new_rust(
13143        lsp::ServerCapabilities {
13144            completion_provider: Some(lsp::CompletionOptions {
13145                resolve_provider: Some(true),
13146                ..Default::default()
13147            }),
13148            ..Default::default()
13149        },
13150        cx,
13151    )
13152    .await;
13153
13154    struct Run {
13155        run_description: &'static str,
13156        initial_state: String,
13157        buffer_marked_text: String,
13158        completion_label: &'static str,
13159        completion_text: &'static str,
13160        expected_with_insert_mode: String,
13161        expected_with_replace_mode: String,
13162        expected_with_replace_subsequence_mode: String,
13163        expected_with_replace_suffix_mode: String,
13164    }
13165
13166    let runs = [
13167        Run {
13168            run_description: "Start of word matches completion text",
13169            initial_state: "before ediˇ after".into(),
13170            buffer_marked_text: "before <edi|> after".into(),
13171            completion_label: "editor",
13172            completion_text: "editor",
13173            expected_with_insert_mode: "before editorˇ after".into(),
13174            expected_with_replace_mode: "before editorˇ after".into(),
13175            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13176            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13177        },
13178        Run {
13179            run_description: "Accept same text at the middle of the word",
13180            initial_state: "before ediˇtor after".into(),
13181            buffer_marked_text: "before <edi|tor> after".into(),
13182            completion_label: "editor",
13183            completion_text: "editor",
13184            expected_with_insert_mode: "before editorˇtor after".into(),
13185            expected_with_replace_mode: "before editorˇ after".into(),
13186            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13187            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13188        },
13189        Run {
13190            run_description: "End of word matches completion text -- cursor at end",
13191            initial_state: "before torˇ after".into(),
13192            buffer_marked_text: "before <tor|> after".into(),
13193            completion_label: "editor",
13194            completion_text: "editor",
13195            expected_with_insert_mode: "before editorˇ after".into(),
13196            expected_with_replace_mode: "before editorˇ after".into(),
13197            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13198            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13199        },
13200        Run {
13201            run_description: "End of word matches completion text -- cursor at start",
13202            initial_state: "before ˇtor after".into(),
13203            buffer_marked_text: "before <|tor> after".into(),
13204            completion_label: "editor",
13205            completion_text: "editor",
13206            expected_with_insert_mode: "before editorˇtor after".into(),
13207            expected_with_replace_mode: "before editorˇ after".into(),
13208            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13209            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13210        },
13211        Run {
13212            run_description: "Prepend text containing whitespace",
13213            initial_state: "pˇfield: bool".into(),
13214            buffer_marked_text: "<p|field>: bool".into(),
13215            completion_label: "pub ",
13216            completion_text: "pub ",
13217            expected_with_insert_mode: "pub ˇfield: bool".into(),
13218            expected_with_replace_mode: "pub ˇ: bool".into(),
13219            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13220            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13221        },
13222        Run {
13223            run_description: "Add element to start of list",
13224            initial_state: "[element_ˇelement_2]".into(),
13225            buffer_marked_text: "[<element_|element_2>]".into(),
13226            completion_label: "element_1",
13227            completion_text: "element_1",
13228            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13229            expected_with_replace_mode: "[element_1ˇ]".into(),
13230            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13231            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13232        },
13233        Run {
13234            run_description: "Add element to start of list -- first and second elements are equal",
13235            initial_state: "[elˇelement]".into(),
13236            buffer_marked_text: "[<el|element>]".into(),
13237            completion_label: "element",
13238            completion_text: "element",
13239            expected_with_insert_mode: "[elementˇelement]".into(),
13240            expected_with_replace_mode: "[elementˇ]".into(),
13241            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13242            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13243        },
13244        Run {
13245            run_description: "Ends with matching suffix",
13246            initial_state: "SubˇError".into(),
13247            buffer_marked_text: "<Sub|Error>".into(),
13248            completion_label: "SubscriptionError",
13249            completion_text: "SubscriptionError",
13250            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13251            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13252            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13253            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13254        },
13255        Run {
13256            run_description: "Suffix is a subsequence -- contiguous",
13257            initial_state: "SubˇErr".into(),
13258            buffer_marked_text: "<Sub|Err>".into(),
13259            completion_label: "SubscriptionError",
13260            completion_text: "SubscriptionError",
13261            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13262            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13263            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13264            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13265        },
13266        Run {
13267            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13268            initial_state: "Suˇscrirr".into(),
13269            buffer_marked_text: "<Su|scrirr>".into(),
13270            completion_label: "SubscriptionError",
13271            completion_text: "SubscriptionError",
13272            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13273            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13274            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13275            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13276        },
13277        Run {
13278            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13279            initial_state: "foo(indˇix)".into(),
13280            buffer_marked_text: "foo(<ind|ix>)".into(),
13281            completion_label: "node_index",
13282            completion_text: "node_index",
13283            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13284            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13285            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13286            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13287        },
13288        Run {
13289            run_description: "Replace range ends before cursor - should extend to cursor",
13290            initial_state: "before editˇo after".into(),
13291            buffer_marked_text: "before <{ed}>it|o after".into(),
13292            completion_label: "editor",
13293            completion_text: "editor",
13294            expected_with_insert_mode: "before editorˇo after".into(),
13295            expected_with_replace_mode: "before editorˇo after".into(),
13296            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
13297            expected_with_replace_suffix_mode: "before editorˇo after".into(),
13298        },
13299        Run {
13300            run_description: "Uses label for suffix matching",
13301            initial_state: "before ediˇtor after".into(),
13302            buffer_marked_text: "before <edi|tor> after".into(),
13303            completion_label: "editor",
13304            completion_text: "editor()",
13305            expected_with_insert_mode: "before editor()ˇtor after".into(),
13306            expected_with_replace_mode: "before editor()ˇ after".into(),
13307            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
13308            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
13309        },
13310        Run {
13311            run_description: "Case insensitive subsequence and suffix matching",
13312            initial_state: "before EDiˇtoR after".into(),
13313            buffer_marked_text: "before <EDi|toR> after".into(),
13314            completion_label: "editor",
13315            completion_text: "editor",
13316            expected_with_insert_mode: "before editorˇtoR after".into(),
13317            expected_with_replace_mode: "before editorˇ after".into(),
13318            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13319            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13320        },
13321    ];
13322
13323    for run in runs {
13324        let run_variations = [
13325            (LspInsertMode::Insert, run.expected_with_insert_mode),
13326            (LspInsertMode::Replace, run.expected_with_replace_mode),
13327            (
13328                LspInsertMode::ReplaceSubsequence,
13329                run.expected_with_replace_subsequence_mode,
13330            ),
13331            (
13332                LspInsertMode::ReplaceSuffix,
13333                run.expected_with_replace_suffix_mode,
13334            ),
13335        ];
13336
13337        for (lsp_insert_mode, expected_text) in run_variations {
13338            eprintln!(
13339                "run = {:?}, mode = {lsp_insert_mode:.?}",
13340                run.run_description,
13341            );
13342
13343            update_test_language_settings(&mut cx, |settings| {
13344                settings.defaults.completions = Some(CompletionSettingsContent {
13345                    lsp_insert_mode: Some(lsp_insert_mode),
13346                    words: Some(WordsCompletionMode::Disabled),
13347                    words_min_length: Some(0),
13348                    ..Default::default()
13349                });
13350            });
13351
13352            cx.set_state(&run.initial_state);
13353            cx.update_editor(|editor, window, cx| {
13354                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13355            });
13356
13357            let counter = Arc::new(AtomicUsize::new(0));
13358            handle_completion_request_with_insert_and_replace(
13359                &mut cx,
13360                &run.buffer_marked_text,
13361                vec![(run.completion_label, run.completion_text)],
13362                counter.clone(),
13363            )
13364            .await;
13365            cx.condition(|editor, _| editor.context_menu_visible())
13366                .await;
13367            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13368
13369            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13370                editor
13371                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13372                    .unwrap()
13373            });
13374            cx.assert_editor_state(&expected_text);
13375            handle_resolve_completion_request(&mut cx, None).await;
13376            apply_additional_edits.await.unwrap();
13377        }
13378    }
13379}
13380
13381#[gpui::test]
13382async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13383    init_test(cx, |_| {});
13384    let mut cx = EditorLspTestContext::new_rust(
13385        lsp::ServerCapabilities {
13386            completion_provider: Some(lsp::CompletionOptions {
13387                resolve_provider: Some(true),
13388                ..Default::default()
13389            }),
13390            ..Default::default()
13391        },
13392        cx,
13393    )
13394    .await;
13395
13396    let initial_state = "SubˇError";
13397    let buffer_marked_text = "<Sub|Error>";
13398    let completion_text = "SubscriptionError";
13399    let expected_with_insert_mode = "SubscriptionErrorˇError";
13400    let expected_with_replace_mode = "SubscriptionErrorˇ";
13401
13402    update_test_language_settings(&mut cx, |settings| {
13403        settings.defaults.completions = Some(CompletionSettingsContent {
13404            words: Some(WordsCompletionMode::Disabled),
13405            words_min_length: Some(0),
13406            // set the opposite here to ensure that the action is overriding the default behavior
13407            lsp_insert_mode: Some(LspInsertMode::Insert),
13408            ..Default::default()
13409        });
13410    });
13411
13412    cx.set_state(initial_state);
13413    cx.update_editor(|editor, window, cx| {
13414        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13415    });
13416
13417    let counter = Arc::new(AtomicUsize::new(0));
13418    handle_completion_request_with_insert_and_replace(
13419        &mut cx,
13420        buffer_marked_text,
13421        vec![(completion_text, completion_text)],
13422        counter.clone(),
13423    )
13424    .await;
13425    cx.condition(|editor, _| editor.context_menu_visible())
13426        .await;
13427    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13428
13429    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13430        editor
13431            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13432            .unwrap()
13433    });
13434    cx.assert_editor_state(expected_with_replace_mode);
13435    handle_resolve_completion_request(&mut cx, None).await;
13436    apply_additional_edits.await.unwrap();
13437
13438    update_test_language_settings(&mut cx, |settings| {
13439        settings.defaults.completions = Some(CompletionSettingsContent {
13440            words: Some(WordsCompletionMode::Disabled),
13441            words_min_length: Some(0),
13442            // set the opposite here to ensure that the action is overriding the default behavior
13443            lsp_insert_mode: Some(LspInsertMode::Replace),
13444            ..Default::default()
13445        });
13446    });
13447
13448    cx.set_state(initial_state);
13449    cx.update_editor(|editor, window, cx| {
13450        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13451    });
13452    handle_completion_request_with_insert_and_replace(
13453        &mut cx,
13454        buffer_marked_text,
13455        vec![(completion_text, completion_text)],
13456        counter.clone(),
13457    )
13458    .await;
13459    cx.condition(|editor, _| editor.context_menu_visible())
13460        .await;
13461    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13462
13463    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13464        editor
13465            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13466            .unwrap()
13467    });
13468    cx.assert_editor_state(expected_with_insert_mode);
13469    handle_resolve_completion_request(&mut cx, None).await;
13470    apply_additional_edits.await.unwrap();
13471}
13472
13473#[gpui::test]
13474async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13475    init_test(cx, |_| {});
13476    let mut cx = EditorLspTestContext::new_rust(
13477        lsp::ServerCapabilities {
13478            completion_provider: Some(lsp::CompletionOptions {
13479                resolve_provider: Some(true),
13480                ..Default::default()
13481            }),
13482            ..Default::default()
13483        },
13484        cx,
13485    )
13486    .await;
13487
13488    // scenario: surrounding text matches completion text
13489    let completion_text = "to_offset";
13490    let initial_state = indoc! {"
13491        1. buf.to_offˇsuffix
13492        2. buf.to_offˇsuf
13493        3. buf.to_offˇfix
13494        4. buf.to_offˇ
13495        5. into_offˇensive
13496        6. ˇsuffix
13497        7. let ˇ //
13498        8. aaˇzz
13499        9. buf.to_off«zzzzzˇ»suffix
13500        10. buf.«ˇzzzzz»suffix
13501        11. to_off«ˇzzzzz»
13502
13503        buf.to_offˇsuffix  // newest cursor
13504    "};
13505    let completion_marked_buffer = indoc! {"
13506        1. buf.to_offsuffix
13507        2. buf.to_offsuf
13508        3. buf.to_offfix
13509        4. buf.to_off
13510        5. into_offensive
13511        6. suffix
13512        7. let  //
13513        8. aazz
13514        9. buf.to_offzzzzzsuffix
13515        10. buf.zzzzzsuffix
13516        11. to_offzzzzz
13517
13518        buf.<to_off|suffix>  // newest cursor
13519    "};
13520    let expected = indoc! {"
13521        1. buf.to_offsetˇ
13522        2. buf.to_offsetˇsuf
13523        3. buf.to_offsetˇfix
13524        4. buf.to_offsetˇ
13525        5. into_offsetˇensive
13526        6. to_offsetˇsuffix
13527        7. let to_offsetˇ //
13528        8. aato_offsetˇzz
13529        9. buf.to_offsetˇ
13530        10. buf.to_offsetˇsuffix
13531        11. to_offsetˇ
13532
13533        buf.to_offsetˇ  // newest cursor
13534    "};
13535    cx.set_state(initial_state);
13536    cx.update_editor(|editor, window, cx| {
13537        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13538    });
13539    handle_completion_request_with_insert_and_replace(
13540        &mut cx,
13541        completion_marked_buffer,
13542        vec![(completion_text, completion_text)],
13543        Arc::new(AtomicUsize::new(0)),
13544    )
13545    .await;
13546    cx.condition(|editor, _| editor.context_menu_visible())
13547        .await;
13548    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13549        editor
13550            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13551            .unwrap()
13552    });
13553    cx.assert_editor_state(expected);
13554    handle_resolve_completion_request(&mut cx, None).await;
13555    apply_additional_edits.await.unwrap();
13556
13557    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13558    let completion_text = "foo_and_bar";
13559    let initial_state = indoc! {"
13560        1. ooanbˇ
13561        2. zooanbˇ
13562        3. ooanbˇz
13563        4. zooanbˇz
13564        5. ooanˇ
13565        6. oanbˇ
13566
13567        ooanbˇ
13568    "};
13569    let completion_marked_buffer = indoc! {"
13570        1. ooanb
13571        2. zooanb
13572        3. ooanbz
13573        4. zooanbz
13574        5. ooan
13575        6. oanb
13576
13577        <ooanb|>
13578    "};
13579    let expected = indoc! {"
13580        1. foo_and_barˇ
13581        2. zfoo_and_barˇ
13582        3. foo_and_barˇz
13583        4. zfoo_and_barˇz
13584        5. ooanfoo_and_barˇ
13585        6. oanbfoo_and_barˇ
13586
13587        foo_and_barˇ
13588    "};
13589    cx.set_state(initial_state);
13590    cx.update_editor(|editor, window, cx| {
13591        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13592    });
13593    handle_completion_request_with_insert_and_replace(
13594        &mut cx,
13595        completion_marked_buffer,
13596        vec![(completion_text, completion_text)],
13597        Arc::new(AtomicUsize::new(0)),
13598    )
13599    .await;
13600    cx.condition(|editor, _| editor.context_menu_visible())
13601        .await;
13602    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13603        editor
13604            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13605            .unwrap()
13606    });
13607    cx.assert_editor_state(expected);
13608    handle_resolve_completion_request(&mut cx, None).await;
13609    apply_additional_edits.await.unwrap();
13610
13611    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13612    // (expects the same as if it was inserted at the end)
13613    let completion_text = "foo_and_bar";
13614    let initial_state = indoc! {"
13615        1. ooˇanb
13616        2. zooˇanb
13617        3. ooˇanbz
13618        4. zooˇanbz
13619
13620        ooˇanb
13621    "};
13622    let completion_marked_buffer = indoc! {"
13623        1. ooanb
13624        2. zooanb
13625        3. ooanbz
13626        4. zooanbz
13627
13628        <oo|anb>
13629    "};
13630    let expected = indoc! {"
13631        1. foo_and_barˇ
13632        2. zfoo_and_barˇ
13633        3. foo_and_barˇz
13634        4. zfoo_and_barˇz
13635
13636        foo_and_barˇ
13637    "};
13638    cx.set_state(initial_state);
13639    cx.update_editor(|editor, window, cx| {
13640        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13641    });
13642    handle_completion_request_with_insert_and_replace(
13643        &mut cx,
13644        completion_marked_buffer,
13645        vec![(completion_text, completion_text)],
13646        Arc::new(AtomicUsize::new(0)),
13647    )
13648    .await;
13649    cx.condition(|editor, _| editor.context_menu_visible())
13650        .await;
13651    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13652        editor
13653            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13654            .unwrap()
13655    });
13656    cx.assert_editor_state(expected);
13657    handle_resolve_completion_request(&mut cx, None).await;
13658    apply_additional_edits.await.unwrap();
13659}
13660
13661// This used to crash
13662#[gpui::test]
13663async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13664    init_test(cx, |_| {});
13665
13666    let buffer_text = indoc! {"
13667        fn main() {
13668            10.satu;
13669
13670            //
13671            // separate cursors so they open in different excerpts (manually reproducible)
13672            //
13673
13674            10.satu20;
13675        }
13676    "};
13677    let multibuffer_text_with_selections = indoc! {"
13678        fn main() {
13679            10.satuˇ;
13680
13681            //
13682
13683            //
13684
13685            10.satuˇ20;
13686        }
13687    "};
13688    let expected_multibuffer = indoc! {"
13689        fn main() {
13690            10.saturating_sub()ˇ;
13691
13692            //
13693
13694            //
13695
13696            10.saturating_sub()ˇ;
13697        }
13698    "};
13699
13700    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13701    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13702
13703    let fs = FakeFs::new(cx.executor());
13704    fs.insert_tree(
13705        path!("/a"),
13706        json!({
13707            "main.rs": buffer_text,
13708        }),
13709    )
13710    .await;
13711
13712    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13713    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13714    language_registry.add(rust_lang());
13715    let mut fake_servers = language_registry.register_fake_lsp(
13716        "Rust",
13717        FakeLspAdapter {
13718            capabilities: lsp::ServerCapabilities {
13719                completion_provider: Some(lsp::CompletionOptions {
13720                    resolve_provider: None,
13721                    ..lsp::CompletionOptions::default()
13722                }),
13723                ..lsp::ServerCapabilities::default()
13724            },
13725            ..FakeLspAdapter::default()
13726        },
13727    );
13728    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13729    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13730    let buffer = project
13731        .update(cx, |project, cx| {
13732            project.open_local_buffer(path!("/a/main.rs"), cx)
13733        })
13734        .await
13735        .unwrap();
13736
13737    let multi_buffer = cx.new(|cx| {
13738        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13739        multi_buffer.push_excerpts(
13740            buffer.clone(),
13741            [ExcerptRange::new(0..first_excerpt_end)],
13742            cx,
13743        );
13744        multi_buffer.push_excerpts(
13745            buffer.clone(),
13746            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13747            cx,
13748        );
13749        multi_buffer
13750    });
13751
13752    let editor = workspace
13753        .update(cx, |_, window, cx| {
13754            cx.new(|cx| {
13755                Editor::new(
13756                    EditorMode::Full {
13757                        scale_ui_elements_with_buffer_font_size: false,
13758                        show_active_line_background: false,
13759                        sized_by_content: false,
13760                    },
13761                    multi_buffer.clone(),
13762                    Some(project.clone()),
13763                    window,
13764                    cx,
13765                )
13766            })
13767        })
13768        .unwrap();
13769
13770    let pane = workspace
13771        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13772        .unwrap();
13773    pane.update_in(cx, |pane, window, cx| {
13774        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13775    });
13776
13777    let fake_server = fake_servers.next().await.unwrap();
13778
13779    editor.update_in(cx, |editor, window, cx| {
13780        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13781            s.select_ranges([
13782                Point::new(1, 11)..Point::new(1, 11),
13783                Point::new(7, 11)..Point::new(7, 11),
13784            ])
13785        });
13786
13787        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13788    });
13789
13790    editor.update_in(cx, |editor, window, cx| {
13791        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13792    });
13793
13794    fake_server
13795        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13796            let completion_item = lsp::CompletionItem {
13797                label: "saturating_sub()".into(),
13798                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13799                    lsp::InsertReplaceEdit {
13800                        new_text: "saturating_sub()".to_owned(),
13801                        insert: lsp::Range::new(
13802                            lsp::Position::new(7, 7),
13803                            lsp::Position::new(7, 11),
13804                        ),
13805                        replace: lsp::Range::new(
13806                            lsp::Position::new(7, 7),
13807                            lsp::Position::new(7, 13),
13808                        ),
13809                    },
13810                )),
13811                ..lsp::CompletionItem::default()
13812            };
13813
13814            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13815        })
13816        .next()
13817        .await
13818        .unwrap();
13819
13820    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13821        .await;
13822
13823    editor
13824        .update_in(cx, |editor, window, cx| {
13825            editor
13826                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13827                .unwrap()
13828        })
13829        .await
13830        .unwrap();
13831
13832    editor.update(cx, |editor, cx| {
13833        assert_text_with_selections(editor, expected_multibuffer, cx);
13834    })
13835}
13836
13837#[gpui::test]
13838async fn test_completion(cx: &mut TestAppContext) {
13839    init_test(cx, |_| {});
13840
13841    let mut cx = EditorLspTestContext::new_rust(
13842        lsp::ServerCapabilities {
13843            completion_provider: Some(lsp::CompletionOptions {
13844                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13845                resolve_provider: Some(true),
13846                ..Default::default()
13847            }),
13848            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13849            ..Default::default()
13850        },
13851        cx,
13852    )
13853    .await;
13854    let counter = Arc::new(AtomicUsize::new(0));
13855
13856    cx.set_state(indoc! {"
13857        oneˇ
13858        two
13859        three
13860    "});
13861    cx.simulate_keystroke(".");
13862    handle_completion_request(
13863        indoc! {"
13864            one.|<>
13865            two
13866            three
13867        "},
13868        vec!["first_completion", "second_completion"],
13869        true,
13870        counter.clone(),
13871        &mut cx,
13872    )
13873    .await;
13874    cx.condition(|editor, _| editor.context_menu_visible())
13875        .await;
13876    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13877
13878    let _handler = handle_signature_help_request(
13879        &mut cx,
13880        lsp::SignatureHelp {
13881            signatures: vec![lsp::SignatureInformation {
13882                label: "test signature".to_string(),
13883                documentation: None,
13884                parameters: Some(vec![lsp::ParameterInformation {
13885                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13886                    documentation: None,
13887                }]),
13888                active_parameter: None,
13889            }],
13890            active_signature: None,
13891            active_parameter: None,
13892        },
13893    );
13894    cx.update_editor(|editor, window, cx| {
13895        assert!(
13896            !editor.signature_help_state.is_shown(),
13897            "No signature help was called for"
13898        );
13899        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13900    });
13901    cx.run_until_parked();
13902    cx.update_editor(|editor, _, _| {
13903        assert!(
13904            !editor.signature_help_state.is_shown(),
13905            "No signature help should be shown when completions menu is open"
13906        );
13907    });
13908
13909    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13910        editor.context_menu_next(&Default::default(), window, cx);
13911        editor
13912            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13913            .unwrap()
13914    });
13915    cx.assert_editor_state(indoc! {"
13916        one.second_completionˇ
13917        two
13918        three
13919    "});
13920
13921    handle_resolve_completion_request(
13922        &mut cx,
13923        Some(vec![
13924            (
13925                //This overlaps with the primary completion edit which is
13926                //misbehavior from the LSP spec, test that we filter it out
13927                indoc! {"
13928                    one.second_ˇcompletion
13929                    two
13930                    threeˇ
13931                "},
13932                "overlapping additional edit",
13933            ),
13934            (
13935                indoc! {"
13936                    one.second_completion
13937                    two
13938                    threeˇ
13939                "},
13940                "\nadditional edit",
13941            ),
13942        ]),
13943    )
13944    .await;
13945    apply_additional_edits.await.unwrap();
13946    cx.assert_editor_state(indoc! {"
13947        one.second_completionˇ
13948        two
13949        three
13950        additional edit
13951    "});
13952
13953    cx.set_state(indoc! {"
13954        one.second_completion
13955        twoˇ
13956        threeˇ
13957        additional edit
13958    "});
13959    cx.simulate_keystroke(" ");
13960    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13961    cx.simulate_keystroke("s");
13962    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13963
13964    cx.assert_editor_state(indoc! {"
13965        one.second_completion
13966        two sˇ
13967        three sˇ
13968        additional edit
13969    "});
13970    handle_completion_request(
13971        indoc! {"
13972            one.second_completion
13973            two s
13974            three <s|>
13975            additional edit
13976        "},
13977        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13978        true,
13979        counter.clone(),
13980        &mut cx,
13981    )
13982    .await;
13983    cx.condition(|editor, _| editor.context_menu_visible())
13984        .await;
13985    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13986
13987    cx.simulate_keystroke("i");
13988
13989    handle_completion_request(
13990        indoc! {"
13991            one.second_completion
13992            two si
13993            three <si|>
13994            additional edit
13995        "},
13996        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13997        true,
13998        counter.clone(),
13999        &mut cx,
14000    )
14001    .await;
14002    cx.condition(|editor, _| editor.context_menu_visible())
14003        .await;
14004    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14005
14006    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14007        editor
14008            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14009            .unwrap()
14010    });
14011    cx.assert_editor_state(indoc! {"
14012        one.second_completion
14013        two sixth_completionˇ
14014        three sixth_completionˇ
14015        additional edit
14016    "});
14017
14018    apply_additional_edits.await.unwrap();
14019
14020    update_test_language_settings(&mut cx, |settings| {
14021        settings.defaults.show_completions_on_input = Some(false);
14022    });
14023    cx.set_state("editorˇ");
14024    cx.simulate_keystroke(".");
14025    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14026    cx.simulate_keystrokes("c l o");
14027    cx.assert_editor_state("editor.cloˇ");
14028    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14029    cx.update_editor(|editor, window, cx| {
14030        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14031    });
14032    handle_completion_request(
14033        "editor.<clo|>",
14034        vec!["close", "clobber"],
14035        true,
14036        counter.clone(),
14037        &mut cx,
14038    )
14039    .await;
14040    cx.condition(|editor, _| editor.context_menu_visible())
14041        .await;
14042    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14043
14044    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14045        editor
14046            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14047            .unwrap()
14048    });
14049    cx.assert_editor_state("editor.clobberˇ");
14050    handle_resolve_completion_request(&mut cx, None).await;
14051    apply_additional_edits.await.unwrap();
14052}
14053
14054#[gpui::test]
14055async fn test_completion_reuse(cx: &mut TestAppContext) {
14056    init_test(cx, |_| {});
14057
14058    let mut cx = EditorLspTestContext::new_rust(
14059        lsp::ServerCapabilities {
14060            completion_provider: Some(lsp::CompletionOptions {
14061                trigger_characters: Some(vec![".".to_string()]),
14062                ..Default::default()
14063            }),
14064            ..Default::default()
14065        },
14066        cx,
14067    )
14068    .await;
14069
14070    let counter = Arc::new(AtomicUsize::new(0));
14071    cx.set_state("objˇ");
14072    cx.simulate_keystroke(".");
14073
14074    // Initial completion request returns complete results
14075    let is_incomplete = false;
14076    handle_completion_request(
14077        "obj.|<>",
14078        vec!["a", "ab", "abc"],
14079        is_incomplete,
14080        counter.clone(),
14081        &mut cx,
14082    )
14083    .await;
14084    cx.run_until_parked();
14085    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14086    cx.assert_editor_state("obj.ˇ");
14087    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14088
14089    // Type "a" - filters existing completions
14090    cx.simulate_keystroke("a");
14091    cx.run_until_parked();
14092    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14093    cx.assert_editor_state("obj.aˇ");
14094    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14095
14096    // Type "b" - filters existing completions
14097    cx.simulate_keystroke("b");
14098    cx.run_until_parked();
14099    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14100    cx.assert_editor_state("obj.abˇ");
14101    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14102
14103    // Type "c" - filters existing completions
14104    cx.simulate_keystroke("c");
14105    cx.run_until_parked();
14106    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14107    cx.assert_editor_state("obj.abcˇ");
14108    check_displayed_completions(vec!["abc"], &mut cx);
14109
14110    // Backspace to delete "c" - filters existing completions
14111    cx.update_editor(|editor, window, cx| {
14112        editor.backspace(&Backspace, window, cx);
14113    });
14114    cx.run_until_parked();
14115    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14116    cx.assert_editor_state("obj.abˇ");
14117    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14118
14119    // Moving cursor to the left dismisses menu.
14120    cx.update_editor(|editor, window, cx| {
14121        editor.move_left(&MoveLeft, window, cx);
14122    });
14123    cx.run_until_parked();
14124    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14125    cx.assert_editor_state("obj.aˇb");
14126    cx.update_editor(|editor, _, _| {
14127        assert_eq!(editor.context_menu_visible(), false);
14128    });
14129
14130    // Type "b" - new request
14131    cx.simulate_keystroke("b");
14132    let is_incomplete = false;
14133    handle_completion_request(
14134        "obj.<ab|>a",
14135        vec!["ab", "abc"],
14136        is_incomplete,
14137        counter.clone(),
14138        &mut cx,
14139    )
14140    .await;
14141    cx.run_until_parked();
14142    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14143    cx.assert_editor_state("obj.abˇb");
14144    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14145
14146    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14147    cx.update_editor(|editor, window, cx| {
14148        editor.backspace(&Backspace, window, cx);
14149    });
14150    let is_incomplete = false;
14151    handle_completion_request(
14152        "obj.<a|>b",
14153        vec!["a", "ab", "abc"],
14154        is_incomplete,
14155        counter.clone(),
14156        &mut cx,
14157    )
14158    .await;
14159    cx.run_until_parked();
14160    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14161    cx.assert_editor_state("obj.aˇb");
14162    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14163
14164    // Backspace to delete "a" - dismisses menu.
14165    cx.update_editor(|editor, window, cx| {
14166        editor.backspace(&Backspace, window, cx);
14167    });
14168    cx.run_until_parked();
14169    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14170    cx.assert_editor_state("obj.ˇb");
14171    cx.update_editor(|editor, _, _| {
14172        assert_eq!(editor.context_menu_visible(), false);
14173    });
14174}
14175
14176#[gpui::test]
14177async fn test_word_completion(cx: &mut TestAppContext) {
14178    let lsp_fetch_timeout_ms = 10;
14179    init_test(cx, |language_settings| {
14180        language_settings.defaults.completions = Some(CompletionSettingsContent {
14181            words_min_length: Some(0),
14182            lsp_fetch_timeout_ms: Some(10),
14183            lsp_insert_mode: Some(LspInsertMode::Insert),
14184            ..Default::default()
14185        });
14186    });
14187
14188    let mut cx = EditorLspTestContext::new_rust(
14189        lsp::ServerCapabilities {
14190            completion_provider: Some(lsp::CompletionOptions {
14191                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14192                ..lsp::CompletionOptions::default()
14193            }),
14194            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14195            ..lsp::ServerCapabilities::default()
14196        },
14197        cx,
14198    )
14199    .await;
14200
14201    let throttle_completions = Arc::new(AtomicBool::new(false));
14202
14203    let lsp_throttle_completions = throttle_completions.clone();
14204    let _completion_requests_handler =
14205        cx.lsp
14206            .server
14207            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14208                let lsp_throttle_completions = lsp_throttle_completions.clone();
14209                let cx = cx.clone();
14210                async move {
14211                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14212                        cx.background_executor()
14213                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14214                            .await;
14215                    }
14216                    Ok(Some(lsp::CompletionResponse::Array(vec![
14217                        lsp::CompletionItem {
14218                            label: "first".into(),
14219                            ..lsp::CompletionItem::default()
14220                        },
14221                        lsp::CompletionItem {
14222                            label: "last".into(),
14223                            ..lsp::CompletionItem::default()
14224                        },
14225                    ])))
14226                }
14227            });
14228
14229    cx.set_state(indoc! {"
14230        oneˇ
14231        two
14232        three
14233    "});
14234    cx.simulate_keystroke(".");
14235    cx.executor().run_until_parked();
14236    cx.condition(|editor, _| editor.context_menu_visible())
14237        .await;
14238    cx.update_editor(|editor, window, cx| {
14239        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14240        {
14241            assert_eq!(
14242                completion_menu_entries(menu),
14243                &["first", "last"],
14244                "When LSP server is fast to reply, no fallback word completions are used"
14245            );
14246        } else {
14247            panic!("expected completion menu to be open");
14248        }
14249        editor.cancel(&Cancel, window, cx);
14250    });
14251    cx.executor().run_until_parked();
14252    cx.condition(|editor, _| !editor.context_menu_visible())
14253        .await;
14254
14255    throttle_completions.store(true, atomic::Ordering::Release);
14256    cx.simulate_keystroke(".");
14257    cx.executor()
14258        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14259    cx.executor().run_until_parked();
14260    cx.condition(|editor, _| editor.context_menu_visible())
14261        .await;
14262    cx.update_editor(|editor, _, _| {
14263        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14264        {
14265            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14266                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14267        } else {
14268            panic!("expected completion menu to be open");
14269        }
14270    });
14271}
14272
14273#[gpui::test]
14274async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14275    init_test(cx, |language_settings| {
14276        language_settings.defaults.completions = Some(CompletionSettingsContent {
14277            words: Some(WordsCompletionMode::Enabled),
14278            words_min_length: Some(0),
14279            lsp_insert_mode: Some(LspInsertMode::Insert),
14280            ..Default::default()
14281        });
14282    });
14283
14284    let mut cx = EditorLspTestContext::new_rust(
14285        lsp::ServerCapabilities {
14286            completion_provider: Some(lsp::CompletionOptions {
14287                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14288                ..lsp::CompletionOptions::default()
14289            }),
14290            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14291            ..lsp::ServerCapabilities::default()
14292        },
14293        cx,
14294    )
14295    .await;
14296
14297    let _completion_requests_handler =
14298        cx.lsp
14299            .server
14300            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14301                Ok(Some(lsp::CompletionResponse::Array(vec![
14302                    lsp::CompletionItem {
14303                        label: "first".into(),
14304                        ..lsp::CompletionItem::default()
14305                    },
14306                    lsp::CompletionItem {
14307                        label: "last".into(),
14308                        ..lsp::CompletionItem::default()
14309                    },
14310                ])))
14311            });
14312
14313    cx.set_state(indoc! {"ˇ
14314        first
14315        last
14316        second
14317    "});
14318    cx.simulate_keystroke(".");
14319    cx.executor().run_until_parked();
14320    cx.condition(|editor, _| editor.context_menu_visible())
14321        .await;
14322    cx.update_editor(|editor, _, _| {
14323        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14324        {
14325            assert_eq!(
14326                completion_menu_entries(menu),
14327                &["first", "last", "second"],
14328                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14329            );
14330        } else {
14331            panic!("expected completion menu to be open");
14332        }
14333    });
14334}
14335
14336#[gpui::test]
14337async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14338    init_test(cx, |language_settings| {
14339        language_settings.defaults.completions = Some(CompletionSettingsContent {
14340            words: Some(WordsCompletionMode::Disabled),
14341            words_min_length: Some(0),
14342            lsp_insert_mode: Some(LspInsertMode::Insert),
14343            ..Default::default()
14344        });
14345    });
14346
14347    let mut cx = EditorLspTestContext::new_rust(
14348        lsp::ServerCapabilities {
14349            completion_provider: Some(lsp::CompletionOptions {
14350                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14351                ..lsp::CompletionOptions::default()
14352            }),
14353            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14354            ..lsp::ServerCapabilities::default()
14355        },
14356        cx,
14357    )
14358    .await;
14359
14360    let _completion_requests_handler =
14361        cx.lsp
14362            .server
14363            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14364                panic!("LSP completions should not be queried when dealing with word completions")
14365            });
14366
14367    cx.set_state(indoc! {"ˇ
14368        first
14369        last
14370        second
14371    "});
14372    cx.update_editor(|editor, window, cx| {
14373        editor.show_word_completions(&ShowWordCompletions, window, cx);
14374    });
14375    cx.executor().run_until_parked();
14376    cx.condition(|editor, _| editor.context_menu_visible())
14377        .await;
14378    cx.update_editor(|editor, _, _| {
14379        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14380        {
14381            assert_eq!(
14382                completion_menu_entries(menu),
14383                &["first", "last", "second"],
14384                "`ShowWordCompletions` action should show word completions"
14385            );
14386        } else {
14387            panic!("expected completion menu to be open");
14388        }
14389    });
14390
14391    cx.simulate_keystroke("l");
14392    cx.executor().run_until_parked();
14393    cx.condition(|editor, _| editor.context_menu_visible())
14394        .await;
14395    cx.update_editor(|editor, _, _| {
14396        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14397        {
14398            assert_eq!(
14399                completion_menu_entries(menu),
14400                &["last"],
14401                "After showing word completions, further editing should filter them and not query the LSP"
14402            );
14403        } else {
14404            panic!("expected completion menu to be open");
14405        }
14406    });
14407}
14408
14409#[gpui::test]
14410async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14411    init_test(cx, |language_settings| {
14412        language_settings.defaults.completions = Some(CompletionSettingsContent {
14413            words_min_length: Some(0),
14414            lsp: Some(false),
14415            lsp_insert_mode: Some(LspInsertMode::Insert),
14416            ..Default::default()
14417        });
14418    });
14419
14420    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14421
14422    cx.set_state(indoc! {"ˇ
14423        0_usize
14424        let
14425        33
14426        4.5f32
14427    "});
14428    cx.update_editor(|editor, window, cx| {
14429        editor.show_completions(&ShowCompletions::default(), window, cx);
14430    });
14431    cx.executor().run_until_parked();
14432    cx.condition(|editor, _| editor.context_menu_visible())
14433        .await;
14434    cx.update_editor(|editor, window, cx| {
14435        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14436        {
14437            assert_eq!(
14438                completion_menu_entries(menu),
14439                &["let"],
14440                "With no digits in the completion query, no digits should be in the word completions"
14441            );
14442        } else {
14443            panic!("expected completion menu to be open");
14444        }
14445        editor.cancel(&Cancel, window, cx);
14446    });
14447
14448    cx.set_state(indoc! {"14449        0_usize
14450        let
14451        3
14452        33.35f32
14453    "});
14454    cx.update_editor(|editor, window, cx| {
14455        editor.show_completions(&ShowCompletions::default(), window, cx);
14456    });
14457    cx.executor().run_until_parked();
14458    cx.condition(|editor, _| editor.context_menu_visible())
14459        .await;
14460    cx.update_editor(|editor, _, _| {
14461        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14462        {
14463            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14464                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14465        } else {
14466            panic!("expected completion menu to be open");
14467        }
14468    });
14469}
14470
14471#[gpui::test]
14472async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14473    init_test(cx, |language_settings| {
14474        language_settings.defaults.completions = Some(CompletionSettingsContent {
14475            words: Some(WordsCompletionMode::Enabled),
14476            words_min_length: Some(3),
14477            lsp_insert_mode: Some(LspInsertMode::Insert),
14478            ..Default::default()
14479        });
14480    });
14481
14482    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14483    cx.set_state(indoc! {"ˇ
14484        wow
14485        wowen
14486        wowser
14487    "});
14488    cx.simulate_keystroke("w");
14489    cx.executor().run_until_parked();
14490    cx.update_editor(|editor, _, _| {
14491        if editor.context_menu.borrow_mut().is_some() {
14492            panic!(
14493                "expected completion menu to be hidden, as words completion threshold is not met"
14494            );
14495        }
14496    });
14497
14498    cx.update_editor(|editor, window, cx| {
14499        editor.show_word_completions(&ShowWordCompletions, window, cx);
14500    });
14501    cx.executor().run_until_parked();
14502    cx.update_editor(|editor, window, cx| {
14503        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14504        {
14505            assert_eq!(completion_menu_entries(menu), &["wowser", "wowen", "wow"], "Even though the threshold is not met, invoking word completions with an action should provide the completions");
14506        } else {
14507            panic!("expected completion menu to be open after the word completions are called with an action");
14508        }
14509
14510        editor.cancel(&Cancel, window, cx);
14511    });
14512    cx.update_editor(|editor, _, _| {
14513        if editor.context_menu.borrow_mut().is_some() {
14514            panic!("expected completion menu to be hidden after canceling");
14515        }
14516    });
14517
14518    cx.simulate_keystroke("o");
14519    cx.executor().run_until_parked();
14520    cx.update_editor(|editor, _, _| {
14521        if editor.context_menu.borrow_mut().is_some() {
14522            panic!(
14523                "expected completion menu to be hidden, as words completion threshold is not met still"
14524            );
14525        }
14526    });
14527
14528    cx.simulate_keystroke("w");
14529    cx.executor().run_until_parked();
14530    cx.update_editor(|editor, _, _| {
14531        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14532        {
14533            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14534        } else {
14535            panic!("expected completion menu to be open after the word completions threshold is met");
14536        }
14537    });
14538}
14539
14540#[gpui::test]
14541async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14542    init_test(cx, |language_settings| {
14543        language_settings.defaults.completions = Some(CompletionSettingsContent {
14544            words: Some(WordsCompletionMode::Enabled),
14545            words_min_length: Some(0),
14546            lsp_insert_mode: Some(LspInsertMode::Insert),
14547            ..Default::default()
14548        });
14549    });
14550
14551    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14552    cx.update_editor(|editor, _, _| {
14553        editor.disable_word_completions();
14554    });
14555    cx.set_state(indoc! {"ˇ
14556        wow
14557        wowen
14558        wowser
14559    "});
14560    cx.simulate_keystroke("w");
14561    cx.executor().run_until_parked();
14562    cx.update_editor(|editor, _, _| {
14563        if editor.context_menu.borrow_mut().is_some() {
14564            panic!(
14565                "expected completion menu to be hidden, as words completion are disabled for this editor"
14566            );
14567        }
14568    });
14569
14570    cx.update_editor(|editor, window, cx| {
14571        editor.show_word_completions(&ShowWordCompletions, window, cx);
14572    });
14573    cx.executor().run_until_parked();
14574    cx.update_editor(|editor, _, _| {
14575        if editor.context_menu.borrow_mut().is_some() {
14576            panic!(
14577                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14578            );
14579        }
14580    });
14581}
14582
14583fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14584    let position = || lsp::Position {
14585        line: params.text_document_position.position.line,
14586        character: params.text_document_position.position.character,
14587    };
14588    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14589        range: lsp::Range {
14590            start: position(),
14591            end: position(),
14592        },
14593        new_text: text.to_string(),
14594    }))
14595}
14596
14597#[gpui::test]
14598async fn test_multiline_completion(cx: &mut TestAppContext) {
14599    init_test(cx, |_| {});
14600
14601    let fs = FakeFs::new(cx.executor());
14602    fs.insert_tree(
14603        path!("/a"),
14604        json!({
14605            "main.ts": "a",
14606        }),
14607    )
14608    .await;
14609
14610    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14611    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14612    let typescript_language = Arc::new(Language::new(
14613        LanguageConfig {
14614            name: "TypeScript".into(),
14615            matcher: LanguageMatcher {
14616                path_suffixes: vec!["ts".to_string()],
14617                ..LanguageMatcher::default()
14618            },
14619            line_comments: vec!["// ".into()],
14620            ..LanguageConfig::default()
14621        },
14622        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14623    ));
14624    language_registry.add(typescript_language.clone());
14625    let mut fake_servers = language_registry.register_fake_lsp(
14626        "TypeScript",
14627        FakeLspAdapter {
14628            capabilities: lsp::ServerCapabilities {
14629                completion_provider: Some(lsp::CompletionOptions {
14630                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14631                    ..lsp::CompletionOptions::default()
14632                }),
14633                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14634                ..lsp::ServerCapabilities::default()
14635            },
14636            // Emulate vtsls label generation
14637            label_for_completion: Some(Box::new(|item, _| {
14638                let text = if let Some(description) = item
14639                    .label_details
14640                    .as_ref()
14641                    .and_then(|label_details| label_details.description.as_ref())
14642                {
14643                    format!("{} {}", item.label, description)
14644                } else if let Some(detail) = &item.detail {
14645                    format!("{} {}", item.label, detail)
14646                } else {
14647                    item.label.clone()
14648                };
14649                let len = text.len();
14650                Some(language::CodeLabel {
14651                    text,
14652                    runs: Vec::new(),
14653                    filter_range: 0..len,
14654                })
14655            })),
14656            ..FakeLspAdapter::default()
14657        },
14658    );
14659    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14660    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14661    let worktree_id = workspace
14662        .update(cx, |workspace, _window, cx| {
14663            workspace.project().update(cx, |project, cx| {
14664                project.worktrees(cx).next().unwrap().read(cx).id()
14665            })
14666        })
14667        .unwrap();
14668    let _buffer = project
14669        .update(cx, |project, cx| {
14670            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14671        })
14672        .await
14673        .unwrap();
14674    let editor = workspace
14675        .update(cx, |workspace, window, cx| {
14676            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14677        })
14678        .unwrap()
14679        .await
14680        .unwrap()
14681        .downcast::<Editor>()
14682        .unwrap();
14683    let fake_server = fake_servers.next().await.unwrap();
14684
14685    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14686    let multiline_label_2 = "a\nb\nc\n";
14687    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14688    let multiline_description = "d\ne\nf\n";
14689    let multiline_detail_2 = "g\nh\ni\n";
14690
14691    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14692        move |params, _| async move {
14693            Ok(Some(lsp::CompletionResponse::Array(vec![
14694                lsp::CompletionItem {
14695                    label: multiline_label.to_string(),
14696                    text_edit: gen_text_edit(&params, "new_text_1"),
14697                    ..lsp::CompletionItem::default()
14698                },
14699                lsp::CompletionItem {
14700                    label: "single line label 1".to_string(),
14701                    detail: Some(multiline_detail.to_string()),
14702                    text_edit: gen_text_edit(&params, "new_text_2"),
14703                    ..lsp::CompletionItem::default()
14704                },
14705                lsp::CompletionItem {
14706                    label: "single line label 2".to_string(),
14707                    label_details: Some(lsp::CompletionItemLabelDetails {
14708                        description: Some(multiline_description.to_string()),
14709                        detail: None,
14710                    }),
14711                    text_edit: gen_text_edit(&params, "new_text_2"),
14712                    ..lsp::CompletionItem::default()
14713                },
14714                lsp::CompletionItem {
14715                    label: multiline_label_2.to_string(),
14716                    detail: Some(multiline_detail_2.to_string()),
14717                    text_edit: gen_text_edit(&params, "new_text_3"),
14718                    ..lsp::CompletionItem::default()
14719                },
14720                lsp::CompletionItem {
14721                    label: "Label with many     spaces and \t but without newlines".to_string(),
14722                    detail: Some(
14723                        "Details with many     spaces and \t but without newlines".to_string(),
14724                    ),
14725                    text_edit: gen_text_edit(&params, "new_text_4"),
14726                    ..lsp::CompletionItem::default()
14727                },
14728            ])))
14729        },
14730    );
14731
14732    editor.update_in(cx, |editor, window, cx| {
14733        cx.focus_self(window);
14734        editor.move_to_end(&MoveToEnd, window, cx);
14735        editor.handle_input(".", window, cx);
14736    });
14737    cx.run_until_parked();
14738    completion_handle.next().await.unwrap();
14739
14740    editor.update(cx, |editor, _| {
14741        assert!(editor.context_menu_visible());
14742        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14743        {
14744            let completion_labels = menu
14745                .completions
14746                .borrow()
14747                .iter()
14748                .map(|c| c.label.text.clone())
14749                .collect::<Vec<_>>();
14750            assert_eq!(
14751                completion_labels,
14752                &[
14753                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14754                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14755                    "single line label 2 d e f ",
14756                    "a b c g h i ",
14757                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14758                ],
14759                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14760            );
14761
14762            for completion in menu
14763                .completions
14764                .borrow()
14765                .iter() {
14766                    assert_eq!(
14767                        completion.label.filter_range,
14768                        0..completion.label.text.len(),
14769                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14770                    );
14771                }
14772        } else {
14773            panic!("expected completion menu to be open");
14774        }
14775    });
14776}
14777
14778#[gpui::test]
14779async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14780    init_test(cx, |_| {});
14781    let mut cx = EditorLspTestContext::new_rust(
14782        lsp::ServerCapabilities {
14783            completion_provider: Some(lsp::CompletionOptions {
14784                trigger_characters: Some(vec![".".to_string()]),
14785                ..Default::default()
14786            }),
14787            ..Default::default()
14788        },
14789        cx,
14790    )
14791    .await;
14792    cx.lsp
14793        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14794            Ok(Some(lsp::CompletionResponse::Array(vec![
14795                lsp::CompletionItem {
14796                    label: "first".into(),
14797                    ..Default::default()
14798                },
14799                lsp::CompletionItem {
14800                    label: "last".into(),
14801                    ..Default::default()
14802                },
14803            ])))
14804        });
14805    cx.set_state("variableˇ");
14806    cx.simulate_keystroke(".");
14807    cx.executor().run_until_parked();
14808
14809    cx.update_editor(|editor, _, _| {
14810        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14811        {
14812            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14813        } else {
14814            panic!("expected completion menu to be open");
14815        }
14816    });
14817
14818    cx.update_editor(|editor, window, cx| {
14819        editor.move_page_down(&MovePageDown::default(), window, cx);
14820        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14821        {
14822            assert!(
14823                menu.selected_item == 1,
14824                "expected PageDown to select the last item from the context menu"
14825            );
14826        } else {
14827            panic!("expected completion menu to stay open after PageDown");
14828        }
14829    });
14830
14831    cx.update_editor(|editor, window, cx| {
14832        editor.move_page_up(&MovePageUp::default(), window, cx);
14833        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14834        {
14835            assert!(
14836                menu.selected_item == 0,
14837                "expected PageUp to select the first item from the context menu"
14838            );
14839        } else {
14840            panic!("expected completion menu to stay open after PageUp");
14841        }
14842    });
14843}
14844
14845#[gpui::test]
14846async fn test_as_is_completions(cx: &mut TestAppContext) {
14847    init_test(cx, |_| {});
14848    let mut cx = EditorLspTestContext::new_rust(
14849        lsp::ServerCapabilities {
14850            completion_provider: Some(lsp::CompletionOptions {
14851                ..Default::default()
14852            }),
14853            ..Default::default()
14854        },
14855        cx,
14856    )
14857    .await;
14858    cx.lsp
14859        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14860            Ok(Some(lsp::CompletionResponse::Array(vec![
14861                lsp::CompletionItem {
14862                    label: "unsafe".into(),
14863                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14864                        range: lsp::Range {
14865                            start: lsp::Position {
14866                                line: 1,
14867                                character: 2,
14868                            },
14869                            end: lsp::Position {
14870                                line: 1,
14871                                character: 3,
14872                            },
14873                        },
14874                        new_text: "unsafe".to_string(),
14875                    })),
14876                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14877                    ..Default::default()
14878                },
14879            ])))
14880        });
14881    cx.set_state("fn a() {}\n");
14882    cx.executor().run_until_parked();
14883    cx.update_editor(|editor, window, cx| {
14884        editor.show_completions(
14885            &ShowCompletions {
14886                trigger: Some("\n".into()),
14887            },
14888            window,
14889            cx,
14890        );
14891    });
14892    cx.executor().run_until_parked();
14893
14894    cx.update_editor(|editor, window, cx| {
14895        editor.confirm_completion(&Default::default(), window, cx)
14896    });
14897    cx.executor().run_until_parked();
14898    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14899}
14900
14901#[gpui::test]
14902async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14903    init_test(cx, |_| {});
14904    let language =
14905        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14906    let mut cx = EditorLspTestContext::new(
14907        language,
14908        lsp::ServerCapabilities {
14909            completion_provider: Some(lsp::CompletionOptions {
14910                ..lsp::CompletionOptions::default()
14911            }),
14912            ..lsp::ServerCapabilities::default()
14913        },
14914        cx,
14915    )
14916    .await;
14917
14918    cx.set_state(
14919        "#ifndef BAR_H
14920#define BAR_H
14921
14922#include <stdbool.h>
14923
14924int fn_branch(bool do_branch1, bool do_branch2);
14925
14926#endif // BAR_H
14927ˇ",
14928    );
14929    cx.executor().run_until_parked();
14930    cx.update_editor(|editor, window, cx| {
14931        editor.handle_input("#", window, cx);
14932    });
14933    cx.executor().run_until_parked();
14934    cx.update_editor(|editor, window, cx| {
14935        editor.handle_input("i", window, cx);
14936    });
14937    cx.executor().run_until_parked();
14938    cx.update_editor(|editor, window, cx| {
14939        editor.handle_input("n", window, cx);
14940    });
14941    cx.executor().run_until_parked();
14942    cx.assert_editor_state(
14943        "#ifndef BAR_H
14944#define BAR_H
14945
14946#include <stdbool.h>
14947
14948int fn_branch(bool do_branch1, bool do_branch2);
14949
14950#endif // BAR_H
14951#inˇ",
14952    );
14953
14954    cx.lsp
14955        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14956            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14957                is_incomplete: false,
14958                item_defaults: None,
14959                items: vec![lsp::CompletionItem {
14960                    kind: Some(lsp::CompletionItemKind::SNIPPET),
14961                    label_details: Some(lsp::CompletionItemLabelDetails {
14962                        detail: Some("header".to_string()),
14963                        description: None,
14964                    }),
14965                    label: " include".to_string(),
14966                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14967                        range: lsp::Range {
14968                            start: lsp::Position {
14969                                line: 8,
14970                                character: 1,
14971                            },
14972                            end: lsp::Position {
14973                                line: 8,
14974                                character: 1,
14975                            },
14976                        },
14977                        new_text: "include \"$0\"".to_string(),
14978                    })),
14979                    sort_text: Some("40b67681include".to_string()),
14980                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14981                    filter_text: Some("include".to_string()),
14982                    insert_text: Some("include \"$0\"".to_string()),
14983                    ..lsp::CompletionItem::default()
14984                }],
14985            })))
14986        });
14987    cx.update_editor(|editor, window, cx| {
14988        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14989    });
14990    cx.executor().run_until_parked();
14991    cx.update_editor(|editor, window, cx| {
14992        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
14993    });
14994    cx.executor().run_until_parked();
14995    cx.assert_editor_state(
14996        "#ifndef BAR_H
14997#define BAR_H
14998
14999#include <stdbool.h>
15000
15001int fn_branch(bool do_branch1, bool do_branch2);
15002
15003#endif // BAR_H
15004#include \"ˇ\"",
15005    );
15006
15007    cx.lsp
15008        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15009            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15010                is_incomplete: true,
15011                item_defaults: None,
15012                items: vec![lsp::CompletionItem {
15013                    kind: Some(lsp::CompletionItemKind::FILE),
15014                    label: "AGL/".to_string(),
15015                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15016                        range: lsp::Range {
15017                            start: lsp::Position {
15018                                line: 8,
15019                                character: 10,
15020                            },
15021                            end: lsp::Position {
15022                                line: 8,
15023                                character: 11,
15024                            },
15025                        },
15026                        new_text: "AGL/".to_string(),
15027                    })),
15028                    sort_text: Some("40b67681AGL/".to_string()),
15029                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15030                    filter_text: Some("AGL/".to_string()),
15031                    insert_text: Some("AGL/".to_string()),
15032                    ..lsp::CompletionItem::default()
15033                }],
15034            })))
15035        });
15036    cx.update_editor(|editor, window, cx| {
15037        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15038    });
15039    cx.executor().run_until_parked();
15040    cx.update_editor(|editor, window, cx| {
15041        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15042    });
15043    cx.executor().run_until_parked();
15044    cx.assert_editor_state(
15045        r##"#ifndef BAR_H
15046#define BAR_H
15047
15048#include <stdbool.h>
15049
15050int fn_branch(bool do_branch1, bool do_branch2);
15051
15052#endif // BAR_H
15053#include "AGL/ˇ"##,
15054    );
15055
15056    cx.update_editor(|editor, window, cx| {
15057        editor.handle_input("\"", window, cx);
15058    });
15059    cx.executor().run_until_parked();
15060    cx.assert_editor_state(
15061        r##"#ifndef BAR_H
15062#define BAR_H
15063
15064#include <stdbool.h>
15065
15066int fn_branch(bool do_branch1, bool do_branch2);
15067
15068#endif // BAR_H
15069#include "AGL/"ˇ"##,
15070    );
15071}
15072
15073#[gpui::test]
15074async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15075    init_test(cx, |_| {});
15076
15077    let mut cx = EditorLspTestContext::new_rust(
15078        lsp::ServerCapabilities {
15079            completion_provider: Some(lsp::CompletionOptions {
15080                trigger_characters: Some(vec![".".to_string()]),
15081                resolve_provider: Some(true),
15082                ..Default::default()
15083            }),
15084            ..Default::default()
15085        },
15086        cx,
15087    )
15088    .await;
15089
15090    cx.set_state("fn main() { let a = 2ˇ; }");
15091    cx.simulate_keystroke(".");
15092    let completion_item = lsp::CompletionItem {
15093        label: "Some".into(),
15094        kind: Some(lsp::CompletionItemKind::SNIPPET),
15095        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15096        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15097            kind: lsp::MarkupKind::Markdown,
15098            value: "```rust\nSome(2)\n```".to_string(),
15099        })),
15100        deprecated: Some(false),
15101        sort_text: Some("Some".to_string()),
15102        filter_text: Some("Some".to_string()),
15103        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15104        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15105            range: lsp::Range {
15106                start: lsp::Position {
15107                    line: 0,
15108                    character: 22,
15109                },
15110                end: lsp::Position {
15111                    line: 0,
15112                    character: 22,
15113                },
15114            },
15115            new_text: "Some(2)".to_string(),
15116        })),
15117        additional_text_edits: Some(vec![lsp::TextEdit {
15118            range: lsp::Range {
15119                start: lsp::Position {
15120                    line: 0,
15121                    character: 20,
15122                },
15123                end: lsp::Position {
15124                    line: 0,
15125                    character: 22,
15126                },
15127            },
15128            new_text: "".to_string(),
15129        }]),
15130        ..Default::default()
15131    };
15132
15133    let closure_completion_item = completion_item.clone();
15134    let counter = Arc::new(AtomicUsize::new(0));
15135    let counter_clone = counter.clone();
15136    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15137        let task_completion_item = closure_completion_item.clone();
15138        counter_clone.fetch_add(1, atomic::Ordering::Release);
15139        async move {
15140            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15141                is_incomplete: true,
15142                item_defaults: None,
15143                items: vec![task_completion_item],
15144            })))
15145        }
15146    });
15147
15148    cx.condition(|editor, _| editor.context_menu_visible())
15149        .await;
15150    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15151    assert!(request.next().await.is_some());
15152    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15153
15154    cx.simulate_keystrokes("S o m");
15155    cx.condition(|editor, _| editor.context_menu_visible())
15156        .await;
15157    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15158    assert!(request.next().await.is_some());
15159    assert!(request.next().await.is_some());
15160    assert!(request.next().await.is_some());
15161    request.close();
15162    assert!(request.next().await.is_none());
15163    assert_eq!(
15164        counter.load(atomic::Ordering::Acquire),
15165        4,
15166        "With the completions menu open, only one LSP request should happen per input"
15167    );
15168}
15169
15170#[gpui::test]
15171async fn test_toggle_comment(cx: &mut TestAppContext) {
15172    init_test(cx, |_| {});
15173    let mut cx = EditorTestContext::new(cx).await;
15174    let language = Arc::new(Language::new(
15175        LanguageConfig {
15176            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15177            ..Default::default()
15178        },
15179        Some(tree_sitter_rust::LANGUAGE.into()),
15180    ));
15181    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15182
15183    // If multiple selections intersect a line, the line is only toggled once.
15184    cx.set_state(indoc! {"
15185        fn a() {
15186            «//b();
15187            ˇ»// «c();
15188            //ˇ»  d();
15189        }
15190    "});
15191
15192    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15193
15194    cx.assert_editor_state(indoc! {"
15195        fn a() {
15196            «b();
15197            c();
15198            ˇ» d();
15199        }
15200    "});
15201
15202    // The comment prefix is inserted at the same column for every line in a
15203    // selection.
15204    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15205
15206    cx.assert_editor_state(indoc! {"
15207        fn a() {
15208            // «b();
15209            // c();
15210            ˇ»//  d();
15211        }
15212    "});
15213
15214    // If a selection ends at the beginning of a line, that line is not toggled.
15215    cx.set_selections_state(indoc! {"
15216        fn a() {
15217            // b();
15218            «// c();
15219        ˇ»    //  d();
15220        }
15221    "});
15222
15223    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15224
15225    cx.assert_editor_state(indoc! {"
15226        fn a() {
15227            // b();
15228            «c();
15229        ˇ»    //  d();
15230        }
15231    "});
15232
15233    // If a selection span a single line and is empty, the line is toggled.
15234    cx.set_state(indoc! {"
15235        fn a() {
15236            a();
15237            b();
15238        ˇ
15239        }
15240    "});
15241
15242    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15243
15244    cx.assert_editor_state(indoc! {"
15245        fn a() {
15246            a();
15247            b();
15248        //•ˇ
15249        }
15250    "});
15251
15252    // If a selection span multiple lines, empty lines are not toggled.
15253    cx.set_state(indoc! {"
15254        fn a() {
15255            «a();
15256
15257            c();ˇ»
15258        }
15259    "});
15260
15261    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15262
15263    cx.assert_editor_state(indoc! {"
15264        fn a() {
15265            // «a();
15266
15267            // c();ˇ»
15268        }
15269    "});
15270
15271    // If a selection includes multiple comment prefixes, all lines are uncommented.
15272    cx.set_state(indoc! {"
15273        fn a() {
15274            «// a();
15275            /// b();
15276            //! c();ˇ»
15277        }
15278    "});
15279
15280    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15281
15282    cx.assert_editor_state(indoc! {"
15283        fn a() {
15284            «a();
15285            b();
15286            c();ˇ»
15287        }
15288    "});
15289}
15290
15291#[gpui::test]
15292async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15293    init_test(cx, |_| {});
15294    let mut cx = EditorTestContext::new(cx).await;
15295    let language = Arc::new(Language::new(
15296        LanguageConfig {
15297            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15298            ..Default::default()
15299        },
15300        Some(tree_sitter_rust::LANGUAGE.into()),
15301    ));
15302    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15303
15304    let toggle_comments = &ToggleComments {
15305        advance_downwards: false,
15306        ignore_indent: true,
15307    };
15308
15309    // If multiple selections intersect a line, the line is only toggled once.
15310    cx.set_state(indoc! {"
15311        fn a() {
15312        //    «b();
15313        //    c();
15314        //    ˇ» d();
15315        }
15316    "});
15317
15318    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15319
15320    cx.assert_editor_state(indoc! {"
15321        fn a() {
15322            «b();
15323            c();
15324            ˇ» d();
15325        }
15326    "});
15327
15328    // The comment prefix is inserted at the beginning of each line
15329    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15330
15331    cx.assert_editor_state(indoc! {"
15332        fn a() {
15333        //    «b();
15334        //    c();
15335        //    ˇ» d();
15336        }
15337    "});
15338
15339    // If a selection ends at the beginning of a line, that line is not toggled.
15340    cx.set_selections_state(indoc! {"
15341        fn a() {
15342        //    b();
15343        //    «c();
15344        ˇ»//     d();
15345        }
15346    "});
15347
15348    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15349
15350    cx.assert_editor_state(indoc! {"
15351        fn a() {
15352        //    b();
15353            «c();
15354        ˇ»//     d();
15355        }
15356    "});
15357
15358    // If a selection span a single line and is empty, the line is toggled.
15359    cx.set_state(indoc! {"
15360        fn a() {
15361            a();
15362            b();
15363        ˇ
15364        }
15365    "});
15366
15367    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15368
15369    cx.assert_editor_state(indoc! {"
15370        fn a() {
15371            a();
15372            b();
15373        //ˇ
15374        }
15375    "});
15376
15377    // If a selection span multiple lines, empty lines are not toggled.
15378    cx.set_state(indoc! {"
15379        fn a() {
15380            «a();
15381
15382            c();ˇ»
15383        }
15384    "});
15385
15386    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15387
15388    cx.assert_editor_state(indoc! {"
15389        fn a() {
15390        //    «a();
15391
15392        //    c();ˇ»
15393        }
15394    "});
15395
15396    // If a selection includes multiple comment prefixes, all lines are uncommented.
15397    cx.set_state(indoc! {"
15398        fn a() {
15399        //    «a();
15400        ///    b();
15401        //!    c();ˇ»
15402        }
15403    "});
15404
15405    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15406
15407    cx.assert_editor_state(indoc! {"
15408        fn a() {
15409            «a();
15410            b();
15411            c();ˇ»
15412        }
15413    "});
15414}
15415
15416#[gpui::test]
15417async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15418    init_test(cx, |_| {});
15419
15420    let language = Arc::new(Language::new(
15421        LanguageConfig {
15422            line_comments: vec!["// ".into()],
15423            ..Default::default()
15424        },
15425        Some(tree_sitter_rust::LANGUAGE.into()),
15426    ));
15427
15428    let mut cx = EditorTestContext::new(cx).await;
15429
15430    cx.language_registry().add(language.clone());
15431    cx.update_buffer(|buffer, cx| {
15432        buffer.set_language(Some(language), cx);
15433    });
15434
15435    let toggle_comments = &ToggleComments {
15436        advance_downwards: true,
15437        ignore_indent: false,
15438    };
15439
15440    // Single cursor on one line -> advance
15441    // Cursor moves horizontally 3 characters as well on non-blank line
15442    cx.set_state(indoc!(
15443        "fn a() {
15444             ˇdog();
15445             cat();
15446        }"
15447    ));
15448    cx.update_editor(|editor, window, cx| {
15449        editor.toggle_comments(toggle_comments, window, cx);
15450    });
15451    cx.assert_editor_state(indoc!(
15452        "fn a() {
15453             // dog();
15454             catˇ();
15455        }"
15456    ));
15457
15458    // Single selection on one line -> don't advance
15459    cx.set_state(indoc!(
15460        "fn a() {
15461             «dog()ˇ»;
15462             cat();
15463        }"
15464    ));
15465    cx.update_editor(|editor, window, cx| {
15466        editor.toggle_comments(toggle_comments, window, cx);
15467    });
15468    cx.assert_editor_state(indoc!(
15469        "fn a() {
15470             // «dog()ˇ»;
15471             cat();
15472        }"
15473    ));
15474
15475    // Multiple cursors on one line -> advance
15476    cx.set_state(indoc!(
15477        "fn a() {
15478             ˇdˇog();
15479             cat();
15480        }"
15481    ));
15482    cx.update_editor(|editor, window, cx| {
15483        editor.toggle_comments(toggle_comments, window, cx);
15484    });
15485    cx.assert_editor_state(indoc!(
15486        "fn a() {
15487             // dog();
15488             catˇ(ˇ);
15489        }"
15490    ));
15491
15492    // Multiple cursors on one line, with selection -> don't advance
15493    cx.set_state(indoc!(
15494        "fn a() {
15495             ˇdˇog«()ˇ»;
15496             cat();
15497        }"
15498    ));
15499    cx.update_editor(|editor, window, cx| {
15500        editor.toggle_comments(toggle_comments, window, cx);
15501    });
15502    cx.assert_editor_state(indoc!(
15503        "fn a() {
15504             // ˇdˇog«()ˇ»;
15505             cat();
15506        }"
15507    ));
15508
15509    // Single cursor on one line -> advance
15510    // Cursor moves to column 0 on blank line
15511    cx.set_state(indoc!(
15512        "fn a() {
15513             ˇdog();
15514
15515             cat();
15516        }"
15517    ));
15518    cx.update_editor(|editor, window, cx| {
15519        editor.toggle_comments(toggle_comments, window, cx);
15520    });
15521    cx.assert_editor_state(indoc!(
15522        "fn a() {
15523             // dog();
15524        ˇ
15525             cat();
15526        }"
15527    ));
15528
15529    // Single cursor on one line -> advance
15530    // Cursor starts and ends at column 0
15531    cx.set_state(indoc!(
15532        "fn a() {
15533         ˇ    dog();
15534             cat();
15535        }"
15536    ));
15537    cx.update_editor(|editor, window, cx| {
15538        editor.toggle_comments(toggle_comments, window, cx);
15539    });
15540    cx.assert_editor_state(indoc!(
15541        "fn a() {
15542             // dog();
15543         ˇ    cat();
15544        }"
15545    ));
15546}
15547
15548#[gpui::test]
15549async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15550    init_test(cx, |_| {});
15551
15552    let mut cx = EditorTestContext::new(cx).await;
15553
15554    let html_language = Arc::new(
15555        Language::new(
15556            LanguageConfig {
15557                name: "HTML".into(),
15558                block_comment: Some(BlockCommentConfig {
15559                    start: "<!-- ".into(),
15560                    prefix: "".into(),
15561                    end: " -->".into(),
15562                    tab_size: 0,
15563                }),
15564                ..Default::default()
15565            },
15566            Some(tree_sitter_html::LANGUAGE.into()),
15567        )
15568        .with_injection_query(
15569            r#"
15570            (script_element
15571                (raw_text) @injection.content
15572                (#set! injection.language "javascript"))
15573            "#,
15574        )
15575        .unwrap(),
15576    );
15577
15578    let javascript_language = Arc::new(Language::new(
15579        LanguageConfig {
15580            name: "JavaScript".into(),
15581            line_comments: vec!["// ".into()],
15582            ..Default::default()
15583        },
15584        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15585    ));
15586
15587    cx.language_registry().add(html_language.clone());
15588    cx.language_registry().add(javascript_language);
15589    cx.update_buffer(|buffer, cx| {
15590        buffer.set_language(Some(html_language), cx);
15591    });
15592
15593    // Toggle comments for empty selections
15594    cx.set_state(
15595        &r#"
15596            <p>A</p>ˇ
15597            <p>B</p>ˇ
15598            <p>C</p>ˇ
15599        "#
15600        .unindent(),
15601    );
15602    cx.update_editor(|editor, window, cx| {
15603        editor.toggle_comments(&ToggleComments::default(), window, cx)
15604    });
15605    cx.assert_editor_state(
15606        &r#"
15607            <!-- <p>A</p>ˇ -->
15608            <!-- <p>B</p>ˇ -->
15609            <!-- <p>C</p>ˇ -->
15610        "#
15611        .unindent(),
15612    );
15613    cx.update_editor(|editor, window, cx| {
15614        editor.toggle_comments(&ToggleComments::default(), window, cx)
15615    });
15616    cx.assert_editor_state(
15617        &r#"
15618            <p>A</p>ˇ
15619            <p>B</p>ˇ
15620            <p>C</p>ˇ
15621        "#
15622        .unindent(),
15623    );
15624
15625    // Toggle comments for mixture of empty and non-empty selections, where
15626    // multiple selections occupy a given line.
15627    cx.set_state(
15628        &r#"
15629            <p>A«</p>
15630            <p>ˇ»B</p>ˇ
15631            <p>C«</p>
15632            <p>ˇ»D</p>ˇ
15633        "#
15634        .unindent(),
15635    );
15636
15637    cx.update_editor(|editor, window, cx| {
15638        editor.toggle_comments(&ToggleComments::default(), window, cx)
15639    });
15640    cx.assert_editor_state(
15641        &r#"
15642            <!-- <p>A«</p>
15643            <p>ˇ»B</p>ˇ -->
15644            <!-- <p>C«</p>
15645            <p>ˇ»D</p>ˇ -->
15646        "#
15647        .unindent(),
15648    );
15649    cx.update_editor(|editor, window, cx| {
15650        editor.toggle_comments(&ToggleComments::default(), window, cx)
15651    });
15652    cx.assert_editor_state(
15653        &r#"
15654            <p>A«</p>
15655            <p>ˇ»B</p>ˇ
15656            <p>C«</p>
15657            <p>ˇ»D</p>ˇ
15658        "#
15659        .unindent(),
15660    );
15661
15662    // Toggle comments when different languages are active for different
15663    // selections.
15664    cx.set_state(
15665        &r#"
15666            ˇ<script>
15667                ˇvar x = new Y();
15668            ˇ</script>
15669        "#
15670        .unindent(),
15671    );
15672    cx.executor().run_until_parked();
15673    cx.update_editor(|editor, window, cx| {
15674        editor.toggle_comments(&ToggleComments::default(), window, cx)
15675    });
15676    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15677    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15678    cx.assert_editor_state(
15679        &r#"
15680            <!-- ˇ<script> -->
15681                // ˇvar x = new Y();
15682            <!-- ˇ</script> -->
15683        "#
15684        .unindent(),
15685    );
15686}
15687
15688#[gpui::test]
15689fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15690    init_test(cx, |_| {});
15691
15692    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15693    let multibuffer = cx.new(|cx| {
15694        let mut multibuffer = MultiBuffer::new(ReadWrite);
15695        multibuffer.push_excerpts(
15696            buffer.clone(),
15697            [
15698                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15699                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15700            ],
15701            cx,
15702        );
15703        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15704        multibuffer
15705    });
15706
15707    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15708    editor.update_in(cx, |editor, window, cx| {
15709        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15710        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15711            s.select_ranges([
15712                Point::new(0, 0)..Point::new(0, 0),
15713                Point::new(1, 0)..Point::new(1, 0),
15714            ])
15715        });
15716
15717        editor.handle_input("X", window, cx);
15718        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15719        assert_eq!(
15720            editor.selections.ranges(cx),
15721            [
15722                Point::new(0, 1)..Point::new(0, 1),
15723                Point::new(1, 1)..Point::new(1, 1),
15724            ]
15725        );
15726
15727        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15728        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15729            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15730        });
15731        editor.backspace(&Default::default(), window, cx);
15732        assert_eq!(editor.text(cx), "Xa\nbbb");
15733        assert_eq!(
15734            editor.selections.ranges(cx),
15735            [Point::new(1, 0)..Point::new(1, 0)]
15736        );
15737
15738        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15739            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15740        });
15741        editor.backspace(&Default::default(), window, cx);
15742        assert_eq!(editor.text(cx), "X\nbb");
15743        assert_eq!(
15744            editor.selections.ranges(cx),
15745            [Point::new(0, 1)..Point::new(0, 1)]
15746        );
15747    });
15748}
15749
15750#[gpui::test]
15751fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15752    init_test(cx, |_| {});
15753
15754    let markers = vec![('[', ']').into(), ('(', ')').into()];
15755    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15756        indoc! {"
15757            [aaaa
15758            (bbbb]
15759            cccc)",
15760        },
15761        markers.clone(),
15762    );
15763    let excerpt_ranges = markers.into_iter().map(|marker| {
15764        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15765        ExcerptRange::new(context)
15766    });
15767    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15768    let multibuffer = cx.new(|cx| {
15769        let mut multibuffer = MultiBuffer::new(ReadWrite);
15770        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15771        multibuffer
15772    });
15773
15774    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15775    editor.update_in(cx, |editor, window, cx| {
15776        let (expected_text, selection_ranges) = marked_text_ranges(
15777            indoc! {"
15778                aaaa
15779                bˇbbb
15780                bˇbbˇb
15781                cccc"
15782            },
15783            true,
15784        );
15785        assert_eq!(editor.text(cx), expected_text);
15786        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15787            s.select_ranges(selection_ranges)
15788        });
15789
15790        editor.handle_input("X", window, cx);
15791
15792        let (expected_text, expected_selections) = marked_text_ranges(
15793            indoc! {"
15794                aaaa
15795                bXˇbbXb
15796                bXˇbbXˇb
15797                cccc"
15798            },
15799            false,
15800        );
15801        assert_eq!(editor.text(cx), expected_text);
15802        assert_eq!(editor.selections.ranges(cx), expected_selections);
15803
15804        editor.newline(&Newline, window, cx);
15805        let (expected_text, expected_selections) = marked_text_ranges(
15806            indoc! {"
15807                aaaa
15808                bX
15809                ˇbbX
15810                b
15811                bX
15812                ˇbbX
15813                ˇb
15814                cccc"
15815            },
15816            false,
15817        );
15818        assert_eq!(editor.text(cx), expected_text);
15819        assert_eq!(editor.selections.ranges(cx), expected_selections);
15820    });
15821}
15822
15823#[gpui::test]
15824fn test_refresh_selections(cx: &mut TestAppContext) {
15825    init_test(cx, |_| {});
15826
15827    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15828    let mut excerpt1_id = None;
15829    let multibuffer = cx.new(|cx| {
15830        let mut multibuffer = MultiBuffer::new(ReadWrite);
15831        excerpt1_id = multibuffer
15832            .push_excerpts(
15833                buffer.clone(),
15834                [
15835                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15836                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15837                ],
15838                cx,
15839            )
15840            .into_iter()
15841            .next();
15842        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15843        multibuffer
15844    });
15845
15846    let editor = cx.add_window(|window, cx| {
15847        let mut editor = build_editor(multibuffer.clone(), window, cx);
15848        let snapshot = editor.snapshot(window, cx);
15849        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15850            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15851        });
15852        editor.begin_selection(
15853            Point::new(2, 1).to_display_point(&snapshot),
15854            true,
15855            1,
15856            window,
15857            cx,
15858        );
15859        assert_eq!(
15860            editor.selections.ranges(cx),
15861            [
15862                Point::new(1, 3)..Point::new(1, 3),
15863                Point::new(2, 1)..Point::new(2, 1),
15864            ]
15865        );
15866        editor
15867    });
15868
15869    // Refreshing selections is a no-op when excerpts haven't changed.
15870    _ = editor.update(cx, |editor, window, cx| {
15871        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15872        assert_eq!(
15873            editor.selections.ranges(cx),
15874            [
15875                Point::new(1, 3)..Point::new(1, 3),
15876                Point::new(2, 1)..Point::new(2, 1),
15877            ]
15878        );
15879    });
15880
15881    multibuffer.update(cx, |multibuffer, cx| {
15882        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15883    });
15884    _ = editor.update(cx, |editor, window, cx| {
15885        // Removing an excerpt causes the first selection to become degenerate.
15886        assert_eq!(
15887            editor.selections.ranges(cx),
15888            [
15889                Point::new(0, 0)..Point::new(0, 0),
15890                Point::new(0, 1)..Point::new(0, 1)
15891            ]
15892        );
15893
15894        // Refreshing selections will relocate the first selection to the original buffer
15895        // location.
15896        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15897        assert_eq!(
15898            editor.selections.ranges(cx),
15899            [
15900                Point::new(0, 1)..Point::new(0, 1),
15901                Point::new(0, 3)..Point::new(0, 3)
15902            ]
15903        );
15904        assert!(editor.selections.pending_anchor().is_some());
15905    });
15906}
15907
15908#[gpui::test]
15909fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15910    init_test(cx, |_| {});
15911
15912    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15913    let mut excerpt1_id = None;
15914    let multibuffer = cx.new(|cx| {
15915        let mut multibuffer = MultiBuffer::new(ReadWrite);
15916        excerpt1_id = multibuffer
15917            .push_excerpts(
15918                buffer.clone(),
15919                [
15920                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15921                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15922                ],
15923                cx,
15924            )
15925            .into_iter()
15926            .next();
15927        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15928        multibuffer
15929    });
15930
15931    let editor = cx.add_window(|window, cx| {
15932        let mut editor = build_editor(multibuffer.clone(), window, cx);
15933        let snapshot = editor.snapshot(window, cx);
15934        editor.begin_selection(
15935            Point::new(1, 3).to_display_point(&snapshot),
15936            false,
15937            1,
15938            window,
15939            cx,
15940        );
15941        assert_eq!(
15942            editor.selections.ranges(cx),
15943            [Point::new(1, 3)..Point::new(1, 3)]
15944        );
15945        editor
15946    });
15947
15948    multibuffer.update(cx, |multibuffer, cx| {
15949        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15950    });
15951    _ = editor.update(cx, |editor, window, cx| {
15952        assert_eq!(
15953            editor.selections.ranges(cx),
15954            [Point::new(0, 0)..Point::new(0, 0)]
15955        );
15956
15957        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
15958        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15959        assert_eq!(
15960            editor.selections.ranges(cx),
15961            [Point::new(0, 3)..Point::new(0, 3)]
15962        );
15963        assert!(editor.selections.pending_anchor().is_some());
15964    });
15965}
15966
15967#[gpui::test]
15968async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
15969    init_test(cx, |_| {});
15970
15971    let language = Arc::new(
15972        Language::new(
15973            LanguageConfig {
15974                brackets: BracketPairConfig {
15975                    pairs: vec![
15976                        BracketPair {
15977                            start: "{".to_string(),
15978                            end: "}".to_string(),
15979                            close: true,
15980                            surround: true,
15981                            newline: true,
15982                        },
15983                        BracketPair {
15984                            start: "/* ".to_string(),
15985                            end: " */".to_string(),
15986                            close: true,
15987                            surround: true,
15988                            newline: true,
15989                        },
15990                    ],
15991                    ..Default::default()
15992                },
15993                ..Default::default()
15994            },
15995            Some(tree_sitter_rust::LANGUAGE.into()),
15996        )
15997        .with_indents_query("")
15998        .unwrap(),
15999    );
16000
16001    let text = concat!(
16002        "{   }\n",     //
16003        "  x\n",       //
16004        "  /*   */\n", //
16005        "x\n",         //
16006        "{{} }\n",     //
16007    );
16008
16009    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16010    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16011    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16012    editor
16013        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16014        .await;
16015
16016    editor.update_in(cx, |editor, window, cx| {
16017        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16018            s.select_display_ranges([
16019                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16020                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16021                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16022            ])
16023        });
16024        editor.newline(&Newline, window, cx);
16025
16026        assert_eq!(
16027            editor.buffer().read(cx).read(cx).text(),
16028            concat!(
16029                "{ \n",    // Suppress rustfmt
16030                "\n",      //
16031                "}\n",     //
16032                "  x\n",   //
16033                "  /* \n", //
16034                "  \n",    //
16035                "  */\n",  //
16036                "x\n",     //
16037                "{{} \n",  //
16038                "}\n",     //
16039            )
16040        );
16041    });
16042}
16043
16044#[gpui::test]
16045fn test_highlighted_ranges(cx: &mut TestAppContext) {
16046    init_test(cx, |_| {});
16047
16048    let editor = cx.add_window(|window, cx| {
16049        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16050        build_editor(buffer, window, cx)
16051    });
16052
16053    _ = editor.update(cx, |editor, window, cx| {
16054        struct Type1;
16055        struct Type2;
16056
16057        let buffer = editor.buffer.read(cx).snapshot(cx);
16058
16059        let anchor_range =
16060            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16061
16062        editor.highlight_background::<Type1>(
16063            &[
16064                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16065                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16066                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16067                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16068            ],
16069            |_| Hsla::red(),
16070            cx,
16071        );
16072        editor.highlight_background::<Type2>(
16073            &[
16074                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16075                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16076                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16077                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16078            ],
16079            |_| Hsla::green(),
16080            cx,
16081        );
16082
16083        let snapshot = editor.snapshot(window, cx);
16084        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16085            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16086            &snapshot,
16087            cx.theme(),
16088        );
16089        assert_eq!(
16090            highlighted_ranges,
16091            &[
16092                (
16093                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16094                    Hsla::green(),
16095                ),
16096                (
16097                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16098                    Hsla::red(),
16099                ),
16100                (
16101                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16102                    Hsla::green(),
16103                ),
16104                (
16105                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16106                    Hsla::red(),
16107                ),
16108            ]
16109        );
16110        assert_eq!(
16111            editor.sorted_background_highlights_in_range(
16112                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16113                &snapshot,
16114                cx.theme(),
16115            ),
16116            &[(
16117                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16118                Hsla::red(),
16119            )]
16120        );
16121    });
16122}
16123
16124#[gpui::test]
16125async fn test_following(cx: &mut TestAppContext) {
16126    init_test(cx, |_| {});
16127
16128    let fs = FakeFs::new(cx.executor());
16129    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16130
16131    let buffer = project.update(cx, |project, cx| {
16132        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16133        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16134    });
16135    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16136    let follower = cx.update(|cx| {
16137        cx.open_window(
16138            WindowOptions {
16139                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16140                    gpui::Point::new(px(0.), px(0.)),
16141                    gpui::Point::new(px(10.), px(80.)),
16142                ))),
16143                ..Default::default()
16144            },
16145            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16146        )
16147        .unwrap()
16148    });
16149
16150    let is_still_following = Rc::new(RefCell::new(true));
16151    let follower_edit_event_count = Rc::new(RefCell::new(0));
16152    let pending_update = Rc::new(RefCell::new(None));
16153    let leader_entity = leader.root(cx).unwrap();
16154    let follower_entity = follower.root(cx).unwrap();
16155    _ = follower.update(cx, {
16156        let update = pending_update.clone();
16157        let is_still_following = is_still_following.clone();
16158        let follower_edit_event_count = follower_edit_event_count.clone();
16159        |_, window, cx| {
16160            cx.subscribe_in(
16161                &leader_entity,
16162                window,
16163                move |_, leader, event, window, cx| {
16164                    leader.read(cx).add_event_to_update_proto(
16165                        event,
16166                        &mut update.borrow_mut(),
16167                        window,
16168                        cx,
16169                    );
16170                },
16171            )
16172            .detach();
16173
16174            cx.subscribe_in(
16175                &follower_entity,
16176                window,
16177                move |_, _, event: &EditorEvent, _window, _cx| {
16178                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16179                        *is_still_following.borrow_mut() = false;
16180                    }
16181
16182                    if let EditorEvent::BufferEdited = event {
16183                        *follower_edit_event_count.borrow_mut() += 1;
16184                    }
16185                },
16186            )
16187            .detach();
16188        }
16189    });
16190
16191    // Update the selections only
16192    _ = leader.update(cx, |leader, window, cx| {
16193        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16194            s.select_ranges([1..1])
16195        });
16196    });
16197    follower
16198        .update(cx, |follower, window, cx| {
16199            follower.apply_update_proto(
16200                &project,
16201                pending_update.borrow_mut().take().unwrap(),
16202                window,
16203                cx,
16204            )
16205        })
16206        .unwrap()
16207        .await
16208        .unwrap();
16209    _ = follower.update(cx, |follower, _, cx| {
16210        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16211    });
16212    assert!(*is_still_following.borrow());
16213    assert_eq!(*follower_edit_event_count.borrow(), 0);
16214
16215    // Update the scroll position only
16216    _ = leader.update(cx, |leader, window, cx| {
16217        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16218    });
16219    follower
16220        .update(cx, |follower, window, cx| {
16221            follower.apply_update_proto(
16222                &project,
16223                pending_update.borrow_mut().take().unwrap(),
16224                window,
16225                cx,
16226            )
16227        })
16228        .unwrap()
16229        .await
16230        .unwrap();
16231    assert_eq!(
16232        follower
16233            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16234            .unwrap(),
16235        gpui::Point::new(1.5, 3.5)
16236    );
16237    assert!(*is_still_following.borrow());
16238    assert_eq!(*follower_edit_event_count.borrow(), 0);
16239
16240    // Update the selections and scroll position. The follower's scroll position is updated
16241    // via autoscroll, not via the leader's exact scroll position.
16242    _ = leader.update(cx, |leader, window, cx| {
16243        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16244            s.select_ranges([0..0])
16245        });
16246        leader.request_autoscroll(Autoscroll::newest(), cx);
16247        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16248    });
16249    follower
16250        .update(cx, |follower, window, cx| {
16251            follower.apply_update_proto(
16252                &project,
16253                pending_update.borrow_mut().take().unwrap(),
16254                window,
16255                cx,
16256            )
16257        })
16258        .unwrap()
16259        .await
16260        .unwrap();
16261    _ = follower.update(cx, |follower, _, cx| {
16262        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16263        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16264    });
16265    assert!(*is_still_following.borrow());
16266
16267    // Creating a pending selection that precedes another selection
16268    _ = leader.update(cx, |leader, window, cx| {
16269        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16270            s.select_ranges([1..1])
16271        });
16272        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16273    });
16274    follower
16275        .update(cx, |follower, window, cx| {
16276            follower.apply_update_proto(
16277                &project,
16278                pending_update.borrow_mut().take().unwrap(),
16279                window,
16280                cx,
16281            )
16282        })
16283        .unwrap()
16284        .await
16285        .unwrap();
16286    _ = follower.update(cx, |follower, _, cx| {
16287        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16288    });
16289    assert!(*is_still_following.borrow());
16290
16291    // Extend the pending selection so that it surrounds another selection
16292    _ = leader.update(cx, |leader, window, cx| {
16293        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16294    });
16295    follower
16296        .update(cx, |follower, window, cx| {
16297            follower.apply_update_proto(
16298                &project,
16299                pending_update.borrow_mut().take().unwrap(),
16300                window,
16301                cx,
16302            )
16303        })
16304        .unwrap()
16305        .await
16306        .unwrap();
16307    _ = follower.update(cx, |follower, _, cx| {
16308        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16309    });
16310
16311    // Scrolling locally breaks the follow
16312    _ = follower.update(cx, |follower, window, cx| {
16313        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16314        follower.set_scroll_anchor(
16315            ScrollAnchor {
16316                anchor: top_anchor,
16317                offset: gpui::Point::new(0.0, 0.5),
16318            },
16319            window,
16320            cx,
16321        );
16322    });
16323    assert!(!(*is_still_following.borrow()));
16324}
16325
16326#[gpui::test]
16327async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16328    init_test(cx, |_| {});
16329
16330    let fs = FakeFs::new(cx.executor());
16331    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16332    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16333    let pane = workspace
16334        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16335        .unwrap();
16336
16337    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16338
16339    let leader = pane.update_in(cx, |_, window, cx| {
16340        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16341        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16342    });
16343
16344    // Start following the editor when it has no excerpts.
16345    let mut state_message =
16346        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16347    let workspace_entity = workspace.root(cx).unwrap();
16348    let follower_1 = cx
16349        .update_window(*workspace.deref(), |_, window, cx| {
16350            Editor::from_state_proto(
16351                workspace_entity,
16352                ViewId {
16353                    creator: CollaboratorId::PeerId(PeerId::default()),
16354                    id: 0,
16355                },
16356                &mut state_message,
16357                window,
16358                cx,
16359            )
16360        })
16361        .unwrap()
16362        .unwrap()
16363        .await
16364        .unwrap();
16365
16366    let update_message = Rc::new(RefCell::new(None));
16367    follower_1.update_in(cx, {
16368        let update = update_message.clone();
16369        |_, window, cx| {
16370            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16371                leader.read(cx).add_event_to_update_proto(
16372                    event,
16373                    &mut update.borrow_mut(),
16374                    window,
16375                    cx,
16376                );
16377            })
16378            .detach();
16379        }
16380    });
16381
16382    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16383        (
16384            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16385            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16386        )
16387    });
16388
16389    // Insert some excerpts.
16390    leader.update(cx, |leader, cx| {
16391        leader.buffer.update(cx, |multibuffer, cx| {
16392            multibuffer.set_excerpts_for_path(
16393                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
16394                buffer_1.clone(),
16395                vec![
16396                    Point::row_range(0..3),
16397                    Point::row_range(1..6),
16398                    Point::row_range(12..15),
16399                ],
16400                0,
16401                cx,
16402            );
16403            multibuffer.set_excerpts_for_path(
16404                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
16405                buffer_2.clone(),
16406                vec![Point::row_range(0..6), Point::row_range(8..12)],
16407                0,
16408                cx,
16409            );
16410        });
16411    });
16412
16413    // Apply the update of adding the excerpts.
16414    follower_1
16415        .update_in(cx, |follower, window, cx| {
16416            follower.apply_update_proto(
16417                &project,
16418                update_message.borrow().clone().unwrap(),
16419                window,
16420                cx,
16421            )
16422        })
16423        .await
16424        .unwrap();
16425    assert_eq!(
16426        follower_1.update(cx, |editor, cx| editor.text(cx)),
16427        leader.update(cx, |editor, cx| editor.text(cx))
16428    );
16429    update_message.borrow_mut().take();
16430
16431    // Start following separately after it already has excerpts.
16432    let mut state_message =
16433        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16434    let workspace_entity = workspace.root(cx).unwrap();
16435    let follower_2 = cx
16436        .update_window(*workspace.deref(), |_, window, cx| {
16437            Editor::from_state_proto(
16438                workspace_entity,
16439                ViewId {
16440                    creator: CollaboratorId::PeerId(PeerId::default()),
16441                    id: 0,
16442                },
16443                &mut state_message,
16444                window,
16445                cx,
16446            )
16447        })
16448        .unwrap()
16449        .unwrap()
16450        .await
16451        .unwrap();
16452    assert_eq!(
16453        follower_2.update(cx, |editor, cx| editor.text(cx)),
16454        leader.update(cx, |editor, cx| editor.text(cx))
16455    );
16456
16457    // Remove some excerpts.
16458    leader.update(cx, |leader, cx| {
16459        leader.buffer.update(cx, |multibuffer, cx| {
16460            let excerpt_ids = multibuffer.excerpt_ids();
16461            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16462            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16463        });
16464    });
16465
16466    // Apply the update of removing the excerpts.
16467    follower_1
16468        .update_in(cx, |follower, window, cx| {
16469            follower.apply_update_proto(
16470                &project,
16471                update_message.borrow().clone().unwrap(),
16472                window,
16473                cx,
16474            )
16475        })
16476        .await
16477        .unwrap();
16478    follower_2
16479        .update_in(cx, |follower, window, cx| {
16480            follower.apply_update_proto(
16481                &project,
16482                update_message.borrow().clone().unwrap(),
16483                window,
16484                cx,
16485            )
16486        })
16487        .await
16488        .unwrap();
16489    update_message.borrow_mut().take();
16490    assert_eq!(
16491        follower_1.update(cx, |editor, cx| editor.text(cx)),
16492        leader.update(cx, |editor, cx| editor.text(cx))
16493    );
16494}
16495
16496#[gpui::test]
16497async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16498    init_test(cx, |_| {});
16499
16500    let mut cx = EditorTestContext::new(cx).await;
16501    let lsp_store =
16502        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16503
16504    cx.set_state(indoc! {"
16505        ˇfn func(abc def: i32) -> u32 {
16506        }
16507    "});
16508
16509    cx.update(|_, cx| {
16510        lsp_store.update(cx, |lsp_store, cx| {
16511            lsp_store
16512                .update_diagnostics(
16513                    LanguageServerId(0),
16514                    lsp::PublishDiagnosticsParams {
16515                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16516                        version: None,
16517                        diagnostics: vec![
16518                            lsp::Diagnostic {
16519                                range: lsp::Range::new(
16520                                    lsp::Position::new(0, 11),
16521                                    lsp::Position::new(0, 12),
16522                                ),
16523                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16524                                ..Default::default()
16525                            },
16526                            lsp::Diagnostic {
16527                                range: lsp::Range::new(
16528                                    lsp::Position::new(0, 12),
16529                                    lsp::Position::new(0, 15),
16530                                ),
16531                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16532                                ..Default::default()
16533                            },
16534                            lsp::Diagnostic {
16535                                range: lsp::Range::new(
16536                                    lsp::Position::new(0, 25),
16537                                    lsp::Position::new(0, 28),
16538                                ),
16539                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16540                                ..Default::default()
16541                            },
16542                        ],
16543                    },
16544                    None,
16545                    DiagnosticSourceKind::Pushed,
16546                    &[],
16547                    cx,
16548                )
16549                .unwrap()
16550        });
16551    });
16552
16553    executor.run_until_parked();
16554
16555    cx.update_editor(|editor, window, cx| {
16556        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16557    });
16558
16559    cx.assert_editor_state(indoc! {"
16560        fn func(abc def: i32) -> ˇu32 {
16561        }
16562    "});
16563
16564    cx.update_editor(|editor, window, cx| {
16565        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16566    });
16567
16568    cx.assert_editor_state(indoc! {"
16569        fn func(abc ˇdef: i32) -> u32 {
16570        }
16571    "});
16572
16573    cx.update_editor(|editor, window, cx| {
16574        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16575    });
16576
16577    cx.assert_editor_state(indoc! {"
16578        fn func(abcˇ def: i32) -> u32 {
16579        }
16580    "});
16581
16582    cx.update_editor(|editor, window, cx| {
16583        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16584    });
16585
16586    cx.assert_editor_state(indoc! {"
16587        fn func(abc def: i32) -> ˇu32 {
16588        }
16589    "});
16590}
16591
16592#[gpui::test]
16593async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16594    init_test(cx, |_| {});
16595
16596    let mut cx = EditorTestContext::new(cx).await;
16597
16598    let diff_base = r#"
16599        use some::mod;
16600
16601        const A: u32 = 42;
16602
16603        fn main() {
16604            println!("hello");
16605
16606            println!("world");
16607        }
16608        "#
16609    .unindent();
16610
16611    // Edits are modified, removed, modified, added
16612    cx.set_state(
16613        &r#"
16614        use some::modified;
16615
16616        ˇ
16617        fn main() {
16618            println!("hello there");
16619
16620            println!("around the");
16621            println!("world");
16622        }
16623        "#
16624        .unindent(),
16625    );
16626
16627    cx.set_head_text(&diff_base);
16628    executor.run_until_parked();
16629
16630    cx.update_editor(|editor, window, cx| {
16631        //Wrap around the bottom of the buffer
16632        for _ in 0..3 {
16633            editor.go_to_next_hunk(&GoToHunk, window, cx);
16634        }
16635    });
16636
16637    cx.assert_editor_state(
16638        &r#"
16639        ˇuse some::modified;
16640
16641
16642        fn main() {
16643            println!("hello there");
16644
16645            println!("around the");
16646            println!("world");
16647        }
16648        "#
16649        .unindent(),
16650    );
16651
16652    cx.update_editor(|editor, window, cx| {
16653        //Wrap around the top of the buffer
16654        for _ in 0..2 {
16655            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16656        }
16657    });
16658
16659    cx.assert_editor_state(
16660        &r#"
16661        use some::modified;
16662
16663
16664        fn main() {
16665        ˇ    println!("hello there");
16666
16667            println!("around the");
16668            println!("world");
16669        }
16670        "#
16671        .unindent(),
16672    );
16673
16674    cx.update_editor(|editor, window, cx| {
16675        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16676    });
16677
16678    cx.assert_editor_state(
16679        &r#"
16680        use some::modified;
16681
16682        ˇ
16683        fn main() {
16684            println!("hello there");
16685
16686            println!("around the");
16687            println!("world");
16688        }
16689        "#
16690        .unindent(),
16691    );
16692
16693    cx.update_editor(|editor, window, cx| {
16694        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16695    });
16696
16697    cx.assert_editor_state(
16698        &r#"
16699        ˇuse some::modified;
16700
16701
16702        fn main() {
16703            println!("hello there");
16704
16705            println!("around the");
16706            println!("world");
16707        }
16708        "#
16709        .unindent(),
16710    );
16711
16712    cx.update_editor(|editor, window, cx| {
16713        for _ in 0..2 {
16714            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16715        }
16716    });
16717
16718    cx.assert_editor_state(
16719        &r#"
16720        use some::modified;
16721
16722
16723        fn main() {
16724        ˇ    println!("hello there");
16725
16726            println!("around the");
16727            println!("world");
16728        }
16729        "#
16730        .unindent(),
16731    );
16732
16733    cx.update_editor(|editor, window, cx| {
16734        editor.fold(&Fold, window, cx);
16735    });
16736
16737    cx.update_editor(|editor, window, cx| {
16738        editor.go_to_next_hunk(&GoToHunk, window, cx);
16739    });
16740
16741    cx.assert_editor_state(
16742        &r#"
16743        ˇuse some::modified;
16744
16745
16746        fn main() {
16747            println!("hello there");
16748
16749            println!("around the");
16750            println!("world");
16751        }
16752        "#
16753        .unindent(),
16754    );
16755}
16756
16757#[test]
16758fn test_split_words() {
16759    fn split(text: &str) -> Vec<&str> {
16760        split_words(text).collect()
16761    }
16762
16763    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16764    assert_eq!(split("hello_world"), &["hello_", "world"]);
16765    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16766    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16767    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16768    assert_eq!(split("helloworld"), &["helloworld"]);
16769
16770    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16771}
16772
16773#[gpui::test]
16774async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16775    init_test(cx, |_| {});
16776
16777    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16778    let mut assert = |before, after| {
16779        let _state_context = cx.set_state(before);
16780        cx.run_until_parked();
16781        cx.update_editor(|editor, window, cx| {
16782            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16783        });
16784        cx.run_until_parked();
16785        cx.assert_editor_state(after);
16786    };
16787
16788    // Outside bracket jumps to outside of matching bracket
16789    assert("console.logˇ(var);", "console.log(var)ˇ;");
16790    assert("console.log(var)ˇ;", "console.logˇ(var);");
16791
16792    // Inside bracket jumps to inside of matching bracket
16793    assert("console.log(ˇvar);", "console.log(varˇ);");
16794    assert("console.log(varˇ);", "console.log(ˇvar);");
16795
16796    // When outside a bracket and inside, favor jumping to the inside bracket
16797    assert(
16798        "console.log('foo', [1, 2, 3]ˇ);",
16799        "console.log(ˇ'foo', [1, 2, 3]);",
16800    );
16801    assert(
16802        "console.log(ˇ'foo', [1, 2, 3]);",
16803        "console.log('foo', [1, 2, 3]ˇ);",
16804    );
16805
16806    // Bias forward if two options are equally likely
16807    assert(
16808        "let result = curried_fun()ˇ();",
16809        "let result = curried_fun()()ˇ;",
16810    );
16811
16812    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16813    assert(
16814        indoc! {"
16815            function test() {
16816                console.log('test')ˇ
16817            }"},
16818        indoc! {"
16819            function test() {
16820                console.logˇ('test')
16821            }"},
16822    );
16823}
16824
16825#[gpui::test]
16826async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16827    init_test(cx, |_| {});
16828
16829    let fs = FakeFs::new(cx.executor());
16830    fs.insert_tree(
16831        path!("/a"),
16832        json!({
16833            "main.rs": "fn main() { let a = 5; }",
16834            "other.rs": "// Test file",
16835        }),
16836    )
16837    .await;
16838    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16839
16840    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16841    language_registry.add(Arc::new(Language::new(
16842        LanguageConfig {
16843            name: "Rust".into(),
16844            matcher: LanguageMatcher {
16845                path_suffixes: vec!["rs".to_string()],
16846                ..Default::default()
16847            },
16848            brackets: BracketPairConfig {
16849                pairs: vec![BracketPair {
16850                    start: "{".to_string(),
16851                    end: "}".to_string(),
16852                    close: true,
16853                    surround: true,
16854                    newline: true,
16855                }],
16856                disabled_scopes_by_bracket_ix: Vec::new(),
16857            },
16858            ..Default::default()
16859        },
16860        Some(tree_sitter_rust::LANGUAGE.into()),
16861    )));
16862    let mut fake_servers = language_registry.register_fake_lsp(
16863        "Rust",
16864        FakeLspAdapter {
16865            capabilities: lsp::ServerCapabilities {
16866                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16867                    first_trigger_character: "{".to_string(),
16868                    more_trigger_character: None,
16869                }),
16870                ..Default::default()
16871            },
16872            ..Default::default()
16873        },
16874    );
16875
16876    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16877
16878    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16879
16880    let worktree_id = workspace
16881        .update(cx, |workspace, _, cx| {
16882            workspace.project().update(cx, |project, cx| {
16883                project.worktrees(cx).next().unwrap().read(cx).id()
16884            })
16885        })
16886        .unwrap();
16887
16888    let buffer = project
16889        .update(cx, |project, cx| {
16890            project.open_local_buffer(path!("/a/main.rs"), cx)
16891        })
16892        .await
16893        .unwrap();
16894    let editor_handle = workspace
16895        .update(cx, |workspace, window, cx| {
16896            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16897        })
16898        .unwrap()
16899        .await
16900        .unwrap()
16901        .downcast::<Editor>()
16902        .unwrap();
16903
16904    cx.executor().start_waiting();
16905    let fake_server = fake_servers.next().await.unwrap();
16906
16907    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16908        |params, _| async move {
16909            assert_eq!(
16910                params.text_document_position.text_document.uri,
16911                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16912            );
16913            assert_eq!(
16914                params.text_document_position.position,
16915                lsp::Position::new(0, 21),
16916            );
16917
16918            Ok(Some(vec![lsp::TextEdit {
16919                new_text: "]".to_string(),
16920                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16921            }]))
16922        },
16923    );
16924
16925    editor_handle.update_in(cx, |editor, window, cx| {
16926        window.focus(&editor.focus_handle(cx));
16927        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16928            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16929        });
16930        editor.handle_input("{", window, cx);
16931    });
16932
16933    cx.executor().run_until_parked();
16934
16935    buffer.update(cx, |buffer, _| {
16936        assert_eq!(
16937            buffer.text(),
16938            "fn main() { let a = {5}; }",
16939            "No extra braces from on type formatting should appear in the buffer"
16940        )
16941    });
16942}
16943
16944#[gpui::test(iterations = 20, seeds(31))]
16945async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
16946    init_test(cx, |_| {});
16947
16948    let mut cx = EditorLspTestContext::new_rust(
16949        lsp::ServerCapabilities {
16950            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16951                first_trigger_character: ".".to_string(),
16952                more_trigger_character: None,
16953            }),
16954            ..Default::default()
16955        },
16956        cx,
16957    )
16958    .await;
16959
16960    cx.update_buffer(|buffer, _| {
16961        // This causes autoindent to be async.
16962        buffer.set_sync_parse_timeout(Duration::ZERO)
16963    });
16964
16965    cx.set_state("fn c() {\n    d()ˇ\n}\n");
16966    cx.simulate_keystroke("\n");
16967    cx.run_until_parked();
16968
16969    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
16970    let mut request =
16971        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
16972            let buffer_cloned = buffer_cloned.clone();
16973            async move {
16974                buffer_cloned.update(&mut cx, |buffer, _| {
16975                    assert_eq!(
16976                        buffer.text(),
16977                        "fn c() {\n    d()\n        .\n}\n",
16978                        "OnTypeFormatting should triggered after autoindent applied"
16979                    )
16980                })?;
16981
16982                Ok(Some(vec![]))
16983            }
16984        });
16985
16986    cx.simulate_keystroke(".");
16987    cx.run_until_parked();
16988
16989    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
16990    assert!(request.next().await.is_some());
16991    request.close();
16992    assert!(request.next().await.is_none());
16993}
16994
16995#[gpui::test]
16996async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
16997    init_test(cx, |_| {});
16998
16999    let fs = FakeFs::new(cx.executor());
17000    fs.insert_tree(
17001        path!("/a"),
17002        json!({
17003            "main.rs": "fn main() { let a = 5; }",
17004            "other.rs": "// Test file",
17005        }),
17006    )
17007    .await;
17008
17009    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17010
17011    let server_restarts = Arc::new(AtomicUsize::new(0));
17012    let closure_restarts = Arc::clone(&server_restarts);
17013    let language_server_name = "test language server";
17014    let language_name: LanguageName = "Rust".into();
17015
17016    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17017    language_registry.add(Arc::new(Language::new(
17018        LanguageConfig {
17019            name: language_name.clone(),
17020            matcher: LanguageMatcher {
17021                path_suffixes: vec!["rs".to_string()],
17022                ..Default::default()
17023            },
17024            ..Default::default()
17025        },
17026        Some(tree_sitter_rust::LANGUAGE.into()),
17027    )));
17028    let mut fake_servers = language_registry.register_fake_lsp(
17029        "Rust",
17030        FakeLspAdapter {
17031            name: language_server_name,
17032            initialization_options: Some(json!({
17033                "testOptionValue": true
17034            })),
17035            initializer: Some(Box::new(move |fake_server| {
17036                let task_restarts = Arc::clone(&closure_restarts);
17037                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17038                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17039                    futures::future::ready(Ok(()))
17040                });
17041            })),
17042            ..Default::default()
17043        },
17044    );
17045
17046    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17047    let _buffer = project
17048        .update(cx, |project, cx| {
17049            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17050        })
17051        .await
17052        .unwrap();
17053    let _fake_server = fake_servers.next().await.unwrap();
17054    update_test_language_settings(cx, |language_settings| {
17055        language_settings.languages.0.insert(
17056            language_name.clone().0,
17057            LanguageSettingsContent {
17058                tab_size: NonZeroU32::new(8),
17059                ..Default::default()
17060            },
17061        );
17062    });
17063    cx.executor().run_until_parked();
17064    assert_eq!(
17065        server_restarts.load(atomic::Ordering::Acquire),
17066        0,
17067        "Should not restart LSP server on an unrelated change"
17068    );
17069
17070    update_test_project_settings(cx, |project_settings| {
17071        project_settings.lsp.insert(
17072            "Some other server name".into(),
17073            LspSettings {
17074                binary: None,
17075                settings: None,
17076                initialization_options: Some(json!({
17077                    "some other init value": false
17078                })),
17079                enable_lsp_tasks: false,
17080                fetch: None,
17081            },
17082        );
17083    });
17084    cx.executor().run_until_parked();
17085    assert_eq!(
17086        server_restarts.load(atomic::Ordering::Acquire),
17087        0,
17088        "Should not restart LSP server on an unrelated LSP settings change"
17089    );
17090
17091    update_test_project_settings(cx, |project_settings| {
17092        project_settings.lsp.insert(
17093            language_server_name.into(),
17094            LspSettings {
17095                binary: None,
17096                settings: None,
17097                initialization_options: Some(json!({
17098                    "anotherInitValue": false
17099                })),
17100                enable_lsp_tasks: false,
17101                fetch: None,
17102            },
17103        );
17104    });
17105    cx.executor().run_until_parked();
17106    assert_eq!(
17107        server_restarts.load(atomic::Ordering::Acquire),
17108        1,
17109        "Should restart LSP server on a related LSP settings change"
17110    );
17111
17112    update_test_project_settings(cx, |project_settings| {
17113        project_settings.lsp.insert(
17114            language_server_name.into(),
17115            LspSettings {
17116                binary: None,
17117                settings: None,
17118                initialization_options: Some(json!({
17119                    "anotherInitValue": false
17120                })),
17121                enable_lsp_tasks: false,
17122                fetch: None,
17123            },
17124        );
17125    });
17126    cx.executor().run_until_parked();
17127    assert_eq!(
17128        server_restarts.load(atomic::Ordering::Acquire),
17129        1,
17130        "Should not restart LSP server on a related LSP settings change that is the same"
17131    );
17132
17133    update_test_project_settings(cx, |project_settings| {
17134        project_settings.lsp.insert(
17135            language_server_name.into(),
17136            LspSettings {
17137                binary: None,
17138                settings: None,
17139                initialization_options: None,
17140                enable_lsp_tasks: false,
17141                fetch: None,
17142            },
17143        );
17144    });
17145    cx.executor().run_until_parked();
17146    assert_eq!(
17147        server_restarts.load(atomic::Ordering::Acquire),
17148        2,
17149        "Should restart LSP server on another related LSP settings change"
17150    );
17151}
17152
17153#[gpui::test]
17154async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17155    init_test(cx, |_| {});
17156
17157    let mut cx = EditorLspTestContext::new_rust(
17158        lsp::ServerCapabilities {
17159            completion_provider: Some(lsp::CompletionOptions {
17160                trigger_characters: Some(vec![".".to_string()]),
17161                resolve_provider: Some(true),
17162                ..Default::default()
17163            }),
17164            ..Default::default()
17165        },
17166        cx,
17167    )
17168    .await;
17169
17170    cx.set_state("fn main() { let a = 2ˇ; }");
17171    cx.simulate_keystroke(".");
17172    let completion_item = lsp::CompletionItem {
17173        label: "some".into(),
17174        kind: Some(lsp::CompletionItemKind::SNIPPET),
17175        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17176        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17177            kind: lsp::MarkupKind::Markdown,
17178            value: "```rust\nSome(2)\n```".to_string(),
17179        })),
17180        deprecated: Some(false),
17181        sort_text: Some("fffffff2".to_string()),
17182        filter_text: Some("some".to_string()),
17183        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17184        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17185            range: lsp::Range {
17186                start: lsp::Position {
17187                    line: 0,
17188                    character: 22,
17189                },
17190                end: lsp::Position {
17191                    line: 0,
17192                    character: 22,
17193                },
17194            },
17195            new_text: "Some(2)".to_string(),
17196        })),
17197        additional_text_edits: Some(vec![lsp::TextEdit {
17198            range: lsp::Range {
17199                start: lsp::Position {
17200                    line: 0,
17201                    character: 20,
17202                },
17203                end: lsp::Position {
17204                    line: 0,
17205                    character: 22,
17206                },
17207            },
17208            new_text: "".to_string(),
17209        }]),
17210        ..Default::default()
17211    };
17212
17213    let closure_completion_item = completion_item.clone();
17214    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17215        let task_completion_item = closure_completion_item.clone();
17216        async move {
17217            Ok(Some(lsp::CompletionResponse::Array(vec![
17218                task_completion_item,
17219            ])))
17220        }
17221    });
17222
17223    request.next().await;
17224
17225    cx.condition(|editor, _| editor.context_menu_visible())
17226        .await;
17227    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17228        editor
17229            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17230            .unwrap()
17231    });
17232    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17233
17234    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17235        let task_completion_item = completion_item.clone();
17236        async move { Ok(task_completion_item) }
17237    })
17238    .next()
17239    .await
17240    .unwrap();
17241    apply_additional_edits.await.unwrap();
17242    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17243}
17244
17245#[gpui::test]
17246async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17247    init_test(cx, |_| {});
17248
17249    let mut cx = EditorLspTestContext::new_rust(
17250        lsp::ServerCapabilities {
17251            completion_provider: Some(lsp::CompletionOptions {
17252                trigger_characters: Some(vec![".".to_string()]),
17253                resolve_provider: Some(true),
17254                ..Default::default()
17255            }),
17256            ..Default::default()
17257        },
17258        cx,
17259    )
17260    .await;
17261
17262    cx.set_state("fn main() { let a = 2ˇ; }");
17263    cx.simulate_keystroke(".");
17264
17265    let item1 = lsp::CompletionItem {
17266        label: "method id()".to_string(),
17267        filter_text: Some("id".to_string()),
17268        detail: None,
17269        documentation: None,
17270        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17271            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17272            new_text: ".id".to_string(),
17273        })),
17274        ..lsp::CompletionItem::default()
17275    };
17276
17277    let item2 = lsp::CompletionItem {
17278        label: "other".to_string(),
17279        filter_text: Some("other".to_string()),
17280        detail: None,
17281        documentation: None,
17282        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17283            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17284            new_text: ".other".to_string(),
17285        })),
17286        ..lsp::CompletionItem::default()
17287    };
17288
17289    let item1 = item1.clone();
17290    cx.set_request_handler::<lsp::request::Completion, _, _>({
17291        let item1 = item1.clone();
17292        move |_, _, _| {
17293            let item1 = item1.clone();
17294            let item2 = item2.clone();
17295            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17296        }
17297    })
17298    .next()
17299    .await;
17300
17301    cx.condition(|editor, _| editor.context_menu_visible())
17302        .await;
17303    cx.update_editor(|editor, _, _| {
17304        let context_menu = editor.context_menu.borrow_mut();
17305        let context_menu = context_menu
17306            .as_ref()
17307            .expect("Should have the context menu deployed");
17308        match context_menu {
17309            CodeContextMenu::Completions(completions_menu) => {
17310                let completions = completions_menu.completions.borrow_mut();
17311                assert_eq!(
17312                    completions
17313                        .iter()
17314                        .map(|completion| &completion.label.text)
17315                        .collect::<Vec<_>>(),
17316                    vec!["method id()", "other"]
17317                )
17318            }
17319            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17320        }
17321    });
17322
17323    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17324        let item1 = item1.clone();
17325        move |_, item_to_resolve, _| {
17326            let item1 = item1.clone();
17327            async move {
17328                if item1 == item_to_resolve {
17329                    Ok(lsp::CompletionItem {
17330                        label: "method id()".to_string(),
17331                        filter_text: Some("id".to_string()),
17332                        detail: Some("Now resolved!".to_string()),
17333                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17334                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17335                            range: lsp::Range::new(
17336                                lsp::Position::new(0, 22),
17337                                lsp::Position::new(0, 22),
17338                            ),
17339                            new_text: ".id".to_string(),
17340                        })),
17341                        ..lsp::CompletionItem::default()
17342                    })
17343                } else {
17344                    Ok(item_to_resolve)
17345                }
17346            }
17347        }
17348    })
17349    .next()
17350    .await
17351    .unwrap();
17352    cx.run_until_parked();
17353
17354    cx.update_editor(|editor, window, cx| {
17355        editor.context_menu_next(&Default::default(), window, cx);
17356    });
17357
17358    cx.update_editor(|editor, _, _| {
17359        let context_menu = editor.context_menu.borrow_mut();
17360        let context_menu = context_menu
17361            .as_ref()
17362            .expect("Should have the context menu deployed");
17363        match context_menu {
17364            CodeContextMenu::Completions(completions_menu) => {
17365                let completions = completions_menu.completions.borrow_mut();
17366                assert_eq!(
17367                    completions
17368                        .iter()
17369                        .map(|completion| &completion.label.text)
17370                        .collect::<Vec<_>>(),
17371                    vec!["method id() Now resolved!", "other"],
17372                    "Should update first completion label, but not second as the filter text did not match."
17373                );
17374            }
17375            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17376        }
17377    });
17378}
17379
17380#[gpui::test]
17381async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17382    init_test(cx, |_| {});
17383    let mut cx = EditorLspTestContext::new_rust(
17384        lsp::ServerCapabilities {
17385            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17386            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17387            completion_provider: Some(lsp::CompletionOptions {
17388                resolve_provider: Some(true),
17389                ..Default::default()
17390            }),
17391            ..Default::default()
17392        },
17393        cx,
17394    )
17395    .await;
17396    cx.set_state(indoc! {"
17397        struct TestStruct {
17398            field: i32
17399        }
17400
17401        fn mainˇ() {
17402            let unused_var = 42;
17403            let test_struct = TestStruct { field: 42 };
17404        }
17405    "});
17406    let symbol_range = cx.lsp_range(indoc! {"
17407        struct TestStruct {
17408            field: i32
17409        }
17410
17411        «fn main»() {
17412            let unused_var = 42;
17413            let test_struct = TestStruct { field: 42 };
17414        }
17415    "});
17416    let mut hover_requests =
17417        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17418            Ok(Some(lsp::Hover {
17419                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17420                    kind: lsp::MarkupKind::Markdown,
17421                    value: "Function documentation".to_string(),
17422                }),
17423                range: Some(symbol_range),
17424            }))
17425        });
17426
17427    // Case 1: Test that code action menu hide hover popover
17428    cx.dispatch_action(Hover);
17429    hover_requests.next().await;
17430    cx.condition(|editor, _| editor.hover_state.visible()).await;
17431    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17432        move |_, _, _| async move {
17433            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17434                lsp::CodeAction {
17435                    title: "Remove unused variable".to_string(),
17436                    kind: Some(CodeActionKind::QUICKFIX),
17437                    edit: Some(lsp::WorkspaceEdit {
17438                        changes: Some(
17439                            [(
17440                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17441                                vec![lsp::TextEdit {
17442                                    range: lsp::Range::new(
17443                                        lsp::Position::new(5, 4),
17444                                        lsp::Position::new(5, 27),
17445                                    ),
17446                                    new_text: "".to_string(),
17447                                }],
17448                            )]
17449                            .into_iter()
17450                            .collect(),
17451                        ),
17452                        ..Default::default()
17453                    }),
17454                    ..Default::default()
17455                },
17456            )]))
17457        },
17458    );
17459    cx.update_editor(|editor, window, cx| {
17460        editor.toggle_code_actions(
17461            &ToggleCodeActions {
17462                deployed_from: None,
17463                quick_launch: false,
17464            },
17465            window,
17466            cx,
17467        );
17468    });
17469    code_action_requests.next().await;
17470    cx.run_until_parked();
17471    cx.condition(|editor, _| editor.context_menu_visible())
17472        .await;
17473    cx.update_editor(|editor, _, _| {
17474        assert!(
17475            !editor.hover_state.visible(),
17476            "Hover popover should be hidden when code action menu is shown"
17477        );
17478        // Hide code actions
17479        editor.context_menu.take();
17480    });
17481
17482    // Case 2: Test that code completions hide hover popover
17483    cx.dispatch_action(Hover);
17484    hover_requests.next().await;
17485    cx.condition(|editor, _| editor.hover_state.visible()).await;
17486    let counter = Arc::new(AtomicUsize::new(0));
17487    let mut completion_requests =
17488        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17489            let counter = counter.clone();
17490            async move {
17491                counter.fetch_add(1, atomic::Ordering::Release);
17492                Ok(Some(lsp::CompletionResponse::Array(vec![
17493                    lsp::CompletionItem {
17494                        label: "main".into(),
17495                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17496                        detail: Some("() -> ()".to_string()),
17497                        ..Default::default()
17498                    },
17499                    lsp::CompletionItem {
17500                        label: "TestStruct".into(),
17501                        kind: Some(lsp::CompletionItemKind::STRUCT),
17502                        detail: Some("struct TestStruct".to_string()),
17503                        ..Default::default()
17504                    },
17505                ])))
17506            }
17507        });
17508    cx.update_editor(|editor, window, cx| {
17509        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17510    });
17511    completion_requests.next().await;
17512    cx.condition(|editor, _| editor.context_menu_visible())
17513        .await;
17514    cx.update_editor(|editor, _, _| {
17515        assert!(
17516            !editor.hover_state.visible(),
17517            "Hover popover should be hidden when completion menu is shown"
17518        );
17519    });
17520}
17521
17522#[gpui::test]
17523async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17524    init_test(cx, |_| {});
17525
17526    let mut cx = EditorLspTestContext::new_rust(
17527        lsp::ServerCapabilities {
17528            completion_provider: Some(lsp::CompletionOptions {
17529                trigger_characters: Some(vec![".".to_string()]),
17530                resolve_provider: Some(true),
17531                ..Default::default()
17532            }),
17533            ..Default::default()
17534        },
17535        cx,
17536    )
17537    .await;
17538
17539    cx.set_state("fn main() { let a = 2ˇ; }");
17540    cx.simulate_keystroke(".");
17541
17542    let unresolved_item_1 = lsp::CompletionItem {
17543        label: "id".to_string(),
17544        filter_text: Some("id".to_string()),
17545        detail: None,
17546        documentation: None,
17547        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17548            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17549            new_text: ".id".to_string(),
17550        })),
17551        ..lsp::CompletionItem::default()
17552    };
17553    let resolved_item_1 = lsp::CompletionItem {
17554        additional_text_edits: Some(vec![lsp::TextEdit {
17555            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17556            new_text: "!!".to_string(),
17557        }]),
17558        ..unresolved_item_1.clone()
17559    };
17560    let unresolved_item_2 = lsp::CompletionItem {
17561        label: "other".to_string(),
17562        filter_text: Some("other".to_string()),
17563        detail: None,
17564        documentation: None,
17565        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17566            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17567            new_text: ".other".to_string(),
17568        })),
17569        ..lsp::CompletionItem::default()
17570    };
17571    let resolved_item_2 = lsp::CompletionItem {
17572        additional_text_edits: Some(vec![lsp::TextEdit {
17573            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17574            new_text: "??".to_string(),
17575        }]),
17576        ..unresolved_item_2.clone()
17577    };
17578
17579    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17580    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17581    cx.lsp
17582        .server
17583        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17584            let unresolved_item_1 = unresolved_item_1.clone();
17585            let resolved_item_1 = resolved_item_1.clone();
17586            let unresolved_item_2 = unresolved_item_2.clone();
17587            let resolved_item_2 = resolved_item_2.clone();
17588            let resolve_requests_1 = resolve_requests_1.clone();
17589            let resolve_requests_2 = resolve_requests_2.clone();
17590            move |unresolved_request, _| {
17591                let unresolved_item_1 = unresolved_item_1.clone();
17592                let resolved_item_1 = resolved_item_1.clone();
17593                let unresolved_item_2 = unresolved_item_2.clone();
17594                let resolved_item_2 = resolved_item_2.clone();
17595                let resolve_requests_1 = resolve_requests_1.clone();
17596                let resolve_requests_2 = resolve_requests_2.clone();
17597                async move {
17598                    if unresolved_request == unresolved_item_1 {
17599                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17600                        Ok(resolved_item_1.clone())
17601                    } else if unresolved_request == unresolved_item_2 {
17602                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17603                        Ok(resolved_item_2.clone())
17604                    } else {
17605                        panic!("Unexpected completion item {unresolved_request:?}")
17606                    }
17607                }
17608            }
17609        })
17610        .detach();
17611
17612    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17613        let unresolved_item_1 = unresolved_item_1.clone();
17614        let unresolved_item_2 = unresolved_item_2.clone();
17615        async move {
17616            Ok(Some(lsp::CompletionResponse::Array(vec![
17617                unresolved_item_1,
17618                unresolved_item_2,
17619            ])))
17620        }
17621    })
17622    .next()
17623    .await;
17624
17625    cx.condition(|editor, _| editor.context_menu_visible())
17626        .await;
17627    cx.update_editor(|editor, _, _| {
17628        let context_menu = editor.context_menu.borrow_mut();
17629        let context_menu = context_menu
17630            .as_ref()
17631            .expect("Should have the context menu deployed");
17632        match context_menu {
17633            CodeContextMenu::Completions(completions_menu) => {
17634                let completions = completions_menu.completions.borrow_mut();
17635                assert_eq!(
17636                    completions
17637                        .iter()
17638                        .map(|completion| &completion.label.text)
17639                        .collect::<Vec<_>>(),
17640                    vec!["id", "other"]
17641                )
17642            }
17643            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17644        }
17645    });
17646    cx.run_until_parked();
17647
17648    cx.update_editor(|editor, window, cx| {
17649        editor.context_menu_next(&ContextMenuNext, window, cx);
17650    });
17651    cx.run_until_parked();
17652    cx.update_editor(|editor, window, cx| {
17653        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17654    });
17655    cx.run_until_parked();
17656    cx.update_editor(|editor, window, cx| {
17657        editor.context_menu_next(&ContextMenuNext, window, cx);
17658    });
17659    cx.run_until_parked();
17660    cx.update_editor(|editor, window, cx| {
17661        editor
17662            .compose_completion(&ComposeCompletion::default(), window, cx)
17663            .expect("No task returned")
17664    })
17665    .await
17666    .expect("Completion failed");
17667    cx.run_until_parked();
17668
17669    cx.update_editor(|editor, _, cx| {
17670        assert_eq!(
17671            resolve_requests_1.load(atomic::Ordering::Acquire),
17672            1,
17673            "Should always resolve once despite multiple selections"
17674        );
17675        assert_eq!(
17676            resolve_requests_2.load(atomic::Ordering::Acquire),
17677            1,
17678            "Should always resolve once after multiple selections and applying the completion"
17679        );
17680        assert_eq!(
17681            editor.text(cx),
17682            "fn main() { let a = ??.other; }",
17683            "Should use resolved data when applying the completion"
17684        );
17685    });
17686}
17687
17688#[gpui::test]
17689async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17690    init_test(cx, |_| {});
17691
17692    let item_0 = lsp::CompletionItem {
17693        label: "abs".into(),
17694        insert_text: Some("abs".into()),
17695        data: Some(json!({ "very": "special"})),
17696        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17697        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17698            lsp::InsertReplaceEdit {
17699                new_text: "abs".to_string(),
17700                insert: lsp::Range::default(),
17701                replace: lsp::Range::default(),
17702            },
17703        )),
17704        ..lsp::CompletionItem::default()
17705    };
17706    let items = iter::once(item_0.clone())
17707        .chain((11..51).map(|i| lsp::CompletionItem {
17708            label: format!("item_{}", i),
17709            insert_text: Some(format!("item_{}", i)),
17710            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17711            ..lsp::CompletionItem::default()
17712        }))
17713        .collect::<Vec<_>>();
17714
17715    let default_commit_characters = vec!["?".to_string()];
17716    let default_data = json!({ "default": "data"});
17717    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17718    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17719    let default_edit_range = lsp::Range {
17720        start: lsp::Position {
17721            line: 0,
17722            character: 5,
17723        },
17724        end: lsp::Position {
17725            line: 0,
17726            character: 5,
17727        },
17728    };
17729
17730    let mut cx = EditorLspTestContext::new_rust(
17731        lsp::ServerCapabilities {
17732            completion_provider: Some(lsp::CompletionOptions {
17733                trigger_characters: Some(vec![".".to_string()]),
17734                resolve_provider: Some(true),
17735                ..Default::default()
17736            }),
17737            ..Default::default()
17738        },
17739        cx,
17740    )
17741    .await;
17742
17743    cx.set_state("fn main() { let a = 2ˇ; }");
17744    cx.simulate_keystroke(".");
17745
17746    let completion_data = default_data.clone();
17747    let completion_characters = default_commit_characters.clone();
17748    let completion_items = items.clone();
17749    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17750        let default_data = completion_data.clone();
17751        let default_commit_characters = completion_characters.clone();
17752        let items = completion_items.clone();
17753        async move {
17754            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17755                items,
17756                item_defaults: Some(lsp::CompletionListItemDefaults {
17757                    data: Some(default_data.clone()),
17758                    commit_characters: Some(default_commit_characters.clone()),
17759                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17760                        default_edit_range,
17761                    )),
17762                    insert_text_format: Some(default_insert_text_format),
17763                    insert_text_mode: Some(default_insert_text_mode),
17764                }),
17765                ..lsp::CompletionList::default()
17766            })))
17767        }
17768    })
17769    .next()
17770    .await;
17771
17772    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17773    cx.lsp
17774        .server
17775        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17776            let closure_resolved_items = resolved_items.clone();
17777            move |item_to_resolve, _| {
17778                let closure_resolved_items = closure_resolved_items.clone();
17779                async move {
17780                    closure_resolved_items.lock().push(item_to_resolve.clone());
17781                    Ok(item_to_resolve)
17782                }
17783            }
17784        })
17785        .detach();
17786
17787    cx.condition(|editor, _| editor.context_menu_visible())
17788        .await;
17789    cx.run_until_parked();
17790    cx.update_editor(|editor, _, _| {
17791        let menu = editor.context_menu.borrow_mut();
17792        match menu.as_ref().expect("should have the completions menu") {
17793            CodeContextMenu::Completions(completions_menu) => {
17794                assert_eq!(
17795                    completions_menu
17796                        .entries
17797                        .borrow()
17798                        .iter()
17799                        .map(|mat| mat.string.clone())
17800                        .collect::<Vec<String>>(),
17801                    items
17802                        .iter()
17803                        .map(|completion| completion.label.clone())
17804                        .collect::<Vec<String>>()
17805                );
17806            }
17807            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17808        }
17809    });
17810    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17811    // with 4 from the end.
17812    assert_eq!(
17813        *resolved_items.lock(),
17814        [&items[0..16], &items[items.len() - 4..items.len()]]
17815            .concat()
17816            .iter()
17817            .cloned()
17818            .map(|mut item| {
17819                if item.data.is_none() {
17820                    item.data = Some(default_data.clone());
17821                }
17822                item
17823            })
17824            .collect::<Vec<lsp::CompletionItem>>(),
17825        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17826    );
17827    resolved_items.lock().clear();
17828
17829    cx.update_editor(|editor, window, cx| {
17830        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17831    });
17832    cx.run_until_parked();
17833    // Completions that have already been resolved are skipped.
17834    assert_eq!(
17835        *resolved_items.lock(),
17836        items[items.len() - 17..items.len() - 4]
17837            .iter()
17838            .cloned()
17839            .map(|mut item| {
17840                if item.data.is_none() {
17841                    item.data = Some(default_data.clone());
17842                }
17843                item
17844            })
17845            .collect::<Vec<lsp::CompletionItem>>()
17846    );
17847    resolved_items.lock().clear();
17848}
17849
17850#[gpui::test]
17851async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17852    init_test(cx, |_| {});
17853
17854    let mut cx = EditorLspTestContext::new(
17855        Language::new(
17856            LanguageConfig {
17857                matcher: LanguageMatcher {
17858                    path_suffixes: vec!["jsx".into()],
17859                    ..Default::default()
17860                },
17861                overrides: [(
17862                    "element".into(),
17863                    LanguageConfigOverride {
17864                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17865                        ..Default::default()
17866                    },
17867                )]
17868                .into_iter()
17869                .collect(),
17870                ..Default::default()
17871            },
17872            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17873        )
17874        .with_override_query("(jsx_self_closing_element) @element")
17875        .unwrap(),
17876        lsp::ServerCapabilities {
17877            completion_provider: Some(lsp::CompletionOptions {
17878                trigger_characters: Some(vec![":".to_string()]),
17879                ..Default::default()
17880            }),
17881            ..Default::default()
17882        },
17883        cx,
17884    )
17885    .await;
17886
17887    cx.lsp
17888        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17889            Ok(Some(lsp::CompletionResponse::Array(vec![
17890                lsp::CompletionItem {
17891                    label: "bg-blue".into(),
17892                    ..Default::default()
17893                },
17894                lsp::CompletionItem {
17895                    label: "bg-red".into(),
17896                    ..Default::default()
17897                },
17898                lsp::CompletionItem {
17899                    label: "bg-yellow".into(),
17900                    ..Default::default()
17901                },
17902            ])))
17903        });
17904
17905    cx.set_state(r#"<p class="bgˇ" />"#);
17906
17907    // Trigger completion when typing a dash, because the dash is an extra
17908    // word character in the 'element' scope, which contains the cursor.
17909    cx.simulate_keystroke("-");
17910    cx.executor().run_until_parked();
17911    cx.update_editor(|editor, _, _| {
17912        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17913        {
17914            assert_eq!(
17915                completion_menu_entries(menu),
17916                &["bg-blue", "bg-red", "bg-yellow"]
17917            );
17918        } else {
17919            panic!("expected completion menu to be open");
17920        }
17921    });
17922
17923    cx.simulate_keystroke("l");
17924    cx.executor().run_until_parked();
17925    cx.update_editor(|editor, _, _| {
17926        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17927        {
17928            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17929        } else {
17930            panic!("expected completion menu to be open");
17931        }
17932    });
17933
17934    // When filtering completions, consider the character after the '-' to
17935    // be the start of a subword.
17936    cx.set_state(r#"<p class="yelˇ" />"#);
17937    cx.simulate_keystroke("l");
17938    cx.executor().run_until_parked();
17939    cx.update_editor(|editor, _, _| {
17940        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17941        {
17942            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
17943        } else {
17944            panic!("expected completion menu to be open");
17945        }
17946    });
17947}
17948
17949fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
17950    let entries = menu.entries.borrow();
17951    entries.iter().map(|mat| mat.string.clone()).collect()
17952}
17953
17954#[gpui::test]
17955async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
17956    init_test(cx, |settings| {
17957        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
17958            Formatter::Prettier,
17959        )))
17960    });
17961
17962    let fs = FakeFs::new(cx.executor());
17963    fs.insert_file(path!("/file.ts"), Default::default()).await;
17964
17965    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
17966    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17967
17968    language_registry.add(Arc::new(Language::new(
17969        LanguageConfig {
17970            name: "TypeScript".into(),
17971            matcher: LanguageMatcher {
17972                path_suffixes: vec!["ts".to_string()],
17973                ..Default::default()
17974            },
17975            ..Default::default()
17976        },
17977        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17978    )));
17979    update_test_language_settings(cx, |settings| {
17980        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
17981    });
17982
17983    let test_plugin = "test_plugin";
17984    let _ = language_registry.register_fake_lsp(
17985        "TypeScript",
17986        FakeLspAdapter {
17987            prettier_plugins: vec![test_plugin],
17988            ..Default::default()
17989        },
17990    );
17991
17992    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
17993    let buffer = project
17994        .update(cx, |project, cx| {
17995            project.open_local_buffer(path!("/file.ts"), cx)
17996        })
17997        .await
17998        .unwrap();
17999
18000    let buffer_text = "one\ntwo\nthree\n";
18001    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18002    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18003    editor.update_in(cx, |editor, window, cx| {
18004        editor.set_text(buffer_text, window, cx)
18005    });
18006
18007    editor
18008        .update_in(cx, |editor, window, cx| {
18009            editor.perform_format(
18010                project.clone(),
18011                FormatTrigger::Manual,
18012                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18013                window,
18014                cx,
18015            )
18016        })
18017        .unwrap()
18018        .await;
18019    assert_eq!(
18020        editor.update(cx, |editor, cx| editor.text(cx)),
18021        buffer_text.to_string() + prettier_format_suffix,
18022        "Test prettier formatting was not applied to the original buffer text",
18023    );
18024
18025    update_test_language_settings(cx, |settings| {
18026        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18027    });
18028    let format = editor.update_in(cx, |editor, window, cx| {
18029        editor.perform_format(
18030            project.clone(),
18031            FormatTrigger::Manual,
18032            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18033            window,
18034            cx,
18035        )
18036    });
18037    format.await.unwrap();
18038    assert_eq!(
18039        editor.update(cx, |editor, cx| editor.text(cx)),
18040        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18041        "Autoformatting (via test prettier) was not applied to the original buffer text",
18042    );
18043}
18044
18045#[gpui::test]
18046async fn test_addition_reverts(cx: &mut TestAppContext) {
18047    init_test(cx, |_| {});
18048    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18049    let base_text = indoc! {r#"
18050        struct Row;
18051        struct Row1;
18052        struct Row2;
18053
18054        struct Row4;
18055        struct Row5;
18056        struct Row6;
18057
18058        struct Row8;
18059        struct Row9;
18060        struct Row10;"#};
18061
18062    // When addition hunks are not adjacent to carets, no hunk revert is performed
18063    assert_hunk_revert(
18064        indoc! {r#"struct Row;
18065                   struct Row1;
18066                   struct Row1.1;
18067                   struct Row1.2;
18068                   struct Row2;ˇ
18069
18070                   struct Row4;
18071                   struct Row5;
18072                   struct Row6;
18073
18074                   struct Row8;
18075                   ˇstruct Row9;
18076                   struct Row9.1;
18077                   struct Row9.2;
18078                   struct Row9.3;
18079                   struct Row10;"#},
18080        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18081        indoc! {r#"struct Row;
18082                   struct Row1;
18083                   struct Row1.1;
18084                   struct Row1.2;
18085                   struct Row2;ˇ
18086
18087                   struct Row4;
18088                   struct Row5;
18089                   struct Row6;
18090
18091                   struct Row8;
18092                   ˇstruct Row9;
18093                   struct Row9.1;
18094                   struct Row9.2;
18095                   struct Row9.3;
18096                   struct Row10;"#},
18097        base_text,
18098        &mut cx,
18099    );
18100    // Same for selections
18101    assert_hunk_revert(
18102        indoc! {r#"struct Row;
18103                   struct Row1;
18104                   struct Row2;
18105                   struct Row2.1;
18106                   struct Row2.2;
18107                   «ˇ
18108                   struct Row4;
18109                   struct» Row5;
18110                   «struct Row6;
18111                   ˇ»
18112                   struct Row9.1;
18113                   struct Row9.2;
18114                   struct Row9.3;
18115                   struct Row8;
18116                   struct Row9;
18117                   struct Row10;"#},
18118        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18119        indoc! {r#"struct Row;
18120                   struct Row1;
18121                   struct Row2;
18122                   struct Row2.1;
18123                   struct Row2.2;
18124                   «ˇ
18125                   struct Row4;
18126                   struct» Row5;
18127                   «struct Row6;
18128                   ˇ»
18129                   struct Row9.1;
18130                   struct Row9.2;
18131                   struct Row9.3;
18132                   struct Row8;
18133                   struct Row9;
18134                   struct Row10;"#},
18135        base_text,
18136        &mut cx,
18137    );
18138
18139    // When carets and selections intersect the addition hunks, those are reverted.
18140    // Adjacent carets got merged.
18141    assert_hunk_revert(
18142        indoc! {r#"struct Row;
18143                   ˇ// something on the top
18144                   struct Row1;
18145                   struct Row2;
18146                   struct Roˇw3.1;
18147                   struct Row2.2;
18148                   struct Row2.3;ˇ
18149
18150                   struct Row4;
18151                   struct ˇRow5.1;
18152                   struct Row5.2;
18153                   struct «Rowˇ»5.3;
18154                   struct Row5;
18155                   struct Row6;
18156                   ˇ
18157                   struct Row9.1;
18158                   struct «Rowˇ»9.2;
18159                   struct «ˇRow»9.3;
18160                   struct Row8;
18161                   struct Row9;
18162                   «ˇ// something on bottom»
18163                   struct Row10;"#},
18164        vec![
18165            DiffHunkStatusKind::Added,
18166            DiffHunkStatusKind::Added,
18167            DiffHunkStatusKind::Added,
18168            DiffHunkStatusKind::Added,
18169            DiffHunkStatusKind::Added,
18170        ],
18171        indoc! {r#"struct Row;
18172                   ˇstruct Row1;
18173                   struct Row2;
18174                   ˇ
18175                   struct Row4;
18176                   ˇstruct Row5;
18177                   struct Row6;
18178                   ˇ
18179                   ˇstruct Row8;
18180                   struct Row9;
18181                   ˇstruct Row10;"#},
18182        base_text,
18183        &mut cx,
18184    );
18185}
18186
18187#[gpui::test]
18188async fn test_modification_reverts(cx: &mut TestAppContext) {
18189    init_test(cx, |_| {});
18190    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18191    let base_text = indoc! {r#"
18192        struct Row;
18193        struct Row1;
18194        struct Row2;
18195
18196        struct Row4;
18197        struct Row5;
18198        struct Row6;
18199
18200        struct Row8;
18201        struct Row9;
18202        struct Row10;"#};
18203
18204    // Modification hunks behave the same as the addition ones.
18205    assert_hunk_revert(
18206        indoc! {r#"struct Row;
18207                   struct Row1;
18208                   struct Row33;
18209                   ˇ
18210                   struct Row4;
18211                   struct Row5;
18212                   struct Row6;
18213                   ˇ
18214                   struct Row99;
18215                   struct Row9;
18216                   struct Row10;"#},
18217        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18218        indoc! {r#"struct Row;
18219                   struct Row1;
18220                   struct Row33;
18221                   ˇ
18222                   struct Row4;
18223                   struct Row5;
18224                   struct Row6;
18225                   ˇ
18226                   struct Row99;
18227                   struct Row9;
18228                   struct Row10;"#},
18229        base_text,
18230        &mut cx,
18231    );
18232    assert_hunk_revert(
18233        indoc! {r#"struct Row;
18234                   struct Row1;
18235                   struct Row33;
18236                   «ˇ
18237                   struct Row4;
18238                   struct» Row5;
18239                   «struct Row6;
18240                   ˇ»
18241                   struct Row99;
18242                   struct Row9;
18243                   struct Row10;"#},
18244        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18245        indoc! {r#"struct Row;
18246                   struct Row1;
18247                   struct Row33;
18248                   «ˇ
18249                   struct Row4;
18250                   struct» Row5;
18251                   «struct Row6;
18252                   ˇ»
18253                   struct Row99;
18254                   struct Row9;
18255                   struct Row10;"#},
18256        base_text,
18257        &mut cx,
18258    );
18259
18260    assert_hunk_revert(
18261        indoc! {r#"ˇstruct Row1.1;
18262                   struct Row1;
18263                   «ˇstr»uct Row22;
18264
18265                   struct ˇRow44;
18266                   struct Row5;
18267                   struct «Rˇ»ow66;ˇ
18268
18269                   «struˇ»ct Row88;
18270                   struct Row9;
18271                   struct Row1011;ˇ"#},
18272        vec![
18273            DiffHunkStatusKind::Modified,
18274            DiffHunkStatusKind::Modified,
18275            DiffHunkStatusKind::Modified,
18276            DiffHunkStatusKind::Modified,
18277            DiffHunkStatusKind::Modified,
18278            DiffHunkStatusKind::Modified,
18279        ],
18280        indoc! {r#"struct Row;
18281                   ˇstruct Row1;
18282                   struct Row2;
18283                   ˇ
18284                   struct Row4;
18285                   ˇstruct Row5;
18286                   struct Row6;
18287                   ˇ
18288                   struct Row8;
18289                   ˇstruct Row9;
18290                   struct Row10;ˇ"#},
18291        base_text,
18292        &mut cx,
18293    );
18294}
18295
18296#[gpui::test]
18297async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18298    init_test(cx, |_| {});
18299    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18300    let base_text = indoc! {r#"
18301        one
18302
18303        two
18304        three
18305        "#};
18306
18307    cx.set_head_text(base_text);
18308    cx.set_state("\nˇ\n");
18309    cx.executor().run_until_parked();
18310    cx.update_editor(|editor, _window, cx| {
18311        editor.expand_selected_diff_hunks(cx);
18312    });
18313    cx.executor().run_until_parked();
18314    cx.update_editor(|editor, window, cx| {
18315        editor.backspace(&Default::default(), window, cx);
18316    });
18317    cx.run_until_parked();
18318    cx.assert_state_with_diff(
18319        indoc! {r#"
18320
18321        - two
18322        - threeˇ
18323        +
18324        "#}
18325        .to_string(),
18326    );
18327}
18328
18329#[gpui::test]
18330async fn test_deletion_reverts(cx: &mut TestAppContext) {
18331    init_test(cx, |_| {});
18332    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18333    let base_text = indoc! {r#"struct Row;
18334struct Row1;
18335struct Row2;
18336
18337struct Row4;
18338struct Row5;
18339struct Row6;
18340
18341struct Row8;
18342struct Row9;
18343struct Row10;"#};
18344
18345    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18346    assert_hunk_revert(
18347        indoc! {r#"struct Row;
18348                   struct Row2;
18349
18350                   ˇstruct Row4;
18351                   struct Row5;
18352                   struct Row6;
18353                   ˇ
18354                   struct Row8;
18355                   struct Row10;"#},
18356        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18357        indoc! {r#"struct Row;
18358                   struct Row2;
18359
18360                   ˇstruct Row4;
18361                   struct Row5;
18362                   struct Row6;
18363                   ˇ
18364                   struct Row8;
18365                   struct Row10;"#},
18366        base_text,
18367        &mut cx,
18368    );
18369    assert_hunk_revert(
18370        indoc! {r#"struct Row;
18371                   struct Row2;
18372
18373                   «ˇstruct Row4;
18374                   struct» Row5;
18375                   «struct Row6;
18376                   ˇ»
18377                   struct Row8;
18378                   struct Row10;"#},
18379        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18380        indoc! {r#"struct Row;
18381                   struct Row2;
18382
18383                   «ˇstruct Row4;
18384                   struct» Row5;
18385                   «struct Row6;
18386                   ˇ»
18387                   struct Row8;
18388                   struct Row10;"#},
18389        base_text,
18390        &mut cx,
18391    );
18392
18393    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18394    assert_hunk_revert(
18395        indoc! {r#"struct Row;
18396                   ˇstruct Row2;
18397
18398                   struct Row4;
18399                   struct Row5;
18400                   struct Row6;
18401
18402                   struct Row8;ˇ
18403                   struct Row10;"#},
18404        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18405        indoc! {r#"struct Row;
18406                   struct Row1;
18407                   ˇstruct Row2;
18408
18409                   struct Row4;
18410                   struct Row5;
18411                   struct Row6;
18412
18413                   struct Row8;ˇ
18414                   struct Row9;
18415                   struct Row10;"#},
18416        base_text,
18417        &mut cx,
18418    );
18419    assert_hunk_revert(
18420        indoc! {r#"struct Row;
18421                   struct Row2«ˇ;
18422                   struct Row4;
18423                   struct» Row5;
18424                   «struct Row6;
18425
18426                   struct Row8;ˇ»
18427                   struct Row10;"#},
18428        vec![
18429            DiffHunkStatusKind::Deleted,
18430            DiffHunkStatusKind::Deleted,
18431            DiffHunkStatusKind::Deleted,
18432        ],
18433        indoc! {r#"struct Row;
18434                   struct Row1;
18435                   struct Row2«ˇ;
18436
18437                   struct Row4;
18438                   struct» Row5;
18439                   «struct Row6;
18440
18441                   struct Row8;ˇ»
18442                   struct Row9;
18443                   struct Row10;"#},
18444        base_text,
18445        &mut cx,
18446    );
18447}
18448
18449#[gpui::test]
18450async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18451    init_test(cx, |_| {});
18452
18453    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18454    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18455    let base_text_3 =
18456        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18457
18458    let text_1 = edit_first_char_of_every_line(base_text_1);
18459    let text_2 = edit_first_char_of_every_line(base_text_2);
18460    let text_3 = edit_first_char_of_every_line(base_text_3);
18461
18462    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18463    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18464    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18465
18466    let multibuffer = cx.new(|cx| {
18467        let mut multibuffer = MultiBuffer::new(ReadWrite);
18468        multibuffer.push_excerpts(
18469            buffer_1.clone(),
18470            [
18471                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18472                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18473                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18474            ],
18475            cx,
18476        );
18477        multibuffer.push_excerpts(
18478            buffer_2.clone(),
18479            [
18480                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18481                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18482                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18483            ],
18484            cx,
18485        );
18486        multibuffer.push_excerpts(
18487            buffer_3.clone(),
18488            [
18489                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18490                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18491                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18492            ],
18493            cx,
18494        );
18495        multibuffer
18496    });
18497
18498    let fs = FakeFs::new(cx.executor());
18499    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18500    let (editor, cx) = cx
18501        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18502    editor.update_in(cx, |editor, _window, cx| {
18503        for (buffer, diff_base) in [
18504            (buffer_1.clone(), base_text_1),
18505            (buffer_2.clone(), base_text_2),
18506            (buffer_3.clone(), base_text_3),
18507        ] {
18508            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18509            editor
18510                .buffer
18511                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18512        }
18513    });
18514    cx.executor().run_until_parked();
18515
18516    editor.update_in(cx, |editor, window, cx| {
18517        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}");
18518        editor.select_all(&SelectAll, window, cx);
18519        editor.git_restore(&Default::default(), window, cx);
18520    });
18521    cx.executor().run_until_parked();
18522
18523    // When all ranges are selected, all buffer hunks are reverted.
18524    editor.update(cx, |editor, cx| {
18525        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");
18526    });
18527    buffer_1.update(cx, |buffer, _| {
18528        assert_eq!(buffer.text(), base_text_1);
18529    });
18530    buffer_2.update(cx, |buffer, _| {
18531        assert_eq!(buffer.text(), base_text_2);
18532    });
18533    buffer_3.update(cx, |buffer, _| {
18534        assert_eq!(buffer.text(), base_text_3);
18535    });
18536
18537    editor.update_in(cx, |editor, window, cx| {
18538        editor.undo(&Default::default(), window, cx);
18539    });
18540
18541    editor.update_in(cx, |editor, window, cx| {
18542        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18543            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18544        });
18545        editor.git_restore(&Default::default(), window, cx);
18546    });
18547
18548    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18549    // but not affect buffer_2 and its related excerpts.
18550    editor.update(cx, |editor, cx| {
18551        assert_eq!(
18552            editor.text(cx),
18553            "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}"
18554        );
18555    });
18556    buffer_1.update(cx, |buffer, _| {
18557        assert_eq!(buffer.text(), base_text_1);
18558    });
18559    buffer_2.update(cx, |buffer, _| {
18560        assert_eq!(
18561            buffer.text(),
18562            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18563        );
18564    });
18565    buffer_3.update(cx, |buffer, _| {
18566        assert_eq!(
18567            buffer.text(),
18568            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18569        );
18570    });
18571
18572    fn edit_first_char_of_every_line(text: &str) -> String {
18573        text.split('\n')
18574            .map(|line| format!("X{}", &line[1..]))
18575            .collect::<Vec<_>>()
18576            .join("\n")
18577    }
18578}
18579
18580#[gpui::test]
18581async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18582    init_test(cx, |_| {});
18583
18584    let cols = 4;
18585    let rows = 10;
18586    let sample_text_1 = sample_text(rows, cols, 'a');
18587    assert_eq!(
18588        sample_text_1,
18589        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18590    );
18591    let sample_text_2 = sample_text(rows, cols, 'l');
18592    assert_eq!(
18593        sample_text_2,
18594        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18595    );
18596    let sample_text_3 = sample_text(rows, cols, 'v');
18597    assert_eq!(
18598        sample_text_3,
18599        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18600    );
18601
18602    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18603    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18604    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18605
18606    let multi_buffer = cx.new(|cx| {
18607        let mut multibuffer = MultiBuffer::new(ReadWrite);
18608        multibuffer.push_excerpts(
18609            buffer_1.clone(),
18610            [
18611                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18612                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18613                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18614            ],
18615            cx,
18616        );
18617        multibuffer.push_excerpts(
18618            buffer_2.clone(),
18619            [
18620                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18621                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18622                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18623            ],
18624            cx,
18625        );
18626        multibuffer.push_excerpts(
18627            buffer_3.clone(),
18628            [
18629                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18630                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18631                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18632            ],
18633            cx,
18634        );
18635        multibuffer
18636    });
18637
18638    let fs = FakeFs::new(cx.executor());
18639    fs.insert_tree(
18640        "/a",
18641        json!({
18642            "main.rs": sample_text_1,
18643            "other.rs": sample_text_2,
18644            "lib.rs": sample_text_3,
18645        }),
18646    )
18647    .await;
18648    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18649    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18650    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18651    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18652        Editor::new(
18653            EditorMode::full(),
18654            multi_buffer,
18655            Some(project.clone()),
18656            window,
18657            cx,
18658        )
18659    });
18660    let multibuffer_item_id = workspace
18661        .update(cx, |workspace, window, cx| {
18662            assert!(
18663                workspace.active_item(cx).is_none(),
18664                "active item should be None before the first item is added"
18665            );
18666            workspace.add_item_to_active_pane(
18667                Box::new(multi_buffer_editor.clone()),
18668                None,
18669                true,
18670                window,
18671                cx,
18672            );
18673            let active_item = workspace
18674                .active_item(cx)
18675                .expect("should have an active item after adding the multi buffer");
18676            assert!(
18677                !active_item.is_singleton(cx),
18678                "A multi buffer was expected to active after adding"
18679            );
18680            active_item.item_id()
18681        })
18682        .unwrap();
18683    cx.executor().run_until_parked();
18684
18685    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18686        editor.change_selections(
18687            SelectionEffects::scroll(Autoscroll::Next),
18688            window,
18689            cx,
18690            |s| s.select_ranges(Some(1..2)),
18691        );
18692        editor.open_excerpts(&OpenExcerpts, window, cx);
18693    });
18694    cx.executor().run_until_parked();
18695    let first_item_id = workspace
18696        .update(cx, |workspace, window, cx| {
18697            let active_item = workspace
18698                .active_item(cx)
18699                .expect("should have an active item after navigating into the 1st buffer");
18700            let first_item_id = active_item.item_id();
18701            assert_ne!(
18702                first_item_id, multibuffer_item_id,
18703                "Should navigate into the 1st buffer and activate it"
18704            );
18705            assert!(
18706                active_item.is_singleton(cx),
18707                "New active item should be a singleton buffer"
18708            );
18709            assert_eq!(
18710                active_item
18711                    .act_as::<Editor>(cx)
18712                    .expect("should have navigated into an editor for the 1st buffer")
18713                    .read(cx)
18714                    .text(cx),
18715                sample_text_1
18716            );
18717
18718            workspace
18719                .go_back(workspace.active_pane().downgrade(), window, cx)
18720                .detach_and_log_err(cx);
18721
18722            first_item_id
18723        })
18724        .unwrap();
18725    cx.executor().run_until_parked();
18726    workspace
18727        .update(cx, |workspace, _, cx| {
18728            let active_item = workspace
18729                .active_item(cx)
18730                .expect("should have an active item after navigating back");
18731            assert_eq!(
18732                active_item.item_id(),
18733                multibuffer_item_id,
18734                "Should navigate back to the multi buffer"
18735            );
18736            assert!(!active_item.is_singleton(cx));
18737        })
18738        .unwrap();
18739
18740    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18741        editor.change_selections(
18742            SelectionEffects::scroll(Autoscroll::Next),
18743            window,
18744            cx,
18745            |s| s.select_ranges(Some(39..40)),
18746        );
18747        editor.open_excerpts(&OpenExcerpts, window, cx);
18748    });
18749    cx.executor().run_until_parked();
18750    let second_item_id = workspace
18751        .update(cx, |workspace, window, cx| {
18752            let active_item = workspace
18753                .active_item(cx)
18754                .expect("should have an active item after navigating into the 2nd buffer");
18755            let second_item_id = active_item.item_id();
18756            assert_ne!(
18757                second_item_id, multibuffer_item_id,
18758                "Should navigate away from the multibuffer"
18759            );
18760            assert_ne!(
18761                second_item_id, first_item_id,
18762                "Should navigate into the 2nd buffer and activate it"
18763            );
18764            assert!(
18765                active_item.is_singleton(cx),
18766                "New active item should be a singleton buffer"
18767            );
18768            assert_eq!(
18769                active_item
18770                    .act_as::<Editor>(cx)
18771                    .expect("should have navigated into an editor")
18772                    .read(cx)
18773                    .text(cx),
18774                sample_text_2
18775            );
18776
18777            workspace
18778                .go_back(workspace.active_pane().downgrade(), window, cx)
18779                .detach_and_log_err(cx);
18780
18781            second_item_id
18782        })
18783        .unwrap();
18784    cx.executor().run_until_parked();
18785    workspace
18786        .update(cx, |workspace, _, cx| {
18787            let active_item = workspace
18788                .active_item(cx)
18789                .expect("should have an active item after navigating back from the 2nd buffer");
18790            assert_eq!(
18791                active_item.item_id(),
18792                multibuffer_item_id,
18793                "Should navigate back from the 2nd buffer to the multi buffer"
18794            );
18795            assert!(!active_item.is_singleton(cx));
18796        })
18797        .unwrap();
18798
18799    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18800        editor.change_selections(
18801            SelectionEffects::scroll(Autoscroll::Next),
18802            window,
18803            cx,
18804            |s| s.select_ranges(Some(70..70)),
18805        );
18806        editor.open_excerpts(&OpenExcerpts, window, cx);
18807    });
18808    cx.executor().run_until_parked();
18809    workspace
18810        .update(cx, |workspace, window, cx| {
18811            let active_item = workspace
18812                .active_item(cx)
18813                .expect("should have an active item after navigating into the 3rd buffer");
18814            let third_item_id = active_item.item_id();
18815            assert_ne!(
18816                third_item_id, multibuffer_item_id,
18817                "Should navigate into the 3rd buffer and activate it"
18818            );
18819            assert_ne!(third_item_id, first_item_id);
18820            assert_ne!(third_item_id, second_item_id);
18821            assert!(
18822                active_item.is_singleton(cx),
18823                "New active item should be a singleton buffer"
18824            );
18825            assert_eq!(
18826                active_item
18827                    .act_as::<Editor>(cx)
18828                    .expect("should have navigated into an editor")
18829                    .read(cx)
18830                    .text(cx),
18831                sample_text_3
18832            );
18833
18834            workspace
18835                .go_back(workspace.active_pane().downgrade(), window, cx)
18836                .detach_and_log_err(cx);
18837        })
18838        .unwrap();
18839    cx.executor().run_until_parked();
18840    workspace
18841        .update(cx, |workspace, _, cx| {
18842            let active_item = workspace
18843                .active_item(cx)
18844                .expect("should have an active item after navigating back from the 3rd buffer");
18845            assert_eq!(
18846                active_item.item_id(),
18847                multibuffer_item_id,
18848                "Should navigate back from the 3rd buffer to the multi buffer"
18849            );
18850            assert!(!active_item.is_singleton(cx));
18851        })
18852        .unwrap();
18853}
18854
18855#[gpui::test]
18856async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18857    init_test(cx, |_| {});
18858
18859    let mut cx = EditorTestContext::new(cx).await;
18860
18861    let diff_base = r#"
18862        use some::mod;
18863
18864        const A: u32 = 42;
18865
18866        fn main() {
18867            println!("hello");
18868
18869            println!("world");
18870        }
18871        "#
18872    .unindent();
18873
18874    cx.set_state(
18875        &r#"
18876        use some::modified;
18877
18878        ˇ
18879        fn main() {
18880            println!("hello there");
18881
18882            println!("around the");
18883            println!("world");
18884        }
18885        "#
18886        .unindent(),
18887    );
18888
18889    cx.set_head_text(&diff_base);
18890    executor.run_until_parked();
18891
18892    cx.update_editor(|editor, window, cx| {
18893        editor.go_to_next_hunk(&GoToHunk, window, cx);
18894        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18895    });
18896    executor.run_until_parked();
18897    cx.assert_state_with_diff(
18898        r#"
18899          use some::modified;
18900
18901
18902          fn main() {
18903        -     println!("hello");
18904        + ˇ    println!("hello there");
18905
18906              println!("around the");
18907              println!("world");
18908          }
18909        "#
18910        .unindent(),
18911    );
18912
18913    cx.update_editor(|editor, window, cx| {
18914        for _ in 0..2 {
18915            editor.go_to_next_hunk(&GoToHunk, window, cx);
18916            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18917        }
18918    });
18919    executor.run_until_parked();
18920    cx.assert_state_with_diff(
18921        r#"
18922        - use some::mod;
18923        + ˇuse some::modified;
18924
18925
18926          fn main() {
18927        -     println!("hello");
18928        +     println!("hello there");
18929
18930        +     println!("around the");
18931              println!("world");
18932          }
18933        "#
18934        .unindent(),
18935    );
18936
18937    cx.update_editor(|editor, window, cx| {
18938        editor.go_to_next_hunk(&GoToHunk, window, cx);
18939        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18940    });
18941    executor.run_until_parked();
18942    cx.assert_state_with_diff(
18943        r#"
18944        - use some::mod;
18945        + use some::modified;
18946
18947        - const A: u32 = 42;
18948          ˇ
18949          fn main() {
18950        -     println!("hello");
18951        +     println!("hello there");
18952
18953        +     println!("around the");
18954              println!("world");
18955          }
18956        "#
18957        .unindent(),
18958    );
18959
18960    cx.update_editor(|editor, window, cx| {
18961        editor.cancel(&Cancel, window, cx);
18962    });
18963
18964    cx.assert_state_with_diff(
18965        r#"
18966          use some::modified;
18967
18968          ˇ
18969          fn main() {
18970              println!("hello there");
18971
18972              println!("around the");
18973              println!("world");
18974          }
18975        "#
18976        .unindent(),
18977    );
18978}
18979
18980#[gpui::test]
18981async fn test_diff_base_change_with_expanded_diff_hunks(
18982    executor: BackgroundExecutor,
18983    cx: &mut TestAppContext,
18984) {
18985    init_test(cx, |_| {});
18986
18987    let mut cx = EditorTestContext::new(cx).await;
18988
18989    let diff_base = r#"
18990        use some::mod1;
18991        use some::mod2;
18992
18993        const A: u32 = 42;
18994        const B: u32 = 42;
18995        const C: u32 = 42;
18996
18997        fn main() {
18998            println!("hello");
18999
19000            println!("world");
19001        }
19002        "#
19003    .unindent();
19004
19005    cx.set_state(
19006        &r#"
19007        use some::mod2;
19008
19009        const A: u32 = 42;
19010        const C: u32 = 42;
19011
19012        fn main(ˇ) {
19013            //println!("hello");
19014
19015            println!("world");
19016            //
19017            //
19018        }
19019        "#
19020        .unindent(),
19021    );
19022
19023    cx.set_head_text(&diff_base);
19024    executor.run_until_parked();
19025
19026    cx.update_editor(|editor, window, cx| {
19027        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19028    });
19029    executor.run_until_parked();
19030    cx.assert_state_with_diff(
19031        r#"
19032        - use some::mod1;
19033          use some::mod2;
19034
19035          const A: u32 = 42;
19036        - const B: u32 = 42;
19037          const C: u32 = 42;
19038
19039          fn main(ˇ) {
19040        -     println!("hello");
19041        +     //println!("hello");
19042
19043              println!("world");
19044        +     //
19045        +     //
19046          }
19047        "#
19048        .unindent(),
19049    );
19050
19051    cx.set_head_text("new diff base!");
19052    executor.run_until_parked();
19053    cx.assert_state_with_diff(
19054        r#"
19055        - new diff base!
19056        + use some::mod2;
19057        +
19058        + const A: u32 = 42;
19059        + const C: u32 = 42;
19060        +
19061        + fn main(ˇ) {
19062        +     //println!("hello");
19063        +
19064        +     println!("world");
19065        +     //
19066        +     //
19067        + }
19068        "#
19069        .unindent(),
19070    );
19071}
19072
19073#[gpui::test]
19074async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19075    init_test(cx, |_| {});
19076
19077    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19078    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19079    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19080    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19081    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19082    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19083
19084    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19085    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19086    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19087
19088    let multi_buffer = cx.new(|cx| {
19089        let mut multibuffer = MultiBuffer::new(ReadWrite);
19090        multibuffer.push_excerpts(
19091            buffer_1.clone(),
19092            [
19093                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19094                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19095                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19096            ],
19097            cx,
19098        );
19099        multibuffer.push_excerpts(
19100            buffer_2.clone(),
19101            [
19102                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19103                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19104                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19105            ],
19106            cx,
19107        );
19108        multibuffer.push_excerpts(
19109            buffer_3.clone(),
19110            [
19111                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19112                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19113                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19114            ],
19115            cx,
19116        );
19117        multibuffer
19118    });
19119
19120    let editor =
19121        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19122    editor
19123        .update(cx, |editor, _window, cx| {
19124            for (buffer, diff_base) in [
19125                (buffer_1.clone(), file_1_old),
19126                (buffer_2.clone(), file_2_old),
19127                (buffer_3.clone(), file_3_old),
19128            ] {
19129                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19130                editor
19131                    .buffer
19132                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19133            }
19134        })
19135        .unwrap();
19136
19137    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19138    cx.run_until_parked();
19139
19140    cx.assert_editor_state(
19141        &"
19142            ˇaaa
19143            ccc
19144            ddd
19145
19146            ggg
19147            hhh
19148
19149
19150            lll
19151            mmm
19152            NNN
19153
19154            qqq
19155            rrr
19156
19157            uuu
19158            111
19159            222
19160            333
19161
19162            666
19163            777
19164
19165            000
19166            !!!"
19167        .unindent(),
19168    );
19169
19170    cx.update_editor(|editor, window, cx| {
19171        editor.select_all(&SelectAll, window, cx);
19172        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19173    });
19174    cx.executor().run_until_parked();
19175
19176    cx.assert_state_with_diff(
19177        "
19178            «aaa
19179          - bbb
19180            ccc
19181            ddd
19182
19183            ggg
19184            hhh
19185
19186
19187            lll
19188            mmm
19189          - nnn
19190          + NNN
19191
19192            qqq
19193            rrr
19194
19195            uuu
19196            111
19197            222
19198            333
19199
19200          + 666
19201            777
19202
19203            000
19204            !!!ˇ»"
19205            .unindent(),
19206    );
19207}
19208
19209#[gpui::test]
19210async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19211    init_test(cx, |_| {});
19212
19213    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19214    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19215
19216    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19217    let multi_buffer = cx.new(|cx| {
19218        let mut multibuffer = MultiBuffer::new(ReadWrite);
19219        multibuffer.push_excerpts(
19220            buffer.clone(),
19221            [
19222                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19223                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19224                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19225            ],
19226            cx,
19227        );
19228        multibuffer
19229    });
19230
19231    let editor =
19232        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19233    editor
19234        .update(cx, |editor, _window, cx| {
19235            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19236            editor
19237                .buffer
19238                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19239        })
19240        .unwrap();
19241
19242    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19243    cx.run_until_parked();
19244
19245    cx.update_editor(|editor, window, cx| {
19246        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19247    });
19248    cx.executor().run_until_parked();
19249
19250    // When the start of a hunk coincides with the start of its excerpt,
19251    // the hunk is expanded. When the start of a hunk is earlier than
19252    // the start of its excerpt, the hunk is not expanded.
19253    cx.assert_state_with_diff(
19254        "
19255            ˇaaa
19256          - bbb
19257          + BBB
19258
19259          - ddd
19260          - eee
19261          + DDD
19262          + EEE
19263            fff
19264
19265            iii
19266        "
19267        .unindent(),
19268    );
19269}
19270
19271#[gpui::test]
19272async fn test_edits_around_expanded_insertion_hunks(
19273    executor: BackgroundExecutor,
19274    cx: &mut TestAppContext,
19275) {
19276    init_test(cx, |_| {});
19277
19278    let mut cx = EditorTestContext::new(cx).await;
19279
19280    let diff_base = r#"
19281        use some::mod1;
19282        use some::mod2;
19283
19284        const A: u32 = 42;
19285
19286        fn main() {
19287            println!("hello");
19288
19289            println!("world");
19290        }
19291        "#
19292    .unindent();
19293    executor.run_until_parked();
19294    cx.set_state(
19295        &r#"
19296        use some::mod1;
19297        use some::mod2;
19298
19299        const A: u32 = 42;
19300        const B: u32 = 42;
19301        const C: u32 = 42;
19302        ˇ
19303
19304        fn main() {
19305            println!("hello");
19306
19307            println!("world");
19308        }
19309        "#
19310        .unindent(),
19311    );
19312
19313    cx.set_head_text(&diff_base);
19314    executor.run_until_parked();
19315
19316    cx.update_editor(|editor, window, cx| {
19317        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19318    });
19319    executor.run_until_parked();
19320
19321    cx.assert_state_with_diff(
19322        r#"
19323        use some::mod1;
19324        use some::mod2;
19325
19326        const A: u32 = 42;
19327      + const B: u32 = 42;
19328      + const C: u32 = 42;
19329      + ˇ
19330
19331        fn main() {
19332            println!("hello");
19333
19334            println!("world");
19335        }
19336      "#
19337        .unindent(),
19338    );
19339
19340    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19341    executor.run_until_parked();
19342
19343    cx.assert_state_with_diff(
19344        r#"
19345        use some::mod1;
19346        use some::mod2;
19347
19348        const A: u32 = 42;
19349      + const B: u32 = 42;
19350      + const C: u32 = 42;
19351      + const D: u32 = 42;
19352      + ˇ
19353
19354        fn main() {
19355            println!("hello");
19356
19357            println!("world");
19358        }
19359      "#
19360        .unindent(),
19361    );
19362
19363    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19364    executor.run_until_parked();
19365
19366    cx.assert_state_with_diff(
19367        r#"
19368        use some::mod1;
19369        use some::mod2;
19370
19371        const A: u32 = 42;
19372      + const B: u32 = 42;
19373      + const C: u32 = 42;
19374      + const D: u32 = 42;
19375      + const E: u32 = 42;
19376      + ˇ
19377
19378        fn main() {
19379            println!("hello");
19380
19381            println!("world");
19382        }
19383      "#
19384        .unindent(),
19385    );
19386
19387    cx.update_editor(|editor, window, cx| {
19388        editor.delete_line(&DeleteLine, window, cx);
19389    });
19390    executor.run_until_parked();
19391
19392    cx.assert_state_with_diff(
19393        r#"
19394        use some::mod1;
19395        use some::mod2;
19396
19397        const A: u32 = 42;
19398      + const B: u32 = 42;
19399      + const C: u32 = 42;
19400      + const D: u32 = 42;
19401      + const E: u32 = 42;
19402        ˇ
19403        fn main() {
19404            println!("hello");
19405
19406            println!("world");
19407        }
19408      "#
19409        .unindent(),
19410    );
19411
19412    cx.update_editor(|editor, window, cx| {
19413        editor.move_up(&MoveUp, window, cx);
19414        editor.delete_line(&DeleteLine, window, cx);
19415        editor.move_up(&MoveUp, window, cx);
19416        editor.delete_line(&DeleteLine, window, cx);
19417        editor.move_up(&MoveUp, window, cx);
19418        editor.delete_line(&DeleteLine, window, cx);
19419    });
19420    executor.run_until_parked();
19421    cx.assert_state_with_diff(
19422        r#"
19423        use some::mod1;
19424        use some::mod2;
19425
19426        const A: u32 = 42;
19427      + const B: u32 = 42;
19428        ˇ
19429        fn main() {
19430            println!("hello");
19431
19432            println!("world");
19433        }
19434      "#
19435        .unindent(),
19436    );
19437
19438    cx.update_editor(|editor, window, cx| {
19439        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19440        editor.delete_line(&DeleteLine, window, cx);
19441    });
19442    executor.run_until_parked();
19443    cx.assert_state_with_diff(
19444        r#"
19445        ˇ
19446        fn main() {
19447            println!("hello");
19448
19449            println!("world");
19450        }
19451      "#
19452        .unindent(),
19453    );
19454}
19455
19456#[gpui::test]
19457async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19458    init_test(cx, |_| {});
19459
19460    let mut cx = EditorTestContext::new(cx).await;
19461    cx.set_head_text(indoc! { "
19462        one
19463        two
19464        three
19465        four
19466        five
19467        "
19468    });
19469    cx.set_state(indoc! { "
19470        one
19471        ˇthree
19472        five
19473    "});
19474    cx.run_until_parked();
19475    cx.update_editor(|editor, window, cx| {
19476        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19477    });
19478    cx.assert_state_with_diff(
19479        indoc! { "
19480        one
19481      - two
19482        ˇthree
19483      - four
19484        five
19485    "}
19486        .to_string(),
19487    );
19488    cx.update_editor(|editor, window, cx| {
19489        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19490    });
19491
19492    cx.assert_state_with_diff(
19493        indoc! { "
19494        one
19495        ˇthree
19496        five
19497    "}
19498        .to_string(),
19499    );
19500
19501    cx.set_state(indoc! { "
19502        one
19503        ˇTWO
19504        three
19505        four
19506        five
19507    "});
19508    cx.run_until_parked();
19509    cx.update_editor(|editor, window, cx| {
19510        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19511    });
19512
19513    cx.assert_state_with_diff(
19514        indoc! { "
19515            one
19516          - two
19517          + ˇTWO
19518            three
19519            four
19520            five
19521        "}
19522        .to_string(),
19523    );
19524    cx.update_editor(|editor, window, cx| {
19525        editor.move_up(&Default::default(), window, cx);
19526        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19527    });
19528    cx.assert_state_with_diff(
19529        indoc! { "
19530            one
19531            ˇTWO
19532            three
19533            four
19534            five
19535        "}
19536        .to_string(),
19537    );
19538}
19539
19540#[gpui::test]
19541async fn test_edits_around_expanded_deletion_hunks(
19542    executor: BackgroundExecutor,
19543    cx: &mut TestAppContext,
19544) {
19545    init_test(cx, |_| {});
19546
19547    let mut cx = EditorTestContext::new(cx).await;
19548
19549    let diff_base = r#"
19550        use some::mod1;
19551        use some::mod2;
19552
19553        const A: u32 = 42;
19554        const B: u32 = 42;
19555        const C: u32 = 42;
19556
19557
19558        fn main() {
19559            println!("hello");
19560
19561            println!("world");
19562        }
19563    "#
19564    .unindent();
19565    executor.run_until_parked();
19566    cx.set_state(
19567        &r#"
19568        use some::mod1;
19569        use some::mod2;
19570
19571        ˇconst B: u32 = 42;
19572        const C: u32 = 42;
19573
19574
19575        fn main() {
19576            println!("hello");
19577
19578            println!("world");
19579        }
19580        "#
19581        .unindent(),
19582    );
19583
19584    cx.set_head_text(&diff_base);
19585    executor.run_until_parked();
19586
19587    cx.update_editor(|editor, window, cx| {
19588        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19589    });
19590    executor.run_until_parked();
19591
19592    cx.assert_state_with_diff(
19593        r#"
19594        use some::mod1;
19595        use some::mod2;
19596
19597      - const A: u32 = 42;
19598        ˇconst B: u32 = 42;
19599        const C: u32 = 42;
19600
19601
19602        fn main() {
19603            println!("hello");
19604
19605            println!("world");
19606        }
19607      "#
19608        .unindent(),
19609    );
19610
19611    cx.update_editor(|editor, window, cx| {
19612        editor.delete_line(&DeleteLine, window, cx);
19613    });
19614    executor.run_until_parked();
19615    cx.assert_state_with_diff(
19616        r#"
19617        use some::mod1;
19618        use some::mod2;
19619
19620      - const A: u32 = 42;
19621      - const B: u32 = 42;
19622        ˇconst C: u32 = 42;
19623
19624
19625        fn main() {
19626            println!("hello");
19627
19628            println!("world");
19629        }
19630      "#
19631        .unindent(),
19632    );
19633
19634    cx.update_editor(|editor, window, cx| {
19635        editor.delete_line(&DeleteLine, window, cx);
19636    });
19637    executor.run_until_parked();
19638    cx.assert_state_with_diff(
19639        r#"
19640        use some::mod1;
19641        use some::mod2;
19642
19643      - const A: u32 = 42;
19644      - const B: u32 = 42;
19645      - const C: u32 = 42;
19646        ˇ
19647
19648        fn main() {
19649            println!("hello");
19650
19651            println!("world");
19652        }
19653      "#
19654        .unindent(),
19655    );
19656
19657    cx.update_editor(|editor, window, cx| {
19658        editor.handle_input("replacement", window, cx);
19659    });
19660    executor.run_until_parked();
19661    cx.assert_state_with_diff(
19662        r#"
19663        use some::mod1;
19664        use some::mod2;
19665
19666      - const A: u32 = 42;
19667      - const B: u32 = 42;
19668      - const C: u32 = 42;
19669      -
19670      + replacementˇ
19671
19672        fn main() {
19673            println!("hello");
19674
19675            println!("world");
19676        }
19677      "#
19678        .unindent(),
19679    );
19680}
19681
19682#[gpui::test]
19683async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19684    init_test(cx, |_| {});
19685
19686    let mut cx = EditorTestContext::new(cx).await;
19687
19688    let base_text = r#"
19689        one
19690        two
19691        three
19692        four
19693        five
19694    "#
19695    .unindent();
19696    executor.run_until_parked();
19697    cx.set_state(
19698        &r#"
19699        one
19700        two
19701        fˇour
19702        five
19703        "#
19704        .unindent(),
19705    );
19706
19707    cx.set_head_text(&base_text);
19708    executor.run_until_parked();
19709
19710    cx.update_editor(|editor, window, cx| {
19711        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19712    });
19713    executor.run_until_parked();
19714
19715    cx.assert_state_with_diff(
19716        r#"
19717          one
19718          two
19719        - three
19720          fˇour
19721          five
19722        "#
19723        .unindent(),
19724    );
19725
19726    cx.update_editor(|editor, window, cx| {
19727        editor.backspace(&Backspace, window, cx);
19728        editor.backspace(&Backspace, window, cx);
19729    });
19730    executor.run_until_parked();
19731    cx.assert_state_with_diff(
19732        r#"
19733          one
19734          two
19735        - threeˇ
19736        - four
19737        + our
19738          five
19739        "#
19740        .unindent(),
19741    );
19742}
19743
19744#[gpui::test]
19745async fn test_edit_after_expanded_modification_hunk(
19746    executor: BackgroundExecutor,
19747    cx: &mut TestAppContext,
19748) {
19749    init_test(cx, |_| {});
19750
19751    let mut cx = EditorTestContext::new(cx).await;
19752
19753    let diff_base = r#"
19754        use some::mod1;
19755        use some::mod2;
19756
19757        const A: u32 = 42;
19758        const B: u32 = 42;
19759        const C: u32 = 42;
19760        const D: u32 = 42;
19761
19762
19763        fn main() {
19764            println!("hello");
19765
19766            println!("world");
19767        }"#
19768    .unindent();
19769
19770    cx.set_state(
19771        &r#"
19772        use some::mod1;
19773        use some::mod2;
19774
19775        const A: u32 = 42;
19776        const B: u32 = 42;
19777        const C: u32 = 43ˇ
19778        const D: u32 = 42;
19779
19780
19781        fn main() {
19782            println!("hello");
19783
19784            println!("world");
19785        }"#
19786        .unindent(),
19787    );
19788
19789    cx.set_head_text(&diff_base);
19790    executor.run_until_parked();
19791    cx.update_editor(|editor, window, cx| {
19792        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19793    });
19794    executor.run_until_parked();
19795
19796    cx.assert_state_with_diff(
19797        r#"
19798        use some::mod1;
19799        use some::mod2;
19800
19801        const A: u32 = 42;
19802        const B: u32 = 42;
19803      - const C: u32 = 42;
19804      + const C: u32 = 43ˇ
19805        const D: u32 = 42;
19806
19807
19808        fn main() {
19809            println!("hello");
19810
19811            println!("world");
19812        }"#
19813        .unindent(),
19814    );
19815
19816    cx.update_editor(|editor, window, cx| {
19817        editor.handle_input("\nnew_line\n", window, cx);
19818    });
19819    executor.run_until_parked();
19820
19821    cx.assert_state_with_diff(
19822        r#"
19823        use some::mod1;
19824        use some::mod2;
19825
19826        const A: u32 = 42;
19827        const B: u32 = 42;
19828      - const C: u32 = 42;
19829      + const C: u32 = 43
19830      + new_line
19831      + ˇ
19832        const D: u32 = 42;
19833
19834
19835        fn main() {
19836            println!("hello");
19837
19838            println!("world");
19839        }"#
19840        .unindent(),
19841    );
19842}
19843
19844#[gpui::test]
19845async fn test_stage_and_unstage_added_file_hunk(
19846    executor: BackgroundExecutor,
19847    cx: &mut TestAppContext,
19848) {
19849    init_test(cx, |_| {});
19850
19851    let mut cx = EditorTestContext::new(cx).await;
19852    cx.update_editor(|editor, _, cx| {
19853        editor.set_expand_all_diff_hunks(cx);
19854    });
19855
19856    let working_copy = r#"
19857            ˇfn main() {
19858                println!("hello, world!");
19859            }
19860        "#
19861    .unindent();
19862
19863    cx.set_state(&working_copy);
19864    executor.run_until_parked();
19865
19866    cx.assert_state_with_diff(
19867        r#"
19868            + ˇfn main() {
19869            +     println!("hello, world!");
19870            + }
19871        "#
19872        .unindent(),
19873    );
19874    cx.assert_index_text(None);
19875
19876    cx.update_editor(|editor, window, cx| {
19877        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19878    });
19879    executor.run_until_parked();
19880    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19881    cx.assert_state_with_diff(
19882        r#"
19883            + ˇfn main() {
19884            +     println!("hello, world!");
19885            + }
19886        "#
19887        .unindent(),
19888    );
19889
19890    cx.update_editor(|editor, window, cx| {
19891        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19892    });
19893    executor.run_until_parked();
19894    cx.assert_index_text(None);
19895}
19896
19897async fn setup_indent_guides_editor(
19898    text: &str,
19899    cx: &mut TestAppContext,
19900) -> (BufferId, EditorTestContext) {
19901    init_test(cx, |_| {});
19902
19903    let mut cx = EditorTestContext::new(cx).await;
19904
19905    let buffer_id = cx.update_editor(|editor, window, cx| {
19906        editor.set_text(text, window, cx);
19907        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19908
19909        buffer_ids[0]
19910    });
19911
19912    (buffer_id, cx)
19913}
19914
19915fn assert_indent_guides(
19916    range: Range<u32>,
19917    expected: Vec<IndentGuide>,
19918    active_indices: Option<Vec<usize>>,
19919    cx: &mut EditorTestContext,
19920) {
19921    let indent_guides = cx.update_editor(|editor, window, cx| {
19922        let snapshot = editor.snapshot(window, cx).display_snapshot;
19923        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19924            editor,
19925            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19926            true,
19927            &snapshot,
19928            cx,
19929        );
19930
19931        indent_guides.sort_by(|a, b| {
19932            a.depth.cmp(&b.depth).then(
19933                a.start_row
19934                    .cmp(&b.start_row)
19935                    .then(a.end_row.cmp(&b.end_row)),
19936            )
19937        });
19938        indent_guides
19939    });
19940
19941    if let Some(expected) = active_indices {
19942        let active_indices = cx.update_editor(|editor, window, cx| {
19943            let snapshot = editor.snapshot(window, cx).display_snapshot;
19944            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
19945        });
19946
19947        assert_eq!(
19948            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
19949            expected,
19950            "Active indent guide indices do not match"
19951        );
19952    }
19953
19954    assert_eq!(indent_guides, expected, "Indent guides do not match");
19955}
19956
19957fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
19958    IndentGuide {
19959        buffer_id,
19960        start_row: MultiBufferRow(start_row),
19961        end_row: MultiBufferRow(end_row),
19962        depth,
19963        tab_size: 4,
19964        settings: IndentGuideSettings {
19965            enabled: true,
19966            line_width: 1,
19967            active_line_width: 1,
19968            ..Default::default()
19969        },
19970    }
19971}
19972
19973#[gpui::test]
19974async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
19975    let (buffer_id, mut cx) = setup_indent_guides_editor(
19976        &"
19977        fn main() {
19978            let a = 1;
19979        }"
19980        .unindent(),
19981        cx,
19982    )
19983    .await;
19984
19985    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19986}
19987
19988#[gpui::test]
19989async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
19990    let (buffer_id, mut cx) = setup_indent_guides_editor(
19991        &"
19992        fn main() {
19993            let a = 1;
19994            let b = 2;
19995        }"
19996        .unindent(),
19997        cx,
19998    )
19999    .await;
20000
20001    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20002}
20003
20004#[gpui::test]
20005async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20006    let (buffer_id, mut cx) = setup_indent_guides_editor(
20007        &"
20008        fn main() {
20009            let a = 1;
20010            if a == 3 {
20011                let b = 2;
20012            } else {
20013                let c = 3;
20014            }
20015        }"
20016        .unindent(),
20017        cx,
20018    )
20019    .await;
20020
20021    assert_indent_guides(
20022        0..8,
20023        vec![
20024            indent_guide(buffer_id, 1, 6, 0),
20025            indent_guide(buffer_id, 3, 3, 1),
20026            indent_guide(buffer_id, 5, 5, 1),
20027        ],
20028        None,
20029        &mut cx,
20030    );
20031}
20032
20033#[gpui::test]
20034async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20035    let (buffer_id, mut cx) = setup_indent_guides_editor(
20036        &"
20037        fn main() {
20038            let a = 1;
20039                let b = 2;
20040            let c = 3;
20041        }"
20042        .unindent(),
20043        cx,
20044    )
20045    .await;
20046
20047    assert_indent_guides(
20048        0..5,
20049        vec![
20050            indent_guide(buffer_id, 1, 3, 0),
20051            indent_guide(buffer_id, 2, 2, 1),
20052        ],
20053        None,
20054        &mut cx,
20055    );
20056}
20057
20058#[gpui::test]
20059async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20060    let (buffer_id, mut cx) = setup_indent_guides_editor(
20061        &"
20062        fn main() {
20063            let a = 1;
20064
20065            let c = 3;
20066        }"
20067        .unindent(),
20068        cx,
20069    )
20070    .await;
20071
20072    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20073}
20074
20075#[gpui::test]
20076async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20077    let (buffer_id, mut cx) = setup_indent_guides_editor(
20078        &"
20079        fn main() {
20080            let a = 1;
20081
20082            let c = 3;
20083
20084            if a == 3 {
20085                let b = 2;
20086            } else {
20087                let c = 3;
20088            }
20089        }"
20090        .unindent(),
20091        cx,
20092    )
20093    .await;
20094
20095    assert_indent_guides(
20096        0..11,
20097        vec![
20098            indent_guide(buffer_id, 1, 9, 0),
20099            indent_guide(buffer_id, 6, 6, 1),
20100            indent_guide(buffer_id, 8, 8, 1),
20101        ],
20102        None,
20103        &mut cx,
20104    );
20105}
20106
20107#[gpui::test]
20108async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20109    let (buffer_id, mut cx) = setup_indent_guides_editor(
20110        &"
20111        fn main() {
20112            let a = 1;
20113
20114            let c = 3;
20115
20116            if a == 3 {
20117                let b = 2;
20118            } else {
20119                let c = 3;
20120            }
20121        }"
20122        .unindent(),
20123        cx,
20124    )
20125    .await;
20126
20127    assert_indent_guides(
20128        1..11,
20129        vec![
20130            indent_guide(buffer_id, 1, 9, 0),
20131            indent_guide(buffer_id, 6, 6, 1),
20132            indent_guide(buffer_id, 8, 8, 1),
20133        ],
20134        None,
20135        &mut cx,
20136    );
20137}
20138
20139#[gpui::test]
20140async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20141    let (buffer_id, mut cx) = setup_indent_guides_editor(
20142        &"
20143        fn main() {
20144            let a = 1;
20145
20146            let c = 3;
20147
20148            if a == 3 {
20149                let b = 2;
20150            } else {
20151                let c = 3;
20152            }
20153        }"
20154        .unindent(),
20155        cx,
20156    )
20157    .await;
20158
20159    assert_indent_guides(
20160        1..10,
20161        vec![
20162            indent_guide(buffer_id, 1, 9, 0),
20163            indent_guide(buffer_id, 6, 6, 1),
20164            indent_guide(buffer_id, 8, 8, 1),
20165        ],
20166        None,
20167        &mut cx,
20168    );
20169}
20170
20171#[gpui::test]
20172async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20173    let (buffer_id, mut cx) = setup_indent_guides_editor(
20174        &"
20175        fn main() {
20176            if a {
20177                b(
20178                    c,
20179                    d,
20180                )
20181            } else {
20182                e(
20183                    f
20184                )
20185            }
20186        }"
20187        .unindent(),
20188        cx,
20189    )
20190    .await;
20191
20192    assert_indent_guides(
20193        0..11,
20194        vec![
20195            indent_guide(buffer_id, 1, 10, 0),
20196            indent_guide(buffer_id, 2, 5, 1),
20197            indent_guide(buffer_id, 7, 9, 1),
20198            indent_guide(buffer_id, 3, 4, 2),
20199            indent_guide(buffer_id, 8, 8, 2),
20200        ],
20201        None,
20202        &mut cx,
20203    );
20204
20205    cx.update_editor(|editor, window, cx| {
20206        editor.fold_at(MultiBufferRow(2), window, cx);
20207        assert_eq!(
20208            editor.display_text(cx),
20209            "
20210            fn main() {
20211                if a {
20212                    b(⋯
20213                    )
20214                } else {
20215                    e(
20216                        f
20217                    )
20218                }
20219            }"
20220            .unindent()
20221        );
20222    });
20223
20224    assert_indent_guides(
20225        0..11,
20226        vec![
20227            indent_guide(buffer_id, 1, 10, 0),
20228            indent_guide(buffer_id, 2, 5, 1),
20229            indent_guide(buffer_id, 7, 9, 1),
20230            indent_guide(buffer_id, 8, 8, 2),
20231        ],
20232        None,
20233        &mut cx,
20234    );
20235}
20236
20237#[gpui::test]
20238async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20239    let (buffer_id, mut cx) = setup_indent_guides_editor(
20240        &"
20241        block1
20242            block2
20243                block3
20244                    block4
20245            block2
20246        block1
20247        block1"
20248            .unindent(),
20249        cx,
20250    )
20251    .await;
20252
20253    assert_indent_guides(
20254        1..10,
20255        vec![
20256            indent_guide(buffer_id, 1, 4, 0),
20257            indent_guide(buffer_id, 2, 3, 1),
20258            indent_guide(buffer_id, 3, 3, 2),
20259        ],
20260        None,
20261        &mut cx,
20262    );
20263}
20264
20265#[gpui::test]
20266async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20267    let (buffer_id, mut cx) = setup_indent_guides_editor(
20268        &"
20269        block1
20270            block2
20271                block3
20272
20273        block1
20274        block1"
20275            .unindent(),
20276        cx,
20277    )
20278    .await;
20279
20280    assert_indent_guides(
20281        0..6,
20282        vec![
20283            indent_guide(buffer_id, 1, 2, 0),
20284            indent_guide(buffer_id, 2, 2, 1),
20285        ],
20286        None,
20287        &mut cx,
20288    );
20289}
20290
20291#[gpui::test]
20292async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20293    let (buffer_id, mut cx) = setup_indent_guides_editor(
20294        &"
20295        function component() {
20296        \treturn (
20297        \t\t\t
20298        \t\t<div>
20299        \t\t\t<abc></abc>
20300        \t\t</div>
20301        \t)
20302        }"
20303        .unindent(),
20304        cx,
20305    )
20306    .await;
20307
20308    assert_indent_guides(
20309        0..8,
20310        vec![
20311            indent_guide(buffer_id, 1, 6, 0),
20312            indent_guide(buffer_id, 2, 5, 1),
20313            indent_guide(buffer_id, 4, 4, 2),
20314        ],
20315        None,
20316        &mut cx,
20317    );
20318}
20319
20320#[gpui::test]
20321async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20322    let (buffer_id, mut cx) = setup_indent_guides_editor(
20323        &"
20324        function component() {
20325        \treturn (
20326        \t
20327        \t\t<div>
20328        \t\t\t<abc></abc>
20329        \t\t</div>
20330        \t)
20331        }"
20332        .unindent(),
20333        cx,
20334    )
20335    .await;
20336
20337    assert_indent_guides(
20338        0..8,
20339        vec![
20340            indent_guide(buffer_id, 1, 6, 0),
20341            indent_guide(buffer_id, 2, 5, 1),
20342            indent_guide(buffer_id, 4, 4, 2),
20343        ],
20344        None,
20345        &mut cx,
20346    );
20347}
20348
20349#[gpui::test]
20350async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20351    let (buffer_id, mut cx) = setup_indent_guides_editor(
20352        &"
20353        block1
20354
20355
20356
20357            block2
20358        "
20359        .unindent(),
20360        cx,
20361    )
20362    .await;
20363
20364    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20365}
20366
20367#[gpui::test]
20368async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20369    let (buffer_id, mut cx) = setup_indent_guides_editor(
20370        &"
20371        def a:
20372        \tb = 3
20373        \tif True:
20374        \t\tc = 4
20375        \t\td = 5
20376        \tprint(b)
20377        "
20378        .unindent(),
20379        cx,
20380    )
20381    .await;
20382
20383    assert_indent_guides(
20384        0..6,
20385        vec![
20386            indent_guide(buffer_id, 1, 5, 0),
20387            indent_guide(buffer_id, 3, 4, 1),
20388        ],
20389        None,
20390        &mut cx,
20391    );
20392}
20393
20394#[gpui::test]
20395async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20396    let (buffer_id, mut cx) = setup_indent_guides_editor(
20397        &"
20398    fn main() {
20399        let a = 1;
20400    }"
20401        .unindent(),
20402        cx,
20403    )
20404    .await;
20405
20406    cx.update_editor(|editor, window, cx| {
20407        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20408            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20409        });
20410    });
20411
20412    assert_indent_guides(
20413        0..3,
20414        vec![indent_guide(buffer_id, 1, 1, 0)],
20415        Some(vec![0]),
20416        &mut cx,
20417    );
20418}
20419
20420#[gpui::test]
20421async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20422    let (buffer_id, mut cx) = setup_indent_guides_editor(
20423        &"
20424    fn main() {
20425        if 1 == 2 {
20426            let a = 1;
20427        }
20428    }"
20429        .unindent(),
20430        cx,
20431    )
20432    .await;
20433
20434    cx.update_editor(|editor, window, cx| {
20435        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20436            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20437        });
20438    });
20439
20440    assert_indent_guides(
20441        0..4,
20442        vec![
20443            indent_guide(buffer_id, 1, 3, 0),
20444            indent_guide(buffer_id, 2, 2, 1),
20445        ],
20446        Some(vec![1]),
20447        &mut cx,
20448    );
20449
20450    cx.update_editor(|editor, window, cx| {
20451        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20452            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20453        });
20454    });
20455
20456    assert_indent_guides(
20457        0..4,
20458        vec![
20459            indent_guide(buffer_id, 1, 3, 0),
20460            indent_guide(buffer_id, 2, 2, 1),
20461        ],
20462        Some(vec![1]),
20463        &mut cx,
20464    );
20465
20466    cx.update_editor(|editor, window, cx| {
20467        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20468            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20469        });
20470    });
20471
20472    assert_indent_guides(
20473        0..4,
20474        vec![
20475            indent_guide(buffer_id, 1, 3, 0),
20476            indent_guide(buffer_id, 2, 2, 1),
20477        ],
20478        Some(vec![0]),
20479        &mut cx,
20480    );
20481}
20482
20483#[gpui::test]
20484async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20485    let (buffer_id, mut cx) = setup_indent_guides_editor(
20486        &"
20487    fn main() {
20488        let a = 1;
20489
20490        let b = 2;
20491    }"
20492        .unindent(),
20493        cx,
20494    )
20495    .await;
20496
20497    cx.update_editor(|editor, window, cx| {
20498        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20499            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20500        });
20501    });
20502
20503    assert_indent_guides(
20504        0..5,
20505        vec![indent_guide(buffer_id, 1, 3, 0)],
20506        Some(vec![0]),
20507        &mut cx,
20508    );
20509}
20510
20511#[gpui::test]
20512async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20513    let (buffer_id, mut cx) = setup_indent_guides_editor(
20514        &"
20515    def m:
20516        a = 1
20517        pass"
20518            .unindent(),
20519        cx,
20520    )
20521    .await;
20522
20523    cx.update_editor(|editor, window, cx| {
20524        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20525            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20526        });
20527    });
20528
20529    assert_indent_guides(
20530        0..3,
20531        vec![indent_guide(buffer_id, 1, 2, 0)],
20532        Some(vec![0]),
20533        &mut cx,
20534    );
20535}
20536
20537#[gpui::test]
20538async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20539    init_test(cx, |_| {});
20540    let mut cx = EditorTestContext::new(cx).await;
20541    let text = indoc! {
20542        "
20543        impl A {
20544            fn b() {
20545                0;
20546                3;
20547                5;
20548                6;
20549                7;
20550            }
20551        }
20552        "
20553    };
20554    let base_text = indoc! {
20555        "
20556        impl A {
20557            fn b() {
20558                0;
20559                1;
20560                2;
20561                3;
20562                4;
20563            }
20564            fn c() {
20565                5;
20566                6;
20567                7;
20568            }
20569        }
20570        "
20571    };
20572
20573    cx.update_editor(|editor, window, cx| {
20574        editor.set_text(text, window, cx);
20575
20576        editor.buffer().update(cx, |multibuffer, cx| {
20577            let buffer = multibuffer.as_singleton().unwrap();
20578            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20579
20580            multibuffer.set_all_diff_hunks_expanded(cx);
20581            multibuffer.add_diff(diff, cx);
20582
20583            buffer.read(cx).remote_id()
20584        })
20585    });
20586    cx.run_until_parked();
20587
20588    cx.assert_state_with_diff(
20589        indoc! { "
20590          impl A {
20591              fn b() {
20592                  0;
20593        -         1;
20594        -         2;
20595                  3;
20596        -         4;
20597        -     }
20598        -     fn c() {
20599                  5;
20600                  6;
20601                  7;
20602              }
20603          }
20604          ˇ"
20605        }
20606        .to_string(),
20607    );
20608
20609    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20610        editor
20611            .snapshot(window, cx)
20612            .buffer_snapshot
20613            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20614            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20615            .collect::<Vec<_>>()
20616    });
20617    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20618    assert_eq!(
20619        actual_guides,
20620        vec![
20621            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20622            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20623            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20624        ]
20625    );
20626}
20627
20628#[gpui::test]
20629async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20630    init_test(cx, |_| {});
20631    let mut cx = EditorTestContext::new(cx).await;
20632
20633    let diff_base = r#"
20634        a
20635        b
20636        c
20637        "#
20638    .unindent();
20639
20640    cx.set_state(
20641        &r#"
20642        ˇA
20643        b
20644        C
20645        "#
20646        .unindent(),
20647    );
20648    cx.set_head_text(&diff_base);
20649    cx.update_editor(|editor, window, cx| {
20650        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20651    });
20652    executor.run_until_parked();
20653
20654    let both_hunks_expanded = r#"
20655        - a
20656        + ˇA
20657          b
20658        - c
20659        + C
20660        "#
20661    .unindent();
20662
20663    cx.assert_state_with_diff(both_hunks_expanded.clone());
20664
20665    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20666        let snapshot = editor.snapshot(window, cx);
20667        let hunks = editor
20668            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20669            .collect::<Vec<_>>();
20670        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20671        let buffer_id = hunks[0].buffer_id;
20672        hunks
20673            .into_iter()
20674            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20675            .collect::<Vec<_>>()
20676    });
20677    assert_eq!(hunk_ranges.len(), 2);
20678
20679    cx.update_editor(|editor, _, cx| {
20680        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20681    });
20682    executor.run_until_parked();
20683
20684    let second_hunk_expanded = r#"
20685          ˇA
20686          b
20687        - c
20688        + C
20689        "#
20690    .unindent();
20691
20692    cx.assert_state_with_diff(second_hunk_expanded);
20693
20694    cx.update_editor(|editor, _, cx| {
20695        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20696    });
20697    executor.run_until_parked();
20698
20699    cx.assert_state_with_diff(both_hunks_expanded.clone());
20700
20701    cx.update_editor(|editor, _, cx| {
20702        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20703    });
20704    executor.run_until_parked();
20705
20706    let first_hunk_expanded = r#"
20707        - a
20708        + ˇA
20709          b
20710          C
20711        "#
20712    .unindent();
20713
20714    cx.assert_state_with_diff(first_hunk_expanded);
20715
20716    cx.update_editor(|editor, _, cx| {
20717        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20718    });
20719    executor.run_until_parked();
20720
20721    cx.assert_state_with_diff(both_hunks_expanded);
20722
20723    cx.set_state(
20724        &r#"
20725        ˇA
20726        b
20727        "#
20728        .unindent(),
20729    );
20730    cx.run_until_parked();
20731
20732    // TODO this cursor position seems bad
20733    cx.assert_state_with_diff(
20734        r#"
20735        - ˇa
20736        + A
20737          b
20738        "#
20739        .unindent(),
20740    );
20741
20742    cx.update_editor(|editor, window, cx| {
20743        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20744    });
20745
20746    cx.assert_state_with_diff(
20747        r#"
20748            - ˇa
20749            + A
20750              b
20751            - c
20752            "#
20753        .unindent(),
20754    );
20755
20756    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20757        let snapshot = editor.snapshot(window, cx);
20758        let hunks = editor
20759            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20760            .collect::<Vec<_>>();
20761        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20762        let buffer_id = hunks[0].buffer_id;
20763        hunks
20764            .into_iter()
20765            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20766            .collect::<Vec<_>>()
20767    });
20768    assert_eq!(hunk_ranges.len(), 2);
20769
20770    cx.update_editor(|editor, _, cx| {
20771        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20772    });
20773    executor.run_until_parked();
20774
20775    cx.assert_state_with_diff(
20776        r#"
20777        - ˇa
20778        + A
20779          b
20780        "#
20781        .unindent(),
20782    );
20783}
20784
20785#[gpui::test]
20786async fn test_toggle_deletion_hunk_at_start_of_file(
20787    executor: BackgroundExecutor,
20788    cx: &mut TestAppContext,
20789) {
20790    init_test(cx, |_| {});
20791    let mut cx = EditorTestContext::new(cx).await;
20792
20793    let diff_base = r#"
20794        a
20795        b
20796        c
20797        "#
20798    .unindent();
20799
20800    cx.set_state(
20801        &r#"
20802        ˇb
20803        c
20804        "#
20805        .unindent(),
20806    );
20807    cx.set_head_text(&diff_base);
20808    cx.update_editor(|editor, window, cx| {
20809        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20810    });
20811    executor.run_until_parked();
20812
20813    let hunk_expanded = r#"
20814        - a
20815          ˇb
20816          c
20817        "#
20818    .unindent();
20819
20820    cx.assert_state_with_diff(hunk_expanded.clone());
20821
20822    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20823        let snapshot = editor.snapshot(window, cx);
20824        let hunks = editor
20825            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20826            .collect::<Vec<_>>();
20827        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20828        let buffer_id = hunks[0].buffer_id;
20829        hunks
20830            .into_iter()
20831            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20832            .collect::<Vec<_>>()
20833    });
20834    assert_eq!(hunk_ranges.len(), 1);
20835
20836    cx.update_editor(|editor, _, cx| {
20837        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20838    });
20839    executor.run_until_parked();
20840
20841    let hunk_collapsed = r#"
20842          ˇb
20843          c
20844        "#
20845    .unindent();
20846
20847    cx.assert_state_with_diff(hunk_collapsed);
20848
20849    cx.update_editor(|editor, _, cx| {
20850        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20851    });
20852    executor.run_until_parked();
20853
20854    cx.assert_state_with_diff(hunk_expanded);
20855}
20856
20857#[gpui::test]
20858async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20859    init_test(cx, |_| {});
20860
20861    let fs = FakeFs::new(cx.executor());
20862    fs.insert_tree(
20863        path!("/test"),
20864        json!({
20865            ".git": {},
20866            "file-1": "ONE\n",
20867            "file-2": "TWO\n",
20868            "file-3": "THREE\n",
20869        }),
20870    )
20871    .await;
20872
20873    fs.set_head_for_repo(
20874        path!("/test/.git").as_ref(),
20875        &[
20876            ("file-1".into(), "one\n".into()),
20877            ("file-2".into(), "two\n".into()),
20878            ("file-3".into(), "three\n".into()),
20879        ],
20880        "deadbeef",
20881    );
20882
20883    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20884    let mut buffers = vec![];
20885    for i in 1..=3 {
20886        let buffer = project
20887            .update(cx, |project, cx| {
20888                let path = format!(path!("/test/file-{}"), i);
20889                project.open_local_buffer(path, cx)
20890            })
20891            .await
20892            .unwrap();
20893        buffers.push(buffer);
20894    }
20895
20896    let multibuffer = cx.new(|cx| {
20897        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20898        multibuffer.set_all_diff_hunks_expanded(cx);
20899        for buffer in &buffers {
20900            let snapshot = buffer.read(cx).snapshot();
20901            multibuffer.set_excerpts_for_path(
20902                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20903                buffer.clone(),
20904                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20905                2,
20906                cx,
20907            );
20908        }
20909        multibuffer
20910    });
20911
20912    let editor = cx.add_window(|window, cx| {
20913        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20914    });
20915    cx.run_until_parked();
20916
20917    let snapshot = editor
20918        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20919        .unwrap();
20920    let hunks = snapshot
20921        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20922        .map(|hunk| match hunk {
20923            DisplayDiffHunk::Unfolded {
20924                display_row_range, ..
20925            } => display_row_range,
20926            DisplayDiffHunk::Folded { .. } => unreachable!(),
20927        })
20928        .collect::<Vec<_>>();
20929    assert_eq!(
20930        hunks,
20931        [
20932            DisplayRow(2)..DisplayRow(4),
20933            DisplayRow(7)..DisplayRow(9),
20934            DisplayRow(12)..DisplayRow(14),
20935        ]
20936    );
20937}
20938
20939#[gpui::test]
20940async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
20941    init_test(cx, |_| {});
20942
20943    let mut cx = EditorTestContext::new(cx).await;
20944    cx.set_head_text(indoc! { "
20945        one
20946        two
20947        three
20948        four
20949        five
20950        "
20951    });
20952    cx.set_index_text(indoc! { "
20953        one
20954        two
20955        three
20956        four
20957        five
20958        "
20959    });
20960    cx.set_state(indoc! {"
20961        one
20962        TWO
20963        ˇTHREE
20964        FOUR
20965        five
20966    "});
20967    cx.run_until_parked();
20968    cx.update_editor(|editor, window, cx| {
20969        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20970    });
20971    cx.run_until_parked();
20972    cx.assert_index_text(Some(indoc! {"
20973        one
20974        TWO
20975        THREE
20976        FOUR
20977        five
20978    "}));
20979    cx.set_state(indoc! { "
20980        one
20981        TWO
20982        ˇTHREE-HUNDRED
20983        FOUR
20984        five
20985    "});
20986    cx.run_until_parked();
20987    cx.update_editor(|editor, window, cx| {
20988        let snapshot = editor.snapshot(window, cx);
20989        let hunks = editor
20990            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20991            .collect::<Vec<_>>();
20992        assert_eq!(hunks.len(), 1);
20993        assert_eq!(
20994            hunks[0].status(),
20995            DiffHunkStatus {
20996                kind: DiffHunkStatusKind::Modified,
20997                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
20998            }
20999        );
21000
21001        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21002    });
21003    cx.run_until_parked();
21004    cx.assert_index_text(Some(indoc! {"
21005        one
21006        TWO
21007        THREE-HUNDRED
21008        FOUR
21009        five
21010    "}));
21011}
21012
21013#[gpui::test]
21014fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21015    init_test(cx, |_| {});
21016
21017    let editor = cx.add_window(|window, cx| {
21018        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21019        build_editor(buffer, window, cx)
21020    });
21021
21022    let render_args = Arc::new(Mutex::new(None));
21023    let snapshot = editor
21024        .update(cx, |editor, window, cx| {
21025            let snapshot = editor.buffer().read(cx).snapshot(cx);
21026            let range =
21027                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21028
21029            struct RenderArgs {
21030                row: MultiBufferRow,
21031                folded: bool,
21032                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21033            }
21034
21035            let crease = Crease::inline(
21036                range,
21037                FoldPlaceholder::test(),
21038                {
21039                    let toggle_callback = render_args.clone();
21040                    move |row, folded, callback, _window, _cx| {
21041                        *toggle_callback.lock() = Some(RenderArgs {
21042                            row,
21043                            folded,
21044                            callback,
21045                        });
21046                        div()
21047                    }
21048                },
21049                |_row, _folded, _window, _cx| div(),
21050            );
21051
21052            editor.insert_creases(Some(crease), cx);
21053            let snapshot = editor.snapshot(window, cx);
21054            let _div =
21055                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21056            snapshot
21057        })
21058        .unwrap();
21059
21060    let render_args = render_args.lock().take().unwrap();
21061    assert_eq!(render_args.row, MultiBufferRow(1));
21062    assert!(!render_args.folded);
21063    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21064
21065    cx.update_window(*editor, |_, window, cx| {
21066        (render_args.callback)(true, window, cx)
21067    })
21068    .unwrap();
21069    let snapshot = editor
21070        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21071        .unwrap();
21072    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21073
21074    cx.update_window(*editor, |_, window, cx| {
21075        (render_args.callback)(false, window, cx)
21076    })
21077    .unwrap();
21078    let snapshot = editor
21079        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21080        .unwrap();
21081    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21082}
21083
21084#[gpui::test]
21085async fn test_input_text(cx: &mut TestAppContext) {
21086    init_test(cx, |_| {});
21087    let mut cx = EditorTestContext::new(cx).await;
21088
21089    cx.set_state(
21090        &r#"ˇone
21091        two
21092
21093        three
21094        fourˇ
21095        five
21096
21097        siˇx"#
21098            .unindent(),
21099    );
21100
21101    cx.dispatch_action(HandleInput(String::new()));
21102    cx.assert_editor_state(
21103        &r#"ˇone
21104        two
21105
21106        three
21107        fourˇ
21108        five
21109
21110        siˇx"#
21111            .unindent(),
21112    );
21113
21114    cx.dispatch_action(HandleInput("AAAA".to_string()));
21115    cx.assert_editor_state(
21116        &r#"AAAAˇone
21117        two
21118
21119        three
21120        fourAAAAˇ
21121        five
21122
21123        siAAAAˇx"#
21124            .unindent(),
21125    );
21126}
21127
21128#[gpui::test]
21129async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21130    init_test(cx, |_| {});
21131
21132    let mut cx = EditorTestContext::new(cx).await;
21133    cx.set_state(
21134        r#"let foo = 1;
21135let foo = 2;
21136let foo = 3;
21137let fooˇ = 4;
21138let foo = 5;
21139let foo = 6;
21140let foo = 7;
21141let foo = 8;
21142let foo = 9;
21143let foo = 10;
21144let foo = 11;
21145let foo = 12;
21146let foo = 13;
21147let foo = 14;
21148let foo = 15;"#,
21149    );
21150
21151    cx.update_editor(|e, window, cx| {
21152        assert_eq!(
21153            e.next_scroll_position,
21154            NextScrollCursorCenterTopBottom::Center,
21155            "Default next scroll direction is center",
21156        );
21157
21158        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21159        assert_eq!(
21160            e.next_scroll_position,
21161            NextScrollCursorCenterTopBottom::Top,
21162            "After center, next scroll direction should be top",
21163        );
21164
21165        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21166        assert_eq!(
21167            e.next_scroll_position,
21168            NextScrollCursorCenterTopBottom::Bottom,
21169            "After top, next scroll direction should be bottom",
21170        );
21171
21172        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21173        assert_eq!(
21174            e.next_scroll_position,
21175            NextScrollCursorCenterTopBottom::Center,
21176            "After bottom, scrolling should start over",
21177        );
21178
21179        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21180        assert_eq!(
21181            e.next_scroll_position,
21182            NextScrollCursorCenterTopBottom::Top,
21183            "Scrolling continues if retriggered fast enough"
21184        );
21185    });
21186
21187    cx.executor()
21188        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21189    cx.executor().run_until_parked();
21190    cx.update_editor(|e, _, _| {
21191        assert_eq!(
21192            e.next_scroll_position,
21193            NextScrollCursorCenterTopBottom::Center,
21194            "If scrolling is not triggered fast enough, it should reset"
21195        );
21196    });
21197}
21198
21199#[gpui::test]
21200async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21201    init_test(cx, |_| {});
21202    let mut cx = EditorLspTestContext::new_rust(
21203        lsp::ServerCapabilities {
21204            definition_provider: Some(lsp::OneOf::Left(true)),
21205            references_provider: Some(lsp::OneOf::Left(true)),
21206            ..lsp::ServerCapabilities::default()
21207        },
21208        cx,
21209    )
21210    .await;
21211
21212    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21213        let go_to_definition = cx
21214            .lsp
21215            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21216                move |params, _| async move {
21217                    if empty_go_to_definition {
21218                        Ok(None)
21219                    } else {
21220                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21221                            uri: params.text_document_position_params.text_document.uri,
21222                            range: lsp::Range::new(
21223                                lsp::Position::new(4, 3),
21224                                lsp::Position::new(4, 6),
21225                            ),
21226                        })))
21227                    }
21228                },
21229            );
21230        let references = cx
21231            .lsp
21232            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21233                Ok(Some(vec![lsp::Location {
21234                    uri: params.text_document_position.text_document.uri,
21235                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21236                }]))
21237            });
21238        (go_to_definition, references)
21239    };
21240
21241    cx.set_state(
21242        &r#"fn one() {
21243            let mut a = ˇtwo();
21244        }
21245
21246        fn two() {}"#
21247            .unindent(),
21248    );
21249    set_up_lsp_handlers(false, &mut cx);
21250    let navigated = cx
21251        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21252        .await
21253        .expect("Failed to navigate to definition");
21254    assert_eq!(
21255        navigated,
21256        Navigated::Yes,
21257        "Should have navigated to definition from the GetDefinition response"
21258    );
21259    cx.assert_editor_state(
21260        &r#"fn one() {
21261            let mut a = two();
21262        }
21263
21264        fn «twoˇ»() {}"#
21265            .unindent(),
21266    );
21267
21268    let editors = cx.update_workspace(|workspace, _, cx| {
21269        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21270    });
21271    cx.update_editor(|_, _, test_editor_cx| {
21272        assert_eq!(
21273            editors.len(),
21274            1,
21275            "Initially, only one, test, editor should be open in the workspace"
21276        );
21277        assert_eq!(
21278            test_editor_cx.entity(),
21279            editors.last().expect("Asserted len is 1").clone()
21280        );
21281    });
21282
21283    set_up_lsp_handlers(true, &mut cx);
21284    let navigated = cx
21285        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21286        .await
21287        .expect("Failed to navigate to lookup references");
21288    assert_eq!(
21289        navigated,
21290        Navigated::Yes,
21291        "Should have navigated to references as a fallback after empty GoToDefinition response"
21292    );
21293    // We should not change the selections in the existing file,
21294    // if opening another milti buffer with the references
21295    cx.assert_editor_state(
21296        &r#"fn one() {
21297            let mut a = two();
21298        }
21299
21300        fn «twoˇ»() {}"#
21301            .unindent(),
21302    );
21303    let editors = cx.update_workspace(|workspace, _, cx| {
21304        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21305    });
21306    cx.update_editor(|_, _, test_editor_cx| {
21307        assert_eq!(
21308            editors.len(),
21309            2,
21310            "After falling back to references search, we open a new editor with the results"
21311        );
21312        let references_fallback_text = editors
21313            .into_iter()
21314            .find(|new_editor| *new_editor != test_editor_cx.entity())
21315            .expect("Should have one non-test editor now")
21316            .read(test_editor_cx)
21317            .text(test_editor_cx);
21318        assert_eq!(
21319            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21320            "Should use the range from the references response and not the GoToDefinition one"
21321        );
21322    });
21323}
21324
21325#[gpui::test]
21326async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21327    init_test(cx, |_| {});
21328    cx.update(|cx| {
21329        let mut editor_settings = EditorSettings::get_global(cx).clone();
21330        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21331        EditorSettings::override_global(editor_settings, cx);
21332    });
21333    let mut cx = EditorLspTestContext::new_rust(
21334        lsp::ServerCapabilities {
21335            definition_provider: Some(lsp::OneOf::Left(true)),
21336            references_provider: Some(lsp::OneOf::Left(true)),
21337            ..lsp::ServerCapabilities::default()
21338        },
21339        cx,
21340    )
21341    .await;
21342    let original_state = r#"fn one() {
21343        let mut a = ˇtwo();
21344    }
21345
21346    fn two() {}"#
21347        .unindent();
21348    cx.set_state(&original_state);
21349
21350    let mut go_to_definition = cx
21351        .lsp
21352        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21353            move |_, _| async move { Ok(None) },
21354        );
21355    let _references = cx
21356        .lsp
21357        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21358            panic!("Should not call for references with no go to definition fallback")
21359        });
21360
21361    let navigated = cx
21362        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21363        .await
21364        .expect("Failed to navigate to lookup references");
21365    go_to_definition
21366        .next()
21367        .await
21368        .expect("Should have called the go_to_definition handler");
21369
21370    assert_eq!(
21371        navigated,
21372        Navigated::No,
21373        "Should have navigated to references as a fallback after empty GoToDefinition response"
21374    );
21375    cx.assert_editor_state(&original_state);
21376    let editors = cx.update_workspace(|workspace, _, cx| {
21377        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21378    });
21379    cx.update_editor(|_, _, _| {
21380        assert_eq!(
21381            editors.len(),
21382            1,
21383            "After unsuccessful fallback, no other editor should have been opened"
21384        );
21385    });
21386}
21387
21388#[gpui::test]
21389async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21390    init_test(cx, |_| {});
21391    let mut cx = EditorLspTestContext::new_rust(
21392        lsp::ServerCapabilities {
21393            references_provider: Some(lsp::OneOf::Left(true)),
21394            ..lsp::ServerCapabilities::default()
21395        },
21396        cx,
21397    )
21398    .await;
21399
21400    cx.set_state(
21401        &r#"
21402        fn one() {
21403            let mut a = two();
21404        }
21405
21406        fn ˇtwo() {}"#
21407            .unindent(),
21408    );
21409    cx.lsp
21410        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21411            Ok(Some(vec![
21412                lsp::Location {
21413                    uri: params.text_document_position.text_document.uri.clone(),
21414                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21415                },
21416                lsp::Location {
21417                    uri: params.text_document_position.text_document.uri,
21418                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21419                },
21420            ]))
21421        });
21422    let navigated = cx
21423        .update_editor(|editor, window, cx| {
21424            editor.find_all_references(&FindAllReferences, window, cx)
21425        })
21426        .unwrap()
21427        .await
21428        .expect("Failed to navigate to references");
21429    assert_eq!(
21430        navigated,
21431        Navigated::Yes,
21432        "Should have navigated to references from the FindAllReferences response"
21433    );
21434    cx.assert_editor_state(
21435        &r#"fn one() {
21436            let mut a = two();
21437        }
21438
21439        fn ˇtwo() {}"#
21440            .unindent(),
21441    );
21442
21443    let editors = cx.update_workspace(|workspace, _, cx| {
21444        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21445    });
21446    cx.update_editor(|_, _, _| {
21447        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21448    });
21449
21450    cx.set_state(
21451        &r#"fn one() {
21452            let mut a = ˇtwo();
21453        }
21454
21455        fn two() {}"#
21456            .unindent(),
21457    );
21458    let navigated = cx
21459        .update_editor(|editor, window, cx| {
21460            editor.find_all_references(&FindAllReferences, window, cx)
21461        })
21462        .unwrap()
21463        .await
21464        .expect("Failed to navigate to references");
21465    assert_eq!(
21466        navigated,
21467        Navigated::Yes,
21468        "Should have navigated to references from the FindAllReferences response"
21469    );
21470    cx.assert_editor_state(
21471        &r#"fn one() {
21472            let mut a = ˇtwo();
21473        }
21474
21475        fn two() {}"#
21476            .unindent(),
21477    );
21478    let editors = cx.update_workspace(|workspace, _, cx| {
21479        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21480    });
21481    cx.update_editor(|_, _, _| {
21482        assert_eq!(
21483            editors.len(),
21484            2,
21485            "should have re-used the previous multibuffer"
21486        );
21487    });
21488
21489    cx.set_state(
21490        &r#"fn one() {
21491            let mut a = ˇtwo();
21492        }
21493        fn three() {}
21494        fn two() {}"#
21495            .unindent(),
21496    );
21497    cx.lsp
21498        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21499            Ok(Some(vec![
21500                lsp::Location {
21501                    uri: params.text_document_position.text_document.uri.clone(),
21502                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21503                },
21504                lsp::Location {
21505                    uri: params.text_document_position.text_document.uri,
21506                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21507                },
21508            ]))
21509        });
21510    let navigated = cx
21511        .update_editor(|editor, window, cx| {
21512            editor.find_all_references(&FindAllReferences, window, cx)
21513        })
21514        .unwrap()
21515        .await
21516        .expect("Failed to navigate to references");
21517    assert_eq!(
21518        navigated,
21519        Navigated::Yes,
21520        "Should have navigated to references from the FindAllReferences response"
21521    );
21522    cx.assert_editor_state(
21523        &r#"fn one() {
21524                let mut a = ˇtwo();
21525            }
21526            fn three() {}
21527            fn two() {}"#
21528            .unindent(),
21529    );
21530    let editors = cx.update_workspace(|workspace, _, cx| {
21531        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21532    });
21533    cx.update_editor(|_, _, _| {
21534        assert_eq!(
21535            editors.len(),
21536            3,
21537            "should have used a new multibuffer as offsets changed"
21538        );
21539    });
21540}
21541#[gpui::test]
21542async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21543    init_test(cx, |_| {});
21544
21545    let language = Arc::new(Language::new(
21546        LanguageConfig::default(),
21547        Some(tree_sitter_rust::LANGUAGE.into()),
21548    ));
21549
21550    let text = r#"
21551        #[cfg(test)]
21552        mod tests() {
21553            #[test]
21554            fn runnable_1() {
21555                let a = 1;
21556            }
21557
21558            #[test]
21559            fn runnable_2() {
21560                let a = 1;
21561                let b = 2;
21562            }
21563        }
21564    "#
21565    .unindent();
21566
21567    let fs = FakeFs::new(cx.executor());
21568    fs.insert_file("/file.rs", Default::default()).await;
21569
21570    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21571    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21572    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21573    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21574    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21575
21576    let editor = cx.new_window_entity(|window, cx| {
21577        Editor::new(
21578            EditorMode::full(),
21579            multi_buffer,
21580            Some(project.clone()),
21581            window,
21582            cx,
21583        )
21584    });
21585
21586    editor.update_in(cx, |editor, window, cx| {
21587        let snapshot = editor.buffer().read(cx).snapshot(cx);
21588        editor.tasks.insert(
21589            (buffer.read(cx).remote_id(), 3),
21590            RunnableTasks {
21591                templates: vec![],
21592                offset: snapshot.anchor_before(43),
21593                column: 0,
21594                extra_variables: HashMap::default(),
21595                context_range: BufferOffset(43)..BufferOffset(85),
21596            },
21597        );
21598        editor.tasks.insert(
21599            (buffer.read(cx).remote_id(), 8),
21600            RunnableTasks {
21601                templates: vec![],
21602                offset: snapshot.anchor_before(86),
21603                column: 0,
21604                extra_variables: HashMap::default(),
21605                context_range: BufferOffset(86)..BufferOffset(191),
21606            },
21607        );
21608
21609        // Test finding task when cursor is inside function body
21610        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21611            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21612        });
21613        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21614        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21615
21616        // Test finding task when cursor is on function name
21617        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21618            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21619        });
21620        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21621        assert_eq!(row, 8, "Should find task when cursor is on function name");
21622    });
21623}
21624
21625#[gpui::test]
21626async fn test_folding_buffers(cx: &mut TestAppContext) {
21627    init_test(cx, |_| {});
21628
21629    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21630    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21631    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21632
21633    let fs = FakeFs::new(cx.executor());
21634    fs.insert_tree(
21635        path!("/a"),
21636        json!({
21637            "first.rs": sample_text_1,
21638            "second.rs": sample_text_2,
21639            "third.rs": sample_text_3,
21640        }),
21641    )
21642    .await;
21643    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21644    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21645    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21646    let worktree = project.update(cx, |project, cx| {
21647        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21648        assert_eq!(worktrees.len(), 1);
21649        worktrees.pop().unwrap()
21650    });
21651    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21652
21653    let buffer_1 = project
21654        .update(cx, |project, cx| {
21655            project.open_buffer((worktree_id, "first.rs"), cx)
21656        })
21657        .await
21658        .unwrap();
21659    let buffer_2 = project
21660        .update(cx, |project, cx| {
21661            project.open_buffer((worktree_id, "second.rs"), cx)
21662        })
21663        .await
21664        .unwrap();
21665    let buffer_3 = project
21666        .update(cx, |project, cx| {
21667            project.open_buffer((worktree_id, "third.rs"), cx)
21668        })
21669        .await
21670        .unwrap();
21671
21672    let multi_buffer = cx.new(|cx| {
21673        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21674        multi_buffer.push_excerpts(
21675            buffer_1.clone(),
21676            [
21677                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21678                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21679                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21680            ],
21681            cx,
21682        );
21683        multi_buffer.push_excerpts(
21684            buffer_2.clone(),
21685            [
21686                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21687                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21688                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21689            ],
21690            cx,
21691        );
21692        multi_buffer.push_excerpts(
21693            buffer_3.clone(),
21694            [
21695                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21696                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21697                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21698            ],
21699            cx,
21700        );
21701        multi_buffer
21702    });
21703    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21704        Editor::new(
21705            EditorMode::full(),
21706            multi_buffer.clone(),
21707            Some(project.clone()),
21708            window,
21709            cx,
21710        )
21711    });
21712
21713    assert_eq!(
21714        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21715        "\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",
21716    );
21717
21718    multi_buffer_editor.update(cx, |editor, cx| {
21719        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21720    });
21721    assert_eq!(
21722        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21723        "\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",
21724        "After folding the first buffer, its text should not be displayed"
21725    );
21726
21727    multi_buffer_editor.update(cx, |editor, cx| {
21728        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21729    });
21730    assert_eq!(
21731        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21732        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21733        "After folding the second buffer, its text should not be displayed"
21734    );
21735
21736    multi_buffer_editor.update(cx, |editor, cx| {
21737        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21738    });
21739    assert_eq!(
21740        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21741        "\n\n\n\n\n",
21742        "After folding the third buffer, its text should not be displayed"
21743    );
21744
21745    // Emulate selection inside the fold logic, that should work
21746    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21747        editor
21748            .snapshot(window, cx)
21749            .next_line_boundary(Point::new(0, 4));
21750    });
21751
21752    multi_buffer_editor.update(cx, |editor, cx| {
21753        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21754    });
21755    assert_eq!(
21756        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21757        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21758        "After unfolding the second buffer, its text should be displayed"
21759    );
21760
21761    // Typing inside of buffer 1 causes that buffer to be unfolded.
21762    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21763        assert_eq!(
21764            multi_buffer
21765                .read(cx)
21766                .snapshot(cx)
21767                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21768                .collect::<String>(),
21769            "bbbb"
21770        );
21771        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21772            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21773        });
21774        editor.handle_input("B", window, cx);
21775    });
21776
21777    assert_eq!(
21778        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21779        "\n\nB\n\n\n\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21780        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21781    );
21782
21783    multi_buffer_editor.update(cx, |editor, cx| {
21784        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21785    });
21786    assert_eq!(
21787        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21788        "\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",
21789        "After unfolding the all buffers, all original text should be displayed"
21790    );
21791}
21792
21793#[gpui::test]
21794async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21795    init_test(cx, |_| {});
21796
21797    let sample_text_1 = "1111\n2222\n3333".to_string();
21798    let sample_text_2 = "4444\n5555\n6666".to_string();
21799    let sample_text_3 = "7777\n8888\n9999".to_string();
21800
21801    let fs = FakeFs::new(cx.executor());
21802    fs.insert_tree(
21803        path!("/a"),
21804        json!({
21805            "first.rs": sample_text_1,
21806            "second.rs": sample_text_2,
21807            "third.rs": sample_text_3,
21808        }),
21809    )
21810    .await;
21811    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21812    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21813    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21814    let worktree = project.update(cx, |project, cx| {
21815        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21816        assert_eq!(worktrees.len(), 1);
21817        worktrees.pop().unwrap()
21818    });
21819    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21820
21821    let buffer_1 = project
21822        .update(cx, |project, cx| {
21823            project.open_buffer((worktree_id, "first.rs"), cx)
21824        })
21825        .await
21826        .unwrap();
21827    let buffer_2 = project
21828        .update(cx, |project, cx| {
21829            project.open_buffer((worktree_id, "second.rs"), cx)
21830        })
21831        .await
21832        .unwrap();
21833    let buffer_3 = project
21834        .update(cx, |project, cx| {
21835            project.open_buffer((worktree_id, "third.rs"), cx)
21836        })
21837        .await
21838        .unwrap();
21839
21840    let multi_buffer = cx.new(|cx| {
21841        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21842        multi_buffer.push_excerpts(
21843            buffer_1.clone(),
21844            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21845            cx,
21846        );
21847        multi_buffer.push_excerpts(
21848            buffer_2.clone(),
21849            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21850            cx,
21851        );
21852        multi_buffer.push_excerpts(
21853            buffer_3.clone(),
21854            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21855            cx,
21856        );
21857        multi_buffer
21858    });
21859
21860    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21861        Editor::new(
21862            EditorMode::full(),
21863            multi_buffer,
21864            Some(project.clone()),
21865            window,
21866            cx,
21867        )
21868    });
21869
21870    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21871    assert_eq!(
21872        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21873        full_text,
21874    );
21875
21876    multi_buffer_editor.update(cx, |editor, cx| {
21877        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21878    });
21879    assert_eq!(
21880        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21881        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21882        "After folding the first buffer, its text should not be displayed"
21883    );
21884
21885    multi_buffer_editor.update(cx, |editor, cx| {
21886        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21887    });
21888
21889    assert_eq!(
21890        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21891        "\n\n\n\n\n\n7777\n8888\n9999",
21892        "After folding the second buffer, its text should not be displayed"
21893    );
21894
21895    multi_buffer_editor.update(cx, |editor, cx| {
21896        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21897    });
21898    assert_eq!(
21899        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21900        "\n\n\n\n\n",
21901        "After folding the third buffer, its text should not be displayed"
21902    );
21903
21904    multi_buffer_editor.update(cx, |editor, cx| {
21905        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21906    });
21907    assert_eq!(
21908        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21909        "\n\n\n\n4444\n5555\n6666\n\n",
21910        "After unfolding the second buffer, its text should be displayed"
21911    );
21912
21913    multi_buffer_editor.update(cx, |editor, cx| {
21914        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21915    });
21916    assert_eq!(
21917        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21918        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21919        "After unfolding the first buffer, its text should be displayed"
21920    );
21921
21922    multi_buffer_editor.update(cx, |editor, cx| {
21923        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21924    });
21925    assert_eq!(
21926        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21927        full_text,
21928        "After unfolding all buffers, all original text should be displayed"
21929    );
21930}
21931
21932#[gpui::test]
21933async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21934    init_test(cx, |_| {});
21935
21936    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21937
21938    let fs = FakeFs::new(cx.executor());
21939    fs.insert_tree(
21940        path!("/a"),
21941        json!({
21942            "main.rs": sample_text,
21943        }),
21944    )
21945    .await;
21946    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21947    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21948    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21949    let worktree = project.update(cx, |project, cx| {
21950        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21951        assert_eq!(worktrees.len(), 1);
21952        worktrees.pop().unwrap()
21953    });
21954    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21955
21956    let buffer_1 = project
21957        .update(cx, |project, cx| {
21958            project.open_buffer((worktree_id, "main.rs"), cx)
21959        })
21960        .await
21961        .unwrap();
21962
21963    let multi_buffer = cx.new(|cx| {
21964        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21965        multi_buffer.push_excerpts(
21966            buffer_1.clone(),
21967            [ExcerptRange::new(
21968                Point::new(0, 0)
21969                    ..Point::new(
21970                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
21971                        0,
21972                    ),
21973            )],
21974            cx,
21975        );
21976        multi_buffer
21977    });
21978    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21979        Editor::new(
21980            EditorMode::full(),
21981            multi_buffer,
21982            Some(project.clone()),
21983            window,
21984            cx,
21985        )
21986    });
21987
21988    let selection_range = Point::new(1, 0)..Point::new(2, 0);
21989    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21990        enum TestHighlight {}
21991        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
21992        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
21993        editor.highlight_text::<TestHighlight>(
21994            vec![highlight_range.clone()],
21995            HighlightStyle::color(Hsla::green()),
21996            cx,
21997        );
21998        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21999            s.select_ranges(Some(highlight_range))
22000        });
22001    });
22002
22003    let full_text = format!("\n\n{sample_text}");
22004    assert_eq!(
22005        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22006        full_text,
22007    );
22008}
22009
22010#[gpui::test]
22011async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22012    init_test(cx, |_| {});
22013    cx.update(|cx| {
22014        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22015            "keymaps/default-linux.json",
22016            cx,
22017        )
22018        .unwrap();
22019        cx.bind_keys(default_key_bindings);
22020    });
22021
22022    let (editor, cx) = cx.add_window_view(|window, cx| {
22023        let multi_buffer = MultiBuffer::build_multi(
22024            [
22025                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22026                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22027                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22028                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22029            ],
22030            cx,
22031        );
22032        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22033
22034        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22035        // fold all but the second buffer, so that we test navigating between two
22036        // adjacent folded buffers, as well as folded buffers at the start and
22037        // end the multibuffer
22038        editor.fold_buffer(buffer_ids[0], cx);
22039        editor.fold_buffer(buffer_ids[2], cx);
22040        editor.fold_buffer(buffer_ids[3], cx);
22041
22042        editor
22043    });
22044    cx.simulate_resize(size(px(1000.), px(1000.)));
22045
22046    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22047    cx.assert_excerpts_with_selections(indoc! {"
22048        [EXCERPT]
22049        ˇ[FOLDED]
22050        [EXCERPT]
22051        a1
22052        b1
22053        [EXCERPT]
22054        [FOLDED]
22055        [EXCERPT]
22056        [FOLDED]
22057        "
22058    });
22059    cx.simulate_keystroke("down");
22060    cx.assert_excerpts_with_selections(indoc! {"
22061        [EXCERPT]
22062        [FOLDED]
22063        [EXCERPT]
22064        ˇa1
22065        b1
22066        [EXCERPT]
22067        [FOLDED]
22068        [EXCERPT]
22069        [FOLDED]
22070        "
22071    });
22072    cx.simulate_keystroke("down");
22073    cx.assert_excerpts_with_selections(indoc! {"
22074        [EXCERPT]
22075        [FOLDED]
22076        [EXCERPT]
22077        a1
22078        ˇb1
22079        [EXCERPT]
22080        [FOLDED]
22081        [EXCERPT]
22082        [FOLDED]
22083        "
22084    });
22085    cx.simulate_keystroke("down");
22086    cx.assert_excerpts_with_selections(indoc! {"
22087        [EXCERPT]
22088        [FOLDED]
22089        [EXCERPT]
22090        a1
22091        b1
22092        ˇ[EXCERPT]
22093        [FOLDED]
22094        [EXCERPT]
22095        [FOLDED]
22096        "
22097    });
22098    cx.simulate_keystroke("down");
22099    cx.assert_excerpts_with_selections(indoc! {"
22100        [EXCERPT]
22101        [FOLDED]
22102        [EXCERPT]
22103        a1
22104        b1
22105        [EXCERPT]
22106        ˇ[FOLDED]
22107        [EXCERPT]
22108        [FOLDED]
22109        "
22110    });
22111    for _ in 0..5 {
22112        cx.simulate_keystroke("down");
22113        cx.assert_excerpts_with_selections(indoc! {"
22114            [EXCERPT]
22115            [FOLDED]
22116            [EXCERPT]
22117            a1
22118            b1
22119            [EXCERPT]
22120            [FOLDED]
22121            [EXCERPT]
22122            ˇ[FOLDED]
22123            "
22124        });
22125    }
22126
22127    cx.simulate_keystroke("up");
22128    cx.assert_excerpts_with_selections(indoc! {"
22129        [EXCERPT]
22130        [FOLDED]
22131        [EXCERPT]
22132        a1
22133        b1
22134        [EXCERPT]
22135        ˇ[FOLDED]
22136        [EXCERPT]
22137        [FOLDED]
22138        "
22139    });
22140    cx.simulate_keystroke("up");
22141    cx.assert_excerpts_with_selections(indoc! {"
22142        [EXCERPT]
22143        [FOLDED]
22144        [EXCERPT]
22145        a1
22146        b1
22147        ˇ[EXCERPT]
22148        [FOLDED]
22149        [EXCERPT]
22150        [FOLDED]
22151        "
22152    });
22153    cx.simulate_keystroke("up");
22154    cx.assert_excerpts_with_selections(indoc! {"
22155        [EXCERPT]
22156        [FOLDED]
22157        [EXCERPT]
22158        a1
22159        ˇb1
22160        [EXCERPT]
22161        [FOLDED]
22162        [EXCERPT]
22163        [FOLDED]
22164        "
22165    });
22166    cx.simulate_keystroke("up");
22167    cx.assert_excerpts_with_selections(indoc! {"
22168        [EXCERPT]
22169        [FOLDED]
22170        [EXCERPT]
22171        ˇa1
22172        b1
22173        [EXCERPT]
22174        [FOLDED]
22175        [EXCERPT]
22176        [FOLDED]
22177        "
22178    });
22179    for _ in 0..5 {
22180        cx.simulate_keystroke("up");
22181        cx.assert_excerpts_with_selections(indoc! {"
22182            [EXCERPT]
22183            ˇ[FOLDED]
22184            [EXCERPT]
22185            a1
22186            b1
22187            [EXCERPT]
22188            [FOLDED]
22189            [EXCERPT]
22190            [FOLDED]
22191            "
22192        });
22193    }
22194}
22195
22196#[gpui::test]
22197async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22198    init_test(cx, |_| {});
22199
22200    // Simple insertion
22201    assert_highlighted_edits(
22202        "Hello, world!",
22203        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22204        true,
22205        cx,
22206        |highlighted_edits, cx| {
22207            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22208            assert_eq!(highlighted_edits.highlights.len(), 1);
22209            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22210            assert_eq!(
22211                highlighted_edits.highlights[0].1.background_color,
22212                Some(cx.theme().status().created_background)
22213            );
22214        },
22215    )
22216    .await;
22217
22218    // Replacement
22219    assert_highlighted_edits(
22220        "This is a test.",
22221        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22222        false,
22223        cx,
22224        |highlighted_edits, cx| {
22225            assert_eq!(highlighted_edits.text, "That is a test.");
22226            assert_eq!(highlighted_edits.highlights.len(), 1);
22227            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22228            assert_eq!(
22229                highlighted_edits.highlights[0].1.background_color,
22230                Some(cx.theme().status().created_background)
22231            );
22232        },
22233    )
22234    .await;
22235
22236    // Multiple edits
22237    assert_highlighted_edits(
22238        "Hello, world!",
22239        vec![
22240            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22241            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22242        ],
22243        false,
22244        cx,
22245        |highlighted_edits, cx| {
22246            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22247            assert_eq!(highlighted_edits.highlights.len(), 2);
22248            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22249            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22250            assert_eq!(
22251                highlighted_edits.highlights[0].1.background_color,
22252                Some(cx.theme().status().created_background)
22253            );
22254            assert_eq!(
22255                highlighted_edits.highlights[1].1.background_color,
22256                Some(cx.theme().status().created_background)
22257            );
22258        },
22259    )
22260    .await;
22261
22262    // Multiple lines with edits
22263    assert_highlighted_edits(
22264        "First line\nSecond line\nThird line\nFourth line",
22265        vec![
22266            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22267            (
22268                Point::new(2, 0)..Point::new(2, 10),
22269                "New third line".to_string(),
22270            ),
22271            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22272        ],
22273        false,
22274        cx,
22275        |highlighted_edits, cx| {
22276            assert_eq!(
22277                highlighted_edits.text,
22278                "Second modified\nNew third line\nFourth updated line"
22279            );
22280            assert_eq!(highlighted_edits.highlights.len(), 3);
22281            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22282            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22283            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22284            for highlight in &highlighted_edits.highlights {
22285                assert_eq!(
22286                    highlight.1.background_color,
22287                    Some(cx.theme().status().created_background)
22288                );
22289            }
22290        },
22291    )
22292    .await;
22293}
22294
22295#[gpui::test]
22296async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22297    init_test(cx, |_| {});
22298
22299    // Deletion
22300    assert_highlighted_edits(
22301        "Hello, world!",
22302        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22303        true,
22304        cx,
22305        |highlighted_edits, cx| {
22306            assert_eq!(highlighted_edits.text, "Hello, world!");
22307            assert_eq!(highlighted_edits.highlights.len(), 1);
22308            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22309            assert_eq!(
22310                highlighted_edits.highlights[0].1.background_color,
22311                Some(cx.theme().status().deleted_background)
22312            );
22313        },
22314    )
22315    .await;
22316
22317    // Insertion
22318    assert_highlighted_edits(
22319        "Hello, world!",
22320        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22321        true,
22322        cx,
22323        |highlighted_edits, cx| {
22324            assert_eq!(highlighted_edits.highlights.len(), 1);
22325            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22326            assert_eq!(
22327                highlighted_edits.highlights[0].1.background_color,
22328                Some(cx.theme().status().created_background)
22329            );
22330        },
22331    )
22332    .await;
22333}
22334
22335async fn assert_highlighted_edits(
22336    text: &str,
22337    edits: Vec<(Range<Point>, String)>,
22338    include_deletions: bool,
22339    cx: &mut TestAppContext,
22340    assertion_fn: impl Fn(HighlightedText, &App),
22341) {
22342    let window = cx.add_window(|window, cx| {
22343        let buffer = MultiBuffer::build_simple(text, cx);
22344        Editor::new(EditorMode::full(), buffer, None, window, cx)
22345    });
22346    let cx = &mut VisualTestContext::from_window(*window, cx);
22347
22348    let (buffer, snapshot) = window
22349        .update(cx, |editor, _window, cx| {
22350            (
22351                editor.buffer().clone(),
22352                editor.buffer().read(cx).snapshot(cx),
22353            )
22354        })
22355        .unwrap();
22356
22357    let edits = edits
22358        .into_iter()
22359        .map(|(range, edit)| {
22360            (
22361                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22362                edit,
22363            )
22364        })
22365        .collect::<Vec<_>>();
22366
22367    let text_anchor_edits = edits
22368        .clone()
22369        .into_iter()
22370        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22371        .collect::<Vec<_>>();
22372
22373    let edit_preview = window
22374        .update(cx, |_, _window, cx| {
22375            buffer
22376                .read(cx)
22377                .as_singleton()
22378                .unwrap()
22379                .read(cx)
22380                .preview_edits(text_anchor_edits.into(), cx)
22381        })
22382        .unwrap()
22383        .await;
22384
22385    cx.update(|_window, cx| {
22386        let highlighted_edits = edit_prediction_edit_text(
22387            snapshot.as_singleton().unwrap().2,
22388            &edits,
22389            &edit_preview,
22390            include_deletions,
22391            cx,
22392        );
22393        assertion_fn(highlighted_edits, cx)
22394    });
22395}
22396
22397#[track_caller]
22398fn assert_breakpoint(
22399    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22400    path: &Arc<Path>,
22401    expected: Vec<(u32, Breakpoint)>,
22402) {
22403    if expected.is_empty() {
22404        assert!(!breakpoints.contains_key(path), "{}", path.display());
22405    } else {
22406        let mut breakpoint = breakpoints
22407            .get(path)
22408            .unwrap()
22409            .iter()
22410            .map(|breakpoint| {
22411                (
22412                    breakpoint.row,
22413                    Breakpoint {
22414                        message: breakpoint.message.clone(),
22415                        state: breakpoint.state,
22416                        condition: breakpoint.condition.clone(),
22417                        hit_condition: breakpoint.hit_condition.clone(),
22418                    },
22419                )
22420            })
22421            .collect::<Vec<_>>();
22422
22423        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22424
22425        assert_eq!(expected, breakpoint);
22426    }
22427}
22428
22429fn add_log_breakpoint_at_cursor(
22430    editor: &mut Editor,
22431    log_message: &str,
22432    window: &mut Window,
22433    cx: &mut Context<Editor>,
22434) {
22435    let (anchor, bp) = editor
22436        .breakpoints_at_cursors(window, cx)
22437        .first()
22438        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22439        .unwrap_or_else(|| {
22440            let cursor_position: Point = editor.selections.newest(cx).head();
22441
22442            let breakpoint_position = editor
22443                .snapshot(window, cx)
22444                .display_snapshot
22445                .buffer_snapshot
22446                .anchor_before(Point::new(cursor_position.row, 0));
22447
22448            (breakpoint_position, Breakpoint::new_log(log_message))
22449        });
22450
22451    editor.edit_breakpoint_at_anchor(
22452        anchor,
22453        bp,
22454        BreakpointEditAction::EditLogMessage(log_message.into()),
22455        cx,
22456    );
22457}
22458
22459#[gpui::test]
22460async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22461    init_test(cx, |_| {});
22462
22463    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22464    let fs = FakeFs::new(cx.executor());
22465    fs.insert_tree(
22466        path!("/a"),
22467        json!({
22468            "main.rs": sample_text,
22469        }),
22470    )
22471    .await;
22472    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22473    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22474    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22475
22476    let fs = FakeFs::new(cx.executor());
22477    fs.insert_tree(
22478        path!("/a"),
22479        json!({
22480            "main.rs": sample_text,
22481        }),
22482    )
22483    .await;
22484    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22485    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22486    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22487    let worktree_id = workspace
22488        .update(cx, |workspace, _window, cx| {
22489            workspace.project().update(cx, |project, cx| {
22490                project.worktrees(cx).next().unwrap().read(cx).id()
22491            })
22492        })
22493        .unwrap();
22494
22495    let buffer = project
22496        .update(cx, |project, cx| {
22497            project.open_buffer((worktree_id, "main.rs"), cx)
22498        })
22499        .await
22500        .unwrap();
22501
22502    let (editor, cx) = cx.add_window_view(|window, cx| {
22503        Editor::new(
22504            EditorMode::full(),
22505            MultiBuffer::build_from_buffer(buffer, cx),
22506            Some(project.clone()),
22507            window,
22508            cx,
22509        )
22510    });
22511
22512    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22513    let abs_path = project.read_with(cx, |project, cx| {
22514        project
22515            .absolute_path(&project_path, cx)
22516            .map(Arc::from)
22517            .unwrap()
22518    });
22519
22520    // assert we can add breakpoint on the first line
22521    editor.update_in(cx, |editor, window, cx| {
22522        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22523        editor.move_to_end(&MoveToEnd, window, cx);
22524        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22525    });
22526
22527    let breakpoints = editor.update(cx, |editor, cx| {
22528        editor
22529            .breakpoint_store()
22530            .as_ref()
22531            .unwrap()
22532            .read(cx)
22533            .all_source_breakpoints(cx)
22534    });
22535
22536    assert_eq!(1, breakpoints.len());
22537    assert_breakpoint(
22538        &breakpoints,
22539        &abs_path,
22540        vec![
22541            (0, Breakpoint::new_standard()),
22542            (3, Breakpoint::new_standard()),
22543        ],
22544    );
22545
22546    editor.update_in(cx, |editor, window, cx| {
22547        editor.move_to_beginning(&MoveToBeginning, window, cx);
22548        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22549    });
22550
22551    let breakpoints = editor.update(cx, |editor, cx| {
22552        editor
22553            .breakpoint_store()
22554            .as_ref()
22555            .unwrap()
22556            .read(cx)
22557            .all_source_breakpoints(cx)
22558    });
22559
22560    assert_eq!(1, breakpoints.len());
22561    assert_breakpoint(
22562        &breakpoints,
22563        &abs_path,
22564        vec![(3, Breakpoint::new_standard())],
22565    );
22566
22567    editor.update_in(cx, |editor, window, cx| {
22568        editor.move_to_end(&MoveToEnd, window, cx);
22569        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22570    });
22571
22572    let breakpoints = editor.update(cx, |editor, cx| {
22573        editor
22574            .breakpoint_store()
22575            .as_ref()
22576            .unwrap()
22577            .read(cx)
22578            .all_source_breakpoints(cx)
22579    });
22580
22581    assert_eq!(0, breakpoints.len());
22582    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22583}
22584
22585#[gpui::test]
22586async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22587    init_test(cx, |_| {});
22588
22589    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22590
22591    let fs = FakeFs::new(cx.executor());
22592    fs.insert_tree(
22593        path!("/a"),
22594        json!({
22595            "main.rs": sample_text,
22596        }),
22597    )
22598    .await;
22599    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22600    let (workspace, cx) =
22601        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22602
22603    let worktree_id = workspace.update(cx, |workspace, cx| {
22604        workspace.project().update(cx, |project, cx| {
22605            project.worktrees(cx).next().unwrap().read(cx).id()
22606        })
22607    });
22608
22609    let buffer = project
22610        .update(cx, |project, cx| {
22611            project.open_buffer((worktree_id, "main.rs"), cx)
22612        })
22613        .await
22614        .unwrap();
22615
22616    let (editor, cx) = cx.add_window_view(|window, cx| {
22617        Editor::new(
22618            EditorMode::full(),
22619            MultiBuffer::build_from_buffer(buffer, cx),
22620            Some(project.clone()),
22621            window,
22622            cx,
22623        )
22624    });
22625
22626    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22627    let abs_path = project.read_with(cx, |project, cx| {
22628        project
22629            .absolute_path(&project_path, cx)
22630            .map(Arc::from)
22631            .unwrap()
22632    });
22633
22634    editor.update_in(cx, |editor, window, cx| {
22635        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22636    });
22637
22638    let breakpoints = editor.update(cx, |editor, cx| {
22639        editor
22640            .breakpoint_store()
22641            .as_ref()
22642            .unwrap()
22643            .read(cx)
22644            .all_source_breakpoints(cx)
22645    });
22646
22647    assert_breakpoint(
22648        &breakpoints,
22649        &abs_path,
22650        vec![(0, Breakpoint::new_log("hello world"))],
22651    );
22652
22653    // Removing a log message from a log breakpoint should remove it
22654    editor.update_in(cx, |editor, window, cx| {
22655        add_log_breakpoint_at_cursor(editor, "", window, cx);
22656    });
22657
22658    let breakpoints = editor.update(cx, |editor, cx| {
22659        editor
22660            .breakpoint_store()
22661            .as_ref()
22662            .unwrap()
22663            .read(cx)
22664            .all_source_breakpoints(cx)
22665    });
22666
22667    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22668
22669    editor.update_in(cx, |editor, window, cx| {
22670        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22671        editor.move_to_end(&MoveToEnd, window, cx);
22672        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22673        // Not adding a log message to a standard breakpoint shouldn't remove it
22674        add_log_breakpoint_at_cursor(editor, "", window, cx);
22675    });
22676
22677    let breakpoints = editor.update(cx, |editor, cx| {
22678        editor
22679            .breakpoint_store()
22680            .as_ref()
22681            .unwrap()
22682            .read(cx)
22683            .all_source_breakpoints(cx)
22684    });
22685
22686    assert_breakpoint(
22687        &breakpoints,
22688        &abs_path,
22689        vec![
22690            (0, Breakpoint::new_standard()),
22691            (3, Breakpoint::new_standard()),
22692        ],
22693    );
22694
22695    editor.update_in(cx, |editor, window, cx| {
22696        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22697    });
22698
22699    let breakpoints = editor.update(cx, |editor, cx| {
22700        editor
22701            .breakpoint_store()
22702            .as_ref()
22703            .unwrap()
22704            .read(cx)
22705            .all_source_breakpoints(cx)
22706    });
22707
22708    assert_breakpoint(
22709        &breakpoints,
22710        &abs_path,
22711        vec![
22712            (0, Breakpoint::new_standard()),
22713            (3, Breakpoint::new_log("hello world")),
22714        ],
22715    );
22716
22717    editor.update_in(cx, |editor, window, cx| {
22718        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22719    });
22720
22721    let breakpoints = editor.update(cx, |editor, cx| {
22722        editor
22723            .breakpoint_store()
22724            .as_ref()
22725            .unwrap()
22726            .read(cx)
22727            .all_source_breakpoints(cx)
22728    });
22729
22730    assert_breakpoint(
22731        &breakpoints,
22732        &abs_path,
22733        vec![
22734            (0, Breakpoint::new_standard()),
22735            (3, Breakpoint::new_log("hello Earth!!")),
22736        ],
22737    );
22738}
22739
22740/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22741/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22742/// or when breakpoints were placed out of order. This tests for a regression too
22743#[gpui::test]
22744async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22745    init_test(cx, |_| {});
22746
22747    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22748    let fs = FakeFs::new(cx.executor());
22749    fs.insert_tree(
22750        path!("/a"),
22751        json!({
22752            "main.rs": sample_text,
22753        }),
22754    )
22755    .await;
22756    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22757    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22758    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22759
22760    let fs = FakeFs::new(cx.executor());
22761    fs.insert_tree(
22762        path!("/a"),
22763        json!({
22764            "main.rs": sample_text,
22765        }),
22766    )
22767    .await;
22768    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22769    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22770    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22771    let worktree_id = workspace
22772        .update(cx, |workspace, _window, cx| {
22773            workspace.project().update(cx, |project, cx| {
22774                project.worktrees(cx).next().unwrap().read(cx).id()
22775            })
22776        })
22777        .unwrap();
22778
22779    let buffer = project
22780        .update(cx, |project, cx| {
22781            project.open_buffer((worktree_id, "main.rs"), cx)
22782        })
22783        .await
22784        .unwrap();
22785
22786    let (editor, cx) = cx.add_window_view(|window, cx| {
22787        Editor::new(
22788            EditorMode::full(),
22789            MultiBuffer::build_from_buffer(buffer, cx),
22790            Some(project.clone()),
22791            window,
22792            cx,
22793        )
22794    });
22795
22796    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22797    let abs_path = project.read_with(cx, |project, cx| {
22798        project
22799            .absolute_path(&project_path, cx)
22800            .map(Arc::from)
22801            .unwrap()
22802    });
22803
22804    // assert we can add breakpoint on the first line
22805    editor.update_in(cx, |editor, window, cx| {
22806        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22807        editor.move_to_end(&MoveToEnd, window, cx);
22808        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22809        editor.move_up(&MoveUp, window, cx);
22810        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22811    });
22812
22813    let breakpoints = editor.update(cx, |editor, cx| {
22814        editor
22815            .breakpoint_store()
22816            .as_ref()
22817            .unwrap()
22818            .read(cx)
22819            .all_source_breakpoints(cx)
22820    });
22821
22822    assert_eq!(1, breakpoints.len());
22823    assert_breakpoint(
22824        &breakpoints,
22825        &abs_path,
22826        vec![
22827            (0, Breakpoint::new_standard()),
22828            (2, Breakpoint::new_standard()),
22829            (3, Breakpoint::new_standard()),
22830        ],
22831    );
22832
22833    editor.update_in(cx, |editor, window, cx| {
22834        editor.move_to_beginning(&MoveToBeginning, window, cx);
22835        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22836        editor.move_to_end(&MoveToEnd, window, cx);
22837        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22838        // Disabling a breakpoint that doesn't exist should do nothing
22839        editor.move_up(&MoveUp, window, cx);
22840        editor.move_up(&MoveUp, window, cx);
22841        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22842    });
22843
22844    let breakpoints = editor.update(cx, |editor, cx| {
22845        editor
22846            .breakpoint_store()
22847            .as_ref()
22848            .unwrap()
22849            .read(cx)
22850            .all_source_breakpoints(cx)
22851    });
22852
22853    let disable_breakpoint = {
22854        let mut bp = Breakpoint::new_standard();
22855        bp.state = BreakpointState::Disabled;
22856        bp
22857    };
22858
22859    assert_eq!(1, breakpoints.len());
22860    assert_breakpoint(
22861        &breakpoints,
22862        &abs_path,
22863        vec![
22864            (0, disable_breakpoint.clone()),
22865            (2, Breakpoint::new_standard()),
22866            (3, disable_breakpoint.clone()),
22867        ],
22868    );
22869
22870    editor.update_in(cx, |editor, window, cx| {
22871        editor.move_to_beginning(&MoveToBeginning, window, cx);
22872        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22873        editor.move_to_end(&MoveToEnd, window, cx);
22874        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22875        editor.move_up(&MoveUp, window, cx);
22876        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22877    });
22878
22879    let breakpoints = editor.update(cx, |editor, cx| {
22880        editor
22881            .breakpoint_store()
22882            .as_ref()
22883            .unwrap()
22884            .read(cx)
22885            .all_source_breakpoints(cx)
22886    });
22887
22888    assert_eq!(1, breakpoints.len());
22889    assert_breakpoint(
22890        &breakpoints,
22891        &abs_path,
22892        vec![
22893            (0, Breakpoint::new_standard()),
22894            (2, disable_breakpoint),
22895            (3, Breakpoint::new_standard()),
22896        ],
22897    );
22898}
22899
22900#[gpui::test]
22901async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22902    init_test(cx, |_| {});
22903    let capabilities = lsp::ServerCapabilities {
22904        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22905            prepare_provider: Some(true),
22906            work_done_progress_options: Default::default(),
22907        })),
22908        ..Default::default()
22909    };
22910    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22911
22912    cx.set_state(indoc! {"
22913        struct Fˇoo {}
22914    "});
22915
22916    cx.update_editor(|editor, _, cx| {
22917        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22918        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22919        editor.highlight_background::<DocumentHighlightRead>(
22920            &[highlight_range],
22921            |theme| theme.colors().editor_document_highlight_read_background,
22922            cx,
22923        );
22924    });
22925
22926    let mut prepare_rename_handler = cx
22927        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22928            move |_, _, _| async move {
22929                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22930                    start: lsp::Position {
22931                        line: 0,
22932                        character: 7,
22933                    },
22934                    end: lsp::Position {
22935                        line: 0,
22936                        character: 10,
22937                    },
22938                })))
22939            },
22940        );
22941    let prepare_rename_task = cx
22942        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22943        .expect("Prepare rename was not started");
22944    prepare_rename_handler.next().await.unwrap();
22945    prepare_rename_task.await.expect("Prepare rename failed");
22946
22947    let mut rename_handler =
22948        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22949            let edit = lsp::TextEdit {
22950                range: lsp::Range {
22951                    start: lsp::Position {
22952                        line: 0,
22953                        character: 7,
22954                    },
22955                    end: lsp::Position {
22956                        line: 0,
22957                        character: 10,
22958                    },
22959                },
22960                new_text: "FooRenamed".to_string(),
22961            };
22962            Ok(Some(lsp::WorkspaceEdit::new(
22963                // Specify the same edit twice
22964                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
22965            )))
22966        });
22967    let rename_task = cx
22968        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22969        .expect("Confirm rename was not started");
22970    rename_handler.next().await.unwrap();
22971    rename_task.await.expect("Confirm rename failed");
22972    cx.run_until_parked();
22973
22974    // Despite two edits, only one is actually applied as those are identical
22975    cx.assert_editor_state(indoc! {"
22976        struct FooRenamedˇ {}
22977    "});
22978}
22979
22980#[gpui::test]
22981async fn test_rename_without_prepare(cx: &mut TestAppContext) {
22982    init_test(cx, |_| {});
22983    // These capabilities indicate that the server does not support prepare rename.
22984    let capabilities = lsp::ServerCapabilities {
22985        rename_provider: Some(lsp::OneOf::Left(true)),
22986        ..Default::default()
22987    };
22988    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22989
22990    cx.set_state(indoc! {"
22991        struct Fˇoo {}
22992    "});
22993
22994    cx.update_editor(|editor, _window, cx| {
22995        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22996        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22997        editor.highlight_background::<DocumentHighlightRead>(
22998            &[highlight_range],
22999            |theme| theme.colors().editor_document_highlight_read_background,
23000            cx,
23001        );
23002    });
23003
23004    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23005        .expect("Prepare rename was not started")
23006        .await
23007        .expect("Prepare rename failed");
23008
23009    let mut rename_handler =
23010        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23011            let edit = lsp::TextEdit {
23012                range: lsp::Range {
23013                    start: lsp::Position {
23014                        line: 0,
23015                        character: 7,
23016                    },
23017                    end: lsp::Position {
23018                        line: 0,
23019                        character: 10,
23020                    },
23021                },
23022                new_text: "FooRenamed".to_string(),
23023            };
23024            Ok(Some(lsp::WorkspaceEdit::new(
23025                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23026            )))
23027        });
23028    let rename_task = cx
23029        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23030        .expect("Confirm rename was not started");
23031    rename_handler.next().await.unwrap();
23032    rename_task.await.expect("Confirm rename failed");
23033    cx.run_until_parked();
23034
23035    // Correct range is renamed, as `surrounding_word` is used to find it.
23036    cx.assert_editor_state(indoc! {"
23037        struct FooRenamedˇ {}
23038    "});
23039}
23040
23041#[gpui::test]
23042async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23043    init_test(cx, |_| {});
23044    let mut cx = EditorTestContext::new(cx).await;
23045
23046    let language = Arc::new(
23047        Language::new(
23048            LanguageConfig::default(),
23049            Some(tree_sitter_html::LANGUAGE.into()),
23050        )
23051        .with_brackets_query(
23052            r#"
23053            ("<" @open "/>" @close)
23054            ("</" @open ">" @close)
23055            ("<" @open ">" @close)
23056            ("\"" @open "\"" @close)
23057            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23058        "#,
23059        )
23060        .unwrap(),
23061    );
23062    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23063
23064    cx.set_state(indoc! {"
23065        <span>ˇ</span>
23066    "});
23067    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23068    cx.assert_editor_state(indoc! {"
23069        <span>
23070        ˇ
23071        </span>
23072    "});
23073
23074    cx.set_state(indoc! {"
23075        <span><span></span>ˇ</span>
23076    "});
23077    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23078    cx.assert_editor_state(indoc! {"
23079        <span><span></span>
23080        ˇ</span>
23081    "});
23082
23083    cx.set_state(indoc! {"
23084        <span>ˇ
23085        </span>
23086    "});
23087    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23088    cx.assert_editor_state(indoc! {"
23089        <span>
23090        ˇ
23091        </span>
23092    "});
23093}
23094
23095#[gpui::test(iterations = 10)]
23096async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23097    init_test(cx, |_| {});
23098
23099    let fs = FakeFs::new(cx.executor());
23100    fs.insert_tree(
23101        path!("/dir"),
23102        json!({
23103            "a.ts": "a",
23104        }),
23105    )
23106    .await;
23107
23108    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23109    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23110    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23111
23112    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23113    language_registry.add(Arc::new(Language::new(
23114        LanguageConfig {
23115            name: "TypeScript".into(),
23116            matcher: LanguageMatcher {
23117                path_suffixes: vec!["ts".to_string()],
23118                ..Default::default()
23119            },
23120            ..Default::default()
23121        },
23122        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23123    )));
23124    let mut fake_language_servers = language_registry.register_fake_lsp(
23125        "TypeScript",
23126        FakeLspAdapter {
23127            capabilities: lsp::ServerCapabilities {
23128                code_lens_provider: Some(lsp::CodeLensOptions {
23129                    resolve_provider: Some(true),
23130                }),
23131                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23132                    commands: vec!["_the/command".to_string()],
23133                    ..lsp::ExecuteCommandOptions::default()
23134                }),
23135                ..lsp::ServerCapabilities::default()
23136            },
23137            ..FakeLspAdapter::default()
23138        },
23139    );
23140
23141    let editor = workspace
23142        .update(cx, |workspace, window, cx| {
23143            workspace.open_abs_path(
23144                PathBuf::from(path!("/dir/a.ts")),
23145                OpenOptions::default(),
23146                window,
23147                cx,
23148            )
23149        })
23150        .unwrap()
23151        .await
23152        .unwrap()
23153        .downcast::<Editor>()
23154        .unwrap();
23155    cx.executor().run_until_parked();
23156
23157    let fake_server = fake_language_servers.next().await.unwrap();
23158
23159    let buffer = editor.update(cx, |editor, cx| {
23160        editor
23161            .buffer()
23162            .read(cx)
23163            .as_singleton()
23164            .expect("have opened a single file by path")
23165    });
23166
23167    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23168    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23169    drop(buffer_snapshot);
23170    let actions = cx
23171        .update_window(*workspace, |_, window, cx| {
23172            project.code_actions(&buffer, anchor..anchor, window, cx)
23173        })
23174        .unwrap();
23175
23176    fake_server
23177        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23178            Ok(Some(vec![
23179                lsp::CodeLens {
23180                    range: lsp::Range::default(),
23181                    command: Some(lsp::Command {
23182                        title: "Code lens command".to_owned(),
23183                        command: "_the/command".to_owned(),
23184                        arguments: None,
23185                    }),
23186                    data: None,
23187                },
23188                lsp::CodeLens {
23189                    range: lsp::Range::default(),
23190                    command: Some(lsp::Command {
23191                        title: "Command not in capabilities".to_owned(),
23192                        command: "not in capabilities".to_owned(),
23193                        arguments: None,
23194                    }),
23195                    data: None,
23196                },
23197                lsp::CodeLens {
23198                    range: lsp::Range {
23199                        start: lsp::Position {
23200                            line: 1,
23201                            character: 1,
23202                        },
23203                        end: lsp::Position {
23204                            line: 1,
23205                            character: 1,
23206                        },
23207                    },
23208                    command: Some(lsp::Command {
23209                        title: "Command not in range".to_owned(),
23210                        command: "_the/command".to_owned(),
23211                        arguments: None,
23212                    }),
23213                    data: None,
23214                },
23215            ]))
23216        })
23217        .next()
23218        .await;
23219
23220    let actions = actions.await.unwrap();
23221    assert_eq!(
23222        actions.len(),
23223        1,
23224        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23225    );
23226    let action = actions[0].clone();
23227    let apply = project.update(cx, |project, cx| {
23228        project.apply_code_action(buffer.clone(), action, true, cx)
23229    });
23230
23231    // Resolving the code action does not populate its edits. In absence of
23232    // edits, we must execute the given command.
23233    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23234        |mut lens, _| async move {
23235            let lens_command = lens.command.as_mut().expect("should have a command");
23236            assert_eq!(lens_command.title, "Code lens command");
23237            lens_command.arguments = Some(vec![json!("the-argument")]);
23238            Ok(lens)
23239        },
23240    );
23241
23242    // While executing the command, the language server sends the editor
23243    // a `workspaceEdit` request.
23244    fake_server
23245        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23246            let fake = fake_server.clone();
23247            move |params, _| {
23248                assert_eq!(params.command, "_the/command");
23249                let fake = fake.clone();
23250                async move {
23251                    fake.server
23252                        .request::<lsp::request::ApplyWorkspaceEdit>(
23253                            lsp::ApplyWorkspaceEditParams {
23254                                label: None,
23255                                edit: lsp::WorkspaceEdit {
23256                                    changes: Some(
23257                                        [(
23258                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23259                                            vec![lsp::TextEdit {
23260                                                range: lsp::Range::new(
23261                                                    lsp::Position::new(0, 0),
23262                                                    lsp::Position::new(0, 0),
23263                                                ),
23264                                                new_text: "X".into(),
23265                                            }],
23266                                        )]
23267                                        .into_iter()
23268                                        .collect(),
23269                                    ),
23270                                    ..lsp::WorkspaceEdit::default()
23271                                },
23272                            },
23273                        )
23274                        .await
23275                        .into_response()
23276                        .unwrap();
23277                    Ok(Some(json!(null)))
23278                }
23279            }
23280        })
23281        .next()
23282        .await;
23283
23284    // Applying the code lens command returns a project transaction containing the edits
23285    // sent by the language server in its `workspaceEdit` request.
23286    let transaction = apply.await.unwrap();
23287    assert!(transaction.0.contains_key(&buffer));
23288    buffer.update(cx, |buffer, cx| {
23289        assert_eq!(buffer.text(), "Xa");
23290        buffer.undo(cx);
23291        assert_eq!(buffer.text(), "a");
23292    });
23293
23294    let actions_after_edits = cx
23295        .update_window(*workspace, |_, window, cx| {
23296            project.code_actions(&buffer, anchor..anchor, window, cx)
23297        })
23298        .unwrap()
23299        .await
23300        .unwrap();
23301    assert_eq!(
23302        actions, actions_after_edits,
23303        "For the same selection, same code lens actions should be returned"
23304    );
23305
23306    let _responses =
23307        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23308            panic!("No more code lens requests are expected");
23309        });
23310    editor.update_in(cx, |editor, window, cx| {
23311        editor.select_all(&SelectAll, window, cx);
23312    });
23313    cx.executor().run_until_parked();
23314    let new_actions = cx
23315        .update_window(*workspace, |_, window, cx| {
23316            project.code_actions(&buffer, anchor..anchor, window, cx)
23317        })
23318        .unwrap()
23319        .await
23320        .unwrap();
23321    assert_eq!(
23322        actions, new_actions,
23323        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23324    );
23325}
23326
23327#[gpui::test]
23328async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23329    init_test(cx, |_| {});
23330
23331    let fs = FakeFs::new(cx.executor());
23332    let main_text = r#"fn main() {
23333println!("1");
23334println!("2");
23335println!("3");
23336println!("4");
23337println!("5");
23338}"#;
23339    let lib_text = "mod foo {}";
23340    fs.insert_tree(
23341        path!("/a"),
23342        json!({
23343            "lib.rs": lib_text,
23344            "main.rs": main_text,
23345        }),
23346    )
23347    .await;
23348
23349    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23350    let (workspace, cx) =
23351        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23352    let worktree_id = workspace.update(cx, |workspace, cx| {
23353        workspace.project().update(cx, |project, cx| {
23354            project.worktrees(cx).next().unwrap().read(cx).id()
23355        })
23356    });
23357
23358    let expected_ranges = vec![
23359        Point::new(0, 0)..Point::new(0, 0),
23360        Point::new(1, 0)..Point::new(1, 1),
23361        Point::new(2, 0)..Point::new(2, 2),
23362        Point::new(3, 0)..Point::new(3, 3),
23363    ];
23364
23365    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23366    let editor_1 = workspace
23367        .update_in(cx, |workspace, window, cx| {
23368            workspace.open_path(
23369                (worktree_id, "main.rs"),
23370                Some(pane_1.downgrade()),
23371                true,
23372                window,
23373                cx,
23374            )
23375        })
23376        .unwrap()
23377        .await
23378        .downcast::<Editor>()
23379        .unwrap();
23380    pane_1.update(cx, |pane, cx| {
23381        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23382        open_editor.update(cx, |editor, cx| {
23383            assert_eq!(
23384                editor.display_text(cx),
23385                main_text,
23386                "Original main.rs text on initial open",
23387            );
23388            assert_eq!(
23389                editor
23390                    .selections
23391                    .all::<Point>(cx)
23392                    .into_iter()
23393                    .map(|s| s.range())
23394                    .collect::<Vec<_>>(),
23395                vec![Point::zero()..Point::zero()],
23396                "Default selections on initial open",
23397            );
23398        })
23399    });
23400    editor_1.update_in(cx, |editor, window, cx| {
23401        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23402            s.select_ranges(expected_ranges.clone());
23403        });
23404    });
23405
23406    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23407        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23408    });
23409    let editor_2 = workspace
23410        .update_in(cx, |workspace, window, cx| {
23411            workspace.open_path(
23412                (worktree_id, "main.rs"),
23413                Some(pane_2.downgrade()),
23414                true,
23415                window,
23416                cx,
23417            )
23418        })
23419        .unwrap()
23420        .await
23421        .downcast::<Editor>()
23422        .unwrap();
23423    pane_2.update(cx, |pane, cx| {
23424        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23425        open_editor.update(cx, |editor, cx| {
23426            assert_eq!(
23427                editor.display_text(cx),
23428                main_text,
23429                "Original main.rs text on initial open in another panel",
23430            );
23431            assert_eq!(
23432                editor
23433                    .selections
23434                    .all::<Point>(cx)
23435                    .into_iter()
23436                    .map(|s| s.range())
23437                    .collect::<Vec<_>>(),
23438                vec![Point::zero()..Point::zero()],
23439                "Default selections on initial open in another panel",
23440            );
23441        })
23442    });
23443
23444    editor_2.update_in(cx, |editor, window, cx| {
23445        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23446    });
23447
23448    let _other_editor_1 = workspace
23449        .update_in(cx, |workspace, window, cx| {
23450            workspace.open_path(
23451                (worktree_id, "lib.rs"),
23452                Some(pane_1.downgrade()),
23453                true,
23454                window,
23455                cx,
23456            )
23457        })
23458        .unwrap()
23459        .await
23460        .downcast::<Editor>()
23461        .unwrap();
23462    pane_1
23463        .update_in(cx, |pane, window, cx| {
23464            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23465        })
23466        .await
23467        .unwrap();
23468    drop(editor_1);
23469    pane_1.update(cx, |pane, cx| {
23470        pane.active_item()
23471            .unwrap()
23472            .downcast::<Editor>()
23473            .unwrap()
23474            .update(cx, |editor, cx| {
23475                assert_eq!(
23476                    editor.display_text(cx),
23477                    lib_text,
23478                    "Other file should be open and active",
23479                );
23480            });
23481        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23482    });
23483
23484    let _other_editor_2 = workspace
23485        .update_in(cx, |workspace, window, cx| {
23486            workspace.open_path(
23487                (worktree_id, "lib.rs"),
23488                Some(pane_2.downgrade()),
23489                true,
23490                window,
23491                cx,
23492            )
23493        })
23494        .unwrap()
23495        .await
23496        .downcast::<Editor>()
23497        .unwrap();
23498    pane_2
23499        .update_in(cx, |pane, window, cx| {
23500            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23501        })
23502        .await
23503        .unwrap();
23504    drop(editor_2);
23505    pane_2.update(cx, |pane, cx| {
23506        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23507        open_editor.update(cx, |editor, cx| {
23508            assert_eq!(
23509                editor.display_text(cx),
23510                lib_text,
23511                "Other file should be open and active in another panel too",
23512            );
23513        });
23514        assert_eq!(
23515            pane.items().count(),
23516            1,
23517            "No other editors should be open in another pane",
23518        );
23519    });
23520
23521    let _editor_1_reopened = workspace
23522        .update_in(cx, |workspace, window, cx| {
23523            workspace.open_path(
23524                (worktree_id, "main.rs"),
23525                Some(pane_1.downgrade()),
23526                true,
23527                window,
23528                cx,
23529            )
23530        })
23531        .unwrap()
23532        .await
23533        .downcast::<Editor>()
23534        .unwrap();
23535    let _editor_2_reopened = workspace
23536        .update_in(cx, |workspace, window, cx| {
23537            workspace.open_path(
23538                (worktree_id, "main.rs"),
23539                Some(pane_2.downgrade()),
23540                true,
23541                window,
23542                cx,
23543            )
23544        })
23545        .unwrap()
23546        .await
23547        .downcast::<Editor>()
23548        .unwrap();
23549    pane_1.update(cx, |pane, cx| {
23550        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23551        open_editor.update(cx, |editor, cx| {
23552            assert_eq!(
23553                editor.display_text(cx),
23554                main_text,
23555                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23556            );
23557            assert_eq!(
23558                editor
23559                    .selections
23560                    .all::<Point>(cx)
23561                    .into_iter()
23562                    .map(|s| s.range())
23563                    .collect::<Vec<_>>(),
23564                expected_ranges,
23565                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23566            );
23567        })
23568    });
23569    pane_2.update(cx, |pane, cx| {
23570        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23571        open_editor.update(cx, |editor, cx| {
23572            assert_eq!(
23573                editor.display_text(cx),
23574                r#"fn main() {
23575⋯rintln!("1");
23576⋯intln!("2");
23577⋯ntln!("3");
23578println!("4");
23579println!("5");
23580}"#,
23581                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23582            );
23583            assert_eq!(
23584                editor
23585                    .selections
23586                    .all::<Point>(cx)
23587                    .into_iter()
23588                    .map(|s| s.range())
23589                    .collect::<Vec<_>>(),
23590                vec![Point::zero()..Point::zero()],
23591                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23592            );
23593        })
23594    });
23595}
23596
23597#[gpui::test]
23598async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23599    init_test(cx, |_| {});
23600
23601    let fs = FakeFs::new(cx.executor());
23602    let main_text = r#"fn main() {
23603println!("1");
23604println!("2");
23605println!("3");
23606println!("4");
23607println!("5");
23608}"#;
23609    let lib_text = "mod foo {}";
23610    fs.insert_tree(
23611        path!("/a"),
23612        json!({
23613            "lib.rs": lib_text,
23614            "main.rs": main_text,
23615        }),
23616    )
23617    .await;
23618
23619    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23620    let (workspace, cx) =
23621        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23622    let worktree_id = workspace.update(cx, |workspace, cx| {
23623        workspace.project().update(cx, |project, cx| {
23624            project.worktrees(cx).next().unwrap().read(cx).id()
23625        })
23626    });
23627
23628    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23629    let editor = workspace
23630        .update_in(cx, |workspace, window, cx| {
23631            workspace.open_path(
23632                (worktree_id, "main.rs"),
23633                Some(pane.downgrade()),
23634                true,
23635                window,
23636                cx,
23637            )
23638        })
23639        .unwrap()
23640        .await
23641        .downcast::<Editor>()
23642        .unwrap();
23643    pane.update(cx, |pane, cx| {
23644        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23645        open_editor.update(cx, |editor, cx| {
23646            assert_eq!(
23647                editor.display_text(cx),
23648                main_text,
23649                "Original main.rs text on initial open",
23650            );
23651        })
23652    });
23653    editor.update_in(cx, |editor, window, cx| {
23654        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23655    });
23656
23657    cx.update_global(|store: &mut SettingsStore, cx| {
23658        store.update_user_settings(cx, |s| {
23659            s.workspace.restore_on_file_reopen = Some(false);
23660        });
23661    });
23662    editor.update_in(cx, |editor, window, cx| {
23663        editor.fold_ranges(
23664            vec![
23665                Point::new(1, 0)..Point::new(1, 1),
23666                Point::new(2, 0)..Point::new(2, 2),
23667                Point::new(3, 0)..Point::new(3, 3),
23668            ],
23669            false,
23670            window,
23671            cx,
23672        );
23673    });
23674    pane.update_in(cx, |pane, window, cx| {
23675        pane.close_all_items(&CloseAllItems::default(), window, cx)
23676    })
23677    .await
23678    .unwrap();
23679    pane.update(cx, |pane, _| {
23680        assert!(pane.active_item().is_none());
23681    });
23682    cx.update_global(|store: &mut SettingsStore, cx| {
23683        store.update_user_settings(cx, |s| {
23684            s.workspace.restore_on_file_reopen = Some(true);
23685        });
23686    });
23687
23688    let _editor_reopened = workspace
23689        .update_in(cx, |workspace, window, cx| {
23690            workspace.open_path(
23691                (worktree_id, "main.rs"),
23692                Some(pane.downgrade()),
23693                true,
23694                window,
23695                cx,
23696            )
23697        })
23698        .unwrap()
23699        .await
23700        .downcast::<Editor>()
23701        .unwrap();
23702    pane.update(cx, |pane, cx| {
23703        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23704        open_editor.update(cx, |editor, cx| {
23705            assert_eq!(
23706                editor.display_text(cx),
23707                main_text,
23708                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23709            );
23710        })
23711    });
23712}
23713
23714#[gpui::test]
23715async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23716    struct EmptyModalView {
23717        focus_handle: gpui::FocusHandle,
23718    }
23719    impl EventEmitter<DismissEvent> for EmptyModalView {}
23720    impl Render for EmptyModalView {
23721        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23722            div()
23723        }
23724    }
23725    impl Focusable for EmptyModalView {
23726        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23727            self.focus_handle.clone()
23728        }
23729    }
23730    impl workspace::ModalView for EmptyModalView {}
23731    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23732        EmptyModalView {
23733            focus_handle: cx.focus_handle(),
23734        }
23735    }
23736
23737    init_test(cx, |_| {});
23738
23739    let fs = FakeFs::new(cx.executor());
23740    let project = Project::test(fs, [], cx).await;
23741    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23742    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23743    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23744    let editor = cx.new_window_entity(|window, cx| {
23745        Editor::new(
23746            EditorMode::full(),
23747            buffer,
23748            Some(project.clone()),
23749            window,
23750            cx,
23751        )
23752    });
23753    workspace
23754        .update(cx, |workspace, window, cx| {
23755            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23756        })
23757        .unwrap();
23758    editor.update_in(cx, |editor, window, cx| {
23759        editor.open_context_menu(&OpenContextMenu, window, cx);
23760        assert!(editor.mouse_context_menu.is_some());
23761    });
23762    workspace
23763        .update(cx, |workspace, window, cx| {
23764            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23765        })
23766        .unwrap();
23767    cx.read(|cx| {
23768        assert!(editor.read(cx).mouse_context_menu.is_none());
23769    });
23770}
23771
23772#[gpui::test]
23773async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23774    init_test(cx, |_| {});
23775
23776    let fs = FakeFs::new(cx.executor());
23777    fs.insert_file(path!("/file.html"), Default::default())
23778        .await;
23779
23780    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23781
23782    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23783    let html_language = Arc::new(Language::new(
23784        LanguageConfig {
23785            name: "HTML".into(),
23786            matcher: LanguageMatcher {
23787                path_suffixes: vec!["html".to_string()],
23788                ..LanguageMatcher::default()
23789            },
23790            brackets: BracketPairConfig {
23791                pairs: vec![BracketPair {
23792                    start: "<".into(),
23793                    end: ">".into(),
23794                    close: true,
23795                    ..Default::default()
23796                }],
23797                ..Default::default()
23798            },
23799            ..Default::default()
23800        },
23801        Some(tree_sitter_html::LANGUAGE.into()),
23802    ));
23803    language_registry.add(html_language);
23804    let mut fake_servers = language_registry.register_fake_lsp(
23805        "HTML",
23806        FakeLspAdapter {
23807            capabilities: lsp::ServerCapabilities {
23808                completion_provider: Some(lsp::CompletionOptions {
23809                    resolve_provider: Some(true),
23810                    ..Default::default()
23811                }),
23812                ..Default::default()
23813            },
23814            ..Default::default()
23815        },
23816    );
23817
23818    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23819    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23820
23821    let worktree_id = workspace
23822        .update(cx, |workspace, _window, cx| {
23823            workspace.project().update(cx, |project, cx| {
23824                project.worktrees(cx).next().unwrap().read(cx).id()
23825            })
23826        })
23827        .unwrap();
23828    project
23829        .update(cx, |project, cx| {
23830            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23831        })
23832        .await
23833        .unwrap();
23834    let editor = workspace
23835        .update(cx, |workspace, window, cx| {
23836            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23837        })
23838        .unwrap()
23839        .await
23840        .unwrap()
23841        .downcast::<Editor>()
23842        .unwrap();
23843
23844    let fake_server = fake_servers.next().await.unwrap();
23845    editor.update_in(cx, |editor, window, cx| {
23846        editor.set_text("<ad></ad>", window, cx);
23847        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23848            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23849        });
23850        let Some((buffer, _)) = editor
23851            .buffer
23852            .read(cx)
23853            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23854        else {
23855            panic!("Failed to get buffer for selection position");
23856        };
23857        let buffer = buffer.read(cx);
23858        let buffer_id = buffer.remote_id();
23859        let opening_range =
23860            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
23861        let closing_range =
23862            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
23863        let mut linked_ranges = HashMap::default();
23864        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23865        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23866    });
23867    let mut completion_handle =
23868        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23869            Ok(Some(lsp::CompletionResponse::Array(vec![
23870                lsp::CompletionItem {
23871                    label: "head".to_string(),
23872                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23873                        lsp::InsertReplaceEdit {
23874                            new_text: "head".to_string(),
23875                            insert: lsp::Range::new(
23876                                lsp::Position::new(0, 1),
23877                                lsp::Position::new(0, 3),
23878                            ),
23879                            replace: lsp::Range::new(
23880                                lsp::Position::new(0, 1),
23881                                lsp::Position::new(0, 3),
23882                            ),
23883                        },
23884                    )),
23885                    ..Default::default()
23886                },
23887            ])))
23888        });
23889    editor.update_in(cx, |editor, window, cx| {
23890        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23891    });
23892    cx.run_until_parked();
23893    completion_handle.next().await.unwrap();
23894    editor.update(cx, |editor, _| {
23895        assert!(
23896            editor.context_menu_visible(),
23897            "Completion menu should be visible"
23898        );
23899    });
23900    editor.update_in(cx, |editor, window, cx| {
23901        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23902    });
23903    cx.executor().run_until_parked();
23904    editor.update(cx, |editor, cx| {
23905        assert_eq!(editor.text(cx), "<head></head>");
23906    });
23907}
23908
23909#[gpui::test]
23910async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
23911    init_test(cx, |_| {});
23912
23913    let fs = FakeFs::new(cx.executor());
23914    fs.insert_tree(
23915        path!("/root"),
23916        json!({
23917            "a": {
23918                "main.rs": "fn main() {}",
23919            },
23920            "foo": {
23921                "bar": {
23922                    "external_file.rs": "pub mod external {}",
23923                }
23924            }
23925        }),
23926    )
23927    .await;
23928
23929    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
23930    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23931    language_registry.add(rust_lang());
23932    let _fake_servers = language_registry.register_fake_lsp(
23933        "Rust",
23934        FakeLspAdapter {
23935            ..FakeLspAdapter::default()
23936        },
23937    );
23938    let (workspace, cx) =
23939        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23940    let worktree_id = workspace.update(cx, |workspace, cx| {
23941        workspace.project().update(cx, |project, cx| {
23942            project.worktrees(cx).next().unwrap().read(cx).id()
23943        })
23944    });
23945
23946    let assert_language_servers_count =
23947        |expected: usize, context: &str, cx: &mut VisualTestContext| {
23948            project.update(cx, |project, cx| {
23949                let current = project
23950                    .lsp_store()
23951                    .read(cx)
23952                    .as_local()
23953                    .unwrap()
23954                    .language_servers
23955                    .len();
23956                assert_eq!(expected, current, "{context}");
23957            });
23958        };
23959
23960    assert_language_servers_count(
23961        0,
23962        "No servers should be running before any file is open",
23963        cx,
23964    );
23965    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23966    let main_editor = workspace
23967        .update_in(cx, |workspace, window, cx| {
23968            workspace.open_path(
23969                (worktree_id, "main.rs"),
23970                Some(pane.downgrade()),
23971                true,
23972                window,
23973                cx,
23974            )
23975        })
23976        .unwrap()
23977        .await
23978        .downcast::<Editor>()
23979        .unwrap();
23980    pane.update(cx, |pane, cx| {
23981        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23982        open_editor.update(cx, |editor, cx| {
23983            assert_eq!(
23984                editor.display_text(cx),
23985                "fn main() {}",
23986                "Original main.rs text on initial open",
23987            );
23988        });
23989        assert_eq!(open_editor, main_editor);
23990    });
23991    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
23992
23993    let external_editor = workspace
23994        .update_in(cx, |workspace, window, cx| {
23995            workspace.open_abs_path(
23996                PathBuf::from("/root/foo/bar/external_file.rs"),
23997                OpenOptions::default(),
23998                window,
23999                cx,
24000            )
24001        })
24002        .await
24003        .expect("opening external file")
24004        .downcast::<Editor>()
24005        .expect("downcasted external file's open element to editor");
24006    pane.update(cx, |pane, cx| {
24007        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24008        open_editor.update(cx, |editor, cx| {
24009            assert_eq!(
24010                editor.display_text(cx),
24011                "pub mod external {}",
24012                "External file is open now",
24013            );
24014        });
24015        assert_eq!(open_editor, external_editor);
24016    });
24017    assert_language_servers_count(
24018        1,
24019        "Second, external, *.rs file should join the existing server",
24020        cx,
24021    );
24022
24023    pane.update_in(cx, |pane, window, cx| {
24024        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24025    })
24026    .await
24027    .unwrap();
24028    pane.update_in(cx, |pane, window, cx| {
24029        pane.navigate_backward(&Default::default(), window, cx);
24030    });
24031    cx.run_until_parked();
24032    pane.update(cx, |pane, cx| {
24033        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24034        open_editor.update(cx, |editor, cx| {
24035            assert_eq!(
24036                editor.display_text(cx),
24037                "pub mod external {}",
24038                "External file is open now",
24039            );
24040        });
24041    });
24042    assert_language_servers_count(
24043        1,
24044        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24045        cx,
24046    );
24047
24048    cx.update(|_, cx| {
24049        workspace::reload(cx);
24050    });
24051    assert_language_servers_count(
24052        1,
24053        "After reloading the worktree with local and external files opened, only one project should be started",
24054        cx,
24055    );
24056}
24057
24058#[gpui::test]
24059async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24060    init_test(cx, |_| {});
24061
24062    let mut cx = EditorTestContext::new(cx).await;
24063    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24064    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24065
24066    // test cursor move to start of each line on tab
24067    // for `if`, `elif`, `else`, `while`, `with` and `for`
24068    cx.set_state(indoc! {"
24069        def main():
24070        ˇ    for item in items:
24071        ˇ        while item.active:
24072        ˇ            if item.value > 10:
24073        ˇ                continue
24074        ˇ            elif item.value < 0:
24075        ˇ                break
24076        ˇ            else:
24077        ˇ                with item.context() as ctx:
24078        ˇ                    yield count
24079        ˇ        else:
24080        ˇ            log('while else')
24081        ˇ    else:
24082        ˇ        log('for else')
24083    "});
24084    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24085    cx.assert_editor_state(indoc! {"
24086        def main():
24087            ˇfor item in items:
24088                ˇwhile item.active:
24089                    ˇif item.value > 10:
24090                        ˇcontinue
24091                    ˇelif item.value < 0:
24092                        ˇbreak
24093                    ˇelse:
24094                        ˇwith item.context() as ctx:
24095                            ˇyield count
24096                ˇelse:
24097                    ˇlog('while else')
24098            ˇelse:
24099                ˇlog('for else')
24100    "});
24101    // test relative indent is preserved when tab
24102    // for `if`, `elif`, `else`, `while`, `with` and `for`
24103    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24104    cx.assert_editor_state(indoc! {"
24105        def main():
24106                ˇfor item in items:
24107                    ˇwhile item.active:
24108                        ˇif item.value > 10:
24109                            ˇcontinue
24110                        ˇelif item.value < 0:
24111                            ˇbreak
24112                        ˇelse:
24113                            ˇwith item.context() as ctx:
24114                                ˇyield count
24115                    ˇelse:
24116                        ˇlog('while else')
24117                ˇelse:
24118                    ˇlog('for else')
24119    "});
24120
24121    // test cursor move to start of each line on tab
24122    // for `try`, `except`, `else`, `finally`, `match` and `def`
24123    cx.set_state(indoc! {"
24124        def main():
24125        ˇ    try:
24126        ˇ        fetch()
24127        ˇ    except ValueError:
24128        ˇ        handle_error()
24129        ˇ    else:
24130        ˇ        match value:
24131        ˇ            case _:
24132        ˇ    finally:
24133        ˇ        def status():
24134        ˇ            return 0
24135    "});
24136    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24137    cx.assert_editor_state(indoc! {"
24138        def main():
24139            ˇtry:
24140                ˇfetch()
24141            ˇexcept ValueError:
24142                ˇhandle_error()
24143            ˇelse:
24144                ˇmatch value:
24145                    ˇcase _:
24146            ˇfinally:
24147                ˇdef status():
24148                    ˇreturn 0
24149    "});
24150    // test relative indent is preserved when tab
24151    // for `try`, `except`, `else`, `finally`, `match` and `def`
24152    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24153    cx.assert_editor_state(indoc! {"
24154        def main():
24155                ˇtry:
24156                    ˇfetch()
24157                ˇexcept ValueError:
24158                    ˇhandle_error()
24159                ˇelse:
24160                    ˇmatch value:
24161                        ˇcase _:
24162                ˇfinally:
24163                    ˇdef status():
24164                        ˇreturn 0
24165    "});
24166}
24167
24168#[gpui::test]
24169async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24170    init_test(cx, |_| {});
24171
24172    let mut cx = EditorTestContext::new(cx).await;
24173    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24174    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24175
24176    // test `else` auto outdents when typed inside `if` block
24177    cx.set_state(indoc! {"
24178        def main():
24179            if i == 2:
24180                return
24181                ˇ
24182    "});
24183    cx.update_editor(|editor, window, cx| {
24184        editor.handle_input("else:", window, cx);
24185    });
24186    cx.assert_editor_state(indoc! {"
24187        def main():
24188            if i == 2:
24189                return
24190            else:ˇ
24191    "});
24192
24193    // test `except` auto outdents when typed inside `try` block
24194    cx.set_state(indoc! {"
24195        def main():
24196            try:
24197                i = 2
24198                ˇ
24199    "});
24200    cx.update_editor(|editor, window, cx| {
24201        editor.handle_input("except:", window, cx);
24202    });
24203    cx.assert_editor_state(indoc! {"
24204        def main():
24205            try:
24206                i = 2
24207            except:ˇ
24208    "});
24209
24210    // test `else` auto outdents when typed inside `except` block
24211    cx.set_state(indoc! {"
24212        def main():
24213            try:
24214                i = 2
24215            except:
24216                j = 2
24217                ˇ
24218    "});
24219    cx.update_editor(|editor, window, cx| {
24220        editor.handle_input("else:", window, cx);
24221    });
24222    cx.assert_editor_state(indoc! {"
24223        def main():
24224            try:
24225                i = 2
24226            except:
24227                j = 2
24228            else:ˇ
24229    "});
24230
24231    // test `finally` auto outdents when typed inside `else` block
24232    cx.set_state(indoc! {"
24233        def main():
24234            try:
24235                i = 2
24236            except:
24237                j = 2
24238            else:
24239                k = 2
24240                ˇ
24241    "});
24242    cx.update_editor(|editor, window, cx| {
24243        editor.handle_input("finally:", window, cx);
24244    });
24245    cx.assert_editor_state(indoc! {"
24246        def main():
24247            try:
24248                i = 2
24249            except:
24250                j = 2
24251            else:
24252                k = 2
24253            finally:ˇ
24254    "});
24255
24256    // test `else` does not outdents when typed inside `except` block right after for block
24257    cx.set_state(indoc! {"
24258        def main():
24259            try:
24260                i = 2
24261            except:
24262                for i in range(n):
24263                    pass
24264                ˇ
24265    "});
24266    cx.update_editor(|editor, window, cx| {
24267        editor.handle_input("else:", window, cx);
24268    });
24269    cx.assert_editor_state(indoc! {"
24270        def main():
24271            try:
24272                i = 2
24273            except:
24274                for i in range(n):
24275                    pass
24276                else:ˇ
24277    "});
24278
24279    // test `finally` auto outdents when typed inside `else` block right after for block
24280    cx.set_state(indoc! {"
24281        def main():
24282            try:
24283                i = 2
24284            except:
24285                j = 2
24286            else:
24287                for i in range(n):
24288                    pass
24289                ˇ
24290    "});
24291    cx.update_editor(|editor, window, cx| {
24292        editor.handle_input("finally:", window, cx);
24293    });
24294    cx.assert_editor_state(indoc! {"
24295        def main():
24296            try:
24297                i = 2
24298            except:
24299                j = 2
24300            else:
24301                for i in range(n):
24302                    pass
24303            finally:ˇ
24304    "});
24305
24306    // test `except` outdents to inner "try" block
24307    cx.set_state(indoc! {"
24308        def main():
24309            try:
24310                i = 2
24311                if i == 2:
24312                    try:
24313                        i = 3
24314                        ˇ
24315    "});
24316    cx.update_editor(|editor, window, cx| {
24317        editor.handle_input("except:", window, cx);
24318    });
24319    cx.assert_editor_state(indoc! {"
24320        def main():
24321            try:
24322                i = 2
24323                if i == 2:
24324                    try:
24325                        i = 3
24326                    except:ˇ
24327    "});
24328
24329    // test `except` outdents to outer "try" block
24330    cx.set_state(indoc! {"
24331        def main():
24332            try:
24333                i = 2
24334                if i == 2:
24335                    try:
24336                        i = 3
24337                ˇ
24338    "});
24339    cx.update_editor(|editor, window, cx| {
24340        editor.handle_input("except:", window, cx);
24341    });
24342    cx.assert_editor_state(indoc! {"
24343        def main():
24344            try:
24345                i = 2
24346                if i == 2:
24347                    try:
24348                        i = 3
24349            except:ˇ
24350    "});
24351
24352    // test `else` stays at correct indent when typed after `for` block
24353    cx.set_state(indoc! {"
24354        def main():
24355            for i in range(10):
24356                if i == 3:
24357                    break
24358            ˇ
24359    "});
24360    cx.update_editor(|editor, window, cx| {
24361        editor.handle_input("else:", window, cx);
24362    });
24363    cx.assert_editor_state(indoc! {"
24364        def main():
24365            for i in range(10):
24366                if i == 3:
24367                    break
24368            else:ˇ
24369    "});
24370
24371    // test does not outdent on typing after line with square brackets
24372    cx.set_state(indoc! {"
24373        def f() -> list[str]:
24374            ˇ
24375    "});
24376    cx.update_editor(|editor, window, cx| {
24377        editor.handle_input("a", window, cx);
24378    });
24379    cx.assert_editor_state(indoc! {"
24380        def f() -> list[str]:
2438124382    "});
24383
24384    // test does not outdent on typing : after case keyword
24385    cx.set_state(indoc! {"
24386        match 1:
24387            caseˇ
24388    "});
24389    cx.update_editor(|editor, window, cx| {
24390        editor.handle_input(":", window, cx);
24391    });
24392    cx.assert_editor_state(indoc! {"
24393        match 1:
24394            case:ˇ
24395    "});
24396}
24397
24398#[gpui::test]
24399async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24400    init_test(cx, |_| {});
24401    update_test_language_settings(cx, |settings| {
24402        settings.defaults.extend_comment_on_newline = Some(false);
24403    });
24404    let mut cx = EditorTestContext::new(cx).await;
24405    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24406    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24407
24408    // test correct indent after newline on comment
24409    cx.set_state(indoc! {"
24410        # COMMENT:ˇ
24411    "});
24412    cx.update_editor(|editor, window, cx| {
24413        editor.newline(&Newline, window, cx);
24414    });
24415    cx.assert_editor_state(indoc! {"
24416        # COMMENT:
24417        ˇ
24418    "});
24419
24420    // test correct indent after newline in brackets
24421    cx.set_state(indoc! {"
24422        {ˇ}
24423    "});
24424    cx.update_editor(|editor, window, cx| {
24425        editor.newline(&Newline, window, cx);
24426    });
24427    cx.run_until_parked();
24428    cx.assert_editor_state(indoc! {"
24429        {
24430            ˇ
24431        }
24432    "});
24433
24434    cx.set_state(indoc! {"
24435        (ˇ)
24436    "});
24437    cx.update_editor(|editor, window, cx| {
24438        editor.newline(&Newline, window, cx);
24439    });
24440    cx.run_until_parked();
24441    cx.assert_editor_state(indoc! {"
24442        (
24443            ˇ
24444        )
24445    "});
24446
24447    // do not indent after empty lists or dictionaries
24448    cx.set_state(indoc! {"
24449        a = []ˇ
24450    "});
24451    cx.update_editor(|editor, window, cx| {
24452        editor.newline(&Newline, window, cx);
24453    });
24454    cx.run_until_parked();
24455    cx.assert_editor_state(indoc! {"
24456        a = []
24457        ˇ
24458    "});
24459}
24460
24461#[gpui::test]
24462async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24463    init_test(cx, |_| {});
24464
24465    let mut cx = EditorTestContext::new(cx).await;
24466    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24467    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24468
24469    // test cursor move to start of each line on tab
24470    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24471    cx.set_state(indoc! {"
24472        function main() {
24473        ˇ    for item in $items; do
24474        ˇ        while [ -n \"$item\" ]; do
24475        ˇ            if [ \"$value\" -gt 10 ]; then
24476        ˇ                continue
24477        ˇ            elif [ \"$value\" -lt 0 ]; then
24478        ˇ                break
24479        ˇ            else
24480        ˇ                echo \"$item\"
24481        ˇ            fi
24482        ˇ        done
24483        ˇ    done
24484        ˇ}
24485    "});
24486    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24487    cx.assert_editor_state(indoc! {"
24488        function main() {
24489            ˇfor item in $items; do
24490                ˇwhile [ -n \"$item\" ]; do
24491                    ˇif [ \"$value\" -gt 10 ]; then
24492                        ˇcontinue
24493                    ˇelif [ \"$value\" -lt 0 ]; then
24494                        ˇbreak
24495                    ˇelse
24496                        ˇecho \"$item\"
24497                    ˇfi
24498                ˇdone
24499            ˇdone
24500        ˇ}
24501    "});
24502    // test relative indent is preserved when tab
24503    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24504    cx.assert_editor_state(indoc! {"
24505        function main() {
24506                ˇfor item in $items; do
24507                    ˇwhile [ -n \"$item\" ]; do
24508                        ˇif [ \"$value\" -gt 10 ]; then
24509                            ˇcontinue
24510                        ˇelif [ \"$value\" -lt 0 ]; then
24511                            ˇbreak
24512                        ˇelse
24513                            ˇecho \"$item\"
24514                        ˇfi
24515                    ˇdone
24516                ˇdone
24517            ˇ}
24518    "});
24519
24520    // test cursor move to start of each line on tab
24521    // for `case` statement with patterns
24522    cx.set_state(indoc! {"
24523        function handle() {
24524        ˇ    case \"$1\" in
24525        ˇ        start)
24526        ˇ            echo \"a\"
24527        ˇ            ;;
24528        ˇ        stop)
24529        ˇ            echo \"b\"
24530        ˇ            ;;
24531        ˇ        *)
24532        ˇ            echo \"c\"
24533        ˇ            ;;
24534        ˇ    esac
24535        ˇ}
24536    "});
24537    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24538    cx.assert_editor_state(indoc! {"
24539        function handle() {
24540            ˇcase \"$1\" in
24541                ˇstart)
24542                    ˇecho \"a\"
24543                    ˇ;;
24544                ˇstop)
24545                    ˇecho \"b\"
24546                    ˇ;;
24547                ˇ*)
24548                    ˇecho \"c\"
24549                    ˇ;;
24550            ˇesac
24551        ˇ}
24552    "});
24553}
24554
24555#[gpui::test]
24556async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24557    init_test(cx, |_| {});
24558
24559    let mut cx = EditorTestContext::new(cx).await;
24560    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24561    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24562
24563    // test indents on comment insert
24564    cx.set_state(indoc! {"
24565        function main() {
24566        ˇ    for item in $items; do
24567        ˇ        while [ -n \"$item\" ]; do
24568        ˇ            if [ \"$value\" -gt 10 ]; then
24569        ˇ                continue
24570        ˇ            elif [ \"$value\" -lt 0 ]; then
24571        ˇ                break
24572        ˇ            else
24573        ˇ                echo \"$item\"
24574        ˇ            fi
24575        ˇ        done
24576        ˇ    done
24577        ˇ}
24578    "});
24579    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24580    cx.assert_editor_state(indoc! {"
24581        function main() {
24582        #ˇ    for item in $items; do
24583        #ˇ        while [ -n \"$item\" ]; do
24584        #ˇ            if [ \"$value\" -gt 10 ]; then
24585        #ˇ                continue
24586        #ˇ            elif [ \"$value\" -lt 0 ]; then
24587        #ˇ                break
24588        #ˇ            else
24589        #ˇ                echo \"$item\"
24590        #ˇ            fi
24591        #ˇ        done
24592        #ˇ    done
24593        #ˇ}
24594    "});
24595}
24596
24597#[gpui::test]
24598async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24599    init_test(cx, |_| {});
24600
24601    let mut cx = EditorTestContext::new(cx).await;
24602    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24603    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24604
24605    // test `else` auto outdents when typed inside `if` block
24606    cx.set_state(indoc! {"
24607        if [ \"$1\" = \"test\" ]; then
24608            echo \"foo bar\"
24609            ˇ
24610    "});
24611    cx.update_editor(|editor, window, cx| {
24612        editor.handle_input("else", window, cx);
24613    });
24614    cx.assert_editor_state(indoc! {"
24615        if [ \"$1\" = \"test\" ]; then
24616            echo \"foo bar\"
24617        elseˇ
24618    "});
24619
24620    // test `elif` auto outdents when typed inside `if` block
24621    cx.set_state(indoc! {"
24622        if [ \"$1\" = \"test\" ]; then
24623            echo \"foo bar\"
24624            ˇ
24625    "});
24626    cx.update_editor(|editor, window, cx| {
24627        editor.handle_input("elif", window, cx);
24628    });
24629    cx.assert_editor_state(indoc! {"
24630        if [ \"$1\" = \"test\" ]; then
24631            echo \"foo bar\"
24632        elifˇ
24633    "});
24634
24635    // test `fi` auto outdents when typed inside `else` block
24636    cx.set_state(indoc! {"
24637        if [ \"$1\" = \"test\" ]; then
24638            echo \"foo bar\"
24639        else
24640            echo \"bar baz\"
24641            ˇ
24642    "});
24643    cx.update_editor(|editor, window, cx| {
24644        editor.handle_input("fi", window, cx);
24645    });
24646    cx.assert_editor_state(indoc! {"
24647        if [ \"$1\" = \"test\" ]; then
24648            echo \"foo bar\"
24649        else
24650            echo \"bar baz\"
24651        fiˇ
24652    "});
24653
24654    // test `done` auto outdents when typed inside `while` block
24655    cx.set_state(indoc! {"
24656        while read line; do
24657            echo \"$line\"
24658            ˇ
24659    "});
24660    cx.update_editor(|editor, window, cx| {
24661        editor.handle_input("done", window, cx);
24662    });
24663    cx.assert_editor_state(indoc! {"
24664        while read line; do
24665            echo \"$line\"
24666        doneˇ
24667    "});
24668
24669    // test `done` auto outdents when typed inside `for` block
24670    cx.set_state(indoc! {"
24671        for file in *.txt; do
24672            cat \"$file\"
24673            ˇ
24674    "});
24675    cx.update_editor(|editor, window, cx| {
24676        editor.handle_input("done", window, cx);
24677    });
24678    cx.assert_editor_state(indoc! {"
24679        for file in *.txt; do
24680            cat \"$file\"
24681        doneˇ
24682    "});
24683
24684    // test `esac` auto outdents when typed inside `case` block
24685    cx.set_state(indoc! {"
24686        case \"$1\" in
24687            start)
24688                echo \"foo bar\"
24689                ;;
24690            stop)
24691                echo \"bar baz\"
24692                ;;
24693            ˇ
24694    "});
24695    cx.update_editor(|editor, window, cx| {
24696        editor.handle_input("esac", window, cx);
24697    });
24698    cx.assert_editor_state(indoc! {"
24699        case \"$1\" in
24700            start)
24701                echo \"foo bar\"
24702                ;;
24703            stop)
24704                echo \"bar baz\"
24705                ;;
24706        esacˇ
24707    "});
24708
24709    // test `*)` auto outdents when typed inside `case` block
24710    cx.set_state(indoc! {"
24711        case \"$1\" in
24712            start)
24713                echo \"foo bar\"
24714                ;;
24715                ˇ
24716    "});
24717    cx.update_editor(|editor, window, cx| {
24718        editor.handle_input("*)", window, cx);
24719    });
24720    cx.assert_editor_state(indoc! {"
24721        case \"$1\" in
24722            start)
24723                echo \"foo bar\"
24724                ;;
24725            *)ˇ
24726    "});
24727
24728    // test `fi` outdents to correct level with nested if blocks
24729    cx.set_state(indoc! {"
24730        if [ \"$1\" = \"test\" ]; then
24731            echo \"outer if\"
24732            if [ \"$2\" = \"debug\" ]; then
24733                echo \"inner if\"
24734                ˇ
24735    "});
24736    cx.update_editor(|editor, window, cx| {
24737        editor.handle_input("fi", window, cx);
24738    });
24739    cx.assert_editor_state(indoc! {"
24740        if [ \"$1\" = \"test\" ]; then
24741            echo \"outer if\"
24742            if [ \"$2\" = \"debug\" ]; then
24743                echo \"inner if\"
24744            fiˇ
24745    "});
24746}
24747
24748#[gpui::test]
24749async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24750    init_test(cx, |_| {});
24751    update_test_language_settings(cx, |settings| {
24752        settings.defaults.extend_comment_on_newline = Some(false);
24753    });
24754    let mut cx = EditorTestContext::new(cx).await;
24755    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24756    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24757
24758    // test correct indent after newline on comment
24759    cx.set_state(indoc! {"
24760        # COMMENT:ˇ
24761    "});
24762    cx.update_editor(|editor, window, cx| {
24763        editor.newline(&Newline, window, cx);
24764    });
24765    cx.assert_editor_state(indoc! {"
24766        # COMMENT:
24767        ˇ
24768    "});
24769
24770    // test correct indent after newline after `then`
24771    cx.set_state(indoc! {"
24772
24773        if [ \"$1\" = \"test\" ]; thenˇ
24774    "});
24775    cx.update_editor(|editor, window, cx| {
24776        editor.newline(&Newline, window, cx);
24777    });
24778    cx.run_until_parked();
24779    cx.assert_editor_state(indoc! {"
24780
24781        if [ \"$1\" = \"test\" ]; then
24782            ˇ
24783    "});
24784
24785    // test correct indent after newline after `else`
24786    cx.set_state(indoc! {"
24787        if [ \"$1\" = \"test\" ]; then
24788        elseˇ
24789    "});
24790    cx.update_editor(|editor, window, cx| {
24791        editor.newline(&Newline, window, cx);
24792    });
24793    cx.run_until_parked();
24794    cx.assert_editor_state(indoc! {"
24795        if [ \"$1\" = \"test\" ]; then
24796        else
24797            ˇ
24798    "});
24799
24800    // test correct indent after newline after `elif`
24801    cx.set_state(indoc! {"
24802        if [ \"$1\" = \"test\" ]; then
24803        elifˇ
24804    "});
24805    cx.update_editor(|editor, window, cx| {
24806        editor.newline(&Newline, window, cx);
24807    });
24808    cx.run_until_parked();
24809    cx.assert_editor_state(indoc! {"
24810        if [ \"$1\" = \"test\" ]; then
24811        elif
24812            ˇ
24813    "});
24814
24815    // test correct indent after newline after `do`
24816    cx.set_state(indoc! {"
24817        for file in *.txt; doˇ
24818    "});
24819    cx.update_editor(|editor, window, cx| {
24820        editor.newline(&Newline, window, cx);
24821    });
24822    cx.run_until_parked();
24823    cx.assert_editor_state(indoc! {"
24824        for file in *.txt; do
24825            ˇ
24826    "});
24827
24828    // test correct indent after newline after case pattern
24829    cx.set_state(indoc! {"
24830        case \"$1\" in
24831            start)ˇ
24832    "});
24833    cx.update_editor(|editor, window, cx| {
24834        editor.newline(&Newline, window, cx);
24835    });
24836    cx.run_until_parked();
24837    cx.assert_editor_state(indoc! {"
24838        case \"$1\" in
24839            start)
24840                ˇ
24841    "});
24842
24843    // test correct indent after newline after case pattern
24844    cx.set_state(indoc! {"
24845        case \"$1\" in
24846            start)
24847                ;;
24848            *)ˇ
24849    "});
24850    cx.update_editor(|editor, window, cx| {
24851        editor.newline(&Newline, window, cx);
24852    });
24853    cx.run_until_parked();
24854    cx.assert_editor_state(indoc! {"
24855        case \"$1\" in
24856            start)
24857                ;;
24858            *)
24859                ˇ
24860    "});
24861
24862    // test correct indent after newline after function opening brace
24863    cx.set_state(indoc! {"
24864        function test() {ˇ}
24865    "});
24866    cx.update_editor(|editor, window, cx| {
24867        editor.newline(&Newline, window, cx);
24868    });
24869    cx.run_until_parked();
24870    cx.assert_editor_state(indoc! {"
24871        function test() {
24872            ˇ
24873        }
24874    "});
24875
24876    // test no extra indent after semicolon on same line
24877    cx.set_state(indoc! {"
24878        echo \"test\"24879    "});
24880    cx.update_editor(|editor, window, cx| {
24881        editor.newline(&Newline, window, cx);
24882    });
24883    cx.run_until_parked();
24884    cx.assert_editor_state(indoc! {"
24885        echo \"test\";
24886        ˇ
24887    "});
24888}
24889
24890fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24891    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24892    point..point
24893}
24894
24895#[track_caller]
24896fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24897    let (text, ranges) = marked_text_ranges(marked_text, true);
24898    assert_eq!(editor.text(cx), text);
24899    assert_eq!(
24900        editor.selections.ranges(cx),
24901        ranges,
24902        "Assert selections are {}",
24903        marked_text
24904    );
24905}
24906
24907pub fn handle_signature_help_request(
24908    cx: &mut EditorLspTestContext,
24909    mocked_response: lsp::SignatureHelp,
24910) -> impl Future<Output = ()> + use<> {
24911    let mut request =
24912        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
24913            let mocked_response = mocked_response.clone();
24914            async move { Ok(Some(mocked_response)) }
24915        });
24916
24917    async move {
24918        request.next().await;
24919    }
24920}
24921
24922#[track_caller]
24923pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
24924    cx.update_editor(|editor, _, _| {
24925        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
24926            let entries = menu.entries.borrow();
24927            let entries = entries
24928                .iter()
24929                .map(|entry| entry.string.as_str())
24930                .collect::<Vec<_>>();
24931            assert_eq!(entries, expected);
24932        } else {
24933            panic!("Expected completions menu");
24934        }
24935    });
24936}
24937
24938/// Handle completion request passing a marked string specifying where the completion
24939/// should be triggered from using '|' character, what range should be replaced, and what completions
24940/// should be returned using '<' and '>' to delimit the range.
24941///
24942/// Also see `handle_completion_request_with_insert_and_replace`.
24943#[track_caller]
24944pub fn handle_completion_request(
24945    marked_string: &str,
24946    completions: Vec<&'static str>,
24947    is_incomplete: bool,
24948    counter: Arc<AtomicUsize>,
24949    cx: &mut EditorLspTestContext,
24950) -> impl Future<Output = ()> {
24951    let complete_from_marker: TextRangeMarker = '|'.into();
24952    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24953    let (_, mut marked_ranges) = marked_text_ranges_by(
24954        marked_string,
24955        vec![complete_from_marker.clone(), replace_range_marker.clone()],
24956    );
24957
24958    let complete_from_position =
24959        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24960    let replace_range =
24961        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24962
24963    let mut request =
24964        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24965            let completions = completions.clone();
24966            counter.fetch_add(1, atomic::Ordering::Release);
24967            async move {
24968                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24969                assert_eq!(
24970                    params.text_document_position.position,
24971                    complete_from_position
24972                );
24973                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
24974                    is_incomplete,
24975                    item_defaults: None,
24976                    items: completions
24977                        .iter()
24978                        .map(|completion_text| lsp::CompletionItem {
24979                            label: completion_text.to_string(),
24980                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
24981                                range: replace_range,
24982                                new_text: completion_text.to_string(),
24983                            })),
24984                            ..Default::default()
24985                        })
24986                        .collect(),
24987                })))
24988            }
24989        });
24990
24991    async move {
24992        request.next().await;
24993    }
24994}
24995
24996/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
24997/// given instead, which also contains an `insert` range.
24998///
24999/// This function uses markers to define ranges:
25000/// - `|` marks the cursor position
25001/// - `<>` marks the replace range
25002/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25003pub fn handle_completion_request_with_insert_and_replace(
25004    cx: &mut EditorLspTestContext,
25005    marked_string: &str,
25006    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25007    counter: Arc<AtomicUsize>,
25008) -> impl Future<Output = ()> {
25009    let complete_from_marker: TextRangeMarker = '|'.into();
25010    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25011    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25012
25013    let (_, mut marked_ranges) = marked_text_ranges_by(
25014        marked_string,
25015        vec![
25016            complete_from_marker.clone(),
25017            replace_range_marker.clone(),
25018            insert_range_marker.clone(),
25019        ],
25020    );
25021
25022    let complete_from_position =
25023        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25024    let replace_range =
25025        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25026
25027    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25028        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25029        _ => lsp::Range {
25030            start: replace_range.start,
25031            end: complete_from_position,
25032        },
25033    };
25034
25035    let mut request =
25036        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25037            let completions = completions.clone();
25038            counter.fetch_add(1, atomic::Ordering::Release);
25039            async move {
25040                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25041                assert_eq!(
25042                    params.text_document_position.position, complete_from_position,
25043                    "marker `|` position doesn't match",
25044                );
25045                Ok(Some(lsp::CompletionResponse::Array(
25046                    completions
25047                        .iter()
25048                        .map(|(label, new_text)| lsp::CompletionItem {
25049                            label: label.to_string(),
25050                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25051                                lsp::InsertReplaceEdit {
25052                                    insert: insert_range,
25053                                    replace: replace_range,
25054                                    new_text: new_text.to_string(),
25055                                },
25056                            )),
25057                            ..Default::default()
25058                        })
25059                        .collect(),
25060                )))
25061            }
25062        });
25063
25064    async move {
25065        request.next().await;
25066    }
25067}
25068
25069fn handle_resolve_completion_request(
25070    cx: &mut EditorLspTestContext,
25071    edits: Option<Vec<(&'static str, &'static str)>>,
25072) -> impl Future<Output = ()> {
25073    let edits = edits.map(|edits| {
25074        edits
25075            .iter()
25076            .map(|(marked_string, new_text)| {
25077                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25078                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25079                lsp::TextEdit::new(replace_range, new_text.to_string())
25080            })
25081            .collect::<Vec<_>>()
25082    });
25083
25084    let mut request =
25085        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25086            let edits = edits.clone();
25087            async move {
25088                Ok(lsp::CompletionItem {
25089                    additional_text_edits: edits,
25090                    ..Default::default()
25091                })
25092            }
25093        });
25094
25095    async move {
25096        request.next().await;
25097    }
25098}
25099
25100pub(crate) fn update_test_language_settings(
25101    cx: &mut TestAppContext,
25102    f: impl Fn(&mut AllLanguageSettingsContent),
25103) {
25104    cx.update(|cx| {
25105        SettingsStore::update_global(cx, |store, cx| {
25106            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25107        });
25108    });
25109}
25110
25111pub(crate) fn update_test_project_settings(
25112    cx: &mut TestAppContext,
25113    f: impl Fn(&mut ProjectSettingsContent),
25114) {
25115    cx.update(|cx| {
25116        SettingsStore::update_global(cx, |store, cx| {
25117            store.update_user_settings(cx, |settings| f(&mut settings.project));
25118        });
25119    });
25120}
25121
25122pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25123    cx.update(|cx| {
25124        assets::Assets.load_test_fonts(cx);
25125        let store = SettingsStore::test(cx);
25126        cx.set_global(store);
25127        theme::init(theme::LoadThemes::JustBase, cx);
25128        release_channel::init(SemanticVersion::default(), cx);
25129        client::init_settings(cx);
25130        language::init(cx);
25131        Project::init_settings(cx);
25132        workspace::init_settings(cx);
25133        crate::init(cx);
25134    });
25135    zlog::init_test();
25136    update_test_language_settings(cx, f);
25137}
25138
25139#[track_caller]
25140fn assert_hunk_revert(
25141    not_reverted_text_with_selections: &str,
25142    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25143    expected_reverted_text_with_selections: &str,
25144    base_text: &str,
25145    cx: &mut EditorLspTestContext,
25146) {
25147    cx.set_state(not_reverted_text_with_selections);
25148    cx.set_head_text(base_text);
25149    cx.executor().run_until_parked();
25150
25151    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25152        let snapshot = editor.snapshot(window, cx);
25153        let reverted_hunk_statuses = snapshot
25154            .buffer_snapshot
25155            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25156            .map(|hunk| hunk.status().kind)
25157            .collect::<Vec<_>>();
25158
25159        editor.git_restore(&Default::default(), window, cx);
25160        reverted_hunk_statuses
25161    });
25162    cx.executor().run_until_parked();
25163    cx.assert_editor_state(expected_reverted_text_with_selections);
25164    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25165}
25166
25167#[gpui::test(iterations = 10)]
25168async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25169    init_test(cx, |_| {});
25170
25171    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25172    let counter = diagnostic_requests.clone();
25173
25174    let fs = FakeFs::new(cx.executor());
25175    fs.insert_tree(
25176        path!("/a"),
25177        json!({
25178            "first.rs": "fn main() { let a = 5; }",
25179            "second.rs": "// Test file",
25180        }),
25181    )
25182    .await;
25183
25184    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25185    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25186    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25187
25188    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25189    language_registry.add(rust_lang());
25190    let mut fake_servers = language_registry.register_fake_lsp(
25191        "Rust",
25192        FakeLspAdapter {
25193            capabilities: lsp::ServerCapabilities {
25194                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25195                    lsp::DiagnosticOptions {
25196                        identifier: None,
25197                        inter_file_dependencies: true,
25198                        workspace_diagnostics: true,
25199                        work_done_progress_options: Default::default(),
25200                    },
25201                )),
25202                ..Default::default()
25203            },
25204            ..Default::default()
25205        },
25206    );
25207
25208    let editor = workspace
25209        .update(cx, |workspace, window, cx| {
25210            workspace.open_abs_path(
25211                PathBuf::from(path!("/a/first.rs")),
25212                OpenOptions::default(),
25213                window,
25214                cx,
25215            )
25216        })
25217        .unwrap()
25218        .await
25219        .unwrap()
25220        .downcast::<Editor>()
25221        .unwrap();
25222    let fake_server = fake_servers.next().await.unwrap();
25223    let server_id = fake_server.server.server_id();
25224    let mut first_request = fake_server
25225        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25226            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25227            let result_id = Some(new_result_id.to_string());
25228            assert_eq!(
25229                params.text_document.uri,
25230                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25231            );
25232            async move {
25233                Ok(lsp::DocumentDiagnosticReportResult::Report(
25234                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25235                        related_documents: None,
25236                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25237                            items: Vec::new(),
25238                            result_id,
25239                        },
25240                    }),
25241                ))
25242            }
25243        });
25244
25245    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25246        project.update(cx, |project, cx| {
25247            let buffer_id = editor
25248                .read(cx)
25249                .buffer()
25250                .read(cx)
25251                .as_singleton()
25252                .expect("created a singleton buffer")
25253                .read(cx)
25254                .remote_id();
25255            let buffer_result_id = project
25256                .lsp_store()
25257                .read(cx)
25258                .result_id(server_id, buffer_id, cx);
25259            assert_eq!(expected, buffer_result_id);
25260        });
25261    };
25262
25263    ensure_result_id(None, cx);
25264    cx.executor().advance_clock(Duration::from_millis(60));
25265    cx.executor().run_until_parked();
25266    assert_eq!(
25267        diagnostic_requests.load(atomic::Ordering::Acquire),
25268        1,
25269        "Opening file should trigger diagnostic request"
25270    );
25271    first_request
25272        .next()
25273        .await
25274        .expect("should have sent the first diagnostics pull request");
25275    ensure_result_id(Some("1".to_string()), cx);
25276
25277    // Editing should trigger diagnostics
25278    editor.update_in(cx, |editor, window, cx| {
25279        editor.handle_input("2", window, cx)
25280    });
25281    cx.executor().advance_clock(Duration::from_millis(60));
25282    cx.executor().run_until_parked();
25283    assert_eq!(
25284        diagnostic_requests.load(atomic::Ordering::Acquire),
25285        2,
25286        "Editing should trigger diagnostic request"
25287    );
25288    ensure_result_id(Some("2".to_string()), cx);
25289
25290    // Moving cursor should not trigger diagnostic request
25291    editor.update_in(cx, |editor, window, cx| {
25292        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25293            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25294        });
25295    });
25296    cx.executor().advance_clock(Duration::from_millis(60));
25297    cx.executor().run_until_parked();
25298    assert_eq!(
25299        diagnostic_requests.load(atomic::Ordering::Acquire),
25300        2,
25301        "Cursor movement should not trigger diagnostic request"
25302    );
25303    ensure_result_id(Some("2".to_string()), cx);
25304    // Multiple rapid edits should be debounced
25305    for _ in 0..5 {
25306        editor.update_in(cx, |editor, window, cx| {
25307            editor.handle_input("x", window, cx)
25308        });
25309    }
25310    cx.executor().advance_clock(Duration::from_millis(60));
25311    cx.executor().run_until_parked();
25312
25313    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25314    assert!(
25315        final_requests <= 4,
25316        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25317    );
25318    ensure_result_id(Some(final_requests.to_string()), cx);
25319}
25320
25321#[gpui::test]
25322async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25323    // Regression test for issue #11671
25324    // Previously, adding a cursor after moving multiple cursors would reset
25325    // the cursor count instead of adding to the existing cursors.
25326    init_test(cx, |_| {});
25327    let mut cx = EditorTestContext::new(cx).await;
25328
25329    // Create a simple buffer with cursor at start
25330    cx.set_state(indoc! {"
25331        ˇaaaa
25332        bbbb
25333        cccc
25334        dddd
25335        eeee
25336        ffff
25337        gggg
25338        hhhh"});
25339
25340    // Add 2 cursors below (so we have 3 total)
25341    cx.update_editor(|editor, window, cx| {
25342        editor.add_selection_below(&Default::default(), window, cx);
25343        editor.add_selection_below(&Default::default(), window, cx);
25344    });
25345
25346    // Verify we have 3 cursors
25347    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25348    assert_eq!(
25349        initial_count, 3,
25350        "Should have 3 cursors after adding 2 below"
25351    );
25352
25353    // Move down one line
25354    cx.update_editor(|editor, window, cx| {
25355        editor.move_down(&MoveDown, window, cx);
25356    });
25357
25358    // Add another cursor below
25359    cx.update_editor(|editor, window, cx| {
25360        editor.add_selection_below(&Default::default(), window, cx);
25361    });
25362
25363    // Should now have 4 cursors (3 original + 1 new)
25364    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25365    assert_eq!(
25366        final_count, 4,
25367        "Should have 4 cursors after moving and adding another"
25368    );
25369}
25370
25371#[gpui::test(iterations = 10)]
25372async fn test_document_colors(cx: &mut TestAppContext) {
25373    let expected_color = Rgba {
25374        r: 0.33,
25375        g: 0.33,
25376        b: 0.33,
25377        a: 0.33,
25378    };
25379
25380    init_test(cx, |_| {});
25381
25382    let fs = FakeFs::new(cx.executor());
25383    fs.insert_tree(
25384        path!("/a"),
25385        json!({
25386            "first.rs": "fn main() { let a = 5; }",
25387        }),
25388    )
25389    .await;
25390
25391    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25392    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25393    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25394
25395    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25396    language_registry.add(rust_lang());
25397    let mut fake_servers = language_registry.register_fake_lsp(
25398        "Rust",
25399        FakeLspAdapter {
25400            capabilities: lsp::ServerCapabilities {
25401                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25402                ..lsp::ServerCapabilities::default()
25403            },
25404            name: "rust-analyzer",
25405            ..FakeLspAdapter::default()
25406        },
25407    );
25408    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25409        "Rust",
25410        FakeLspAdapter {
25411            capabilities: lsp::ServerCapabilities {
25412                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25413                ..lsp::ServerCapabilities::default()
25414            },
25415            name: "not-rust-analyzer",
25416            ..FakeLspAdapter::default()
25417        },
25418    );
25419
25420    let editor = workspace
25421        .update(cx, |workspace, window, cx| {
25422            workspace.open_abs_path(
25423                PathBuf::from(path!("/a/first.rs")),
25424                OpenOptions::default(),
25425                window,
25426                cx,
25427            )
25428        })
25429        .unwrap()
25430        .await
25431        .unwrap()
25432        .downcast::<Editor>()
25433        .unwrap();
25434    let fake_language_server = fake_servers.next().await.unwrap();
25435    let fake_language_server_without_capabilities =
25436        fake_servers_without_capabilities.next().await.unwrap();
25437    let requests_made = Arc::new(AtomicUsize::new(0));
25438    let closure_requests_made = Arc::clone(&requests_made);
25439    let mut color_request_handle = fake_language_server
25440        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25441            let requests_made = Arc::clone(&closure_requests_made);
25442            async move {
25443                assert_eq!(
25444                    params.text_document.uri,
25445                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25446                );
25447                requests_made.fetch_add(1, atomic::Ordering::Release);
25448                Ok(vec![
25449                    lsp::ColorInformation {
25450                        range: lsp::Range {
25451                            start: lsp::Position {
25452                                line: 0,
25453                                character: 0,
25454                            },
25455                            end: lsp::Position {
25456                                line: 0,
25457                                character: 1,
25458                            },
25459                        },
25460                        color: lsp::Color {
25461                            red: 0.33,
25462                            green: 0.33,
25463                            blue: 0.33,
25464                            alpha: 0.33,
25465                        },
25466                    },
25467                    lsp::ColorInformation {
25468                        range: lsp::Range {
25469                            start: lsp::Position {
25470                                line: 0,
25471                                character: 0,
25472                            },
25473                            end: lsp::Position {
25474                                line: 0,
25475                                character: 1,
25476                            },
25477                        },
25478                        color: lsp::Color {
25479                            red: 0.33,
25480                            green: 0.33,
25481                            blue: 0.33,
25482                            alpha: 0.33,
25483                        },
25484                    },
25485                ])
25486            }
25487        });
25488
25489    let _handle = fake_language_server_without_capabilities
25490        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25491            panic!("Should not be called");
25492        });
25493    cx.executor().advance_clock(Duration::from_millis(100));
25494    color_request_handle.next().await.unwrap();
25495    cx.run_until_parked();
25496    assert_eq!(
25497        1,
25498        requests_made.load(atomic::Ordering::Acquire),
25499        "Should query for colors once per editor open"
25500    );
25501    editor.update_in(cx, |editor, _, cx| {
25502        assert_eq!(
25503            vec![expected_color],
25504            extract_color_inlays(editor, cx),
25505            "Should have an initial inlay"
25506        );
25507    });
25508
25509    // opening another file in a split should not influence the LSP query counter
25510    workspace
25511        .update(cx, |workspace, window, cx| {
25512            assert_eq!(
25513                workspace.panes().len(),
25514                1,
25515                "Should have one pane with one editor"
25516            );
25517            workspace.move_item_to_pane_in_direction(
25518                &MoveItemToPaneInDirection {
25519                    direction: SplitDirection::Right,
25520                    focus: false,
25521                    clone: true,
25522                },
25523                window,
25524                cx,
25525            );
25526        })
25527        .unwrap();
25528    cx.run_until_parked();
25529    workspace
25530        .update(cx, |workspace, _, cx| {
25531            let panes = workspace.panes();
25532            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25533            for pane in panes {
25534                let editor = pane
25535                    .read(cx)
25536                    .active_item()
25537                    .and_then(|item| item.downcast::<Editor>())
25538                    .expect("Should have opened an editor in each split");
25539                let editor_file = editor
25540                    .read(cx)
25541                    .buffer()
25542                    .read(cx)
25543                    .as_singleton()
25544                    .expect("test deals with singleton buffers")
25545                    .read(cx)
25546                    .file()
25547                    .expect("test buffese should have a file")
25548                    .path();
25549                assert_eq!(
25550                    editor_file.as_ref(),
25551                    Path::new("first.rs"),
25552                    "Both editors should be opened for the same file"
25553                )
25554            }
25555        })
25556        .unwrap();
25557
25558    cx.executor().advance_clock(Duration::from_millis(500));
25559    let save = editor.update_in(cx, |editor, window, cx| {
25560        editor.move_to_end(&MoveToEnd, window, cx);
25561        editor.handle_input("dirty", window, cx);
25562        editor.save(
25563            SaveOptions {
25564                format: true,
25565                autosave: true,
25566            },
25567            project.clone(),
25568            window,
25569            cx,
25570        )
25571    });
25572    save.await.unwrap();
25573
25574    color_request_handle.next().await.unwrap();
25575    cx.run_until_parked();
25576    assert_eq!(
25577        3,
25578        requests_made.load(atomic::Ordering::Acquire),
25579        "Should query for colors once per save and once per formatting after save"
25580    );
25581
25582    drop(editor);
25583    let close = workspace
25584        .update(cx, |workspace, window, cx| {
25585            workspace.active_pane().update(cx, |pane, cx| {
25586                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25587            })
25588        })
25589        .unwrap();
25590    close.await.unwrap();
25591    let close = workspace
25592        .update(cx, |workspace, window, cx| {
25593            workspace.active_pane().update(cx, |pane, cx| {
25594                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25595            })
25596        })
25597        .unwrap();
25598    close.await.unwrap();
25599    assert_eq!(
25600        3,
25601        requests_made.load(atomic::Ordering::Acquire),
25602        "After saving and closing all editors, no extra requests should be made"
25603    );
25604    workspace
25605        .update(cx, |workspace, _, cx| {
25606            assert!(
25607                workspace.active_item(cx).is_none(),
25608                "Should close all editors"
25609            )
25610        })
25611        .unwrap();
25612
25613    workspace
25614        .update(cx, |workspace, window, cx| {
25615            workspace.active_pane().update(cx, |pane, cx| {
25616                pane.navigate_backward(&Default::default(), window, cx);
25617            })
25618        })
25619        .unwrap();
25620    cx.executor().advance_clock(Duration::from_millis(100));
25621    cx.run_until_parked();
25622    let editor = workspace
25623        .update(cx, |workspace, _, cx| {
25624            workspace
25625                .active_item(cx)
25626                .expect("Should have reopened the editor again after navigating back")
25627                .downcast::<Editor>()
25628                .expect("Should be an editor")
25629        })
25630        .unwrap();
25631    color_request_handle.next().await.unwrap();
25632    assert_eq!(
25633        3,
25634        requests_made.load(atomic::Ordering::Acquire),
25635        "Cache should be reused on buffer close and reopen"
25636    );
25637    editor.update(cx, |editor, cx| {
25638        assert_eq!(
25639            vec![expected_color],
25640            extract_color_inlays(editor, cx),
25641            "Should have an initial inlay"
25642        );
25643    });
25644}
25645
25646#[gpui::test]
25647async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25648    init_test(cx, |_| {});
25649    let (editor, cx) = cx.add_window_view(Editor::single_line);
25650    editor.update_in(cx, |editor, window, cx| {
25651        editor.set_text("oops\n\nwow\n", window, cx)
25652    });
25653    cx.run_until_parked();
25654    editor.update(cx, |editor, cx| {
25655        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25656    });
25657    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25658    cx.run_until_parked();
25659    editor.update(cx, |editor, cx| {
25660        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25661    });
25662}
25663
25664#[gpui::test]
25665async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25666    init_test(cx, |_| {});
25667
25668    cx.update(|cx| {
25669        register_project_item::<Editor>(cx);
25670    });
25671
25672    let fs = FakeFs::new(cx.executor());
25673    fs.insert_tree("/root1", json!({})).await;
25674    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25675        .await;
25676
25677    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25678    let (workspace, cx) =
25679        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25680
25681    let worktree_id = project.update(cx, |project, cx| {
25682        project.worktrees(cx).next().unwrap().read(cx).id()
25683    });
25684
25685    let handle = workspace
25686        .update_in(cx, |workspace, window, cx| {
25687            let project_path = (worktree_id, "one.pdf");
25688            workspace.open_path(project_path, None, true, window, cx)
25689        })
25690        .await
25691        .unwrap();
25692
25693    assert_eq!(
25694        handle.to_any().entity_type(),
25695        TypeId::of::<InvalidBufferView>()
25696    );
25697}
25698
25699#[gpui::test]
25700async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25701    init_test(cx, |_| {});
25702
25703    let language = Arc::new(Language::new(
25704        LanguageConfig::default(),
25705        Some(tree_sitter_rust::LANGUAGE.into()),
25706    ));
25707
25708    // Test hierarchical sibling navigation
25709    let text = r#"
25710        fn outer() {
25711            if condition {
25712                let a = 1;
25713            }
25714            let b = 2;
25715        }
25716
25717        fn another() {
25718            let c = 3;
25719        }
25720    "#;
25721
25722    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25723    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25724    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25725
25726    // Wait for parsing to complete
25727    editor
25728        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25729        .await;
25730
25731    editor.update_in(cx, |editor, window, cx| {
25732        // Start by selecting "let a = 1;" inside the if block
25733        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25734            s.select_display_ranges([
25735                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25736            ]);
25737        });
25738
25739        let initial_selection = editor.selections.display_ranges(cx);
25740        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25741
25742        // Test select next sibling - should move up levels to find the next sibling
25743        // Since "let a = 1;" has no siblings in the if block, it should move up
25744        // to find "let b = 2;" which is a sibling of the if block
25745        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25746        let next_selection = editor.selections.display_ranges(cx);
25747
25748        // Should have a selection and it should be different from the initial
25749        assert_eq!(
25750            next_selection.len(),
25751            1,
25752            "Should have one selection after next"
25753        );
25754        assert_ne!(
25755            next_selection[0], initial_selection[0],
25756            "Next sibling selection should be different"
25757        );
25758
25759        // Test hierarchical navigation by going to the end of the current function
25760        // and trying to navigate to the next function
25761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25762            s.select_display_ranges([
25763                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25764            ]);
25765        });
25766
25767        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25768        let function_next_selection = editor.selections.display_ranges(cx);
25769
25770        // Should move to the next function
25771        assert_eq!(
25772            function_next_selection.len(),
25773            1,
25774            "Should have one selection after function next"
25775        );
25776
25777        // Test select previous sibling navigation
25778        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25779        let prev_selection = editor.selections.display_ranges(cx);
25780
25781        // Should have a selection and it should be different
25782        assert_eq!(
25783            prev_selection.len(),
25784            1,
25785            "Should have one selection after prev"
25786        );
25787        assert_ne!(
25788            prev_selection[0], function_next_selection[0],
25789            "Previous sibling selection should be different from next"
25790        );
25791    });
25792}
25793
25794#[gpui::test]
25795async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
25796    init_test(cx, |_| {});
25797
25798    let mut cx = EditorTestContext::new(cx).await;
25799    cx.set_state(
25800        "let ˇvariable = 42;
25801let another = variable + 1;
25802let result = variable * 2;",
25803    );
25804
25805    // Set up document highlights manually (simulating LSP response)
25806    cx.update_editor(|editor, _window, cx| {
25807        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25808
25809        // Create highlights for "variable" occurrences
25810        let highlight_ranges = [
25811            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
25812            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
25813            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
25814        ];
25815
25816        let anchor_ranges: Vec<_> = highlight_ranges
25817            .iter()
25818            .map(|range| range.clone().to_anchors(&buffer_snapshot))
25819            .collect();
25820
25821        editor.highlight_background::<DocumentHighlightRead>(
25822            &anchor_ranges,
25823            |theme| theme.colors().editor_document_highlight_read_background,
25824            cx,
25825        );
25826    });
25827
25828    // Go to next highlight - should move to second "variable"
25829    cx.update_editor(|editor, window, cx| {
25830        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25831    });
25832    cx.assert_editor_state(
25833        "let variable = 42;
25834let another = ˇvariable + 1;
25835let result = variable * 2;",
25836    );
25837
25838    // Go to next highlight - should move to third "variable"
25839    cx.update_editor(|editor, window, cx| {
25840        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25841    });
25842    cx.assert_editor_state(
25843        "let variable = 42;
25844let another = variable + 1;
25845let result = ˇvariable * 2;",
25846    );
25847
25848    // Go to next highlight - should stay at third "variable" (no wrap-around)
25849    cx.update_editor(|editor, window, cx| {
25850        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25851    });
25852    cx.assert_editor_state(
25853        "let variable = 42;
25854let another = variable + 1;
25855let result = ˇvariable * 2;",
25856    );
25857
25858    // Now test going backwards from third position
25859    cx.update_editor(|editor, window, cx| {
25860        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25861    });
25862    cx.assert_editor_state(
25863        "let variable = 42;
25864let another = ˇvariable + 1;
25865let result = variable * 2;",
25866    );
25867
25868    // Go to previous highlight - should move to first "variable"
25869    cx.update_editor(|editor, window, cx| {
25870        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25871    });
25872    cx.assert_editor_state(
25873        "let ˇvariable = 42;
25874let another = variable + 1;
25875let result = variable * 2;",
25876    );
25877
25878    // Go to previous highlight - should stay on first "variable"
25879    cx.update_editor(|editor, window, cx| {
25880        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25881    });
25882    cx.assert_editor_state(
25883        "let ˇvariable = 42;
25884let another = variable + 1;
25885let result = variable * 2;",
25886    );
25887}
25888
25889#[track_caller]
25890fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
25891    editor
25892        .all_inlays(cx)
25893        .into_iter()
25894        .filter_map(|inlay| inlay.get_color())
25895        .map(Rgba::from)
25896        .collect()
25897}