editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    linked_editing_ranges::LinkedEditingRanges,
    7    scroll::scroll_amount::ScrollAmount,
    8    test::{
    9        assert_text_with_selections, build_editor,
   10        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   11        editor_test_context::EditorTestContext,
   12        select_ranges,
   13    },
   14};
   15use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   16use futures::StreamExt;
   17use gpui::{
   18    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
   19    VisualTestContext, WindowBounds, WindowOptions, div,
   20};
   21use indoc::indoc;
   22use language::{
   23    BracketPairConfig,
   24    Capability::ReadWrite,
   25    DiagnosticSourceKind, FakeLspAdapter, LanguageConfig, LanguageConfigOverride, LanguageMatcher,
   26    LanguageName, Override, Point,
   27    language_settings::{
   28        CompletionSettings, FormatterList, LanguageSettingsContent, LspInsertMode,
   29        SelectedFormatter,
   30    },
   31    tree_sitter_python,
   32};
   33use language_settings::{Formatter, IndentGuideSettings};
   34use lsp::CompletionParams;
   35use multi_buffer::{IndentGuide, PathKey};
   36use parking_lot::Mutex;
   37use pretty_assertions::{assert_eq, assert_ne};
   38use project::{
   39    FakeFs,
   40    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   41    project_settings::LspSettings,
   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 = 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 = 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(CompletionSettings {
13345                    lsp_insert_mode,
13346                    words: WordsCompletionMode::Disabled,
13347                    words_min_length: 0,
13348                    lsp: true,
13349                    lsp_fetch_timeout_ms: 0,
13350                });
13351            });
13352
13353            cx.set_state(&run.initial_state);
13354            cx.update_editor(|editor, window, cx| {
13355                editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13356            });
13357
13358            let counter = Arc::new(AtomicUsize::new(0));
13359            handle_completion_request_with_insert_and_replace(
13360                &mut cx,
13361                &run.buffer_marked_text,
13362                vec![(run.completion_label, run.completion_text)],
13363                counter.clone(),
13364            )
13365            .await;
13366            cx.condition(|editor, _| editor.context_menu_visible())
13367                .await;
13368            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13369
13370            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13371                editor
13372                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
13373                    .unwrap()
13374            });
13375            cx.assert_editor_state(&expected_text);
13376            handle_resolve_completion_request(&mut cx, None).await;
13377            apply_additional_edits.await.unwrap();
13378        }
13379    }
13380}
13381
13382#[gpui::test]
13383async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
13384    init_test(cx, |_| {});
13385    let mut cx = EditorLspTestContext::new_rust(
13386        lsp::ServerCapabilities {
13387            completion_provider: Some(lsp::CompletionOptions {
13388                resolve_provider: Some(true),
13389                ..Default::default()
13390            }),
13391            ..Default::default()
13392        },
13393        cx,
13394    )
13395    .await;
13396
13397    let initial_state = "SubˇError";
13398    let buffer_marked_text = "<Sub|Error>";
13399    let completion_text = "SubscriptionError";
13400    let expected_with_insert_mode = "SubscriptionErrorˇError";
13401    let expected_with_replace_mode = "SubscriptionErrorˇ";
13402
13403    update_test_language_settings(&mut cx, |settings| {
13404        settings.defaults.completions = Some(CompletionSettings {
13405            words: WordsCompletionMode::Disabled,
13406            words_min_length: 0,
13407            // set the opposite here to ensure that the action is overriding the default behavior
13408            lsp_insert_mode: LspInsertMode::Insert,
13409            lsp: true,
13410            lsp_fetch_timeout_ms: 0,
13411        });
13412    });
13413
13414    cx.set_state(initial_state);
13415    cx.update_editor(|editor, window, cx| {
13416        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13417    });
13418
13419    let counter = Arc::new(AtomicUsize::new(0));
13420    handle_completion_request_with_insert_and_replace(
13421        &mut cx,
13422        buffer_marked_text,
13423        vec![(completion_text, completion_text)],
13424        counter.clone(),
13425    )
13426    .await;
13427    cx.condition(|editor, _| editor.context_menu_visible())
13428        .await;
13429    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13430
13431    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13432        editor
13433            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13434            .unwrap()
13435    });
13436    cx.assert_editor_state(expected_with_replace_mode);
13437    handle_resolve_completion_request(&mut cx, None).await;
13438    apply_additional_edits.await.unwrap();
13439
13440    update_test_language_settings(&mut cx, |settings| {
13441        settings.defaults.completions = Some(CompletionSettings {
13442            words: WordsCompletionMode::Disabled,
13443            words_min_length: 0,
13444            // set the opposite here to ensure that the action is overriding the default behavior
13445            lsp_insert_mode: LspInsertMode::Replace,
13446            lsp: true,
13447            lsp_fetch_timeout_ms: 0,
13448        });
13449    });
13450
13451    cx.set_state(initial_state);
13452    cx.update_editor(|editor, window, cx| {
13453        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13454    });
13455    handle_completion_request_with_insert_and_replace(
13456        &mut cx,
13457        buffer_marked_text,
13458        vec![(completion_text, completion_text)],
13459        counter.clone(),
13460    )
13461    .await;
13462    cx.condition(|editor, _| editor.context_menu_visible())
13463        .await;
13464    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13465
13466    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13467        editor
13468            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
13469            .unwrap()
13470    });
13471    cx.assert_editor_state(expected_with_insert_mode);
13472    handle_resolve_completion_request(&mut cx, None).await;
13473    apply_additional_edits.await.unwrap();
13474}
13475
13476#[gpui::test]
13477async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
13478    init_test(cx, |_| {});
13479    let mut cx = EditorLspTestContext::new_rust(
13480        lsp::ServerCapabilities {
13481            completion_provider: Some(lsp::CompletionOptions {
13482                resolve_provider: Some(true),
13483                ..Default::default()
13484            }),
13485            ..Default::default()
13486        },
13487        cx,
13488    )
13489    .await;
13490
13491    // scenario: surrounding text matches completion text
13492    let completion_text = "to_offset";
13493    let initial_state = indoc! {"
13494        1. buf.to_offˇsuffix
13495        2. buf.to_offˇsuf
13496        3. buf.to_offˇfix
13497        4. buf.to_offˇ
13498        5. into_offˇensive
13499        6. ˇsuffix
13500        7. let ˇ //
13501        8. aaˇzz
13502        9. buf.to_off«zzzzzˇ»suffix
13503        10. buf.«ˇzzzzz»suffix
13504        11. to_off«ˇzzzzz»
13505
13506        buf.to_offˇsuffix  // newest cursor
13507    "};
13508    let completion_marked_buffer = indoc! {"
13509        1. buf.to_offsuffix
13510        2. buf.to_offsuf
13511        3. buf.to_offfix
13512        4. buf.to_off
13513        5. into_offensive
13514        6. suffix
13515        7. let  //
13516        8. aazz
13517        9. buf.to_offzzzzzsuffix
13518        10. buf.zzzzzsuffix
13519        11. to_offzzzzz
13520
13521        buf.<to_off|suffix>  // newest cursor
13522    "};
13523    let expected = indoc! {"
13524        1. buf.to_offsetˇ
13525        2. buf.to_offsetˇsuf
13526        3. buf.to_offsetˇfix
13527        4. buf.to_offsetˇ
13528        5. into_offsetˇensive
13529        6. to_offsetˇsuffix
13530        7. let to_offsetˇ //
13531        8. aato_offsetˇzz
13532        9. buf.to_offsetˇ
13533        10. buf.to_offsetˇsuffix
13534        11. to_offsetˇ
13535
13536        buf.to_offsetˇ  // newest cursor
13537    "};
13538    cx.set_state(initial_state);
13539    cx.update_editor(|editor, window, cx| {
13540        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13541    });
13542    handle_completion_request_with_insert_and_replace(
13543        &mut cx,
13544        completion_marked_buffer,
13545        vec![(completion_text, completion_text)],
13546        Arc::new(AtomicUsize::new(0)),
13547    )
13548    .await;
13549    cx.condition(|editor, _| editor.context_menu_visible())
13550        .await;
13551    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13552        editor
13553            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13554            .unwrap()
13555    });
13556    cx.assert_editor_state(expected);
13557    handle_resolve_completion_request(&mut cx, None).await;
13558    apply_additional_edits.await.unwrap();
13559
13560    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
13561    let completion_text = "foo_and_bar";
13562    let initial_state = indoc! {"
13563        1. ooanbˇ
13564        2. zooanbˇ
13565        3. ooanbˇz
13566        4. zooanbˇz
13567        5. ooanˇ
13568        6. oanbˇ
13569
13570        ooanbˇ
13571    "};
13572    let completion_marked_buffer = indoc! {"
13573        1. ooanb
13574        2. zooanb
13575        3. ooanbz
13576        4. zooanbz
13577        5. ooan
13578        6. oanb
13579
13580        <ooanb|>
13581    "};
13582    let expected = indoc! {"
13583        1. foo_and_barˇ
13584        2. zfoo_and_barˇ
13585        3. foo_and_barˇz
13586        4. zfoo_and_barˇz
13587        5. ooanfoo_and_barˇ
13588        6. oanbfoo_and_barˇ
13589
13590        foo_and_barˇ
13591    "};
13592    cx.set_state(initial_state);
13593    cx.update_editor(|editor, window, cx| {
13594        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13595    });
13596    handle_completion_request_with_insert_and_replace(
13597        &mut cx,
13598        completion_marked_buffer,
13599        vec![(completion_text, completion_text)],
13600        Arc::new(AtomicUsize::new(0)),
13601    )
13602    .await;
13603    cx.condition(|editor, _| editor.context_menu_visible())
13604        .await;
13605    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13606        editor
13607            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13608            .unwrap()
13609    });
13610    cx.assert_editor_state(expected);
13611    handle_resolve_completion_request(&mut cx, None).await;
13612    apply_additional_edits.await.unwrap();
13613
13614    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
13615    // (expects the same as if it was inserted at the end)
13616    let completion_text = "foo_and_bar";
13617    let initial_state = indoc! {"
13618        1. ooˇanb
13619        2. zooˇanb
13620        3. ooˇanbz
13621        4. zooˇanbz
13622
13623        ooˇanb
13624    "};
13625    let completion_marked_buffer = indoc! {"
13626        1. ooanb
13627        2. zooanb
13628        3. ooanbz
13629        4. zooanbz
13630
13631        <oo|anb>
13632    "};
13633    let expected = indoc! {"
13634        1. foo_and_barˇ
13635        2. zfoo_and_barˇ
13636        3. foo_and_barˇz
13637        4. zfoo_and_barˇz
13638
13639        foo_and_barˇ
13640    "};
13641    cx.set_state(initial_state);
13642    cx.update_editor(|editor, window, cx| {
13643        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13644    });
13645    handle_completion_request_with_insert_and_replace(
13646        &mut cx,
13647        completion_marked_buffer,
13648        vec![(completion_text, completion_text)],
13649        Arc::new(AtomicUsize::new(0)),
13650    )
13651    .await;
13652    cx.condition(|editor, _| editor.context_menu_visible())
13653        .await;
13654    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13655        editor
13656            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13657            .unwrap()
13658    });
13659    cx.assert_editor_state(expected);
13660    handle_resolve_completion_request(&mut cx, None).await;
13661    apply_additional_edits.await.unwrap();
13662}
13663
13664// This used to crash
13665#[gpui::test]
13666async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
13667    init_test(cx, |_| {});
13668
13669    let buffer_text = indoc! {"
13670        fn main() {
13671            10.satu;
13672
13673            //
13674            // separate cursors so they open in different excerpts (manually reproducible)
13675            //
13676
13677            10.satu20;
13678        }
13679    "};
13680    let multibuffer_text_with_selections = indoc! {"
13681        fn main() {
13682            10.satuˇ;
13683
13684            //
13685
13686            //
13687
13688            10.satuˇ20;
13689        }
13690    "};
13691    let expected_multibuffer = indoc! {"
13692        fn main() {
13693            10.saturating_sub()ˇ;
13694
13695            //
13696
13697            //
13698
13699            10.saturating_sub()ˇ;
13700        }
13701    "};
13702
13703    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
13704    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
13705
13706    let fs = FakeFs::new(cx.executor());
13707    fs.insert_tree(
13708        path!("/a"),
13709        json!({
13710            "main.rs": buffer_text,
13711        }),
13712    )
13713    .await;
13714
13715    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
13716    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
13717    language_registry.add(rust_lang());
13718    let mut fake_servers = language_registry.register_fake_lsp(
13719        "Rust",
13720        FakeLspAdapter {
13721            capabilities: lsp::ServerCapabilities {
13722                completion_provider: Some(lsp::CompletionOptions {
13723                    resolve_provider: None,
13724                    ..lsp::CompletionOptions::default()
13725                }),
13726                ..lsp::ServerCapabilities::default()
13727            },
13728            ..FakeLspAdapter::default()
13729        },
13730    );
13731    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
13732    let cx = &mut VisualTestContext::from_window(*workspace, cx);
13733    let buffer = project
13734        .update(cx, |project, cx| {
13735            project.open_local_buffer(path!("/a/main.rs"), cx)
13736        })
13737        .await
13738        .unwrap();
13739
13740    let multi_buffer = cx.new(|cx| {
13741        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
13742        multi_buffer.push_excerpts(
13743            buffer.clone(),
13744            [ExcerptRange::new(0..first_excerpt_end)],
13745            cx,
13746        );
13747        multi_buffer.push_excerpts(
13748            buffer.clone(),
13749            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
13750            cx,
13751        );
13752        multi_buffer
13753    });
13754
13755    let editor = workspace
13756        .update(cx, |_, window, cx| {
13757            cx.new(|cx| {
13758                Editor::new(
13759                    EditorMode::Full {
13760                        scale_ui_elements_with_buffer_font_size: false,
13761                        show_active_line_background: false,
13762                        sized_by_content: false,
13763                    },
13764                    multi_buffer.clone(),
13765                    Some(project.clone()),
13766                    window,
13767                    cx,
13768                )
13769            })
13770        })
13771        .unwrap();
13772
13773    let pane = workspace
13774        .update(cx, |workspace, _, _| workspace.active_pane().clone())
13775        .unwrap();
13776    pane.update_in(cx, |pane, window, cx| {
13777        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
13778    });
13779
13780    let fake_server = fake_servers.next().await.unwrap();
13781
13782    editor.update_in(cx, |editor, window, cx| {
13783        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13784            s.select_ranges([
13785                Point::new(1, 11)..Point::new(1, 11),
13786                Point::new(7, 11)..Point::new(7, 11),
13787            ])
13788        });
13789
13790        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
13791    });
13792
13793    editor.update_in(cx, |editor, window, cx| {
13794        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
13795    });
13796
13797    fake_server
13798        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
13799            let completion_item = lsp::CompletionItem {
13800                label: "saturating_sub()".into(),
13801                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
13802                    lsp::InsertReplaceEdit {
13803                        new_text: "saturating_sub()".to_owned(),
13804                        insert: lsp::Range::new(
13805                            lsp::Position::new(7, 7),
13806                            lsp::Position::new(7, 11),
13807                        ),
13808                        replace: lsp::Range::new(
13809                            lsp::Position::new(7, 7),
13810                            lsp::Position::new(7, 13),
13811                        ),
13812                    },
13813                )),
13814                ..lsp::CompletionItem::default()
13815            };
13816
13817            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
13818        })
13819        .next()
13820        .await
13821        .unwrap();
13822
13823    cx.condition(&editor, |editor, _| editor.context_menu_visible())
13824        .await;
13825
13826    editor
13827        .update_in(cx, |editor, window, cx| {
13828            editor
13829                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
13830                .unwrap()
13831        })
13832        .await
13833        .unwrap();
13834
13835    editor.update(cx, |editor, cx| {
13836        assert_text_with_selections(editor, expected_multibuffer, cx);
13837    })
13838}
13839
13840#[gpui::test]
13841async fn test_completion(cx: &mut TestAppContext) {
13842    init_test(cx, |_| {});
13843
13844    let mut cx = EditorLspTestContext::new_rust(
13845        lsp::ServerCapabilities {
13846            completion_provider: Some(lsp::CompletionOptions {
13847                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
13848                resolve_provider: Some(true),
13849                ..Default::default()
13850            }),
13851            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
13852            ..Default::default()
13853        },
13854        cx,
13855    )
13856    .await;
13857    let counter = Arc::new(AtomicUsize::new(0));
13858
13859    cx.set_state(indoc! {"
13860        oneˇ
13861        two
13862        three
13863    "});
13864    cx.simulate_keystroke(".");
13865    handle_completion_request(
13866        indoc! {"
13867            one.|<>
13868            two
13869            three
13870        "},
13871        vec!["first_completion", "second_completion"],
13872        true,
13873        counter.clone(),
13874        &mut cx,
13875    )
13876    .await;
13877    cx.condition(|editor, _| editor.context_menu_visible())
13878        .await;
13879    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
13880
13881    let _handler = handle_signature_help_request(
13882        &mut cx,
13883        lsp::SignatureHelp {
13884            signatures: vec![lsp::SignatureInformation {
13885                label: "test signature".to_string(),
13886                documentation: None,
13887                parameters: Some(vec![lsp::ParameterInformation {
13888                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
13889                    documentation: None,
13890                }]),
13891                active_parameter: None,
13892            }],
13893            active_signature: None,
13894            active_parameter: None,
13895        },
13896    );
13897    cx.update_editor(|editor, window, cx| {
13898        assert!(
13899            !editor.signature_help_state.is_shown(),
13900            "No signature help was called for"
13901        );
13902        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13903    });
13904    cx.run_until_parked();
13905    cx.update_editor(|editor, _, _| {
13906        assert!(
13907            !editor.signature_help_state.is_shown(),
13908            "No signature help should be shown when completions menu is open"
13909        );
13910    });
13911
13912    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
13913        editor.context_menu_next(&Default::default(), window, cx);
13914        editor
13915            .confirm_completion(&ConfirmCompletion::default(), window, cx)
13916            .unwrap()
13917    });
13918    cx.assert_editor_state(indoc! {"
13919        one.second_completionˇ
13920        two
13921        three
13922    "});
13923
13924    handle_resolve_completion_request(
13925        &mut cx,
13926        Some(vec![
13927            (
13928                //This overlaps with the primary completion edit which is
13929                //misbehavior from the LSP spec, test that we filter it out
13930                indoc! {"
13931                    one.second_ˇcompletion
13932                    two
13933                    threeˇ
13934                "},
13935                "overlapping additional edit",
13936            ),
13937            (
13938                indoc! {"
13939                    one.second_completion
13940                    two
13941                    threeˇ
13942                "},
13943                "\nadditional edit",
13944            ),
13945        ]),
13946    )
13947    .await;
13948    apply_additional_edits.await.unwrap();
13949    cx.assert_editor_state(indoc! {"
13950        one.second_completionˇ
13951        two
13952        three
13953        additional edit
13954    "});
13955
13956    cx.set_state(indoc! {"
13957        one.second_completion
13958        twoˇ
13959        threeˇ
13960        additional edit
13961    "});
13962    cx.simulate_keystroke(" ");
13963    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13964    cx.simulate_keystroke("s");
13965    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
13966
13967    cx.assert_editor_state(indoc! {"
13968        one.second_completion
13969        two sˇ
13970        three sˇ
13971        additional edit
13972    "});
13973    handle_completion_request(
13974        indoc! {"
13975            one.second_completion
13976            two s
13977            three <s|>
13978            additional edit
13979        "},
13980        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
13981        true,
13982        counter.clone(),
13983        &mut cx,
13984    )
13985    .await;
13986    cx.condition(|editor, _| editor.context_menu_visible())
13987        .await;
13988    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
13989
13990    cx.simulate_keystroke("i");
13991
13992    handle_completion_request(
13993        indoc! {"
13994            one.second_completion
13995            two si
13996            three <si|>
13997            additional edit
13998        "},
13999        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14000        true,
14001        counter.clone(),
14002        &mut cx,
14003    )
14004    .await;
14005    cx.condition(|editor, _| editor.context_menu_visible())
14006        .await;
14007    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14008
14009    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14010        editor
14011            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14012            .unwrap()
14013    });
14014    cx.assert_editor_state(indoc! {"
14015        one.second_completion
14016        two sixth_completionˇ
14017        three sixth_completionˇ
14018        additional edit
14019    "});
14020
14021    apply_additional_edits.await.unwrap();
14022
14023    update_test_language_settings(&mut cx, |settings| {
14024        settings.defaults.show_completions_on_input = Some(false);
14025    });
14026    cx.set_state("editorˇ");
14027    cx.simulate_keystroke(".");
14028    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14029    cx.simulate_keystrokes("c l o");
14030    cx.assert_editor_state("editor.cloˇ");
14031    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14032    cx.update_editor(|editor, window, cx| {
14033        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14034    });
14035    handle_completion_request(
14036        "editor.<clo|>",
14037        vec!["close", "clobber"],
14038        true,
14039        counter.clone(),
14040        &mut cx,
14041    )
14042    .await;
14043    cx.condition(|editor, _| editor.context_menu_visible())
14044        .await;
14045    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14046
14047    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14048        editor
14049            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14050            .unwrap()
14051    });
14052    cx.assert_editor_state("editor.clobberˇ");
14053    handle_resolve_completion_request(&mut cx, None).await;
14054    apply_additional_edits.await.unwrap();
14055}
14056
14057#[gpui::test]
14058async fn test_completion_reuse(cx: &mut TestAppContext) {
14059    init_test(cx, |_| {});
14060
14061    let mut cx = EditorLspTestContext::new_rust(
14062        lsp::ServerCapabilities {
14063            completion_provider: Some(lsp::CompletionOptions {
14064                trigger_characters: Some(vec![".".to_string()]),
14065                ..Default::default()
14066            }),
14067            ..Default::default()
14068        },
14069        cx,
14070    )
14071    .await;
14072
14073    let counter = Arc::new(AtomicUsize::new(0));
14074    cx.set_state("objˇ");
14075    cx.simulate_keystroke(".");
14076
14077    // Initial completion request returns complete results
14078    let is_incomplete = false;
14079    handle_completion_request(
14080        "obj.|<>",
14081        vec!["a", "ab", "abc"],
14082        is_incomplete,
14083        counter.clone(),
14084        &mut cx,
14085    )
14086    .await;
14087    cx.run_until_parked();
14088    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14089    cx.assert_editor_state("obj.ˇ");
14090    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14091
14092    // Type "a" - filters existing completions
14093    cx.simulate_keystroke("a");
14094    cx.run_until_parked();
14095    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14096    cx.assert_editor_state("obj.aˇ");
14097    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14098
14099    // Type "b" - filters existing completions
14100    cx.simulate_keystroke("b");
14101    cx.run_until_parked();
14102    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14103    cx.assert_editor_state("obj.abˇ");
14104    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14105
14106    // Type "c" - filters existing completions
14107    cx.simulate_keystroke("c");
14108    cx.run_until_parked();
14109    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14110    cx.assert_editor_state("obj.abcˇ");
14111    check_displayed_completions(vec!["abc"], &mut cx);
14112
14113    // Backspace to delete "c" - filters existing completions
14114    cx.update_editor(|editor, window, cx| {
14115        editor.backspace(&Backspace, window, cx);
14116    });
14117    cx.run_until_parked();
14118    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14119    cx.assert_editor_state("obj.abˇ");
14120    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14121
14122    // Moving cursor to the left dismisses menu.
14123    cx.update_editor(|editor, window, cx| {
14124        editor.move_left(&MoveLeft, window, cx);
14125    });
14126    cx.run_until_parked();
14127    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14128    cx.assert_editor_state("obj.aˇb");
14129    cx.update_editor(|editor, _, _| {
14130        assert_eq!(editor.context_menu_visible(), false);
14131    });
14132
14133    // Type "b" - new request
14134    cx.simulate_keystroke("b");
14135    let is_incomplete = false;
14136    handle_completion_request(
14137        "obj.<ab|>a",
14138        vec!["ab", "abc"],
14139        is_incomplete,
14140        counter.clone(),
14141        &mut cx,
14142    )
14143    .await;
14144    cx.run_until_parked();
14145    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14146    cx.assert_editor_state("obj.abˇb");
14147    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14148
14149    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14150    cx.update_editor(|editor, window, cx| {
14151        editor.backspace(&Backspace, window, cx);
14152    });
14153    let is_incomplete = false;
14154    handle_completion_request(
14155        "obj.<a|>b",
14156        vec!["a", "ab", "abc"],
14157        is_incomplete,
14158        counter.clone(),
14159        &mut cx,
14160    )
14161    .await;
14162    cx.run_until_parked();
14163    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14164    cx.assert_editor_state("obj.aˇb");
14165    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14166
14167    // Backspace to delete "a" - dismisses menu.
14168    cx.update_editor(|editor, window, cx| {
14169        editor.backspace(&Backspace, window, cx);
14170    });
14171    cx.run_until_parked();
14172    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14173    cx.assert_editor_state("obj.ˇb");
14174    cx.update_editor(|editor, _, _| {
14175        assert_eq!(editor.context_menu_visible(), false);
14176    });
14177}
14178
14179#[gpui::test]
14180async fn test_word_completion(cx: &mut TestAppContext) {
14181    let lsp_fetch_timeout_ms = 10;
14182    init_test(cx, |language_settings| {
14183        language_settings.defaults.completions = Some(CompletionSettings {
14184            words: WordsCompletionMode::Fallback,
14185            words_min_length: 0,
14186            lsp: true,
14187            lsp_fetch_timeout_ms: 10,
14188            lsp_insert_mode: LspInsertMode::Insert,
14189        });
14190    });
14191
14192    let mut cx = EditorLspTestContext::new_rust(
14193        lsp::ServerCapabilities {
14194            completion_provider: Some(lsp::CompletionOptions {
14195                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14196                ..lsp::CompletionOptions::default()
14197            }),
14198            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14199            ..lsp::ServerCapabilities::default()
14200        },
14201        cx,
14202    )
14203    .await;
14204
14205    let throttle_completions = Arc::new(AtomicBool::new(false));
14206
14207    let lsp_throttle_completions = throttle_completions.clone();
14208    let _completion_requests_handler =
14209        cx.lsp
14210            .server
14211            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14212                let lsp_throttle_completions = lsp_throttle_completions.clone();
14213                let cx = cx.clone();
14214                async move {
14215                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14216                        cx.background_executor()
14217                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14218                            .await;
14219                    }
14220                    Ok(Some(lsp::CompletionResponse::Array(vec![
14221                        lsp::CompletionItem {
14222                            label: "first".into(),
14223                            ..lsp::CompletionItem::default()
14224                        },
14225                        lsp::CompletionItem {
14226                            label: "last".into(),
14227                            ..lsp::CompletionItem::default()
14228                        },
14229                    ])))
14230                }
14231            });
14232
14233    cx.set_state(indoc! {"
14234        oneˇ
14235        two
14236        three
14237    "});
14238    cx.simulate_keystroke(".");
14239    cx.executor().run_until_parked();
14240    cx.condition(|editor, _| editor.context_menu_visible())
14241        .await;
14242    cx.update_editor(|editor, window, cx| {
14243        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14244        {
14245            assert_eq!(
14246                completion_menu_entries(menu),
14247                &["first", "last"],
14248                "When LSP server is fast to reply, no fallback word completions are used"
14249            );
14250        } else {
14251            panic!("expected completion menu to be open");
14252        }
14253        editor.cancel(&Cancel, window, cx);
14254    });
14255    cx.executor().run_until_parked();
14256    cx.condition(|editor, _| !editor.context_menu_visible())
14257        .await;
14258
14259    throttle_completions.store(true, atomic::Ordering::Release);
14260    cx.simulate_keystroke(".");
14261    cx.executor()
14262        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14263    cx.executor().run_until_parked();
14264    cx.condition(|editor, _| editor.context_menu_visible())
14265        .await;
14266    cx.update_editor(|editor, _, _| {
14267        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14268        {
14269            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14270                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14271        } else {
14272            panic!("expected completion menu to be open");
14273        }
14274    });
14275}
14276
14277#[gpui::test]
14278async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14279    init_test(cx, |language_settings| {
14280        language_settings.defaults.completions = Some(CompletionSettings {
14281            words: WordsCompletionMode::Enabled,
14282            words_min_length: 0,
14283            lsp: true,
14284            lsp_fetch_timeout_ms: 0,
14285            lsp_insert_mode: LspInsertMode::Insert,
14286        });
14287    });
14288
14289    let mut cx = EditorLspTestContext::new_rust(
14290        lsp::ServerCapabilities {
14291            completion_provider: Some(lsp::CompletionOptions {
14292                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14293                ..lsp::CompletionOptions::default()
14294            }),
14295            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14296            ..lsp::ServerCapabilities::default()
14297        },
14298        cx,
14299    )
14300    .await;
14301
14302    let _completion_requests_handler =
14303        cx.lsp
14304            .server
14305            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14306                Ok(Some(lsp::CompletionResponse::Array(vec![
14307                    lsp::CompletionItem {
14308                        label: "first".into(),
14309                        ..lsp::CompletionItem::default()
14310                    },
14311                    lsp::CompletionItem {
14312                        label: "last".into(),
14313                        ..lsp::CompletionItem::default()
14314                    },
14315                ])))
14316            });
14317
14318    cx.set_state(indoc! {"ˇ
14319        first
14320        last
14321        second
14322    "});
14323    cx.simulate_keystroke(".");
14324    cx.executor().run_until_parked();
14325    cx.condition(|editor, _| editor.context_menu_visible())
14326        .await;
14327    cx.update_editor(|editor, _, _| {
14328        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14329        {
14330            assert_eq!(
14331                completion_menu_entries(menu),
14332                &["first", "last", "second"],
14333                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
14334            );
14335        } else {
14336            panic!("expected completion menu to be open");
14337        }
14338    });
14339}
14340
14341#[gpui::test]
14342async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
14343    init_test(cx, |language_settings| {
14344        language_settings.defaults.completions = Some(CompletionSettings {
14345            words: WordsCompletionMode::Disabled,
14346            words_min_length: 0,
14347            lsp: true,
14348            lsp_fetch_timeout_ms: 0,
14349            lsp_insert_mode: LspInsertMode::Insert,
14350        });
14351    });
14352
14353    let mut cx = EditorLspTestContext::new_rust(
14354        lsp::ServerCapabilities {
14355            completion_provider: Some(lsp::CompletionOptions {
14356                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14357                ..lsp::CompletionOptions::default()
14358            }),
14359            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14360            ..lsp::ServerCapabilities::default()
14361        },
14362        cx,
14363    )
14364    .await;
14365
14366    let _completion_requests_handler =
14367        cx.lsp
14368            .server
14369            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
14370                panic!("LSP completions should not be queried when dealing with word completions")
14371            });
14372
14373    cx.set_state(indoc! {"ˇ
14374        first
14375        last
14376        second
14377    "});
14378    cx.update_editor(|editor, window, cx| {
14379        editor.show_word_completions(&ShowWordCompletions, window, cx);
14380    });
14381    cx.executor().run_until_parked();
14382    cx.condition(|editor, _| editor.context_menu_visible())
14383        .await;
14384    cx.update_editor(|editor, _, _| {
14385        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14386        {
14387            assert_eq!(
14388                completion_menu_entries(menu),
14389                &["first", "last", "second"],
14390                "`ShowWordCompletions` action should show word completions"
14391            );
14392        } else {
14393            panic!("expected completion menu to be open");
14394        }
14395    });
14396
14397    cx.simulate_keystroke("l");
14398    cx.executor().run_until_parked();
14399    cx.condition(|editor, _| editor.context_menu_visible())
14400        .await;
14401    cx.update_editor(|editor, _, _| {
14402        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14403        {
14404            assert_eq!(
14405                completion_menu_entries(menu),
14406                &["last"],
14407                "After showing word completions, further editing should filter them and not query the LSP"
14408            );
14409        } else {
14410            panic!("expected completion menu to be open");
14411        }
14412    });
14413}
14414
14415#[gpui::test]
14416async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
14417    init_test(cx, |language_settings| {
14418        language_settings.defaults.completions = Some(CompletionSettings {
14419            words: WordsCompletionMode::Fallback,
14420            words_min_length: 0,
14421            lsp: false,
14422            lsp_fetch_timeout_ms: 0,
14423            lsp_insert_mode: LspInsertMode::Insert,
14424        });
14425    });
14426
14427    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14428
14429    cx.set_state(indoc! {"ˇ
14430        0_usize
14431        let
14432        33
14433        4.5f32
14434    "});
14435    cx.update_editor(|editor, window, cx| {
14436        editor.show_completions(&ShowCompletions::default(), window, cx);
14437    });
14438    cx.executor().run_until_parked();
14439    cx.condition(|editor, _| editor.context_menu_visible())
14440        .await;
14441    cx.update_editor(|editor, window, cx| {
14442        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14443        {
14444            assert_eq!(
14445                completion_menu_entries(menu),
14446                &["let"],
14447                "With no digits in the completion query, no digits should be in the word completions"
14448            );
14449        } else {
14450            panic!("expected completion menu to be open");
14451        }
14452        editor.cancel(&Cancel, window, cx);
14453    });
14454
14455    cx.set_state(indoc! {"14456        0_usize
14457        let
14458        3
14459        33.35f32
14460    "});
14461    cx.update_editor(|editor, window, cx| {
14462        editor.show_completions(&ShowCompletions::default(), window, cx);
14463    });
14464    cx.executor().run_until_parked();
14465    cx.condition(|editor, _| editor.context_menu_visible())
14466        .await;
14467    cx.update_editor(|editor, _, _| {
14468        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14469        {
14470            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
14471                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
14472        } else {
14473            panic!("expected completion menu to be open");
14474        }
14475    });
14476}
14477
14478#[gpui::test]
14479async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
14480    init_test(cx, |language_settings| {
14481        language_settings.defaults.completions = Some(CompletionSettings {
14482            words: WordsCompletionMode::Enabled,
14483            words_min_length: 3,
14484            lsp: true,
14485            lsp_fetch_timeout_ms: 0,
14486            lsp_insert_mode: LspInsertMode::Insert,
14487        });
14488    });
14489
14490    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14491    cx.set_state(indoc! {"ˇ
14492        wow
14493        wowen
14494        wowser
14495    "});
14496    cx.simulate_keystroke("w");
14497    cx.executor().run_until_parked();
14498    cx.update_editor(|editor, _, _| {
14499        if editor.context_menu.borrow_mut().is_some() {
14500            panic!(
14501                "expected completion menu to be hidden, as words completion threshold is not met"
14502            );
14503        }
14504    });
14505
14506    cx.update_editor(|editor, window, cx| {
14507        editor.show_word_completions(&ShowWordCompletions, window, cx);
14508    });
14509    cx.executor().run_until_parked();
14510    cx.update_editor(|editor, window, cx| {
14511        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14512        {
14513            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");
14514        } else {
14515            panic!("expected completion menu to be open after the word completions are called with an action");
14516        }
14517
14518        editor.cancel(&Cancel, window, cx);
14519    });
14520    cx.update_editor(|editor, _, _| {
14521        if editor.context_menu.borrow_mut().is_some() {
14522            panic!("expected completion menu to be hidden after canceling");
14523        }
14524    });
14525
14526    cx.simulate_keystroke("o");
14527    cx.executor().run_until_parked();
14528    cx.update_editor(|editor, _, _| {
14529        if editor.context_menu.borrow_mut().is_some() {
14530            panic!(
14531                "expected completion menu to be hidden, as words completion threshold is not met still"
14532            );
14533        }
14534    });
14535
14536    cx.simulate_keystroke("w");
14537    cx.executor().run_until_parked();
14538    cx.update_editor(|editor, _, _| {
14539        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14540        {
14541            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
14542        } else {
14543            panic!("expected completion menu to be open after the word completions threshold is met");
14544        }
14545    });
14546}
14547
14548#[gpui::test]
14549async fn test_word_completions_disabled(cx: &mut TestAppContext) {
14550    init_test(cx, |language_settings| {
14551        language_settings.defaults.completions = Some(CompletionSettings {
14552            words: WordsCompletionMode::Enabled,
14553            words_min_length: 0,
14554            lsp: true,
14555            lsp_fetch_timeout_ms: 0,
14556            lsp_insert_mode: LspInsertMode::Insert,
14557        });
14558    });
14559
14560    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
14561    cx.update_editor(|editor, _, _| {
14562        editor.disable_word_completions();
14563    });
14564    cx.set_state(indoc! {"ˇ
14565        wow
14566        wowen
14567        wowser
14568    "});
14569    cx.simulate_keystroke("w");
14570    cx.executor().run_until_parked();
14571    cx.update_editor(|editor, _, _| {
14572        if editor.context_menu.borrow_mut().is_some() {
14573            panic!(
14574                "expected completion menu to be hidden, as words completion are disabled for this editor"
14575            );
14576        }
14577    });
14578
14579    cx.update_editor(|editor, window, cx| {
14580        editor.show_word_completions(&ShowWordCompletions, window, cx);
14581    });
14582    cx.executor().run_until_parked();
14583    cx.update_editor(|editor, _, _| {
14584        if editor.context_menu.borrow_mut().is_some() {
14585            panic!(
14586                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
14587            );
14588        }
14589    });
14590}
14591
14592fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
14593    let position = || lsp::Position {
14594        line: params.text_document_position.position.line,
14595        character: params.text_document_position.position.character,
14596    };
14597    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14598        range: lsp::Range {
14599            start: position(),
14600            end: position(),
14601        },
14602        new_text: text.to_string(),
14603    }))
14604}
14605
14606#[gpui::test]
14607async fn test_multiline_completion(cx: &mut TestAppContext) {
14608    init_test(cx, |_| {});
14609
14610    let fs = FakeFs::new(cx.executor());
14611    fs.insert_tree(
14612        path!("/a"),
14613        json!({
14614            "main.ts": "a",
14615        }),
14616    )
14617    .await;
14618
14619    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14620    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14621    let typescript_language = Arc::new(Language::new(
14622        LanguageConfig {
14623            name: "TypeScript".into(),
14624            matcher: LanguageMatcher {
14625                path_suffixes: vec!["ts".to_string()],
14626                ..LanguageMatcher::default()
14627            },
14628            line_comments: vec!["// ".into()],
14629            ..LanguageConfig::default()
14630        },
14631        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
14632    ));
14633    language_registry.add(typescript_language.clone());
14634    let mut fake_servers = language_registry.register_fake_lsp(
14635        "TypeScript",
14636        FakeLspAdapter {
14637            capabilities: lsp::ServerCapabilities {
14638                completion_provider: Some(lsp::CompletionOptions {
14639                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14640                    ..lsp::CompletionOptions::default()
14641                }),
14642                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14643                ..lsp::ServerCapabilities::default()
14644            },
14645            // Emulate vtsls label generation
14646            label_for_completion: Some(Box::new(|item, _| {
14647                let text = if let Some(description) = item
14648                    .label_details
14649                    .as_ref()
14650                    .and_then(|label_details| label_details.description.as_ref())
14651                {
14652                    format!("{} {}", item.label, description)
14653                } else if let Some(detail) = &item.detail {
14654                    format!("{} {}", item.label, detail)
14655                } else {
14656                    item.label.clone()
14657                };
14658                let len = text.len();
14659                Some(language::CodeLabel {
14660                    text,
14661                    runs: Vec::new(),
14662                    filter_range: 0..len,
14663                })
14664            })),
14665            ..FakeLspAdapter::default()
14666        },
14667    );
14668    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14669    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14670    let worktree_id = workspace
14671        .update(cx, |workspace, _window, cx| {
14672            workspace.project().update(cx, |project, cx| {
14673                project.worktrees(cx).next().unwrap().read(cx).id()
14674            })
14675        })
14676        .unwrap();
14677    let _buffer = project
14678        .update(cx, |project, cx| {
14679            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
14680        })
14681        .await
14682        .unwrap();
14683    let editor = workspace
14684        .update(cx, |workspace, window, cx| {
14685            workspace.open_path((worktree_id, "main.ts"), None, true, window, cx)
14686        })
14687        .unwrap()
14688        .await
14689        .unwrap()
14690        .downcast::<Editor>()
14691        .unwrap();
14692    let fake_server = fake_servers.next().await.unwrap();
14693
14694    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
14695    let multiline_label_2 = "a\nb\nc\n";
14696    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
14697    let multiline_description = "d\ne\nf\n";
14698    let multiline_detail_2 = "g\nh\ni\n";
14699
14700    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14701        move |params, _| async move {
14702            Ok(Some(lsp::CompletionResponse::Array(vec![
14703                lsp::CompletionItem {
14704                    label: multiline_label.to_string(),
14705                    text_edit: gen_text_edit(&params, "new_text_1"),
14706                    ..lsp::CompletionItem::default()
14707                },
14708                lsp::CompletionItem {
14709                    label: "single line label 1".to_string(),
14710                    detail: Some(multiline_detail.to_string()),
14711                    text_edit: gen_text_edit(&params, "new_text_2"),
14712                    ..lsp::CompletionItem::default()
14713                },
14714                lsp::CompletionItem {
14715                    label: "single line label 2".to_string(),
14716                    label_details: Some(lsp::CompletionItemLabelDetails {
14717                        description: Some(multiline_description.to_string()),
14718                        detail: None,
14719                    }),
14720                    text_edit: gen_text_edit(&params, "new_text_2"),
14721                    ..lsp::CompletionItem::default()
14722                },
14723                lsp::CompletionItem {
14724                    label: multiline_label_2.to_string(),
14725                    detail: Some(multiline_detail_2.to_string()),
14726                    text_edit: gen_text_edit(&params, "new_text_3"),
14727                    ..lsp::CompletionItem::default()
14728                },
14729                lsp::CompletionItem {
14730                    label: "Label with many     spaces and \t but without newlines".to_string(),
14731                    detail: Some(
14732                        "Details with many     spaces and \t but without newlines".to_string(),
14733                    ),
14734                    text_edit: gen_text_edit(&params, "new_text_4"),
14735                    ..lsp::CompletionItem::default()
14736                },
14737            ])))
14738        },
14739    );
14740
14741    editor.update_in(cx, |editor, window, cx| {
14742        cx.focus_self(window);
14743        editor.move_to_end(&MoveToEnd, window, cx);
14744        editor.handle_input(".", window, cx);
14745    });
14746    cx.run_until_parked();
14747    completion_handle.next().await.unwrap();
14748
14749    editor.update(cx, |editor, _| {
14750        assert!(editor.context_menu_visible());
14751        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14752        {
14753            let completion_labels = menu
14754                .completions
14755                .borrow()
14756                .iter()
14757                .map(|c| c.label.text.clone())
14758                .collect::<Vec<_>>();
14759            assert_eq!(
14760                completion_labels,
14761                &[
14762                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
14763                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
14764                    "single line label 2 d e f ",
14765                    "a b c g h i ",
14766                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
14767                ],
14768                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
14769            );
14770
14771            for completion in menu
14772                .completions
14773                .borrow()
14774                .iter() {
14775                    assert_eq!(
14776                        completion.label.filter_range,
14777                        0..completion.label.text.len(),
14778                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
14779                    );
14780                }
14781        } else {
14782            panic!("expected completion menu to be open");
14783        }
14784    });
14785}
14786
14787#[gpui::test]
14788async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
14789    init_test(cx, |_| {});
14790    let mut cx = EditorLspTestContext::new_rust(
14791        lsp::ServerCapabilities {
14792            completion_provider: Some(lsp::CompletionOptions {
14793                trigger_characters: Some(vec![".".to_string()]),
14794                ..Default::default()
14795            }),
14796            ..Default::default()
14797        },
14798        cx,
14799    )
14800    .await;
14801    cx.lsp
14802        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14803            Ok(Some(lsp::CompletionResponse::Array(vec![
14804                lsp::CompletionItem {
14805                    label: "first".into(),
14806                    ..Default::default()
14807                },
14808                lsp::CompletionItem {
14809                    label: "last".into(),
14810                    ..Default::default()
14811                },
14812            ])))
14813        });
14814    cx.set_state("variableˇ");
14815    cx.simulate_keystroke(".");
14816    cx.executor().run_until_parked();
14817
14818    cx.update_editor(|editor, _, _| {
14819        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14820        {
14821            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
14822        } else {
14823            panic!("expected completion menu to be open");
14824        }
14825    });
14826
14827    cx.update_editor(|editor, window, cx| {
14828        editor.move_page_down(&MovePageDown::default(), window, cx);
14829        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14830        {
14831            assert!(
14832                menu.selected_item == 1,
14833                "expected PageDown to select the last item from the context menu"
14834            );
14835        } else {
14836            panic!("expected completion menu to stay open after PageDown");
14837        }
14838    });
14839
14840    cx.update_editor(|editor, window, cx| {
14841        editor.move_page_up(&MovePageUp::default(), window, cx);
14842        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14843        {
14844            assert!(
14845                menu.selected_item == 0,
14846                "expected PageUp to select the first item from the context menu"
14847            );
14848        } else {
14849            panic!("expected completion menu to stay open after PageUp");
14850        }
14851    });
14852}
14853
14854#[gpui::test]
14855async fn test_as_is_completions(cx: &mut TestAppContext) {
14856    init_test(cx, |_| {});
14857    let mut cx = EditorLspTestContext::new_rust(
14858        lsp::ServerCapabilities {
14859            completion_provider: Some(lsp::CompletionOptions {
14860                ..Default::default()
14861            }),
14862            ..Default::default()
14863        },
14864        cx,
14865    )
14866    .await;
14867    cx.lsp
14868        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14869            Ok(Some(lsp::CompletionResponse::Array(vec![
14870                lsp::CompletionItem {
14871                    label: "unsafe".into(),
14872                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14873                        range: lsp::Range {
14874                            start: lsp::Position {
14875                                line: 1,
14876                                character: 2,
14877                            },
14878                            end: lsp::Position {
14879                                line: 1,
14880                                character: 3,
14881                            },
14882                        },
14883                        new_text: "unsafe".to_string(),
14884                    })),
14885                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
14886                    ..Default::default()
14887                },
14888            ])))
14889        });
14890    cx.set_state("fn a() {}\n");
14891    cx.executor().run_until_parked();
14892    cx.update_editor(|editor, window, cx| {
14893        editor.show_completions(
14894            &ShowCompletions {
14895                trigger: Some("\n".into()),
14896            },
14897            window,
14898            cx,
14899        );
14900    });
14901    cx.executor().run_until_parked();
14902
14903    cx.update_editor(|editor, window, cx| {
14904        editor.confirm_completion(&Default::default(), window, cx)
14905    });
14906    cx.executor().run_until_parked();
14907    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
14908}
14909
14910#[gpui::test]
14911async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
14912    init_test(cx, |_| {});
14913    let language =
14914        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
14915    let mut cx = EditorLspTestContext::new(
14916        language,
14917        lsp::ServerCapabilities {
14918            completion_provider: Some(lsp::CompletionOptions {
14919                ..lsp::CompletionOptions::default()
14920            }),
14921            ..lsp::ServerCapabilities::default()
14922        },
14923        cx,
14924    )
14925    .await;
14926
14927    cx.set_state(
14928        "#ifndef BAR_H
14929#define BAR_H
14930
14931#include <stdbool.h>
14932
14933int fn_branch(bool do_branch1, bool do_branch2);
14934
14935#endif // BAR_H
14936ˇ",
14937    );
14938    cx.executor().run_until_parked();
14939    cx.update_editor(|editor, window, cx| {
14940        editor.handle_input("#", window, cx);
14941    });
14942    cx.executor().run_until_parked();
14943    cx.update_editor(|editor, window, cx| {
14944        editor.handle_input("i", window, cx);
14945    });
14946    cx.executor().run_until_parked();
14947    cx.update_editor(|editor, window, cx| {
14948        editor.handle_input("n", window, cx);
14949    });
14950    cx.executor().run_until_parked();
14951    cx.assert_editor_state(
14952        "#ifndef BAR_H
14953#define BAR_H
14954
14955#include <stdbool.h>
14956
14957int fn_branch(bool do_branch1, bool do_branch2);
14958
14959#endif // BAR_H
14960#inˇ",
14961    );
14962
14963    cx.lsp
14964        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14965            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
14966                is_incomplete: false,
14967                item_defaults: None,
14968                items: vec![lsp::CompletionItem {
14969                    kind: Some(lsp::CompletionItemKind::SNIPPET),
14970                    label_details: Some(lsp::CompletionItemLabelDetails {
14971                        detail: Some("header".to_string()),
14972                        description: None,
14973                    }),
14974                    label: " include".to_string(),
14975                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
14976                        range: lsp::Range {
14977                            start: lsp::Position {
14978                                line: 8,
14979                                character: 1,
14980                            },
14981                            end: lsp::Position {
14982                                line: 8,
14983                                character: 1,
14984                            },
14985                        },
14986                        new_text: "include \"$0\"".to_string(),
14987                    })),
14988                    sort_text: Some("40b67681include".to_string()),
14989                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
14990                    filter_text: Some("include".to_string()),
14991                    insert_text: Some("include \"$0\"".to_string()),
14992                    ..lsp::CompletionItem::default()
14993                }],
14994            })))
14995        });
14996    cx.update_editor(|editor, window, cx| {
14997        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
14998    });
14999    cx.executor().run_until_parked();
15000    cx.update_editor(|editor, window, cx| {
15001        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15002    });
15003    cx.executor().run_until_parked();
15004    cx.assert_editor_state(
15005        "#ifndef BAR_H
15006#define BAR_H
15007
15008#include <stdbool.h>
15009
15010int fn_branch(bool do_branch1, bool do_branch2);
15011
15012#endif // BAR_H
15013#include \"ˇ\"",
15014    );
15015
15016    cx.lsp
15017        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15018            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15019                is_incomplete: true,
15020                item_defaults: None,
15021                items: vec![lsp::CompletionItem {
15022                    kind: Some(lsp::CompletionItemKind::FILE),
15023                    label: "AGL/".to_string(),
15024                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15025                        range: lsp::Range {
15026                            start: lsp::Position {
15027                                line: 8,
15028                                character: 10,
15029                            },
15030                            end: lsp::Position {
15031                                line: 8,
15032                                character: 11,
15033                            },
15034                        },
15035                        new_text: "AGL/".to_string(),
15036                    })),
15037                    sort_text: Some("40b67681AGL/".to_string()),
15038                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15039                    filter_text: Some("AGL/".to_string()),
15040                    insert_text: Some("AGL/".to_string()),
15041                    ..lsp::CompletionItem::default()
15042                }],
15043            })))
15044        });
15045    cx.update_editor(|editor, window, cx| {
15046        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
15047    });
15048    cx.executor().run_until_parked();
15049    cx.update_editor(|editor, window, cx| {
15050        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15051    });
15052    cx.executor().run_until_parked();
15053    cx.assert_editor_state(
15054        r##"#ifndef BAR_H
15055#define BAR_H
15056
15057#include <stdbool.h>
15058
15059int fn_branch(bool do_branch1, bool do_branch2);
15060
15061#endif // BAR_H
15062#include "AGL/ˇ"##,
15063    );
15064
15065    cx.update_editor(|editor, window, cx| {
15066        editor.handle_input("\"", window, cx);
15067    });
15068    cx.executor().run_until_parked();
15069    cx.assert_editor_state(
15070        r##"#ifndef BAR_H
15071#define BAR_H
15072
15073#include <stdbool.h>
15074
15075int fn_branch(bool do_branch1, bool do_branch2);
15076
15077#endif // BAR_H
15078#include "AGL/"ˇ"##,
15079    );
15080}
15081
15082#[gpui::test]
15083async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15084    init_test(cx, |_| {});
15085
15086    let mut cx = EditorLspTestContext::new_rust(
15087        lsp::ServerCapabilities {
15088            completion_provider: Some(lsp::CompletionOptions {
15089                trigger_characters: Some(vec![".".to_string()]),
15090                resolve_provider: Some(true),
15091                ..Default::default()
15092            }),
15093            ..Default::default()
15094        },
15095        cx,
15096    )
15097    .await;
15098
15099    cx.set_state("fn main() { let a = 2ˇ; }");
15100    cx.simulate_keystroke(".");
15101    let completion_item = lsp::CompletionItem {
15102        label: "Some".into(),
15103        kind: Some(lsp::CompletionItemKind::SNIPPET),
15104        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15105        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15106            kind: lsp::MarkupKind::Markdown,
15107            value: "```rust\nSome(2)\n```".to_string(),
15108        })),
15109        deprecated: Some(false),
15110        sort_text: Some("Some".to_string()),
15111        filter_text: Some("Some".to_string()),
15112        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15113        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15114            range: lsp::Range {
15115                start: lsp::Position {
15116                    line: 0,
15117                    character: 22,
15118                },
15119                end: lsp::Position {
15120                    line: 0,
15121                    character: 22,
15122                },
15123            },
15124            new_text: "Some(2)".to_string(),
15125        })),
15126        additional_text_edits: Some(vec![lsp::TextEdit {
15127            range: lsp::Range {
15128                start: lsp::Position {
15129                    line: 0,
15130                    character: 20,
15131                },
15132                end: lsp::Position {
15133                    line: 0,
15134                    character: 22,
15135                },
15136            },
15137            new_text: "".to_string(),
15138        }]),
15139        ..Default::default()
15140    };
15141
15142    let closure_completion_item = completion_item.clone();
15143    let counter = Arc::new(AtomicUsize::new(0));
15144    let counter_clone = counter.clone();
15145    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15146        let task_completion_item = closure_completion_item.clone();
15147        counter_clone.fetch_add(1, atomic::Ordering::Release);
15148        async move {
15149            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15150                is_incomplete: true,
15151                item_defaults: None,
15152                items: vec![task_completion_item],
15153            })))
15154        }
15155    });
15156
15157    cx.condition(|editor, _| editor.context_menu_visible())
15158        .await;
15159    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15160    assert!(request.next().await.is_some());
15161    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15162
15163    cx.simulate_keystrokes("S o m");
15164    cx.condition(|editor, _| editor.context_menu_visible())
15165        .await;
15166    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15167    assert!(request.next().await.is_some());
15168    assert!(request.next().await.is_some());
15169    assert!(request.next().await.is_some());
15170    request.close();
15171    assert!(request.next().await.is_none());
15172    assert_eq!(
15173        counter.load(atomic::Ordering::Acquire),
15174        4,
15175        "With the completions menu open, only one LSP request should happen per input"
15176    );
15177}
15178
15179#[gpui::test]
15180async fn test_toggle_comment(cx: &mut TestAppContext) {
15181    init_test(cx, |_| {});
15182    let mut cx = EditorTestContext::new(cx).await;
15183    let language = Arc::new(Language::new(
15184        LanguageConfig {
15185            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15186            ..Default::default()
15187        },
15188        Some(tree_sitter_rust::LANGUAGE.into()),
15189    ));
15190    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15191
15192    // If multiple selections intersect a line, the line is only toggled once.
15193    cx.set_state(indoc! {"
15194        fn a() {
15195            «//b();
15196            ˇ»// «c();
15197            //ˇ»  d();
15198        }
15199    "});
15200
15201    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15202
15203    cx.assert_editor_state(indoc! {"
15204        fn a() {
15205            «b();
15206            c();
15207            ˇ» d();
15208        }
15209    "});
15210
15211    // The comment prefix is inserted at the same column for every line in a
15212    // selection.
15213    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15214
15215    cx.assert_editor_state(indoc! {"
15216        fn a() {
15217            // «b();
15218            // c();
15219            ˇ»//  d();
15220        }
15221    "});
15222
15223    // If a selection ends at the beginning of a line, that line is not toggled.
15224    cx.set_selections_state(indoc! {"
15225        fn a() {
15226            // b();
15227            «// c();
15228        ˇ»    //  d();
15229        }
15230    "});
15231
15232    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15233
15234    cx.assert_editor_state(indoc! {"
15235        fn a() {
15236            // b();
15237            «c();
15238        ˇ»    //  d();
15239        }
15240    "});
15241
15242    // If a selection span a single line and is empty, the line is toggled.
15243    cx.set_state(indoc! {"
15244        fn a() {
15245            a();
15246            b();
15247        ˇ
15248        }
15249    "});
15250
15251    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15252
15253    cx.assert_editor_state(indoc! {"
15254        fn a() {
15255            a();
15256            b();
15257        //•ˇ
15258        }
15259    "});
15260
15261    // If a selection span multiple lines, empty lines are not toggled.
15262    cx.set_state(indoc! {"
15263        fn a() {
15264            «a();
15265
15266            c();ˇ»
15267        }
15268    "});
15269
15270    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15271
15272    cx.assert_editor_state(indoc! {"
15273        fn a() {
15274            // «a();
15275
15276            // c();ˇ»
15277        }
15278    "});
15279
15280    // If a selection includes multiple comment prefixes, all lines are uncommented.
15281    cx.set_state(indoc! {"
15282        fn a() {
15283            «// a();
15284            /// b();
15285            //! c();ˇ»
15286        }
15287    "});
15288
15289    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15290
15291    cx.assert_editor_state(indoc! {"
15292        fn a() {
15293            «a();
15294            b();
15295            c();ˇ»
15296        }
15297    "});
15298}
15299
15300#[gpui::test]
15301async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
15302    init_test(cx, |_| {});
15303    let mut cx = EditorTestContext::new(cx).await;
15304    let language = Arc::new(Language::new(
15305        LanguageConfig {
15306            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15307            ..Default::default()
15308        },
15309        Some(tree_sitter_rust::LANGUAGE.into()),
15310    ));
15311    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15312
15313    let toggle_comments = &ToggleComments {
15314        advance_downwards: false,
15315        ignore_indent: true,
15316    };
15317
15318    // If multiple selections intersect a line, the line is only toggled once.
15319    cx.set_state(indoc! {"
15320        fn a() {
15321        //    «b();
15322        //    c();
15323        //    ˇ» d();
15324        }
15325    "});
15326
15327    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15328
15329    cx.assert_editor_state(indoc! {"
15330        fn a() {
15331            «b();
15332            c();
15333            ˇ» d();
15334        }
15335    "});
15336
15337    // The comment prefix is inserted at the beginning of each line
15338    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15339
15340    cx.assert_editor_state(indoc! {"
15341        fn a() {
15342        //    «b();
15343        //    c();
15344        //    ˇ» d();
15345        }
15346    "});
15347
15348    // If a selection ends at the beginning of a line, that line is not toggled.
15349    cx.set_selections_state(indoc! {"
15350        fn a() {
15351        //    b();
15352        //    «c();
15353        ˇ»//     d();
15354        }
15355    "});
15356
15357    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15358
15359    cx.assert_editor_state(indoc! {"
15360        fn a() {
15361        //    b();
15362            «c();
15363        ˇ»//     d();
15364        }
15365    "});
15366
15367    // If a selection span a single line and is empty, the line is toggled.
15368    cx.set_state(indoc! {"
15369        fn a() {
15370            a();
15371            b();
15372        ˇ
15373        }
15374    "});
15375
15376    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15377
15378    cx.assert_editor_state(indoc! {"
15379        fn a() {
15380            a();
15381            b();
15382        //ˇ
15383        }
15384    "});
15385
15386    // If a selection span multiple lines, empty lines are not toggled.
15387    cx.set_state(indoc! {"
15388        fn a() {
15389            «a();
15390
15391            c();ˇ»
15392        }
15393    "});
15394
15395    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15396
15397    cx.assert_editor_state(indoc! {"
15398        fn a() {
15399        //    «a();
15400
15401        //    c();ˇ»
15402        }
15403    "});
15404
15405    // If a selection includes multiple comment prefixes, all lines are uncommented.
15406    cx.set_state(indoc! {"
15407        fn a() {
15408        //    «a();
15409        ///    b();
15410        //!    c();ˇ»
15411        }
15412    "});
15413
15414    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
15415
15416    cx.assert_editor_state(indoc! {"
15417        fn a() {
15418            «a();
15419            b();
15420            c();ˇ»
15421        }
15422    "});
15423}
15424
15425#[gpui::test]
15426async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
15427    init_test(cx, |_| {});
15428
15429    let language = Arc::new(Language::new(
15430        LanguageConfig {
15431            line_comments: vec!["// ".into()],
15432            ..Default::default()
15433        },
15434        Some(tree_sitter_rust::LANGUAGE.into()),
15435    ));
15436
15437    let mut cx = EditorTestContext::new(cx).await;
15438
15439    cx.language_registry().add(language.clone());
15440    cx.update_buffer(|buffer, cx| {
15441        buffer.set_language(Some(language), cx);
15442    });
15443
15444    let toggle_comments = &ToggleComments {
15445        advance_downwards: true,
15446        ignore_indent: false,
15447    };
15448
15449    // Single cursor on one line -> advance
15450    // Cursor moves horizontally 3 characters as well on non-blank line
15451    cx.set_state(indoc!(
15452        "fn a() {
15453             ˇdog();
15454             cat();
15455        }"
15456    ));
15457    cx.update_editor(|editor, window, cx| {
15458        editor.toggle_comments(toggle_comments, window, cx);
15459    });
15460    cx.assert_editor_state(indoc!(
15461        "fn a() {
15462             // dog();
15463             catˇ();
15464        }"
15465    ));
15466
15467    // Single selection on one line -> don't advance
15468    cx.set_state(indoc!(
15469        "fn a() {
15470             «dog()ˇ»;
15471             cat();
15472        }"
15473    ));
15474    cx.update_editor(|editor, window, cx| {
15475        editor.toggle_comments(toggle_comments, window, cx);
15476    });
15477    cx.assert_editor_state(indoc!(
15478        "fn a() {
15479             // «dog()ˇ»;
15480             cat();
15481        }"
15482    ));
15483
15484    // Multiple cursors on one line -> advance
15485    cx.set_state(indoc!(
15486        "fn a() {
15487             ˇdˇog();
15488             cat();
15489        }"
15490    ));
15491    cx.update_editor(|editor, window, cx| {
15492        editor.toggle_comments(toggle_comments, window, cx);
15493    });
15494    cx.assert_editor_state(indoc!(
15495        "fn a() {
15496             // dog();
15497             catˇ(ˇ);
15498        }"
15499    ));
15500
15501    // Multiple cursors on one line, with selection -> don't advance
15502    cx.set_state(indoc!(
15503        "fn a() {
15504             ˇdˇog«()ˇ»;
15505             cat();
15506        }"
15507    ));
15508    cx.update_editor(|editor, window, cx| {
15509        editor.toggle_comments(toggle_comments, window, cx);
15510    });
15511    cx.assert_editor_state(indoc!(
15512        "fn a() {
15513             // ˇdˇog«()ˇ»;
15514             cat();
15515        }"
15516    ));
15517
15518    // Single cursor on one line -> advance
15519    // Cursor moves to column 0 on blank line
15520    cx.set_state(indoc!(
15521        "fn a() {
15522             ˇdog();
15523
15524             cat();
15525        }"
15526    ));
15527    cx.update_editor(|editor, window, cx| {
15528        editor.toggle_comments(toggle_comments, window, cx);
15529    });
15530    cx.assert_editor_state(indoc!(
15531        "fn a() {
15532             // dog();
15533        ˇ
15534             cat();
15535        }"
15536    ));
15537
15538    // Single cursor on one line -> advance
15539    // Cursor starts and ends at column 0
15540    cx.set_state(indoc!(
15541        "fn a() {
15542         ˇ    dog();
15543             cat();
15544        }"
15545    ));
15546    cx.update_editor(|editor, window, cx| {
15547        editor.toggle_comments(toggle_comments, window, cx);
15548    });
15549    cx.assert_editor_state(indoc!(
15550        "fn a() {
15551             // dog();
15552         ˇ    cat();
15553        }"
15554    ));
15555}
15556
15557#[gpui::test]
15558async fn test_toggle_block_comment(cx: &mut TestAppContext) {
15559    init_test(cx, |_| {});
15560
15561    let mut cx = EditorTestContext::new(cx).await;
15562
15563    let html_language = Arc::new(
15564        Language::new(
15565            LanguageConfig {
15566                name: "HTML".into(),
15567                block_comment: Some(BlockCommentConfig {
15568                    start: "<!-- ".into(),
15569                    prefix: "".into(),
15570                    end: " -->".into(),
15571                    tab_size: 0,
15572                }),
15573                ..Default::default()
15574            },
15575            Some(tree_sitter_html::LANGUAGE.into()),
15576        )
15577        .with_injection_query(
15578            r#"
15579            (script_element
15580                (raw_text) @injection.content
15581                (#set! injection.language "javascript"))
15582            "#,
15583        )
15584        .unwrap(),
15585    );
15586
15587    let javascript_language = Arc::new(Language::new(
15588        LanguageConfig {
15589            name: "JavaScript".into(),
15590            line_comments: vec!["// ".into()],
15591            ..Default::default()
15592        },
15593        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
15594    ));
15595
15596    cx.language_registry().add(html_language.clone());
15597    cx.language_registry().add(javascript_language);
15598    cx.update_buffer(|buffer, cx| {
15599        buffer.set_language(Some(html_language), cx);
15600    });
15601
15602    // Toggle comments for empty selections
15603    cx.set_state(
15604        &r#"
15605            <p>A</p>ˇ
15606            <p>B</p>ˇ
15607            <p>C</p>ˇ
15608        "#
15609        .unindent(),
15610    );
15611    cx.update_editor(|editor, window, cx| {
15612        editor.toggle_comments(&ToggleComments::default(), window, cx)
15613    });
15614    cx.assert_editor_state(
15615        &r#"
15616            <!-- <p>A</p>ˇ -->
15617            <!-- <p>B</p>ˇ -->
15618            <!-- <p>C</p>ˇ -->
15619        "#
15620        .unindent(),
15621    );
15622    cx.update_editor(|editor, window, cx| {
15623        editor.toggle_comments(&ToggleComments::default(), window, cx)
15624    });
15625    cx.assert_editor_state(
15626        &r#"
15627            <p>A</p>ˇ
15628            <p>B</p>ˇ
15629            <p>C</p>ˇ
15630        "#
15631        .unindent(),
15632    );
15633
15634    // Toggle comments for mixture of empty and non-empty selections, where
15635    // multiple selections occupy a given line.
15636    cx.set_state(
15637        &r#"
15638            <p>A«</p>
15639            <p>ˇ»B</p>ˇ
15640            <p>C«</p>
15641            <p>ˇ»D</p>ˇ
15642        "#
15643        .unindent(),
15644    );
15645
15646    cx.update_editor(|editor, window, cx| {
15647        editor.toggle_comments(&ToggleComments::default(), window, cx)
15648    });
15649    cx.assert_editor_state(
15650        &r#"
15651            <!-- <p>A«</p>
15652            <p>ˇ»B</p>ˇ -->
15653            <!-- <p>C«</p>
15654            <p>ˇ»D</p>ˇ -->
15655        "#
15656        .unindent(),
15657    );
15658    cx.update_editor(|editor, window, cx| {
15659        editor.toggle_comments(&ToggleComments::default(), window, cx)
15660    });
15661    cx.assert_editor_state(
15662        &r#"
15663            <p>A«</p>
15664            <p>ˇ»B</p>ˇ
15665            <p>C«</p>
15666            <p>ˇ»D</p>ˇ
15667        "#
15668        .unindent(),
15669    );
15670
15671    // Toggle comments when different languages are active for different
15672    // selections.
15673    cx.set_state(
15674        &r#"
15675            ˇ<script>
15676                ˇvar x = new Y();
15677            ˇ</script>
15678        "#
15679        .unindent(),
15680    );
15681    cx.executor().run_until_parked();
15682    cx.update_editor(|editor, window, cx| {
15683        editor.toggle_comments(&ToggleComments::default(), window, cx)
15684    });
15685    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
15686    // Uncommenting and commenting from this position brings in even more wrong artifacts.
15687    cx.assert_editor_state(
15688        &r#"
15689            <!-- ˇ<script> -->
15690                // ˇvar x = new Y();
15691            <!-- ˇ</script> -->
15692        "#
15693        .unindent(),
15694    );
15695}
15696
15697#[gpui::test]
15698fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
15699    init_test(cx, |_| {});
15700
15701    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15702    let multibuffer = cx.new(|cx| {
15703        let mut multibuffer = MultiBuffer::new(ReadWrite);
15704        multibuffer.push_excerpts(
15705            buffer.clone(),
15706            [
15707                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
15708                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
15709            ],
15710            cx,
15711        );
15712        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
15713        multibuffer
15714    });
15715
15716    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15717    editor.update_in(cx, |editor, window, cx| {
15718        assert_eq!(editor.text(cx), "aaaa\nbbbb");
15719        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15720            s.select_ranges([
15721                Point::new(0, 0)..Point::new(0, 0),
15722                Point::new(1, 0)..Point::new(1, 0),
15723            ])
15724        });
15725
15726        editor.handle_input("X", window, cx);
15727        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
15728        assert_eq!(
15729            editor.selections.ranges(cx),
15730            [
15731                Point::new(0, 1)..Point::new(0, 1),
15732                Point::new(1, 1)..Point::new(1, 1),
15733            ]
15734        );
15735
15736        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
15737        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15738            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
15739        });
15740        editor.backspace(&Default::default(), window, cx);
15741        assert_eq!(editor.text(cx), "Xa\nbbb");
15742        assert_eq!(
15743            editor.selections.ranges(cx),
15744            [Point::new(1, 0)..Point::new(1, 0)]
15745        );
15746
15747        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15748            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
15749        });
15750        editor.backspace(&Default::default(), window, cx);
15751        assert_eq!(editor.text(cx), "X\nbb");
15752        assert_eq!(
15753            editor.selections.ranges(cx),
15754            [Point::new(0, 1)..Point::new(0, 1)]
15755        );
15756    });
15757}
15758
15759#[gpui::test]
15760fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
15761    init_test(cx, |_| {});
15762
15763    let markers = vec![('[', ']').into(), ('(', ')').into()];
15764    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
15765        indoc! {"
15766            [aaaa
15767            (bbbb]
15768            cccc)",
15769        },
15770        markers.clone(),
15771    );
15772    let excerpt_ranges = markers.into_iter().map(|marker| {
15773        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
15774        ExcerptRange::new(context)
15775    });
15776    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
15777    let multibuffer = cx.new(|cx| {
15778        let mut multibuffer = MultiBuffer::new(ReadWrite);
15779        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
15780        multibuffer
15781    });
15782
15783    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
15784    editor.update_in(cx, |editor, window, cx| {
15785        let (expected_text, selection_ranges) = marked_text_ranges(
15786            indoc! {"
15787                aaaa
15788                bˇbbb
15789                bˇbbˇb
15790                cccc"
15791            },
15792            true,
15793        );
15794        assert_eq!(editor.text(cx), expected_text);
15795        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15796            s.select_ranges(selection_ranges)
15797        });
15798
15799        editor.handle_input("X", window, cx);
15800
15801        let (expected_text, expected_selections) = marked_text_ranges(
15802            indoc! {"
15803                aaaa
15804                bXˇbbXb
15805                bXˇbbXˇb
15806                cccc"
15807            },
15808            false,
15809        );
15810        assert_eq!(editor.text(cx), expected_text);
15811        assert_eq!(editor.selections.ranges(cx), expected_selections);
15812
15813        editor.newline(&Newline, window, cx);
15814        let (expected_text, expected_selections) = marked_text_ranges(
15815            indoc! {"
15816                aaaa
15817                bX
15818                ˇbbX
15819                b
15820                bX
15821                ˇbbX
15822                ˇb
15823                cccc"
15824            },
15825            false,
15826        );
15827        assert_eq!(editor.text(cx), expected_text);
15828        assert_eq!(editor.selections.ranges(cx), expected_selections);
15829    });
15830}
15831
15832#[gpui::test]
15833fn test_refresh_selections(cx: &mut TestAppContext) {
15834    init_test(cx, |_| {});
15835
15836    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15837    let mut excerpt1_id = None;
15838    let multibuffer = cx.new(|cx| {
15839        let mut multibuffer = MultiBuffer::new(ReadWrite);
15840        excerpt1_id = multibuffer
15841            .push_excerpts(
15842                buffer.clone(),
15843                [
15844                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15845                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15846                ],
15847                cx,
15848            )
15849            .into_iter()
15850            .next();
15851        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15852        multibuffer
15853    });
15854
15855    let editor = cx.add_window(|window, cx| {
15856        let mut editor = build_editor(multibuffer.clone(), window, cx);
15857        let snapshot = editor.snapshot(window, cx);
15858        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15859            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
15860        });
15861        editor.begin_selection(
15862            Point::new(2, 1).to_display_point(&snapshot),
15863            true,
15864            1,
15865            window,
15866            cx,
15867        );
15868        assert_eq!(
15869            editor.selections.ranges(cx),
15870            [
15871                Point::new(1, 3)..Point::new(1, 3),
15872                Point::new(2, 1)..Point::new(2, 1),
15873            ]
15874        );
15875        editor
15876    });
15877
15878    // Refreshing selections is a no-op when excerpts haven't changed.
15879    _ = editor.update(cx, |editor, window, cx| {
15880        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15881        assert_eq!(
15882            editor.selections.ranges(cx),
15883            [
15884                Point::new(1, 3)..Point::new(1, 3),
15885                Point::new(2, 1)..Point::new(2, 1),
15886            ]
15887        );
15888    });
15889
15890    multibuffer.update(cx, |multibuffer, cx| {
15891        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15892    });
15893    _ = editor.update(cx, |editor, window, cx| {
15894        // Removing an excerpt causes the first selection to become degenerate.
15895        assert_eq!(
15896            editor.selections.ranges(cx),
15897            [
15898                Point::new(0, 0)..Point::new(0, 0),
15899                Point::new(0, 1)..Point::new(0, 1)
15900            ]
15901        );
15902
15903        // Refreshing selections will relocate the first selection to the original buffer
15904        // location.
15905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15906        assert_eq!(
15907            editor.selections.ranges(cx),
15908            [
15909                Point::new(0, 1)..Point::new(0, 1),
15910                Point::new(0, 3)..Point::new(0, 3)
15911            ]
15912        );
15913        assert!(editor.selections.pending_anchor().is_some());
15914    });
15915}
15916
15917#[gpui::test]
15918fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
15919    init_test(cx, |_| {});
15920
15921    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
15922    let mut excerpt1_id = None;
15923    let multibuffer = cx.new(|cx| {
15924        let mut multibuffer = MultiBuffer::new(ReadWrite);
15925        excerpt1_id = multibuffer
15926            .push_excerpts(
15927                buffer.clone(),
15928                [
15929                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
15930                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
15931                ],
15932                cx,
15933            )
15934            .into_iter()
15935            .next();
15936        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
15937        multibuffer
15938    });
15939
15940    let editor = cx.add_window(|window, cx| {
15941        let mut editor = build_editor(multibuffer.clone(), window, cx);
15942        let snapshot = editor.snapshot(window, cx);
15943        editor.begin_selection(
15944            Point::new(1, 3).to_display_point(&snapshot),
15945            false,
15946            1,
15947            window,
15948            cx,
15949        );
15950        assert_eq!(
15951            editor.selections.ranges(cx),
15952            [Point::new(1, 3)..Point::new(1, 3)]
15953        );
15954        editor
15955    });
15956
15957    multibuffer.update(cx, |multibuffer, cx| {
15958        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
15959    });
15960    _ = editor.update(cx, |editor, window, cx| {
15961        assert_eq!(
15962            editor.selections.ranges(cx),
15963            [Point::new(0, 0)..Point::new(0, 0)]
15964        );
15965
15966        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
15967        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
15968        assert_eq!(
15969            editor.selections.ranges(cx),
15970            [Point::new(0, 3)..Point::new(0, 3)]
15971        );
15972        assert!(editor.selections.pending_anchor().is_some());
15973    });
15974}
15975
15976#[gpui::test]
15977async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
15978    init_test(cx, |_| {});
15979
15980    let language = Arc::new(
15981        Language::new(
15982            LanguageConfig {
15983                brackets: BracketPairConfig {
15984                    pairs: vec![
15985                        BracketPair {
15986                            start: "{".to_string(),
15987                            end: "}".to_string(),
15988                            close: true,
15989                            surround: true,
15990                            newline: true,
15991                        },
15992                        BracketPair {
15993                            start: "/* ".to_string(),
15994                            end: " */".to_string(),
15995                            close: true,
15996                            surround: true,
15997                            newline: true,
15998                        },
15999                    ],
16000                    ..Default::default()
16001                },
16002                ..Default::default()
16003            },
16004            Some(tree_sitter_rust::LANGUAGE.into()),
16005        )
16006        .with_indents_query("")
16007        .unwrap(),
16008    );
16009
16010    let text = concat!(
16011        "{   }\n",     //
16012        "  x\n",       //
16013        "  /*   */\n", //
16014        "x\n",         //
16015        "{{} }\n",     //
16016    );
16017
16018    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16019    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16020    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16021    editor
16022        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16023        .await;
16024
16025    editor.update_in(cx, |editor, window, cx| {
16026        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16027            s.select_display_ranges([
16028                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16029                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16030                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16031            ])
16032        });
16033        editor.newline(&Newline, window, cx);
16034
16035        assert_eq!(
16036            editor.buffer().read(cx).read(cx).text(),
16037            concat!(
16038                "{ \n",    // Suppress rustfmt
16039                "\n",      //
16040                "}\n",     //
16041                "  x\n",   //
16042                "  /* \n", //
16043                "  \n",    //
16044                "  */\n",  //
16045                "x\n",     //
16046                "{{} \n",  //
16047                "}\n",     //
16048            )
16049        );
16050    });
16051}
16052
16053#[gpui::test]
16054fn test_highlighted_ranges(cx: &mut TestAppContext) {
16055    init_test(cx, |_| {});
16056
16057    let editor = cx.add_window(|window, cx| {
16058        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16059        build_editor(buffer, window, cx)
16060    });
16061
16062    _ = editor.update(cx, |editor, window, cx| {
16063        struct Type1;
16064        struct Type2;
16065
16066        let buffer = editor.buffer.read(cx).snapshot(cx);
16067
16068        let anchor_range =
16069            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16070
16071        editor.highlight_background::<Type1>(
16072            &[
16073                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16074                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16075                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16076                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16077            ],
16078            |_| Hsla::red(),
16079            cx,
16080        );
16081        editor.highlight_background::<Type2>(
16082            &[
16083                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16084                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16085                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16086                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16087            ],
16088            |_| Hsla::green(),
16089            cx,
16090        );
16091
16092        let snapshot = editor.snapshot(window, cx);
16093        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16094            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16095            &snapshot,
16096            cx.theme(),
16097        );
16098        assert_eq!(
16099            highlighted_ranges,
16100            &[
16101                (
16102                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16103                    Hsla::green(),
16104                ),
16105                (
16106                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16107                    Hsla::red(),
16108                ),
16109                (
16110                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16111                    Hsla::green(),
16112                ),
16113                (
16114                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16115                    Hsla::red(),
16116                ),
16117            ]
16118        );
16119        assert_eq!(
16120            editor.sorted_background_highlights_in_range(
16121                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16122                &snapshot,
16123                cx.theme(),
16124            ),
16125            &[(
16126                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16127                Hsla::red(),
16128            )]
16129        );
16130    });
16131}
16132
16133#[gpui::test]
16134async fn test_following(cx: &mut TestAppContext) {
16135    init_test(cx, |_| {});
16136
16137    let fs = FakeFs::new(cx.executor());
16138    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16139
16140    let buffer = project.update(cx, |project, cx| {
16141        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16142        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16143    });
16144    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16145    let follower = cx.update(|cx| {
16146        cx.open_window(
16147            WindowOptions {
16148                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16149                    gpui::Point::new(px(0.), px(0.)),
16150                    gpui::Point::new(px(10.), px(80.)),
16151                ))),
16152                ..Default::default()
16153            },
16154            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16155        )
16156        .unwrap()
16157    });
16158
16159    let is_still_following = Rc::new(RefCell::new(true));
16160    let follower_edit_event_count = Rc::new(RefCell::new(0));
16161    let pending_update = Rc::new(RefCell::new(None));
16162    let leader_entity = leader.root(cx).unwrap();
16163    let follower_entity = follower.root(cx).unwrap();
16164    _ = follower.update(cx, {
16165        let update = pending_update.clone();
16166        let is_still_following = is_still_following.clone();
16167        let follower_edit_event_count = follower_edit_event_count.clone();
16168        |_, window, cx| {
16169            cx.subscribe_in(
16170                &leader_entity,
16171                window,
16172                move |_, leader, event, window, cx| {
16173                    leader.read(cx).add_event_to_update_proto(
16174                        event,
16175                        &mut update.borrow_mut(),
16176                        window,
16177                        cx,
16178                    );
16179                },
16180            )
16181            .detach();
16182
16183            cx.subscribe_in(
16184                &follower_entity,
16185                window,
16186                move |_, _, event: &EditorEvent, _window, _cx| {
16187                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16188                        *is_still_following.borrow_mut() = false;
16189                    }
16190
16191                    if let EditorEvent::BufferEdited = event {
16192                        *follower_edit_event_count.borrow_mut() += 1;
16193                    }
16194                },
16195            )
16196            .detach();
16197        }
16198    });
16199
16200    // Update the selections only
16201    _ = leader.update(cx, |leader, window, cx| {
16202        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16203            s.select_ranges([1..1])
16204        });
16205    });
16206    follower
16207        .update(cx, |follower, window, cx| {
16208            follower.apply_update_proto(
16209                &project,
16210                pending_update.borrow_mut().take().unwrap(),
16211                window,
16212                cx,
16213            )
16214        })
16215        .unwrap()
16216        .await
16217        .unwrap();
16218    _ = follower.update(cx, |follower, _, cx| {
16219        assert_eq!(follower.selections.ranges(cx), vec![1..1]);
16220    });
16221    assert!(*is_still_following.borrow());
16222    assert_eq!(*follower_edit_event_count.borrow(), 0);
16223
16224    // Update the scroll position only
16225    _ = leader.update(cx, |leader, window, cx| {
16226        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16227    });
16228    follower
16229        .update(cx, |follower, window, cx| {
16230            follower.apply_update_proto(
16231                &project,
16232                pending_update.borrow_mut().take().unwrap(),
16233                window,
16234                cx,
16235            )
16236        })
16237        .unwrap()
16238        .await
16239        .unwrap();
16240    assert_eq!(
16241        follower
16242            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16243            .unwrap(),
16244        gpui::Point::new(1.5, 3.5)
16245    );
16246    assert!(*is_still_following.borrow());
16247    assert_eq!(*follower_edit_event_count.borrow(), 0);
16248
16249    // Update the selections and scroll position. The follower's scroll position is updated
16250    // via autoscroll, not via the leader's exact scroll position.
16251    _ = leader.update(cx, |leader, window, cx| {
16252        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16253            s.select_ranges([0..0])
16254        });
16255        leader.request_autoscroll(Autoscroll::newest(), cx);
16256        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16257    });
16258    follower
16259        .update(cx, |follower, window, cx| {
16260            follower.apply_update_proto(
16261                &project,
16262                pending_update.borrow_mut().take().unwrap(),
16263                window,
16264                cx,
16265            )
16266        })
16267        .unwrap()
16268        .await
16269        .unwrap();
16270    _ = follower.update(cx, |follower, _, cx| {
16271        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
16272        assert_eq!(follower.selections.ranges(cx), vec![0..0]);
16273    });
16274    assert!(*is_still_following.borrow());
16275
16276    // Creating a pending selection that precedes another selection
16277    _ = leader.update(cx, |leader, window, cx| {
16278        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16279            s.select_ranges([1..1])
16280        });
16281        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
16282    });
16283    follower
16284        .update(cx, |follower, window, cx| {
16285            follower.apply_update_proto(
16286                &project,
16287                pending_update.borrow_mut().take().unwrap(),
16288                window,
16289                cx,
16290            )
16291        })
16292        .unwrap()
16293        .await
16294        .unwrap();
16295    _ = follower.update(cx, |follower, _, cx| {
16296        assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]);
16297    });
16298    assert!(*is_still_following.borrow());
16299
16300    // Extend the pending selection so that it surrounds another selection
16301    _ = leader.update(cx, |leader, window, cx| {
16302        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
16303    });
16304    follower
16305        .update(cx, |follower, window, cx| {
16306            follower.apply_update_proto(
16307                &project,
16308                pending_update.borrow_mut().take().unwrap(),
16309                window,
16310                cx,
16311            )
16312        })
16313        .unwrap()
16314        .await
16315        .unwrap();
16316    _ = follower.update(cx, |follower, _, cx| {
16317        assert_eq!(follower.selections.ranges(cx), vec![0..2]);
16318    });
16319
16320    // Scrolling locally breaks the follow
16321    _ = follower.update(cx, |follower, window, cx| {
16322        let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0);
16323        follower.set_scroll_anchor(
16324            ScrollAnchor {
16325                anchor: top_anchor,
16326                offset: gpui::Point::new(0.0, 0.5),
16327            },
16328            window,
16329            cx,
16330        );
16331    });
16332    assert!(!(*is_still_following.borrow()));
16333}
16334
16335#[gpui::test]
16336async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
16337    init_test(cx, |_| {});
16338
16339    let fs = FakeFs::new(cx.executor());
16340    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16341    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16342    let pane = workspace
16343        .update(cx, |workspace, _, _| workspace.active_pane().clone())
16344        .unwrap();
16345
16346    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
16347
16348    let leader = pane.update_in(cx, |_, window, cx| {
16349        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
16350        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
16351    });
16352
16353    // Start following the editor when it has no excerpts.
16354    let mut state_message =
16355        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16356    let workspace_entity = workspace.root(cx).unwrap();
16357    let follower_1 = cx
16358        .update_window(*workspace.deref(), |_, window, cx| {
16359            Editor::from_state_proto(
16360                workspace_entity,
16361                ViewId {
16362                    creator: CollaboratorId::PeerId(PeerId::default()),
16363                    id: 0,
16364                },
16365                &mut state_message,
16366                window,
16367                cx,
16368            )
16369        })
16370        .unwrap()
16371        .unwrap()
16372        .await
16373        .unwrap();
16374
16375    let update_message = Rc::new(RefCell::new(None));
16376    follower_1.update_in(cx, {
16377        let update = update_message.clone();
16378        |_, window, cx| {
16379            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
16380                leader.read(cx).add_event_to_update_proto(
16381                    event,
16382                    &mut update.borrow_mut(),
16383                    window,
16384                    cx,
16385                );
16386            })
16387            .detach();
16388        }
16389    });
16390
16391    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
16392        (
16393            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
16394            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
16395        )
16396    });
16397
16398    // Insert some excerpts.
16399    leader.update(cx, |leader, cx| {
16400        leader.buffer.update(cx, |multibuffer, cx| {
16401            multibuffer.set_excerpts_for_path(
16402                PathKey::namespaced(1, Arc::from(Path::new("b.txt"))),
16403                buffer_1.clone(),
16404                vec![
16405                    Point::row_range(0..3),
16406                    Point::row_range(1..6),
16407                    Point::row_range(12..15),
16408                ],
16409                0,
16410                cx,
16411            );
16412            multibuffer.set_excerpts_for_path(
16413                PathKey::namespaced(1, Arc::from(Path::new("a.txt"))),
16414                buffer_2.clone(),
16415                vec![Point::row_range(0..6), Point::row_range(8..12)],
16416                0,
16417                cx,
16418            );
16419        });
16420    });
16421
16422    // Apply the update of adding the excerpts.
16423    follower_1
16424        .update_in(cx, |follower, window, cx| {
16425            follower.apply_update_proto(
16426                &project,
16427                update_message.borrow().clone().unwrap(),
16428                window,
16429                cx,
16430            )
16431        })
16432        .await
16433        .unwrap();
16434    assert_eq!(
16435        follower_1.update(cx, |editor, cx| editor.text(cx)),
16436        leader.update(cx, |editor, cx| editor.text(cx))
16437    );
16438    update_message.borrow_mut().take();
16439
16440    // Start following separately after it already has excerpts.
16441    let mut state_message =
16442        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
16443    let workspace_entity = workspace.root(cx).unwrap();
16444    let follower_2 = cx
16445        .update_window(*workspace.deref(), |_, window, cx| {
16446            Editor::from_state_proto(
16447                workspace_entity,
16448                ViewId {
16449                    creator: CollaboratorId::PeerId(PeerId::default()),
16450                    id: 0,
16451                },
16452                &mut state_message,
16453                window,
16454                cx,
16455            )
16456        })
16457        .unwrap()
16458        .unwrap()
16459        .await
16460        .unwrap();
16461    assert_eq!(
16462        follower_2.update(cx, |editor, cx| editor.text(cx)),
16463        leader.update(cx, |editor, cx| editor.text(cx))
16464    );
16465
16466    // Remove some excerpts.
16467    leader.update(cx, |leader, cx| {
16468        leader.buffer.update(cx, |multibuffer, cx| {
16469            let excerpt_ids = multibuffer.excerpt_ids();
16470            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
16471            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
16472        });
16473    });
16474
16475    // Apply the update of removing the excerpts.
16476    follower_1
16477        .update_in(cx, |follower, window, cx| {
16478            follower.apply_update_proto(
16479                &project,
16480                update_message.borrow().clone().unwrap(),
16481                window,
16482                cx,
16483            )
16484        })
16485        .await
16486        .unwrap();
16487    follower_2
16488        .update_in(cx, |follower, window, cx| {
16489            follower.apply_update_proto(
16490                &project,
16491                update_message.borrow().clone().unwrap(),
16492                window,
16493                cx,
16494            )
16495        })
16496        .await
16497        .unwrap();
16498    update_message.borrow_mut().take();
16499    assert_eq!(
16500        follower_1.update(cx, |editor, cx| editor.text(cx)),
16501        leader.update(cx, |editor, cx| editor.text(cx))
16502    );
16503}
16504
16505#[gpui::test]
16506async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16507    init_test(cx, |_| {});
16508
16509    let mut cx = EditorTestContext::new(cx).await;
16510    let lsp_store =
16511        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
16512
16513    cx.set_state(indoc! {"
16514        ˇfn func(abc def: i32) -> u32 {
16515        }
16516    "});
16517
16518    cx.update(|_, cx| {
16519        lsp_store.update(cx, |lsp_store, cx| {
16520            lsp_store
16521                .update_diagnostics(
16522                    LanguageServerId(0),
16523                    lsp::PublishDiagnosticsParams {
16524                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
16525                        version: None,
16526                        diagnostics: vec![
16527                            lsp::Diagnostic {
16528                                range: lsp::Range::new(
16529                                    lsp::Position::new(0, 11),
16530                                    lsp::Position::new(0, 12),
16531                                ),
16532                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16533                                ..Default::default()
16534                            },
16535                            lsp::Diagnostic {
16536                                range: lsp::Range::new(
16537                                    lsp::Position::new(0, 12),
16538                                    lsp::Position::new(0, 15),
16539                                ),
16540                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16541                                ..Default::default()
16542                            },
16543                            lsp::Diagnostic {
16544                                range: lsp::Range::new(
16545                                    lsp::Position::new(0, 25),
16546                                    lsp::Position::new(0, 28),
16547                                ),
16548                                severity: Some(lsp::DiagnosticSeverity::ERROR),
16549                                ..Default::default()
16550                            },
16551                        ],
16552                    },
16553                    None,
16554                    DiagnosticSourceKind::Pushed,
16555                    &[],
16556                    cx,
16557                )
16558                .unwrap()
16559        });
16560    });
16561
16562    executor.run_until_parked();
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    cx.update_editor(|editor, window, cx| {
16592        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
16593    });
16594
16595    cx.assert_editor_state(indoc! {"
16596        fn func(abc def: i32) -> ˇu32 {
16597        }
16598    "});
16599}
16600
16601#[gpui::test]
16602async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
16603    init_test(cx, |_| {});
16604
16605    let mut cx = EditorTestContext::new(cx).await;
16606
16607    let diff_base = r#"
16608        use some::mod;
16609
16610        const A: u32 = 42;
16611
16612        fn main() {
16613            println!("hello");
16614
16615            println!("world");
16616        }
16617        "#
16618    .unindent();
16619
16620    // Edits are modified, removed, modified, added
16621    cx.set_state(
16622        &r#"
16623        use some::modified;
16624
16625        ˇ
16626        fn main() {
16627            println!("hello there");
16628
16629            println!("around the");
16630            println!("world");
16631        }
16632        "#
16633        .unindent(),
16634    );
16635
16636    cx.set_head_text(&diff_base);
16637    executor.run_until_parked();
16638
16639    cx.update_editor(|editor, window, cx| {
16640        //Wrap around the bottom of the buffer
16641        for _ in 0..3 {
16642            editor.go_to_next_hunk(&GoToHunk, window, cx);
16643        }
16644    });
16645
16646    cx.assert_editor_state(
16647        &r#"
16648        ˇuse some::modified;
16649
16650
16651        fn main() {
16652            println!("hello there");
16653
16654            println!("around the");
16655            println!("world");
16656        }
16657        "#
16658        .unindent(),
16659    );
16660
16661    cx.update_editor(|editor, window, cx| {
16662        //Wrap around the top of the buffer
16663        for _ in 0..2 {
16664            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16665        }
16666    });
16667
16668    cx.assert_editor_state(
16669        &r#"
16670        use some::modified;
16671
16672
16673        fn main() {
16674        ˇ    println!("hello there");
16675
16676            println!("around the");
16677            println!("world");
16678        }
16679        "#
16680        .unindent(),
16681    );
16682
16683    cx.update_editor(|editor, window, cx| {
16684        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16685    });
16686
16687    cx.assert_editor_state(
16688        &r#"
16689        use some::modified;
16690
16691        ˇ
16692        fn main() {
16693            println!("hello there");
16694
16695            println!("around the");
16696            println!("world");
16697        }
16698        "#
16699        .unindent(),
16700    );
16701
16702    cx.update_editor(|editor, window, cx| {
16703        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16704    });
16705
16706    cx.assert_editor_state(
16707        &r#"
16708        ˇuse some::modified;
16709
16710
16711        fn main() {
16712            println!("hello there");
16713
16714            println!("around the");
16715            println!("world");
16716        }
16717        "#
16718        .unindent(),
16719    );
16720
16721    cx.update_editor(|editor, window, cx| {
16722        for _ in 0..2 {
16723            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
16724        }
16725    });
16726
16727    cx.assert_editor_state(
16728        &r#"
16729        use some::modified;
16730
16731
16732        fn main() {
16733        ˇ    println!("hello there");
16734
16735            println!("around the");
16736            println!("world");
16737        }
16738        "#
16739        .unindent(),
16740    );
16741
16742    cx.update_editor(|editor, window, cx| {
16743        editor.fold(&Fold, window, cx);
16744    });
16745
16746    cx.update_editor(|editor, window, cx| {
16747        editor.go_to_next_hunk(&GoToHunk, window, cx);
16748    });
16749
16750    cx.assert_editor_state(
16751        &r#"
16752        ˇuse some::modified;
16753
16754
16755        fn main() {
16756            println!("hello there");
16757
16758            println!("around the");
16759            println!("world");
16760        }
16761        "#
16762        .unindent(),
16763    );
16764}
16765
16766#[test]
16767fn test_split_words() {
16768    fn split(text: &str) -> Vec<&str> {
16769        split_words(text).collect()
16770    }
16771
16772    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
16773    assert_eq!(split("hello_world"), &["hello_", "world"]);
16774    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
16775    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
16776    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
16777    assert_eq!(split("helloworld"), &["helloworld"]);
16778
16779    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
16780}
16781
16782#[gpui::test]
16783async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
16784    init_test(cx, |_| {});
16785
16786    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
16787    let mut assert = |before, after| {
16788        let _state_context = cx.set_state(before);
16789        cx.run_until_parked();
16790        cx.update_editor(|editor, window, cx| {
16791            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
16792        });
16793        cx.run_until_parked();
16794        cx.assert_editor_state(after);
16795    };
16796
16797    // Outside bracket jumps to outside of matching bracket
16798    assert("console.logˇ(var);", "console.log(var)ˇ;");
16799    assert("console.log(var)ˇ;", "console.logˇ(var);");
16800
16801    // Inside bracket jumps to inside of matching bracket
16802    assert("console.log(ˇvar);", "console.log(varˇ);");
16803    assert("console.log(varˇ);", "console.log(ˇvar);");
16804
16805    // When outside a bracket and inside, favor jumping to the inside bracket
16806    assert(
16807        "console.log('foo', [1, 2, 3]ˇ);",
16808        "console.log(ˇ'foo', [1, 2, 3]);",
16809    );
16810    assert(
16811        "console.log(ˇ'foo', [1, 2, 3]);",
16812        "console.log('foo', [1, 2, 3]ˇ);",
16813    );
16814
16815    // Bias forward if two options are equally likely
16816    assert(
16817        "let result = curried_fun()ˇ();",
16818        "let result = curried_fun()()ˇ;",
16819    );
16820
16821    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
16822    assert(
16823        indoc! {"
16824            function test() {
16825                console.log('test')ˇ
16826            }"},
16827        indoc! {"
16828            function test() {
16829                console.logˇ('test')
16830            }"},
16831    );
16832}
16833
16834#[gpui::test]
16835async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
16836    init_test(cx, |_| {});
16837
16838    let fs = FakeFs::new(cx.executor());
16839    fs.insert_tree(
16840        path!("/a"),
16841        json!({
16842            "main.rs": "fn main() { let a = 5; }",
16843            "other.rs": "// Test file",
16844        }),
16845    )
16846    .await;
16847    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
16848
16849    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
16850    language_registry.add(Arc::new(Language::new(
16851        LanguageConfig {
16852            name: "Rust".into(),
16853            matcher: LanguageMatcher {
16854                path_suffixes: vec!["rs".to_string()],
16855                ..Default::default()
16856            },
16857            brackets: BracketPairConfig {
16858                pairs: vec![BracketPair {
16859                    start: "{".to_string(),
16860                    end: "}".to_string(),
16861                    close: true,
16862                    surround: true,
16863                    newline: true,
16864                }],
16865                disabled_scopes_by_bracket_ix: Vec::new(),
16866            },
16867            ..Default::default()
16868        },
16869        Some(tree_sitter_rust::LANGUAGE.into()),
16870    )));
16871    let mut fake_servers = language_registry.register_fake_lsp(
16872        "Rust",
16873        FakeLspAdapter {
16874            capabilities: lsp::ServerCapabilities {
16875                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16876                    first_trigger_character: "{".to_string(),
16877                    more_trigger_character: None,
16878                }),
16879                ..Default::default()
16880            },
16881            ..Default::default()
16882        },
16883    );
16884
16885    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
16886
16887    let cx = &mut VisualTestContext::from_window(*workspace, cx);
16888
16889    let worktree_id = workspace
16890        .update(cx, |workspace, _, cx| {
16891            workspace.project().update(cx, |project, cx| {
16892                project.worktrees(cx).next().unwrap().read(cx).id()
16893            })
16894        })
16895        .unwrap();
16896
16897    let buffer = project
16898        .update(cx, |project, cx| {
16899            project.open_local_buffer(path!("/a/main.rs"), cx)
16900        })
16901        .await
16902        .unwrap();
16903    let editor_handle = workspace
16904        .update(cx, |workspace, window, cx| {
16905            workspace.open_path((worktree_id, "main.rs"), None, true, window, cx)
16906        })
16907        .unwrap()
16908        .await
16909        .unwrap()
16910        .downcast::<Editor>()
16911        .unwrap();
16912
16913    cx.executor().start_waiting();
16914    let fake_server = fake_servers.next().await.unwrap();
16915
16916    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
16917        |params, _| async move {
16918            assert_eq!(
16919                params.text_document_position.text_document.uri,
16920                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
16921            );
16922            assert_eq!(
16923                params.text_document_position.position,
16924                lsp::Position::new(0, 21),
16925            );
16926
16927            Ok(Some(vec![lsp::TextEdit {
16928                new_text: "]".to_string(),
16929                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
16930            }]))
16931        },
16932    );
16933
16934    editor_handle.update_in(cx, |editor, window, cx| {
16935        window.focus(&editor.focus_handle(cx));
16936        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16937            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
16938        });
16939        editor.handle_input("{", window, cx);
16940    });
16941
16942    cx.executor().run_until_parked();
16943
16944    buffer.update(cx, |buffer, _| {
16945        assert_eq!(
16946            buffer.text(),
16947            "fn main() { let a = {5}; }",
16948            "No extra braces from on type formatting should appear in the buffer"
16949        )
16950    });
16951}
16952
16953#[gpui::test(iterations = 20, seeds(31))]
16954async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
16955    init_test(cx, |_| {});
16956
16957    let mut cx = EditorLspTestContext::new_rust(
16958        lsp::ServerCapabilities {
16959            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
16960                first_trigger_character: ".".to_string(),
16961                more_trigger_character: None,
16962            }),
16963            ..Default::default()
16964        },
16965        cx,
16966    )
16967    .await;
16968
16969    cx.update_buffer(|buffer, _| {
16970        // This causes autoindent to be async.
16971        buffer.set_sync_parse_timeout(Duration::ZERO)
16972    });
16973
16974    cx.set_state("fn c() {\n    d()ˇ\n}\n");
16975    cx.simulate_keystroke("\n");
16976    cx.run_until_parked();
16977
16978    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
16979    let mut request =
16980        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
16981            let buffer_cloned = buffer_cloned.clone();
16982            async move {
16983                buffer_cloned.update(&mut cx, |buffer, _| {
16984                    assert_eq!(
16985                        buffer.text(),
16986                        "fn c() {\n    d()\n        .\n}\n",
16987                        "OnTypeFormatting should triggered after autoindent applied"
16988                    )
16989                })?;
16990
16991                Ok(Some(vec![]))
16992            }
16993        });
16994
16995    cx.simulate_keystroke(".");
16996    cx.run_until_parked();
16997
16998    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
16999    assert!(request.next().await.is_some());
17000    request.close();
17001    assert!(request.next().await.is_none());
17002}
17003
17004#[gpui::test]
17005async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17006    init_test(cx, |_| {});
17007
17008    let fs = FakeFs::new(cx.executor());
17009    fs.insert_tree(
17010        path!("/a"),
17011        json!({
17012            "main.rs": "fn main() { let a = 5; }",
17013            "other.rs": "// Test file",
17014        }),
17015    )
17016    .await;
17017
17018    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17019
17020    let server_restarts = Arc::new(AtomicUsize::new(0));
17021    let closure_restarts = Arc::clone(&server_restarts);
17022    let language_server_name = "test language server";
17023    let language_name: LanguageName = "Rust".into();
17024
17025    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17026    language_registry.add(Arc::new(Language::new(
17027        LanguageConfig {
17028            name: language_name.clone(),
17029            matcher: LanguageMatcher {
17030                path_suffixes: vec!["rs".to_string()],
17031                ..Default::default()
17032            },
17033            ..Default::default()
17034        },
17035        Some(tree_sitter_rust::LANGUAGE.into()),
17036    )));
17037    let mut fake_servers = language_registry.register_fake_lsp(
17038        "Rust",
17039        FakeLspAdapter {
17040            name: language_server_name,
17041            initialization_options: Some(json!({
17042                "testOptionValue": true
17043            })),
17044            initializer: Some(Box::new(move |fake_server| {
17045                let task_restarts = Arc::clone(&closure_restarts);
17046                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17047                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17048                    futures::future::ready(Ok(()))
17049                });
17050            })),
17051            ..Default::default()
17052        },
17053    );
17054
17055    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17056    let _buffer = project
17057        .update(cx, |project, cx| {
17058            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17059        })
17060        .await
17061        .unwrap();
17062    let _fake_server = fake_servers.next().await.unwrap();
17063    update_test_language_settings(cx, |language_settings| {
17064        language_settings.languages.0.insert(
17065            language_name.clone().0,
17066            LanguageSettingsContent {
17067                tab_size: NonZeroU32::new(8),
17068                ..Default::default()
17069            },
17070        );
17071    });
17072    cx.executor().run_until_parked();
17073    assert_eq!(
17074        server_restarts.load(atomic::Ordering::Acquire),
17075        0,
17076        "Should not restart LSP server on an unrelated change"
17077    );
17078
17079    update_test_project_settings(cx, |project_settings| {
17080        project_settings.lsp.insert(
17081            "Some other server name".into(),
17082            LspSettings {
17083                binary: None,
17084                settings: None,
17085                initialization_options: Some(json!({
17086                    "some other init value": false
17087                })),
17088                enable_lsp_tasks: false,
17089                fetch: None,
17090            },
17091        );
17092    });
17093    cx.executor().run_until_parked();
17094    assert_eq!(
17095        server_restarts.load(atomic::Ordering::Acquire),
17096        0,
17097        "Should not restart LSP server on an unrelated LSP settings change"
17098    );
17099
17100    update_test_project_settings(cx, |project_settings| {
17101        project_settings.lsp.insert(
17102            language_server_name.into(),
17103            LspSettings {
17104                binary: None,
17105                settings: None,
17106                initialization_options: Some(json!({
17107                    "anotherInitValue": false
17108                })),
17109                enable_lsp_tasks: false,
17110                fetch: None,
17111            },
17112        );
17113    });
17114    cx.executor().run_until_parked();
17115    assert_eq!(
17116        server_restarts.load(atomic::Ordering::Acquire),
17117        1,
17118        "Should restart LSP server on a related LSP settings change"
17119    );
17120
17121    update_test_project_settings(cx, |project_settings| {
17122        project_settings.lsp.insert(
17123            language_server_name.into(),
17124            LspSettings {
17125                binary: None,
17126                settings: None,
17127                initialization_options: Some(json!({
17128                    "anotherInitValue": false
17129                })),
17130                enable_lsp_tasks: false,
17131                fetch: None,
17132            },
17133        );
17134    });
17135    cx.executor().run_until_parked();
17136    assert_eq!(
17137        server_restarts.load(atomic::Ordering::Acquire),
17138        1,
17139        "Should not restart LSP server on a related LSP settings change that is the same"
17140    );
17141
17142    update_test_project_settings(cx, |project_settings| {
17143        project_settings.lsp.insert(
17144            language_server_name.into(),
17145            LspSettings {
17146                binary: None,
17147                settings: None,
17148                initialization_options: None,
17149                enable_lsp_tasks: false,
17150                fetch: None,
17151            },
17152        );
17153    });
17154    cx.executor().run_until_parked();
17155    assert_eq!(
17156        server_restarts.load(atomic::Ordering::Acquire),
17157        2,
17158        "Should restart LSP server on another related LSP settings change"
17159    );
17160}
17161
17162#[gpui::test]
17163async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
17164    init_test(cx, |_| {});
17165
17166    let mut cx = EditorLspTestContext::new_rust(
17167        lsp::ServerCapabilities {
17168            completion_provider: Some(lsp::CompletionOptions {
17169                trigger_characters: Some(vec![".".to_string()]),
17170                resolve_provider: Some(true),
17171                ..Default::default()
17172            }),
17173            ..Default::default()
17174        },
17175        cx,
17176    )
17177    .await;
17178
17179    cx.set_state("fn main() { let a = 2ˇ; }");
17180    cx.simulate_keystroke(".");
17181    let completion_item = lsp::CompletionItem {
17182        label: "some".into(),
17183        kind: Some(lsp::CompletionItemKind::SNIPPET),
17184        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
17185        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
17186            kind: lsp::MarkupKind::Markdown,
17187            value: "```rust\nSome(2)\n```".to_string(),
17188        })),
17189        deprecated: Some(false),
17190        sort_text: Some("fffffff2".to_string()),
17191        filter_text: Some("some".to_string()),
17192        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
17193        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17194            range: lsp::Range {
17195                start: lsp::Position {
17196                    line: 0,
17197                    character: 22,
17198                },
17199                end: lsp::Position {
17200                    line: 0,
17201                    character: 22,
17202                },
17203            },
17204            new_text: "Some(2)".to_string(),
17205        })),
17206        additional_text_edits: Some(vec![lsp::TextEdit {
17207            range: lsp::Range {
17208                start: lsp::Position {
17209                    line: 0,
17210                    character: 20,
17211                },
17212                end: lsp::Position {
17213                    line: 0,
17214                    character: 22,
17215                },
17216            },
17217            new_text: "".to_string(),
17218        }]),
17219        ..Default::default()
17220    };
17221
17222    let closure_completion_item = completion_item.clone();
17223    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17224        let task_completion_item = closure_completion_item.clone();
17225        async move {
17226            Ok(Some(lsp::CompletionResponse::Array(vec![
17227                task_completion_item,
17228            ])))
17229        }
17230    });
17231
17232    request.next().await;
17233
17234    cx.condition(|editor, _| editor.context_menu_visible())
17235        .await;
17236    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
17237        editor
17238            .confirm_completion(&ConfirmCompletion::default(), window, cx)
17239            .unwrap()
17240    });
17241    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
17242
17243    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
17244        let task_completion_item = completion_item.clone();
17245        async move { Ok(task_completion_item) }
17246    })
17247    .next()
17248    .await
17249    .unwrap();
17250    apply_additional_edits.await.unwrap();
17251    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
17252}
17253
17254#[gpui::test]
17255async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
17256    init_test(cx, |_| {});
17257
17258    let mut cx = EditorLspTestContext::new_rust(
17259        lsp::ServerCapabilities {
17260            completion_provider: Some(lsp::CompletionOptions {
17261                trigger_characters: Some(vec![".".to_string()]),
17262                resolve_provider: Some(true),
17263                ..Default::default()
17264            }),
17265            ..Default::default()
17266        },
17267        cx,
17268    )
17269    .await;
17270
17271    cx.set_state("fn main() { let a = 2ˇ; }");
17272    cx.simulate_keystroke(".");
17273
17274    let item1 = lsp::CompletionItem {
17275        label: "method id()".to_string(),
17276        filter_text: Some("id".to_string()),
17277        detail: None,
17278        documentation: None,
17279        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17280            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17281            new_text: ".id".to_string(),
17282        })),
17283        ..lsp::CompletionItem::default()
17284    };
17285
17286    let item2 = lsp::CompletionItem {
17287        label: "other".to_string(),
17288        filter_text: Some("other".to_string()),
17289        detail: None,
17290        documentation: None,
17291        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17292            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17293            new_text: ".other".to_string(),
17294        })),
17295        ..lsp::CompletionItem::default()
17296    };
17297
17298    let item1 = item1.clone();
17299    cx.set_request_handler::<lsp::request::Completion, _, _>({
17300        let item1 = item1.clone();
17301        move |_, _, _| {
17302            let item1 = item1.clone();
17303            let item2 = item2.clone();
17304            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
17305        }
17306    })
17307    .next()
17308    .await;
17309
17310    cx.condition(|editor, _| editor.context_menu_visible())
17311        .await;
17312    cx.update_editor(|editor, _, _| {
17313        let context_menu = editor.context_menu.borrow_mut();
17314        let context_menu = context_menu
17315            .as_ref()
17316            .expect("Should have the context menu deployed");
17317        match context_menu {
17318            CodeContextMenu::Completions(completions_menu) => {
17319                let completions = completions_menu.completions.borrow_mut();
17320                assert_eq!(
17321                    completions
17322                        .iter()
17323                        .map(|completion| &completion.label.text)
17324                        .collect::<Vec<_>>(),
17325                    vec!["method id()", "other"]
17326                )
17327            }
17328            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17329        }
17330    });
17331
17332    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
17333        let item1 = item1.clone();
17334        move |_, item_to_resolve, _| {
17335            let item1 = item1.clone();
17336            async move {
17337                if item1 == item_to_resolve {
17338                    Ok(lsp::CompletionItem {
17339                        label: "method id()".to_string(),
17340                        filter_text: Some("id".to_string()),
17341                        detail: Some("Now resolved!".to_string()),
17342                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
17343                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17344                            range: lsp::Range::new(
17345                                lsp::Position::new(0, 22),
17346                                lsp::Position::new(0, 22),
17347                            ),
17348                            new_text: ".id".to_string(),
17349                        })),
17350                        ..lsp::CompletionItem::default()
17351                    })
17352                } else {
17353                    Ok(item_to_resolve)
17354                }
17355            }
17356        }
17357    })
17358    .next()
17359    .await
17360    .unwrap();
17361    cx.run_until_parked();
17362
17363    cx.update_editor(|editor, window, cx| {
17364        editor.context_menu_next(&Default::default(), window, cx);
17365    });
17366
17367    cx.update_editor(|editor, _, _| {
17368        let context_menu = editor.context_menu.borrow_mut();
17369        let context_menu = context_menu
17370            .as_ref()
17371            .expect("Should have the context menu deployed");
17372        match context_menu {
17373            CodeContextMenu::Completions(completions_menu) => {
17374                let completions = completions_menu.completions.borrow_mut();
17375                assert_eq!(
17376                    completions
17377                        .iter()
17378                        .map(|completion| &completion.label.text)
17379                        .collect::<Vec<_>>(),
17380                    vec!["method id() Now resolved!", "other"],
17381                    "Should update first completion label, but not second as the filter text did not match."
17382                );
17383            }
17384            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17385        }
17386    });
17387}
17388
17389#[gpui::test]
17390async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
17391    init_test(cx, |_| {});
17392    let mut cx = EditorLspTestContext::new_rust(
17393        lsp::ServerCapabilities {
17394            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
17395            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
17396            completion_provider: Some(lsp::CompletionOptions {
17397                resolve_provider: Some(true),
17398                ..Default::default()
17399            }),
17400            ..Default::default()
17401        },
17402        cx,
17403    )
17404    .await;
17405    cx.set_state(indoc! {"
17406        struct TestStruct {
17407            field: i32
17408        }
17409
17410        fn mainˇ() {
17411            let unused_var = 42;
17412            let test_struct = TestStruct { field: 42 };
17413        }
17414    "});
17415    let symbol_range = cx.lsp_range(indoc! {"
17416        struct TestStruct {
17417            field: i32
17418        }
17419
17420        «fn main»() {
17421            let unused_var = 42;
17422            let test_struct = TestStruct { field: 42 };
17423        }
17424    "});
17425    let mut hover_requests =
17426        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
17427            Ok(Some(lsp::Hover {
17428                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
17429                    kind: lsp::MarkupKind::Markdown,
17430                    value: "Function documentation".to_string(),
17431                }),
17432                range: Some(symbol_range),
17433            }))
17434        });
17435
17436    // Case 1: Test that code action menu hide hover popover
17437    cx.dispatch_action(Hover);
17438    hover_requests.next().await;
17439    cx.condition(|editor, _| editor.hover_state.visible()).await;
17440    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
17441        move |_, _, _| async move {
17442            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
17443                lsp::CodeAction {
17444                    title: "Remove unused variable".to_string(),
17445                    kind: Some(CodeActionKind::QUICKFIX),
17446                    edit: Some(lsp::WorkspaceEdit {
17447                        changes: Some(
17448                            [(
17449                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
17450                                vec![lsp::TextEdit {
17451                                    range: lsp::Range::new(
17452                                        lsp::Position::new(5, 4),
17453                                        lsp::Position::new(5, 27),
17454                                    ),
17455                                    new_text: "".to_string(),
17456                                }],
17457                            )]
17458                            .into_iter()
17459                            .collect(),
17460                        ),
17461                        ..Default::default()
17462                    }),
17463                    ..Default::default()
17464                },
17465            )]))
17466        },
17467    );
17468    cx.update_editor(|editor, window, cx| {
17469        editor.toggle_code_actions(
17470            &ToggleCodeActions {
17471                deployed_from: None,
17472                quick_launch: false,
17473            },
17474            window,
17475            cx,
17476        );
17477    });
17478    code_action_requests.next().await;
17479    cx.run_until_parked();
17480    cx.condition(|editor, _| editor.context_menu_visible())
17481        .await;
17482    cx.update_editor(|editor, _, _| {
17483        assert!(
17484            !editor.hover_state.visible(),
17485            "Hover popover should be hidden when code action menu is shown"
17486        );
17487        // Hide code actions
17488        editor.context_menu.take();
17489    });
17490
17491    // Case 2: Test that code completions hide hover popover
17492    cx.dispatch_action(Hover);
17493    hover_requests.next().await;
17494    cx.condition(|editor, _| editor.hover_state.visible()).await;
17495    let counter = Arc::new(AtomicUsize::new(0));
17496    let mut completion_requests =
17497        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17498            let counter = counter.clone();
17499            async move {
17500                counter.fetch_add(1, atomic::Ordering::Release);
17501                Ok(Some(lsp::CompletionResponse::Array(vec![
17502                    lsp::CompletionItem {
17503                        label: "main".into(),
17504                        kind: Some(lsp::CompletionItemKind::FUNCTION),
17505                        detail: Some("() -> ()".to_string()),
17506                        ..Default::default()
17507                    },
17508                    lsp::CompletionItem {
17509                        label: "TestStruct".into(),
17510                        kind: Some(lsp::CompletionItemKind::STRUCT),
17511                        detail: Some("struct TestStruct".to_string()),
17512                        ..Default::default()
17513                    },
17514                ])))
17515            }
17516        });
17517    cx.update_editor(|editor, window, cx| {
17518        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
17519    });
17520    completion_requests.next().await;
17521    cx.condition(|editor, _| editor.context_menu_visible())
17522        .await;
17523    cx.update_editor(|editor, _, _| {
17524        assert!(
17525            !editor.hover_state.visible(),
17526            "Hover popover should be hidden when completion menu is shown"
17527        );
17528    });
17529}
17530
17531#[gpui::test]
17532async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
17533    init_test(cx, |_| {});
17534
17535    let mut cx = EditorLspTestContext::new_rust(
17536        lsp::ServerCapabilities {
17537            completion_provider: Some(lsp::CompletionOptions {
17538                trigger_characters: Some(vec![".".to_string()]),
17539                resolve_provider: Some(true),
17540                ..Default::default()
17541            }),
17542            ..Default::default()
17543        },
17544        cx,
17545    )
17546    .await;
17547
17548    cx.set_state("fn main() { let a = 2ˇ; }");
17549    cx.simulate_keystroke(".");
17550
17551    let unresolved_item_1 = lsp::CompletionItem {
17552        label: "id".to_string(),
17553        filter_text: Some("id".to_string()),
17554        detail: None,
17555        documentation: None,
17556        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17557            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17558            new_text: ".id".to_string(),
17559        })),
17560        ..lsp::CompletionItem::default()
17561    };
17562    let resolved_item_1 = lsp::CompletionItem {
17563        additional_text_edits: Some(vec![lsp::TextEdit {
17564            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17565            new_text: "!!".to_string(),
17566        }]),
17567        ..unresolved_item_1.clone()
17568    };
17569    let unresolved_item_2 = lsp::CompletionItem {
17570        label: "other".to_string(),
17571        filter_text: Some("other".to_string()),
17572        detail: None,
17573        documentation: None,
17574        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
17575            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17576            new_text: ".other".to_string(),
17577        })),
17578        ..lsp::CompletionItem::default()
17579    };
17580    let resolved_item_2 = lsp::CompletionItem {
17581        additional_text_edits: Some(vec![lsp::TextEdit {
17582            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
17583            new_text: "??".to_string(),
17584        }]),
17585        ..unresolved_item_2.clone()
17586    };
17587
17588    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
17589    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
17590    cx.lsp
17591        .server
17592        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17593            let unresolved_item_1 = unresolved_item_1.clone();
17594            let resolved_item_1 = resolved_item_1.clone();
17595            let unresolved_item_2 = unresolved_item_2.clone();
17596            let resolved_item_2 = resolved_item_2.clone();
17597            let resolve_requests_1 = resolve_requests_1.clone();
17598            let resolve_requests_2 = resolve_requests_2.clone();
17599            move |unresolved_request, _| {
17600                let unresolved_item_1 = unresolved_item_1.clone();
17601                let resolved_item_1 = resolved_item_1.clone();
17602                let unresolved_item_2 = unresolved_item_2.clone();
17603                let resolved_item_2 = resolved_item_2.clone();
17604                let resolve_requests_1 = resolve_requests_1.clone();
17605                let resolve_requests_2 = resolve_requests_2.clone();
17606                async move {
17607                    if unresolved_request == unresolved_item_1 {
17608                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
17609                        Ok(resolved_item_1.clone())
17610                    } else if unresolved_request == unresolved_item_2 {
17611                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
17612                        Ok(resolved_item_2.clone())
17613                    } else {
17614                        panic!("Unexpected completion item {unresolved_request:?}")
17615                    }
17616                }
17617            }
17618        })
17619        .detach();
17620
17621    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17622        let unresolved_item_1 = unresolved_item_1.clone();
17623        let unresolved_item_2 = unresolved_item_2.clone();
17624        async move {
17625            Ok(Some(lsp::CompletionResponse::Array(vec![
17626                unresolved_item_1,
17627                unresolved_item_2,
17628            ])))
17629        }
17630    })
17631    .next()
17632    .await;
17633
17634    cx.condition(|editor, _| editor.context_menu_visible())
17635        .await;
17636    cx.update_editor(|editor, _, _| {
17637        let context_menu = editor.context_menu.borrow_mut();
17638        let context_menu = context_menu
17639            .as_ref()
17640            .expect("Should have the context menu deployed");
17641        match context_menu {
17642            CodeContextMenu::Completions(completions_menu) => {
17643                let completions = completions_menu.completions.borrow_mut();
17644                assert_eq!(
17645                    completions
17646                        .iter()
17647                        .map(|completion| &completion.label.text)
17648                        .collect::<Vec<_>>(),
17649                    vec!["id", "other"]
17650                )
17651            }
17652            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
17653        }
17654    });
17655    cx.run_until_parked();
17656
17657    cx.update_editor(|editor, window, cx| {
17658        editor.context_menu_next(&ContextMenuNext, window, cx);
17659    });
17660    cx.run_until_parked();
17661    cx.update_editor(|editor, window, cx| {
17662        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17663    });
17664    cx.run_until_parked();
17665    cx.update_editor(|editor, window, cx| {
17666        editor.context_menu_next(&ContextMenuNext, window, cx);
17667    });
17668    cx.run_until_parked();
17669    cx.update_editor(|editor, window, cx| {
17670        editor
17671            .compose_completion(&ComposeCompletion::default(), window, cx)
17672            .expect("No task returned")
17673    })
17674    .await
17675    .expect("Completion failed");
17676    cx.run_until_parked();
17677
17678    cx.update_editor(|editor, _, cx| {
17679        assert_eq!(
17680            resolve_requests_1.load(atomic::Ordering::Acquire),
17681            1,
17682            "Should always resolve once despite multiple selections"
17683        );
17684        assert_eq!(
17685            resolve_requests_2.load(atomic::Ordering::Acquire),
17686            1,
17687            "Should always resolve once after multiple selections and applying the completion"
17688        );
17689        assert_eq!(
17690            editor.text(cx),
17691            "fn main() { let a = ??.other; }",
17692            "Should use resolved data when applying the completion"
17693        );
17694    });
17695}
17696
17697#[gpui::test]
17698async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
17699    init_test(cx, |_| {});
17700
17701    let item_0 = lsp::CompletionItem {
17702        label: "abs".into(),
17703        insert_text: Some("abs".into()),
17704        data: Some(json!({ "very": "special"})),
17705        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
17706        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
17707            lsp::InsertReplaceEdit {
17708                new_text: "abs".to_string(),
17709                insert: lsp::Range::default(),
17710                replace: lsp::Range::default(),
17711            },
17712        )),
17713        ..lsp::CompletionItem::default()
17714    };
17715    let items = iter::once(item_0.clone())
17716        .chain((11..51).map(|i| lsp::CompletionItem {
17717            label: format!("item_{}", i),
17718            insert_text: Some(format!("item_{}", i)),
17719            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
17720            ..lsp::CompletionItem::default()
17721        }))
17722        .collect::<Vec<_>>();
17723
17724    let default_commit_characters = vec!["?".to_string()];
17725    let default_data = json!({ "default": "data"});
17726    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
17727    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
17728    let default_edit_range = lsp::Range {
17729        start: lsp::Position {
17730            line: 0,
17731            character: 5,
17732        },
17733        end: lsp::Position {
17734            line: 0,
17735            character: 5,
17736        },
17737    };
17738
17739    let mut cx = EditorLspTestContext::new_rust(
17740        lsp::ServerCapabilities {
17741            completion_provider: Some(lsp::CompletionOptions {
17742                trigger_characters: Some(vec![".".to_string()]),
17743                resolve_provider: Some(true),
17744                ..Default::default()
17745            }),
17746            ..Default::default()
17747        },
17748        cx,
17749    )
17750    .await;
17751
17752    cx.set_state("fn main() { let a = 2ˇ; }");
17753    cx.simulate_keystroke(".");
17754
17755    let completion_data = default_data.clone();
17756    let completion_characters = default_commit_characters.clone();
17757    let completion_items = items.clone();
17758    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
17759        let default_data = completion_data.clone();
17760        let default_commit_characters = completion_characters.clone();
17761        let items = completion_items.clone();
17762        async move {
17763            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
17764                items,
17765                item_defaults: Some(lsp::CompletionListItemDefaults {
17766                    data: Some(default_data.clone()),
17767                    commit_characters: Some(default_commit_characters.clone()),
17768                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
17769                        default_edit_range,
17770                    )),
17771                    insert_text_format: Some(default_insert_text_format),
17772                    insert_text_mode: Some(default_insert_text_mode),
17773                }),
17774                ..lsp::CompletionList::default()
17775            })))
17776        }
17777    })
17778    .next()
17779    .await;
17780
17781    let resolved_items = Arc::new(Mutex::new(Vec::new()));
17782    cx.lsp
17783        .server
17784        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
17785            let closure_resolved_items = resolved_items.clone();
17786            move |item_to_resolve, _| {
17787                let closure_resolved_items = closure_resolved_items.clone();
17788                async move {
17789                    closure_resolved_items.lock().push(item_to_resolve.clone());
17790                    Ok(item_to_resolve)
17791                }
17792            }
17793        })
17794        .detach();
17795
17796    cx.condition(|editor, _| editor.context_menu_visible())
17797        .await;
17798    cx.run_until_parked();
17799    cx.update_editor(|editor, _, _| {
17800        let menu = editor.context_menu.borrow_mut();
17801        match menu.as_ref().expect("should have the completions menu") {
17802            CodeContextMenu::Completions(completions_menu) => {
17803                assert_eq!(
17804                    completions_menu
17805                        .entries
17806                        .borrow()
17807                        .iter()
17808                        .map(|mat| mat.string.clone())
17809                        .collect::<Vec<String>>(),
17810                    items
17811                        .iter()
17812                        .map(|completion| completion.label.clone())
17813                        .collect::<Vec<String>>()
17814                );
17815            }
17816            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
17817        }
17818    });
17819    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
17820    // with 4 from the end.
17821    assert_eq!(
17822        *resolved_items.lock(),
17823        [&items[0..16], &items[items.len() - 4..items.len()]]
17824            .concat()
17825            .iter()
17826            .cloned()
17827            .map(|mut item| {
17828                if item.data.is_none() {
17829                    item.data = Some(default_data.clone());
17830                }
17831                item
17832            })
17833            .collect::<Vec<lsp::CompletionItem>>(),
17834        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
17835    );
17836    resolved_items.lock().clear();
17837
17838    cx.update_editor(|editor, window, cx| {
17839        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
17840    });
17841    cx.run_until_parked();
17842    // Completions that have already been resolved are skipped.
17843    assert_eq!(
17844        *resolved_items.lock(),
17845        items[items.len() - 17..items.len() - 4]
17846            .iter()
17847            .cloned()
17848            .map(|mut item| {
17849                if item.data.is_none() {
17850                    item.data = Some(default_data.clone());
17851                }
17852                item
17853            })
17854            .collect::<Vec<lsp::CompletionItem>>()
17855    );
17856    resolved_items.lock().clear();
17857}
17858
17859#[gpui::test]
17860async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
17861    init_test(cx, |_| {});
17862
17863    let mut cx = EditorLspTestContext::new(
17864        Language::new(
17865            LanguageConfig {
17866                matcher: LanguageMatcher {
17867                    path_suffixes: vec!["jsx".into()],
17868                    ..Default::default()
17869                },
17870                overrides: [(
17871                    "element".into(),
17872                    LanguageConfigOverride {
17873                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
17874                        ..Default::default()
17875                    },
17876                )]
17877                .into_iter()
17878                .collect(),
17879                ..Default::default()
17880            },
17881            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
17882        )
17883        .with_override_query("(jsx_self_closing_element) @element")
17884        .unwrap(),
17885        lsp::ServerCapabilities {
17886            completion_provider: Some(lsp::CompletionOptions {
17887                trigger_characters: Some(vec![":".to_string()]),
17888                ..Default::default()
17889            }),
17890            ..Default::default()
17891        },
17892        cx,
17893    )
17894    .await;
17895
17896    cx.lsp
17897        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
17898            Ok(Some(lsp::CompletionResponse::Array(vec![
17899                lsp::CompletionItem {
17900                    label: "bg-blue".into(),
17901                    ..Default::default()
17902                },
17903                lsp::CompletionItem {
17904                    label: "bg-red".into(),
17905                    ..Default::default()
17906                },
17907                lsp::CompletionItem {
17908                    label: "bg-yellow".into(),
17909                    ..Default::default()
17910                },
17911            ])))
17912        });
17913
17914    cx.set_state(r#"<p class="bgˇ" />"#);
17915
17916    // Trigger completion when typing a dash, because the dash is an extra
17917    // word character in the 'element' scope, which contains the cursor.
17918    cx.simulate_keystroke("-");
17919    cx.executor().run_until_parked();
17920    cx.update_editor(|editor, _, _| {
17921        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17922        {
17923            assert_eq!(
17924                completion_menu_entries(menu),
17925                &["bg-blue", "bg-red", "bg-yellow"]
17926            );
17927        } else {
17928            panic!("expected completion menu to be open");
17929        }
17930    });
17931
17932    cx.simulate_keystroke("l");
17933    cx.executor().run_until_parked();
17934    cx.update_editor(|editor, _, _| {
17935        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17936        {
17937            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
17938        } else {
17939            panic!("expected completion menu to be open");
17940        }
17941    });
17942
17943    // When filtering completions, consider the character after the '-' to
17944    // be the start of a subword.
17945    cx.set_state(r#"<p class="yelˇ" />"#);
17946    cx.simulate_keystroke("l");
17947    cx.executor().run_until_parked();
17948    cx.update_editor(|editor, _, _| {
17949        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
17950        {
17951            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
17952        } else {
17953            panic!("expected completion menu to be open");
17954        }
17955    });
17956}
17957
17958fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
17959    let entries = menu.entries.borrow();
17960    entries.iter().map(|mat| mat.string.clone()).collect()
17961}
17962
17963#[gpui::test]
17964async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
17965    init_test(cx, |settings| {
17966        settings.defaults.formatter = Some(SelectedFormatter::List(FormatterList::Single(
17967            Formatter::Prettier,
17968        )))
17969    });
17970
17971    let fs = FakeFs::new(cx.executor());
17972    fs.insert_file(path!("/file.ts"), Default::default()).await;
17973
17974    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
17975    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17976
17977    language_registry.add(Arc::new(Language::new(
17978        LanguageConfig {
17979            name: "TypeScript".into(),
17980            matcher: LanguageMatcher {
17981                path_suffixes: vec!["ts".to_string()],
17982                ..Default::default()
17983            },
17984            ..Default::default()
17985        },
17986        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
17987    )));
17988    update_test_language_settings(cx, |settings| {
17989        settings.defaults.prettier.get_or_insert_default().allowed = true;
17990    });
17991
17992    let test_plugin = "test_plugin";
17993    let _ = language_registry.register_fake_lsp(
17994        "TypeScript",
17995        FakeLspAdapter {
17996            prettier_plugins: vec![test_plugin],
17997            ..Default::default()
17998        },
17999    );
18000
18001    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18002    let buffer = project
18003        .update(cx, |project, cx| {
18004            project.open_local_buffer(path!("/file.ts"), cx)
18005        })
18006        .await
18007        .unwrap();
18008
18009    let buffer_text = "one\ntwo\nthree\n";
18010    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18011    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18012    editor.update_in(cx, |editor, window, cx| {
18013        editor.set_text(buffer_text, window, cx)
18014    });
18015
18016    editor
18017        .update_in(cx, |editor, window, cx| {
18018            editor.perform_format(
18019                project.clone(),
18020                FormatTrigger::Manual,
18021                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18022                window,
18023                cx,
18024            )
18025        })
18026        .unwrap()
18027        .await;
18028    assert_eq!(
18029        editor.update(cx, |editor, cx| editor.text(cx)),
18030        buffer_text.to_string() + prettier_format_suffix,
18031        "Test prettier formatting was not applied to the original buffer text",
18032    );
18033
18034    update_test_language_settings(cx, |settings| {
18035        settings.defaults.formatter = Some(SelectedFormatter::Auto)
18036    });
18037    let format = editor.update_in(cx, |editor, window, cx| {
18038        editor.perform_format(
18039            project.clone(),
18040            FormatTrigger::Manual,
18041            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18042            window,
18043            cx,
18044        )
18045    });
18046    format.await.unwrap();
18047    assert_eq!(
18048        editor.update(cx, |editor, cx| editor.text(cx)),
18049        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18050        "Autoformatting (via test prettier) was not applied to the original buffer text",
18051    );
18052}
18053
18054#[gpui::test]
18055async fn test_addition_reverts(cx: &mut TestAppContext) {
18056    init_test(cx, |_| {});
18057    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18058    let base_text = indoc! {r#"
18059        struct Row;
18060        struct Row1;
18061        struct Row2;
18062
18063        struct Row4;
18064        struct Row5;
18065        struct Row6;
18066
18067        struct Row8;
18068        struct Row9;
18069        struct Row10;"#};
18070
18071    // When addition hunks are not adjacent to carets, no hunk revert is performed
18072    assert_hunk_revert(
18073        indoc! {r#"struct Row;
18074                   struct Row1;
18075                   struct Row1.1;
18076                   struct Row1.2;
18077                   struct Row2;ˇ
18078
18079                   struct Row4;
18080                   struct Row5;
18081                   struct Row6;
18082
18083                   struct Row8;
18084                   ˇstruct Row9;
18085                   struct Row9.1;
18086                   struct Row9.2;
18087                   struct Row9.3;
18088                   struct Row10;"#},
18089        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18090        indoc! {r#"struct Row;
18091                   struct Row1;
18092                   struct Row1.1;
18093                   struct Row1.2;
18094                   struct Row2;ˇ
18095
18096                   struct Row4;
18097                   struct Row5;
18098                   struct Row6;
18099
18100                   struct Row8;
18101                   ˇstruct Row9;
18102                   struct Row9.1;
18103                   struct Row9.2;
18104                   struct Row9.3;
18105                   struct Row10;"#},
18106        base_text,
18107        &mut cx,
18108    );
18109    // Same for selections
18110    assert_hunk_revert(
18111        indoc! {r#"struct Row;
18112                   struct Row1;
18113                   struct Row2;
18114                   struct Row2.1;
18115                   struct Row2.2;
18116                   «ˇ
18117                   struct Row4;
18118                   struct» Row5;
18119                   «struct Row6;
18120                   ˇ»
18121                   struct Row9.1;
18122                   struct Row9.2;
18123                   struct Row9.3;
18124                   struct Row8;
18125                   struct Row9;
18126                   struct Row10;"#},
18127        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18128        indoc! {r#"struct Row;
18129                   struct Row1;
18130                   struct Row2;
18131                   struct Row2.1;
18132                   struct Row2.2;
18133                   «ˇ
18134                   struct Row4;
18135                   struct» Row5;
18136                   «struct Row6;
18137                   ˇ»
18138                   struct Row9.1;
18139                   struct Row9.2;
18140                   struct Row9.3;
18141                   struct Row8;
18142                   struct Row9;
18143                   struct Row10;"#},
18144        base_text,
18145        &mut cx,
18146    );
18147
18148    // When carets and selections intersect the addition hunks, those are reverted.
18149    // Adjacent carets got merged.
18150    assert_hunk_revert(
18151        indoc! {r#"struct Row;
18152                   ˇ// something on the top
18153                   struct Row1;
18154                   struct Row2;
18155                   struct Roˇw3.1;
18156                   struct Row2.2;
18157                   struct Row2.3;ˇ
18158
18159                   struct Row4;
18160                   struct ˇRow5.1;
18161                   struct Row5.2;
18162                   struct «Rowˇ»5.3;
18163                   struct Row5;
18164                   struct Row6;
18165                   ˇ
18166                   struct Row9.1;
18167                   struct «Rowˇ»9.2;
18168                   struct «ˇRow»9.3;
18169                   struct Row8;
18170                   struct Row9;
18171                   «ˇ// something on bottom»
18172                   struct Row10;"#},
18173        vec![
18174            DiffHunkStatusKind::Added,
18175            DiffHunkStatusKind::Added,
18176            DiffHunkStatusKind::Added,
18177            DiffHunkStatusKind::Added,
18178            DiffHunkStatusKind::Added,
18179        ],
18180        indoc! {r#"struct Row;
18181                   ˇstruct Row1;
18182                   struct Row2;
18183                   ˇ
18184                   struct Row4;
18185                   ˇstruct Row5;
18186                   struct Row6;
18187                   ˇ
18188                   ˇstruct Row8;
18189                   struct Row9;
18190                   ˇstruct Row10;"#},
18191        base_text,
18192        &mut cx,
18193    );
18194}
18195
18196#[gpui::test]
18197async fn test_modification_reverts(cx: &mut TestAppContext) {
18198    init_test(cx, |_| {});
18199    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18200    let base_text = indoc! {r#"
18201        struct Row;
18202        struct Row1;
18203        struct Row2;
18204
18205        struct Row4;
18206        struct Row5;
18207        struct Row6;
18208
18209        struct Row8;
18210        struct Row9;
18211        struct Row10;"#};
18212
18213    // Modification hunks behave the same as the addition ones.
18214    assert_hunk_revert(
18215        indoc! {r#"struct Row;
18216                   struct Row1;
18217                   struct Row33;
18218                   ˇ
18219                   struct Row4;
18220                   struct Row5;
18221                   struct Row6;
18222                   ˇ
18223                   struct Row99;
18224                   struct Row9;
18225                   struct Row10;"#},
18226        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18227        indoc! {r#"struct Row;
18228                   struct Row1;
18229                   struct Row33;
18230                   ˇ
18231                   struct Row4;
18232                   struct Row5;
18233                   struct Row6;
18234                   ˇ
18235                   struct Row99;
18236                   struct Row9;
18237                   struct Row10;"#},
18238        base_text,
18239        &mut cx,
18240    );
18241    assert_hunk_revert(
18242        indoc! {r#"struct Row;
18243                   struct Row1;
18244                   struct Row33;
18245                   «ˇ
18246                   struct Row4;
18247                   struct» Row5;
18248                   «struct Row6;
18249                   ˇ»
18250                   struct Row99;
18251                   struct Row9;
18252                   struct Row10;"#},
18253        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
18254        indoc! {r#"struct Row;
18255                   struct Row1;
18256                   struct Row33;
18257                   «ˇ
18258                   struct Row4;
18259                   struct» Row5;
18260                   «struct Row6;
18261                   ˇ»
18262                   struct Row99;
18263                   struct Row9;
18264                   struct Row10;"#},
18265        base_text,
18266        &mut cx,
18267    );
18268
18269    assert_hunk_revert(
18270        indoc! {r#"ˇstruct Row1.1;
18271                   struct Row1;
18272                   «ˇstr»uct Row22;
18273
18274                   struct ˇRow44;
18275                   struct Row5;
18276                   struct «Rˇ»ow66;ˇ
18277
18278                   «struˇ»ct Row88;
18279                   struct Row9;
18280                   struct Row1011;ˇ"#},
18281        vec![
18282            DiffHunkStatusKind::Modified,
18283            DiffHunkStatusKind::Modified,
18284            DiffHunkStatusKind::Modified,
18285            DiffHunkStatusKind::Modified,
18286            DiffHunkStatusKind::Modified,
18287            DiffHunkStatusKind::Modified,
18288        ],
18289        indoc! {r#"struct Row;
18290                   ˇstruct Row1;
18291                   struct Row2;
18292                   ˇ
18293                   struct Row4;
18294                   ˇstruct Row5;
18295                   struct Row6;
18296                   ˇ
18297                   struct Row8;
18298                   ˇstruct Row9;
18299                   struct Row10;ˇ"#},
18300        base_text,
18301        &mut cx,
18302    );
18303}
18304
18305#[gpui::test]
18306async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
18307    init_test(cx, |_| {});
18308    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18309    let base_text = indoc! {r#"
18310        one
18311
18312        two
18313        three
18314        "#};
18315
18316    cx.set_head_text(base_text);
18317    cx.set_state("\nˇ\n");
18318    cx.executor().run_until_parked();
18319    cx.update_editor(|editor, _window, cx| {
18320        editor.expand_selected_diff_hunks(cx);
18321    });
18322    cx.executor().run_until_parked();
18323    cx.update_editor(|editor, window, cx| {
18324        editor.backspace(&Default::default(), window, cx);
18325    });
18326    cx.run_until_parked();
18327    cx.assert_state_with_diff(
18328        indoc! {r#"
18329
18330        - two
18331        - threeˇ
18332        +
18333        "#}
18334        .to_string(),
18335    );
18336}
18337
18338#[gpui::test]
18339async fn test_deletion_reverts(cx: &mut TestAppContext) {
18340    init_test(cx, |_| {});
18341    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18342    let base_text = indoc! {r#"struct Row;
18343struct Row1;
18344struct Row2;
18345
18346struct Row4;
18347struct Row5;
18348struct Row6;
18349
18350struct Row8;
18351struct Row9;
18352struct Row10;"#};
18353
18354    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
18355    assert_hunk_revert(
18356        indoc! {r#"struct Row;
18357                   struct Row2;
18358
18359                   ˇstruct Row4;
18360                   struct Row5;
18361                   struct Row6;
18362                   ˇ
18363                   struct Row8;
18364                   struct Row10;"#},
18365        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18366        indoc! {r#"struct Row;
18367                   struct Row2;
18368
18369                   ˇstruct Row4;
18370                   struct Row5;
18371                   struct Row6;
18372                   ˇ
18373                   struct Row8;
18374                   struct Row10;"#},
18375        base_text,
18376        &mut cx,
18377    );
18378    assert_hunk_revert(
18379        indoc! {r#"struct Row;
18380                   struct Row2;
18381
18382                   «ˇstruct Row4;
18383                   struct» Row5;
18384                   «struct Row6;
18385                   ˇ»
18386                   struct Row8;
18387                   struct Row10;"#},
18388        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18389        indoc! {r#"struct Row;
18390                   struct Row2;
18391
18392                   «ˇstruct Row4;
18393                   struct» Row5;
18394                   «struct Row6;
18395                   ˇ»
18396                   struct Row8;
18397                   struct Row10;"#},
18398        base_text,
18399        &mut cx,
18400    );
18401
18402    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
18403    assert_hunk_revert(
18404        indoc! {r#"struct Row;
18405                   ˇstruct Row2;
18406
18407                   struct Row4;
18408                   struct Row5;
18409                   struct Row6;
18410
18411                   struct Row8;ˇ
18412                   struct Row10;"#},
18413        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
18414        indoc! {r#"struct Row;
18415                   struct Row1;
18416                   ˇstruct Row2;
18417
18418                   struct Row4;
18419                   struct Row5;
18420                   struct Row6;
18421
18422                   struct Row8;ˇ
18423                   struct Row9;
18424                   struct Row10;"#},
18425        base_text,
18426        &mut cx,
18427    );
18428    assert_hunk_revert(
18429        indoc! {r#"struct Row;
18430                   struct Row2«ˇ;
18431                   struct Row4;
18432                   struct» Row5;
18433                   «struct Row6;
18434
18435                   struct Row8;ˇ»
18436                   struct Row10;"#},
18437        vec![
18438            DiffHunkStatusKind::Deleted,
18439            DiffHunkStatusKind::Deleted,
18440            DiffHunkStatusKind::Deleted,
18441        ],
18442        indoc! {r#"struct Row;
18443                   struct Row1;
18444                   struct Row2«ˇ;
18445
18446                   struct Row4;
18447                   struct» Row5;
18448                   «struct Row6;
18449
18450                   struct Row8;ˇ»
18451                   struct Row9;
18452                   struct Row10;"#},
18453        base_text,
18454        &mut cx,
18455    );
18456}
18457
18458#[gpui::test]
18459async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
18460    init_test(cx, |_| {});
18461
18462    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
18463    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
18464    let base_text_3 =
18465        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
18466
18467    let text_1 = edit_first_char_of_every_line(base_text_1);
18468    let text_2 = edit_first_char_of_every_line(base_text_2);
18469    let text_3 = edit_first_char_of_every_line(base_text_3);
18470
18471    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
18472    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
18473    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
18474
18475    let multibuffer = cx.new(|cx| {
18476        let mut multibuffer = MultiBuffer::new(ReadWrite);
18477        multibuffer.push_excerpts(
18478            buffer_1.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_2.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.push_excerpts(
18496            buffer_3.clone(),
18497            [
18498                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18499                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18500                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18501            ],
18502            cx,
18503        );
18504        multibuffer
18505    });
18506
18507    let fs = FakeFs::new(cx.executor());
18508    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
18509    let (editor, cx) = cx
18510        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
18511    editor.update_in(cx, |editor, _window, cx| {
18512        for (buffer, diff_base) in [
18513            (buffer_1.clone(), base_text_1),
18514            (buffer_2.clone(), base_text_2),
18515            (buffer_3.clone(), base_text_3),
18516        ] {
18517            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
18518            editor
18519                .buffer
18520                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
18521        }
18522    });
18523    cx.executor().run_until_parked();
18524
18525    editor.update_in(cx, |editor, window, cx| {
18526        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}");
18527        editor.select_all(&SelectAll, window, cx);
18528        editor.git_restore(&Default::default(), window, cx);
18529    });
18530    cx.executor().run_until_parked();
18531
18532    // When all ranges are selected, all buffer hunks are reverted.
18533    editor.update(cx, |editor, cx| {
18534        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");
18535    });
18536    buffer_1.update(cx, |buffer, _| {
18537        assert_eq!(buffer.text(), base_text_1);
18538    });
18539    buffer_2.update(cx, |buffer, _| {
18540        assert_eq!(buffer.text(), base_text_2);
18541    });
18542    buffer_3.update(cx, |buffer, _| {
18543        assert_eq!(buffer.text(), base_text_3);
18544    });
18545
18546    editor.update_in(cx, |editor, window, cx| {
18547        editor.undo(&Default::default(), window, cx);
18548    });
18549
18550    editor.update_in(cx, |editor, window, cx| {
18551        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18552            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
18553        });
18554        editor.git_restore(&Default::default(), window, cx);
18555    });
18556
18557    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
18558    // but not affect buffer_2 and its related excerpts.
18559    editor.update(cx, |editor, cx| {
18560        assert_eq!(
18561            editor.text(cx),
18562            "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}"
18563        );
18564    });
18565    buffer_1.update(cx, |buffer, _| {
18566        assert_eq!(buffer.text(), base_text_1);
18567    });
18568    buffer_2.update(cx, |buffer, _| {
18569        assert_eq!(
18570            buffer.text(),
18571            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
18572        );
18573    });
18574    buffer_3.update(cx, |buffer, _| {
18575        assert_eq!(
18576            buffer.text(),
18577            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
18578        );
18579    });
18580
18581    fn edit_first_char_of_every_line(text: &str) -> String {
18582        text.split('\n')
18583            .map(|line| format!("X{}", &line[1..]))
18584            .collect::<Vec<_>>()
18585            .join("\n")
18586    }
18587}
18588
18589#[gpui::test]
18590async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
18591    init_test(cx, |_| {});
18592
18593    let cols = 4;
18594    let rows = 10;
18595    let sample_text_1 = sample_text(rows, cols, 'a');
18596    assert_eq!(
18597        sample_text_1,
18598        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
18599    );
18600    let sample_text_2 = sample_text(rows, cols, 'l');
18601    assert_eq!(
18602        sample_text_2,
18603        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
18604    );
18605    let sample_text_3 = sample_text(rows, cols, 'v');
18606    assert_eq!(
18607        sample_text_3,
18608        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
18609    );
18610
18611    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
18612    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
18613    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
18614
18615    let multi_buffer = cx.new(|cx| {
18616        let mut multibuffer = MultiBuffer::new(ReadWrite);
18617        multibuffer.push_excerpts(
18618            buffer_1.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_2.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.push_excerpts(
18636            buffer_3.clone(),
18637            [
18638                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
18639                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
18640                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
18641            ],
18642            cx,
18643        );
18644        multibuffer
18645    });
18646
18647    let fs = FakeFs::new(cx.executor());
18648    fs.insert_tree(
18649        "/a",
18650        json!({
18651            "main.rs": sample_text_1,
18652            "other.rs": sample_text_2,
18653            "lib.rs": sample_text_3,
18654        }),
18655    )
18656    .await;
18657    let project = Project::test(fs, ["/a".as_ref()], cx).await;
18658    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18659    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
18660    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
18661        Editor::new(
18662            EditorMode::full(),
18663            multi_buffer,
18664            Some(project.clone()),
18665            window,
18666            cx,
18667        )
18668    });
18669    let multibuffer_item_id = workspace
18670        .update(cx, |workspace, window, cx| {
18671            assert!(
18672                workspace.active_item(cx).is_none(),
18673                "active item should be None before the first item is added"
18674            );
18675            workspace.add_item_to_active_pane(
18676                Box::new(multi_buffer_editor.clone()),
18677                None,
18678                true,
18679                window,
18680                cx,
18681            );
18682            let active_item = workspace
18683                .active_item(cx)
18684                .expect("should have an active item after adding the multi buffer");
18685            assert!(
18686                !active_item.is_singleton(cx),
18687                "A multi buffer was expected to active after adding"
18688            );
18689            active_item.item_id()
18690        })
18691        .unwrap();
18692    cx.executor().run_until_parked();
18693
18694    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18695        editor.change_selections(
18696            SelectionEffects::scroll(Autoscroll::Next),
18697            window,
18698            cx,
18699            |s| s.select_ranges(Some(1..2)),
18700        );
18701        editor.open_excerpts(&OpenExcerpts, window, cx);
18702    });
18703    cx.executor().run_until_parked();
18704    let first_item_id = workspace
18705        .update(cx, |workspace, window, cx| {
18706            let active_item = workspace
18707                .active_item(cx)
18708                .expect("should have an active item after navigating into the 1st buffer");
18709            let first_item_id = active_item.item_id();
18710            assert_ne!(
18711                first_item_id, multibuffer_item_id,
18712                "Should navigate into the 1st buffer and activate it"
18713            );
18714            assert!(
18715                active_item.is_singleton(cx),
18716                "New active item should be a singleton buffer"
18717            );
18718            assert_eq!(
18719                active_item
18720                    .act_as::<Editor>(cx)
18721                    .expect("should have navigated into an editor for the 1st buffer")
18722                    .read(cx)
18723                    .text(cx),
18724                sample_text_1
18725            );
18726
18727            workspace
18728                .go_back(workspace.active_pane().downgrade(), window, cx)
18729                .detach_and_log_err(cx);
18730
18731            first_item_id
18732        })
18733        .unwrap();
18734    cx.executor().run_until_parked();
18735    workspace
18736        .update(cx, |workspace, _, cx| {
18737            let active_item = workspace
18738                .active_item(cx)
18739                .expect("should have an active item after navigating back");
18740            assert_eq!(
18741                active_item.item_id(),
18742                multibuffer_item_id,
18743                "Should navigate back to the multi buffer"
18744            );
18745            assert!(!active_item.is_singleton(cx));
18746        })
18747        .unwrap();
18748
18749    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18750        editor.change_selections(
18751            SelectionEffects::scroll(Autoscroll::Next),
18752            window,
18753            cx,
18754            |s| s.select_ranges(Some(39..40)),
18755        );
18756        editor.open_excerpts(&OpenExcerpts, window, cx);
18757    });
18758    cx.executor().run_until_parked();
18759    let second_item_id = workspace
18760        .update(cx, |workspace, window, cx| {
18761            let active_item = workspace
18762                .active_item(cx)
18763                .expect("should have an active item after navigating into the 2nd buffer");
18764            let second_item_id = active_item.item_id();
18765            assert_ne!(
18766                second_item_id, multibuffer_item_id,
18767                "Should navigate away from the multibuffer"
18768            );
18769            assert_ne!(
18770                second_item_id, first_item_id,
18771                "Should navigate into the 2nd buffer and activate it"
18772            );
18773            assert!(
18774                active_item.is_singleton(cx),
18775                "New active item should be a singleton buffer"
18776            );
18777            assert_eq!(
18778                active_item
18779                    .act_as::<Editor>(cx)
18780                    .expect("should have navigated into an editor")
18781                    .read(cx)
18782                    .text(cx),
18783                sample_text_2
18784            );
18785
18786            workspace
18787                .go_back(workspace.active_pane().downgrade(), window, cx)
18788                .detach_and_log_err(cx);
18789
18790            second_item_id
18791        })
18792        .unwrap();
18793    cx.executor().run_until_parked();
18794    workspace
18795        .update(cx, |workspace, _, cx| {
18796            let active_item = workspace
18797                .active_item(cx)
18798                .expect("should have an active item after navigating back from the 2nd buffer");
18799            assert_eq!(
18800                active_item.item_id(),
18801                multibuffer_item_id,
18802                "Should navigate back from the 2nd buffer to the multi buffer"
18803            );
18804            assert!(!active_item.is_singleton(cx));
18805        })
18806        .unwrap();
18807
18808    multi_buffer_editor.update_in(cx, |editor, window, cx| {
18809        editor.change_selections(
18810            SelectionEffects::scroll(Autoscroll::Next),
18811            window,
18812            cx,
18813            |s| s.select_ranges(Some(70..70)),
18814        );
18815        editor.open_excerpts(&OpenExcerpts, window, cx);
18816    });
18817    cx.executor().run_until_parked();
18818    workspace
18819        .update(cx, |workspace, window, cx| {
18820            let active_item = workspace
18821                .active_item(cx)
18822                .expect("should have an active item after navigating into the 3rd buffer");
18823            let third_item_id = active_item.item_id();
18824            assert_ne!(
18825                third_item_id, multibuffer_item_id,
18826                "Should navigate into the 3rd buffer and activate it"
18827            );
18828            assert_ne!(third_item_id, first_item_id);
18829            assert_ne!(third_item_id, second_item_id);
18830            assert!(
18831                active_item.is_singleton(cx),
18832                "New active item should be a singleton buffer"
18833            );
18834            assert_eq!(
18835                active_item
18836                    .act_as::<Editor>(cx)
18837                    .expect("should have navigated into an editor")
18838                    .read(cx)
18839                    .text(cx),
18840                sample_text_3
18841            );
18842
18843            workspace
18844                .go_back(workspace.active_pane().downgrade(), window, cx)
18845                .detach_and_log_err(cx);
18846        })
18847        .unwrap();
18848    cx.executor().run_until_parked();
18849    workspace
18850        .update(cx, |workspace, _, cx| {
18851            let active_item = workspace
18852                .active_item(cx)
18853                .expect("should have an active item after navigating back from the 3rd buffer");
18854            assert_eq!(
18855                active_item.item_id(),
18856                multibuffer_item_id,
18857                "Should navigate back from the 3rd buffer to the multi buffer"
18858            );
18859            assert!(!active_item.is_singleton(cx));
18860        })
18861        .unwrap();
18862}
18863
18864#[gpui::test]
18865async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
18866    init_test(cx, |_| {});
18867
18868    let mut cx = EditorTestContext::new(cx).await;
18869
18870    let diff_base = r#"
18871        use some::mod;
18872
18873        const A: u32 = 42;
18874
18875        fn main() {
18876            println!("hello");
18877
18878            println!("world");
18879        }
18880        "#
18881    .unindent();
18882
18883    cx.set_state(
18884        &r#"
18885        use some::modified;
18886
18887        ˇ
18888        fn main() {
18889            println!("hello there");
18890
18891            println!("around the");
18892            println!("world");
18893        }
18894        "#
18895        .unindent(),
18896    );
18897
18898    cx.set_head_text(&diff_base);
18899    executor.run_until_parked();
18900
18901    cx.update_editor(|editor, window, cx| {
18902        editor.go_to_next_hunk(&GoToHunk, window, cx);
18903        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18904    });
18905    executor.run_until_parked();
18906    cx.assert_state_with_diff(
18907        r#"
18908          use some::modified;
18909
18910
18911          fn main() {
18912        -     println!("hello");
18913        + ˇ    println!("hello there");
18914
18915              println!("around the");
18916              println!("world");
18917          }
18918        "#
18919        .unindent(),
18920    );
18921
18922    cx.update_editor(|editor, window, cx| {
18923        for _ in 0..2 {
18924            editor.go_to_next_hunk(&GoToHunk, window, cx);
18925            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18926        }
18927    });
18928    executor.run_until_parked();
18929    cx.assert_state_with_diff(
18930        r#"
18931        - use some::mod;
18932        + ˇuse some::modified;
18933
18934
18935          fn main() {
18936        -     println!("hello");
18937        +     println!("hello there");
18938
18939        +     println!("around the");
18940              println!("world");
18941          }
18942        "#
18943        .unindent(),
18944    );
18945
18946    cx.update_editor(|editor, window, cx| {
18947        editor.go_to_next_hunk(&GoToHunk, window, cx);
18948        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
18949    });
18950    executor.run_until_parked();
18951    cx.assert_state_with_diff(
18952        r#"
18953        - use some::mod;
18954        + use some::modified;
18955
18956        - const A: u32 = 42;
18957          ˇ
18958          fn main() {
18959        -     println!("hello");
18960        +     println!("hello there");
18961
18962        +     println!("around the");
18963              println!("world");
18964          }
18965        "#
18966        .unindent(),
18967    );
18968
18969    cx.update_editor(|editor, window, cx| {
18970        editor.cancel(&Cancel, window, cx);
18971    });
18972
18973    cx.assert_state_with_diff(
18974        r#"
18975          use some::modified;
18976
18977          ˇ
18978          fn main() {
18979              println!("hello there");
18980
18981              println!("around the");
18982              println!("world");
18983          }
18984        "#
18985        .unindent(),
18986    );
18987}
18988
18989#[gpui::test]
18990async fn test_diff_base_change_with_expanded_diff_hunks(
18991    executor: BackgroundExecutor,
18992    cx: &mut TestAppContext,
18993) {
18994    init_test(cx, |_| {});
18995
18996    let mut cx = EditorTestContext::new(cx).await;
18997
18998    let diff_base = r#"
18999        use some::mod1;
19000        use some::mod2;
19001
19002        const A: u32 = 42;
19003        const B: u32 = 42;
19004        const C: u32 = 42;
19005
19006        fn main() {
19007            println!("hello");
19008
19009            println!("world");
19010        }
19011        "#
19012    .unindent();
19013
19014    cx.set_state(
19015        &r#"
19016        use some::mod2;
19017
19018        const A: u32 = 42;
19019        const C: u32 = 42;
19020
19021        fn main(ˇ) {
19022            //println!("hello");
19023
19024            println!("world");
19025            //
19026            //
19027        }
19028        "#
19029        .unindent(),
19030    );
19031
19032    cx.set_head_text(&diff_base);
19033    executor.run_until_parked();
19034
19035    cx.update_editor(|editor, window, cx| {
19036        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19037    });
19038    executor.run_until_parked();
19039    cx.assert_state_with_diff(
19040        r#"
19041        - use some::mod1;
19042          use some::mod2;
19043
19044          const A: u32 = 42;
19045        - const B: u32 = 42;
19046          const C: u32 = 42;
19047
19048          fn main(ˇ) {
19049        -     println!("hello");
19050        +     //println!("hello");
19051
19052              println!("world");
19053        +     //
19054        +     //
19055          }
19056        "#
19057        .unindent(),
19058    );
19059
19060    cx.set_head_text("new diff base!");
19061    executor.run_until_parked();
19062    cx.assert_state_with_diff(
19063        r#"
19064        - new diff base!
19065        + use some::mod2;
19066        +
19067        + const A: u32 = 42;
19068        + const C: u32 = 42;
19069        +
19070        + fn main(ˇ) {
19071        +     //println!("hello");
19072        +
19073        +     println!("world");
19074        +     //
19075        +     //
19076        + }
19077        "#
19078        .unindent(),
19079    );
19080}
19081
19082#[gpui::test]
19083async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19084    init_test(cx, |_| {});
19085
19086    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19087    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19088    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19089    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19090    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19091    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19092
19093    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19094    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19095    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19096
19097    let multi_buffer = cx.new(|cx| {
19098        let mut multibuffer = MultiBuffer::new(ReadWrite);
19099        multibuffer.push_excerpts(
19100            buffer_1.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_2.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.push_excerpts(
19118            buffer_3.clone(),
19119            [
19120                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19121                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19122                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19123            ],
19124            cx,
19125        );
19126        multibuffer
19127    });
19128
19129    let editor =
19130        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19131    editor
19132        .update(cx, |editor, _window, cx| {
19133            for (buffer, diff_base) in [
19134                (buffer_1.clone(), file_1_old),
19135                (buffer_2.clone(), file_2_old),
19136                (buffer_3.clone(), file_3_old),
19137            ] {
19138                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19139                editor
19140                    .buffer
19141                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19142            }
19143        })
19144        .unwrap();
19145
19146    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19147    cx.run_until_parked();
19148
19149    cx.assert_editor_state(
19150        &"
19151            ˇaaa
19152            ccc
19153            ddd
19154
19155            ggg
19156            hhh
19157
19158
19159            lll
19160            mmm
19161            NNN
19162
19163            qqq
19164            rrr
19165
19166            uuu
19167            111
19168            222
19169            333
19170
19171            666
19172            777
19173
19174            000
19175            !!!"
19176        .unindent(),
19177    );
19178
19179    cx.update_editor(|editor, window, cx| {
19180        editor.select_all(&SelectAll, window, cx);
19181        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19182    });
19183    cx.executor().run_until_parked();
19184
19185    cx.assert_state_with_diff(
19186        "
19187            «aaa
19188          - bbb
19189            ccc
19190            ddd
19191
19192            ggg
19193            hhh
19194
19195
19196            lll
19197            mmm
19198          - nnn
19199          + NNN
19200
19201            qqq
19202            rrr
19203
19204            uuu
19205            111
19206            222
19207            333
19208
19209          + 666
19210            777
19211
19212            000
19213            !!!ˇ»"
19214            .unindent(),
19215    );
19216}
19217
19218#[gpui::test]
19219async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
19220    init_test(cx, |_| {});
19221
19222    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
19223    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
19224
19225    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
19226    let multi_buffer = cx.new(|cx| {
19227        let mut multibuffer = MultiBuffer::new(ReadWrite);
19228        multibuffer.push_excerpts(
19229            buffer.clone(),
19230            [
19231                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
19232                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
19233                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
19234            ],
19235            cx,
19236        );
19237        multibuffer
19238    });
19239
19240    let editor =
19241        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
19242    editor
19243        .update(cx, |editor, _window, cx| {
19244            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
19245            editor
19246                .buffer
19247                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
19248        })
19249        .unwrap();
19250
19251    let mut cx = EditorTestContext::for_editor(editor, cx).await;
19252    cx.run_until_parked();
19253
19254    cx.update_editor(|editor, window, cx| {
19255        editor.expand_all_diff_hunks(&Default::default(), window, cx)
19256    });
19257    cx.executor().run_until_parked();
19258
19259    // When the start of a hunk coincides with the start of its excerpt,
19260    // the hunk is expanded. When the start of a a hunk is earlier than
19261    // the start of its excerpt, the hunk is not expanded.
19262    cx.assert_state_with_diff(
19263        "
19264            ˇaaa
19265          - bbb
19266          + BBB
19267
19268          - ddd
19269          - eee
19270          + DDD
19271          + EEE
19272            fff
19273
19274            iii
19275        "
19276        .unindent(),
19277    );
19278}
19279
19280#[gpui::test]
19281async fn test_edits_around_expanded_insertion_hunks(
19282    executor: BackgroundExecutor,
19283    cx: &mut TestAppContext,
19284) {
19285    init_test(cx, |_| {});
19286
19287    let mut cx = EditorTestContext::new(cx).await;
19288
19289    let diff_base = r#"
19290        use some::mod1;
19291        use some::mod2;
19292
19293        const A: u32 = 42;
19294
19295        fn main() {
19296            println!("hello");
19297
19298            println!("world");
19299        }
19300        "#
19301    .unindent();
19302    executor.run_until_parked();
19303    cx.set_state(
19304        &r#"
19305        use some::mod1;
19306        use some::mod2;
19307
19308        const A: u32 = 42;
19309        const B: u32 = 42;
19310        const C: u32 = 42;
19311        ˇ
19312
19313        fn main() {
19314            println!("hello");
19315
19316            println!("world");
19317        }
19318        "#
19319        .unindent(),
19320    );
19321
19322    cx.set_head_text(&diff_base);
19323    executor.run_until_parked();
19324
19325    cx.update_editor(|editor, window, cx| {
19326        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19327    });
19328    executor.run_until_parked();
19329
19330    cx.assert_state_with_diff(
19331        r#"
19332        use some::mod1;
19333        use some::mod2;
19334
19335        const A: u32 = 42;
19336      + const B: u32 = 42;
19337      + const C: u32 = 42;
19338      + ˇ
19339
19340        fn main() {
19341            println!("hello");
19342
19343            println!("world");
19344        }
19345      "#
19346        .unindent(),
19347    );
19348
19349    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
19350    executor.run_until_parked();
19351
19352    cx.assert_state_with_diff(
19353        r#"
19354        use some::mod1;
19355        use some::mod2;
19356
19357        const A: u32 = 42;
19358      + const B: u32 = 42;
19359      + const C: u32 = 42;
19360      + const D: u32 = 42;
19361      + ˇ
19362
19363        fn main() {
19364            println!("hello");
19365
19366            println!("world");
19367        }
19368      "#
19369        .unindent(),
19370    );
19371
19372    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
19373    executor.run_until_parked();
19374
19375    cx.assert_state_with_diff(
19376        r#"
19377        use some::mod1;
19378        use some::mod2;
19379
19380        const A: u32 = 42;
19381      + const B: u32 = 42;
19382      + const C: u32 = 42;
19383      + const D: u32 = 42;
19384      + const E: u32 = 42;
19385      + ˇ
19386
19387        fn main() {
19388            println!("hello");
19389
19390            println!("world");
19391        }
19392      "#
19393        .unindent(),
19394    );
19395
19396    cx.update_editor(|editor, window, cx| {
19397        editor.delete_line(&DeleteLine, window, cx);
19398    });
19399    executor.run_until_parked();
19400
19401    cx.assert_state_with_diff(
19402        r#"
19403        use some::mod1;
19404        use some::mod2;
19405
19406        const A: u32 = 42;
19407      + const B: u32 = 42;
19408      + const C: u32 = 42;
19409      + const D: u32 = 42;
19410      + const E: u32 = 42;
19411        ˇ
19412        fn main() {
19413            println!("hello");
19414
19415            println!("world");
19416        }
19417      "#
19418        .unindent(),
19419    );
19420
19421    cx.update_editor(|editor, window, cx| {
19422        editor.move_up(&MoveUp, window, cx);
19423        editor.delete_line(&DeleteLine, window, cx);
19424        editor.move_up(&MoveUp, window, cx);
19425        editor.delete_line(&DeleteLine, window, cx);
19426        editor.move_up(&MoveUp, window, cx);
19427        editor.delete_line(&DeleteLine, window, cx);
19428    });
19429    executor.run_until_parked();
19430    cx.assert_state_with_diff(
19431        r#"
19432        use some::mod1;
19433        use some::mod2;
19434
19435        const A: u32 = 42;
19436      + const B: u32 = 42;
19437        ˇ
19438        fn main() {
19439            println!("hello");
19440
19441            println!("world");
19442        }
19443      "#
19444        .unindent(),
19445    );
19446
19447    cx.update_editor(|editor, window, cx| {
19448        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
19449        editor.delete_line(&DeleteLine, window, cx);
19450    });
19451    executor.run_until_parked();
19452    cx.assert_state_with_diff(
19453        r#"
19454        ˇ
19455        fn main() {
19456            println!("hello");
19457
19458            println!("world");
19459        }
19460      "#
19461        .unindent(),
19462    );
19463}
19464
19465#[gpui::test]
19466async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
19467    init_test(cx, |_| {});
19468
19469    let mut cx = EditorTestContext::new(cx).await;
19470    cx.set_head_text(indoc! { "
19471        one
19472        two
19473        three
19474        four
19475        five
19476        "
19477    });
19478    cx.set_state(indoc! { "
19479        one
19480        ˇthree
19481        five
19482    "});
19483    cx.run_until_parked();
19484    cx.update_editor(|editor, window, cx| {
19485        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19486    });
19487    cx.assert_state_with_diff(
19488        indoc! { "
19489        one
19490      - two
19491        ˇthree
19492      - four
19493        five
19494    "}
19495        .to_string(),
19496    );
19497    cx.update_editor(|editor, window, cx| {
19498        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19499    });
19500
19501    cx.assert_state_with_diff(
19502        indoc! { "
19503        one
19504        ˇthree
19505        five
19506    "}
19507        .to_string(),
19508    );
19509
19510    cx.set_state(indoc! { "
19511        one
19512        ˇTWO
19513        three
19514        four
19515        five
19516    "});
19517    cx.run_until_parked();
19518    cx.update_editor(|editor, window, cx| {
19519        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19520    });
19521
19522    cx.assert_state_with_diff(
19523        indoc! { "
19524            one
19525          - two
19526          + ˇTWO
19527            three
19528            four
19529            five
19530        "}
19531        .to_string(),
19532    );
19533    cx.update_editor(|editor, window, cx| {
19534        editor.move_up(&Default::default(), window, cx);
19535        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
19536    });
19537    cx.assert_state_with_diff(
19538        indoc! { "
19539            one
19540            ˇTWO
19541            three
19542            four
19543            five
19544        "}
19545        .to_string(),
19546    );
19547}
19548
19549#[gpui::test]
19550async fn test_edits_around_expanded_deletion_hunks(
19551    executor: BackgroundExecutor,
19552    cx: &mut TestAppContext,
19553) {
19554    init_test(cx, |_| {});
19555
19556    let mut cx = EditorTestContext::new(cx).await;
19557
19558    let diff_base = r#"
19559        use some::mod1;
19560        use some::mod2;
19561
19562        const A: u32 = 42;
19563        const B: u32 = 42;
19564        const C: u32 = 42;
19565
19566
19567        fn main() {
19568            println!("hello");
19569
19570            println!("world");
19571        }
19572    "#
19573    .unindent();
19574    executor.run_until_parked();
19575    cx.set_state(
19576        &r#"
19577        use some::mod1;
19578        use some::mod2;
19579
19580        ˇconst B: u32 = 42;
19581        const C: u32 = 42;
19582
19583
19584        fn main() {
19585            println!("hello");
19586
19587            println!("world");
19588        }
19589        "#
19590        .unindent(),
19591    );
19592
19593    cx.set_head_text(&diff_base);
19594    executor.run_until_parked();
19595
19596    cx.update_editor(|editor, window, cx| {
19597        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19598    });
19599    executor.run_until_parked();
19600
19601    cx.assert_state_with_diff(
19602        r#"
19603        use some::mod1;
19604        use some::mod2;
19605
19606      - const A: u32 = 42;
19607        ˇconst B: u32 = 42;
19608        const C: u32 = 42;
19609
19610
19611        fn main() {
19612            println!("hello");
19613
19614            println!("world");
19615        }
19616      "#
19617        .unindent(),
19618    );
19619
19620    cx.update_editor(|editor, window, cx| {
19621        editor.delete_line(&DeleteLine, window, cx);
19622    });
19623    executor.run_until_parked();
19624    cx.assert_state_with_diff(
19625        r#"
19626        use some::mod1;
19627        use some::mod2;
19628
19629      - const A: u32 = 42;
19630      - const B: u32 = 42;
19631        ˇconst C: u32 = 42;
19632
19633
19634        fn main() {
19635            println!("hello");
19636
19637            println!("world");
19638        }
19639      "#
19640        .unindent(),
19641    );
19642
19643    cx.update_editor(|editor, window, cx| {
19644        editor.delete_line(&DeleteLine, window, cx);
19645    });
19646    executor.run_until_parked();
19647    cx.assert_state_with_diff(
19648        r#"
19649        use some::mod1;
19650        use some::mod2;
19651
19652      - const A: u32 = 42;
19653      - const B: u32 = 42;
19654      - const C: u32 = 42;
19655        ˇ
19656
19657        fn main() {
19658            println!("hello");
19659
19660            println!("world");
19661        }
19662      "#
19663        .unindent(),
19664    );
19665
19666    cx.update_editor(|editor, window, cx| {
19667        editor.handle_input("replacement", window, cx);
19668    });
19669    executor.run_until_parked();
19670    cx.assert_state_with_diff(
19671        r#"
19672        use some::mod1;
19673        use some::mod2;
19674
19675      - const A: u32 = 42;
19676      - const B: u32 = 42;
19677      - const C: u32 = 42;
19678      -
19679      + replacementˇ
19680
19681        fn main() {
19682            println!("hello");
19683
19684            println!("world");
19685        }
19686      "#
19687        .unindent(),
19688    );
19689}
19690
19691#[gpui::test]
19692async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19693    init_test(cx, |_| {});
19694
19695    let mut cx = EditorTestContext::new(cx).await;
19696
19697    let base_text = r#"
19698        one
19699        two
19700        three
19701        four
19702        five
19703    "#
19704    .unindent();
19705    executor.run_until_parked();
19706    cx.set_state(
19707        &r#"
19708        one
19709        two
19710        fˇour
19711        five
19712        "#
19713        .unindent(),
19714    );
19715
19716    cx.set_head_text(&base_text);
19717    executor.run_until_parked();
19718
19719    cx.update_editor(|editor, window, cx| {
19720        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19721    });
19722    executor.run_until_parked();
19723
19724    cx.assert_state_with_diff(
19725        r#"
19726          one
19727          two
19728        - three
19729          fˇour
19730          five
19731        "#
19732        .unindent(),
19733    );
19734
19735    cx.update_editor(|editor, window, cx| {
19736        editor.backspace(&Backspace, window, cx);
19737        editor.backspace(&Backspace, window, cx);
19738    });
19739    executor.run_until_parked();
19740    cx.assert_state_with_diff(
19741        r#"
19742          one
19743          two
19744        - threeˇ
19745        - four
19746        + our
19747          five
19748        "#
19749        .unindent(),
19750    );
19751}
19752
19753#[gpui::test]
19754async fn test_edit_after_expanded_modification_hunk(
19755    executor: BackgroundExecutor,
19756    cx: &mut TestAppContext,
19757) {
19758    init_test(cx, |_| {});
19759
19760    let mut cx = EditorTestContext::new(cx).await;
19761
19762    let diff_base = r#"
19763        use some::mod1;
19764        use some::mod2;
19765
19766        const A: u32 = 42;
19767        const B: u32 = 42;
19768        const C: u32 = 42;
19769        const D: u32 = 42;
19770
19771
19772        fn main() {
19773            println!("hello");
19774
19775            println!("world");
19776        }"#
19777    .unindent();
19778
19779    cx.set_state(
19780        &r#"
19781        use some::mod1;
19782        use some::mod2;
19783
19784        const A: u32 = 42;
19785        const B: u32 = 42;
19786        const C: u32 = 43ˇ
19787        const D: u32 = 42;
19788
19789
19790        fn main() {
19791            println!("hello");
19792
19793            println!("world");
19794        }"#
19795        .unindent(),
19796    );
19797
19798    cx.set_head_text(&diff_base);
19799    executor.run_until_parked();
19800    cx.update_editor(|editor, window, cx| {
19801        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19802    });
19803    executor.run_until_parked();
19804
19805    cx.assert_state_with_diff(
19806        r#"
19807        use some::mod1;
19808        use some::mod2;
19809
19810        const A: u32 = 42;
19811        const B: u32 = 42;
19812      - const C: u32 = 42;
19813      + const C: u32 = 43ˇ
19814        const D: u32 = 42;
19815
19816
19817        fn main() {
19818            println!("hello");
19819
19820            println!("world");
19821        }"#
19822        .unindent(),
19823    );
19824
19825    cx.update_editor(|editor, window, cx| {
19826        editor.handle_input("\nnew_line\n", window, cx);
19827    });
19828    executor.run_until_parked();
19829
19830    cx.assert_state_with_diff(
19831        r#"
19832        use some::mod1;
19833        use some::mod2;
19834
19835        const A: u32 = 42;
19836        const B: u32 = 42;
19837      - const C: u32 = 42;
19838      + const C: u32 = 43
19839      + new_line
19840      + ˇ
19841        const D: u32 = 42;
19842
19843
19844        fn main() {
19845            println!("hello");
19846
19847            println!("world");
19848        }"#
19849        .unindent(),
19850    );
19851}
19852
19853#[gpui::test]
19854async fn test_stage_and_unstage_added_file_hunk(
19855    executor: BackgroundExecutor,
19856    cx: &mut TestAppContext,
19857) {
19858    init_test(cx, |_| {});
19859
19860    let mut cx = EditorTestContext::new(cx).await;
19861    cx.update_editor(|editor, _, cx| {
19862        editor.set_expand_all_diff_hunks(cx);
19863    });
19864
19865    let working_copy = r#"
19866            ˇfn main() {
19867                println!("hello, world!");
19868            }
19869        "#
19870    .unindent();
19871
19872    cx.set_state(&working_copy);
19873    executor.run_until_parked();
19874
19875    cx.assert_state_with_diff(
19876        r#"
19877            + ˇfn main() {
19878            +     println!("hello, world!");
19879            + }
19880        "#
19881        .unindent(),
19882    );
19883    cx.assert_index_text(None);
19884
19885    cx.update_editor(|editor, window, cx| {
19886        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19887    });
19888    executor.run_until_parked();
19889    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
19890    cx.assert_state_with_diff(
19891        r#"
19892            + ˇfn main() {
19893            +     println!("hello, world!");
19894            + }
19895        "#
19896        .unindent(),
19897    );
19898
19899    cx.update_editor(|editor, window, cx| {
19900        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
19901    });
19902    executor.run_until_parked();
19903    cx.assert_index_text(None);
19904}
19905
19906async fn setup_indent_guides_editor(
19907    text: &str,
19908    cx: &mut TestAppContext,
19909) -> (BufferId, EditorTestContext) {
19910    init_test(cx, |_| {});
19911
19912    let mut cx = EditorTestContext::new(cx).await;
19913
19914    let buffer_id = cx.update_editor(|editor, window, cx| {
19915        editor.set_text(text, window, cx);
19916        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
19917
19918        buffer_ids[0]
19919    });
19920
19921    (buffer_id, cx)
19922}
19923
19924fn assert_indent_guides(
19925    range: Range<u32>,
19926    expected: Vec<IndentGuide>,
19927    active_indices: Option<Vec<usize>>,
19928    cx: &mut EditorTestContext,
19929) {
19930    let indent_guides = cx.update_editor(|editor, window, cx| {
19931        let snapshot = editor.snapshot(window, cx).display_snapshot;
19932        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
19933            editor,
19934            MultiBufferRow(range.start)..MultiBufferRow(range.end),
19935            true,
19936            &snapshot,
19937            cx,
19938        );
19939
19940        indent_guides.sort_by(|a, b| {
19941            a.depth.cmp(&b.depth).then(
19942                a.start_row
19943                    .cmp(&b.start_row)
19944                    .then(a.end_row.cmp(&b.end_row)),
19945            )
19946        });
19947        indent_guides
19948    });
19949
19950    if let Some(expected) = active_indices {
19951        let active_indices = cx.update_editor(|editor, window, cx| {
19952            let snapshot = editor.snapshot(window, cx).display_snapshot;
19953            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
19954        });
19955
19956        assert_eq!(
19957            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
19958            expected,
19959            "Active indent guide indices do not match"
19960        );
19961    }
19962
19963    assert_eq!(indent_guides, expected, "Indent guides do not match");
19964}
19965
19966fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
19967    IndentGuide {
19968        buffer_id,
19969        start_row: MultiBufferRow(start_row),
19970        end_row: MultiBufferRow(end_row),
19971        depth,
19972        tab_size: 4,
19973        settings: IndentGuideSettings {
19974            enabled: true,
19975            line_width: 1,
19976            active_line_width: 1,
19977            ..Default::default()
19978        },
19979    }
19980}
19981
19982#[gpui::test]
19983async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
19984    let (buffer_id, mut cx) = setup_indent_guides_editor(
19985        &"
19986        fn main() {
19987            let a = 1;
19988        }"
19989        .unindent(),
19990        cx,
19991    )
19992    .await;
19993
19994    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
19995}
19996
19997#[gpui::test]
19998async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
19999    let (buffer_id, mut cx) = setup_indent_guides_editor(
20000        &"
20001        fn main() {
20002            let a = 1;
20003            let b = 2;
20004        }"
20005        .unindent(),
20006        cx,
20007    )
20008    .await;
20009
20010    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20011}
20012
20013#[gpui::test]
20014async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20015    let (buffer_id, mut cx) = setup_indent_guides_editor(
20016        &"
20017        fn main() {
20018            let a = 1;
20019            if a == 3 {
20020                let b = 2;
20021            } else {
20022                let c = 3;
20023            }
20024        }"
20025        .unindent(),
20026        cx,
20027    )
20028    .await;
20029
20030    assert_indent_guides(
20031        0..8,
20032        vec![
20033            indent_guide(buffer_id, 1, 6, 0),
20034            indent_guide(buffer_id, 3, 3, 1),
20035            indent_guide(buffer_id, 5, 5, 1),
20036        ],
20037        None,
20038        &mut cx,
20039    );
20040}
20041
20042#[gpui::test]
20043async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20044    let (buffer_id, mut cx) = setup_indent_guides_editor(
20045        &"
20046        fn main() {
20047            let a = 1;
20048                let b = 2;
20049            let c = 3;
20050        }"
20051        .unindent(),
20052        cx,
20053    )
20054    .await;
20055
20056    assert_indent_guides(
20057        0..5,
20058        vec![
20059            indent_guide(buffer_id, 1, 3, 0),
20060            indent_guide(buffer_id, 2, 2, 1),
20061        ],
20062        None,
20063        &mut cx,
20064    );
20065}
20066
20067#[gpui::test]
20068async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20069    let (buffer_id, mut cx) = setup_indent_guides_editor(
20070        &"
20071        fn main() {
20072            let a = 1;
20073
20074            let c = 3;
20075        }"
20076        .unindent(),
20077        cx,
20078    )
20079    .await;
20080
20081    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20082}
20083
20084#[gpui::test]
20085async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20086    let (buffer_id, mut cx) = setup_indent_guides_editor(
20087        &"
20088        fn main() {
20089            let a = 1;
20090
20091            let c = 3;
20092
20093            if a == 3 {
20094                let b = 2;
20095            } else {
20096                let c = 3;
20097            }
20098        }"
20099        .unindent(),
20100        cx,
20101    )
20102    .await;
20103
20104    assert_indent_guides(
20105        0..11,
20106        vec![
20107            indent_guide(buffer_id, 1, 9, 0),
20108            indent_guide(buffer_id, 6, 6, 1),
20109            indent_guide(buffer_id, 8, 8, 1),
20110        ],
20111        None,
20112        &mut cx,
20113    );
20114}
20115
20116#[gpui::test]
20117async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20118    let (buffer_id, mut cx) = setup_indent_guides_editor(
20119        &"
20120        fn main() {
20121            let a = 1;
20122
20123            let c = 3;
20124
20125            if a == 3 {
20126                let b = 2;
20127            } else {
20128                let c = 3;
20129            }
20130        }"
20131        .unindent(),
20132        cx,
20133    )
20134    .await;
20135
20136    assert_indent_guides(
20137        1..11,
20138        vec![
20139            indent_guide(buffer_id, 1, 9, 0),
20140            indent_guide(buffer_id, 6, 6, 1),
20141            indent_guide(buffer_id, 8, 8, 1),
20142        ],
20143        None,
20144        &mut cx,
20145    );
20146}
20147
20148#[gpui::test]
20149async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
20150    let (buffer_id, mut cx) = setup_indent_guides_editor(
20151        &"
20152        fn main() {
20153            let a = 1;
20154
20155            let c = 3;
20156
20157            if a == 3 {
20158                let b = 2;
20159            } else {
20160                let c = 3;
20161            }
20162        }"
20163        .unindent(),
20164        cx,
20165    )
20166    .await;
20167
20168    assert_indent_guides(
20169        1..10,
20170        vec![
20171            indent_guide(buffer_id, 1, 9, 0),
20172            indent_guide(buffer_id, 6, 6, 1),
20173            indent_guide(buffer_id, 8, 8, 1),
20174        ],
20175        None,
20176        &mut cx,
20177    );
20178}
20179
20180#[gpui::test]
20181async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
20182    let (buffer_id, mut cx) = setup_indent_guides_editor(
20183        &"
20184        fn main() {
20185            if a {
20186                b(
20187                    c,
20188                    d,
20189                )
20190            } else {
20191                e(
20192                    f
20193                )
20194            }
20195        }"
20196        .unindent(),
20197        cx,
20198    )
20199    .await;
20200
20201    assert_indent_guides(
20202        0..11,
20203        vec![
20204            indent_guide(buffer_id, 1, 10, 0),
20205            indent_guide(buffer_id, 2, 5, 1),
20206            indent_guide(buffer_id, 7, 9, 1),
20207            indent_guide(buffer_id, 3, 4, 2),
20208            indent_guide(buffer_id, 8, 8, 2),
20209        ],
20210        None,
20211        &mut cx,
20212    );
20213
20214    cx.update_editor(|editor, window, cx| {
20215        editor.fold_at(MultiBufferRow(2), window, cx);
20216        assert_eq!(
20217            editor.display_text(cx),
20218            "
20219            fn main() {
20220                if a {
20221                    b(⋯
20222                    )
20223                } else {
20224                    e(
20225                        f
20226                    )
20227                }
20228            }"
20229            .unindent()
20230        );
20231    });
20232
20233    assert_indent_guides(
20234        0..11,
20235        vec![
20236            indent_guide(buffer_id, 1, 10, 0),
20237            indent_guide(buffer_id, 2, 5, 1),
20238            indent_guide(buffer_id, 7, 9, 1),
20239            indent_guide(buffer_id, 8, 8, 2),
20240        ],
20241        None,
20242        &mut cx,
20243    );
20244}
20245
20246#[gpui::test]
20247async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
20248    let (buffer_id, mut cx) = setup_indent_guides_editor(
20249        &"
20250        block1
20251            block2
20252                block3
20253                    block4
20254            block2
20255        block1
20256        block1"
20257            .unindent(),
20258        cx,
20259    )
20260    .await;
20261
20262    assert_indent_guides(
20263        1..10,
20264        vec![
20265            indent_guide(buffer_id, 1, 4, 0),
20266            indent_guide(buffer_id, 2, 3, 1),
20267            indent_guide(buffer_id, 3, 3, 2),
20268        ],
20269        None,
20270        &mut cx,
20271    );
20272}
20273
20274#[gpui::test]
20275async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
20276    let (buffer_id, mut cx) = setup_indent_guides_editor(
20277        &"
20278        block1
20279            block2
20280                block3
20281
20282        block1
20283        block1"
20284            .unindent(),
20285        cx,
20286    )
20287    .await;
20288
20289    assert_indent_guides(
20290        0..6,
20291        vec![
20292            indent_guide(buffer_id, 1, 2, 0),
20293            indent_guide(buffer_id, 2, 2, 1),
20294        ],
20295        None,
20296        &mut cx,
20297    );
20298}
20299
20300#[gpui::test]
20301async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
20302    let (buffer_id, mut cx) = setup_indent_guides_editor(
20303        &"
20304        function component() {
20305        \treturn (
20306        \t\t\t
20307        \t\t<div>
20308        \t\t\t<abc></abc>
20309        \t\t</div>
20310        \t)
20311        }"
20312        .unindent(),
20313        cx,
20314    )
20315    .await;
20316
20317    assert_indent_guides(
20318        0..8,
20319        vec![
20320            indent_guide(buffer_id, 1, 6, 0),
20321            indent_guide(buffer_id, 2, 5, 1),
20322            indent_guide(buffer_id, 4, 4, 2),
20323        ],
20324        None,
20325        &mut cx,
20326    );
20327}
20328
20329#[gpui::test]
20330async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
20331    let (buffer_id, mut cx) = setup_indent_guides_editor(
20332        &"
20333        function component() {
20334        \treturn (
20335        \t
20336        \t\t<div>
20337        \t\t\t<abc></abc>
20338        \t\t</div>
20339        \t)
20340        }"
20341        .unindent(),
20342        cx,
20343    )
20344    .await;
20345
20346    assert_indent_guides(
20347        0..8,
20348        vec![
20349            indent_guide(buffer_id, 1, 6, 0),
20350            indent_guide(buffer_id, 2, 5, 1),
20351            indent_guide(buffer_id, 4, 4, 2),
20352        ],
20353        None,
20354        &mut cx,
20355    );
20356}
20357
20358#[gpui::test]
20359async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
20360    let (buffer_id, mut cx) = setup_indent_guides_editor(
20361        &"
20362        block1
20363
20364
20365
20366            block2
20367        "
20368        .unindent(),
20369        cx,
20370    )
20371    .await;
20372
20373    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20374}
20375
20376#[gpui::test]
20377async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
20378    let (buffer_id, mut cx) = setup_indent_guides_editor(
20379        &"
20380        def a:
20381        \tb = 3
20382        \tif True:
20383        \t\tc = 4
20384        \t\td = 5
20385        \tprint(b)
20386        "
20387        .unindent(),
20388        cx,
20389    )
20390    .await;
20391
20392    assert_indent_guides(
20393        0..6,
20394        vec![
20395            indent_guide(buffer_id, 1, 5, 0),
20396            indent_guide(buffer_id, 3, 4, 1),
20397        ],
20398        None,
20399        &mut cx,
20400    );
20401}
20402
20403#[gpui::test]
20404async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
20405    let (buffer_id, mut cx) = setup_indent_guides_editor(
20406        &"
20407    fn main() {
20408        let a = 1;
20409    }"
20410        .unindent(),
20411        cx,
20412    )
20413    .await;
20414
20415    cx.update_editor(|editor, window, cx| {
20416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20417            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20418        });
20419    });
20420
20421    assert_indent_guides(
20422        0..3,
20423        vec![indent_guide(buffer_id, 1, 1, 0)],
20424        Some(vec![0]),
20425        &mut cx,
20426    );
20427}
20428
20429#[gpui::test]
20430async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
20431    let (buffer_id, mut cx) = setup_indent_guides_editor(
20432        &"
20433    fn main() {
20434        if 1 == 2 {
20435            let a = 1;
20436        }
20437    }"
20438        .unindent(),
20439        cx,
20440    )
20441    .await;
20442
20443    cx.update_editor(|editor, window, cx| {
20444        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20445            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20446        });
20447    });
20448
20449    assert_indent_guides(
20450        0..4,
20451        vec![
20452            indent_guide(buffer_id, 1, 3, 0),
20453            indent_guide(buffer_id, 2, 2, 1),
20454        ],
20455        Some(vec![1]),
20456        &mut cx,
20457    );
20458
20459    cx.update_editor(|editor, window, cx| {
20460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20461            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20462        });
20463    });
20464
20465    assert_indent_guides(
20466        0..4,
20467        vec![
20468            indent_guide(buffer_id, 1, 3, 0),
20469            indent_guide(buffer_id, 2, 2, 1),
20470        ],
20471        Some(vec![1]),
20472        &mut cx,
20473    );
20474
20475    cx.update_editor(|editor, window, cx| {
20476        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20477            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
20478        });
20479    });
20480
20481    assert_indent_guides(
20482        0..4,
20483        vec![
20484            indent_guide(buffer_id, 1, 3, 0),
20485            indent_guide(buffer_id, 2, 2, 1),
20486        ],
20487        Some(vec![0]),
20488        &mut cx,
20489    );
20490}
20491
20492#[gpui::test]
20493async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
20494    let (buffer_id, mut cx) = setup_indent_guides_editor(
20495        &"
20496    fn main() {
20497        let a = 1;
20498
20499        let b = 2;
20500    }"
20501        .unindent(),
20502        cx,
20503    )
20504    .await;
20505
20506    cx.update_editor(|editor, window, cx| {
20507        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20508            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
20509        });
20510    });
20511
20512    assert_indent_guides(
20513        0..5,
20514        vec![indent_guide(buffer_id, 1, 3, 0)],
20515        Some(vec![0]),
20516        &mut cx,
20517    );
20518}
20519
20520#[gpui::test]
20521async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
20522    let (buffer_id, mut cx) = setup_indent_guides_editor(
20523        &"
20524    def m:
20525        a = 1
20526        pass"
20527            .unindent(),
20528        cx,
20529    )
20530    .await;
20531
20532    cx.update_editor(|editor, window, cx| {
20533        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
20534            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
20535        });
20536    });
20537
20538    assert_indent_guides(
20539        0..3,
20540        vec![indent_guide(buffer_id, 1, 2, 0)],
20541        Some(vec![0]),
20542        &mut cx,
20543    );
20544}
20545
20546#[gpui::test]
20547async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
20548    init_test(cx, |_| {});
20549    let mut cx = EditorTestContext::new(cx).await;
20550    let text = indoc! {
20551        "
20552        impl A {
20553            fn b() {
20554                0;
20555                3;
20556                5;
20557                6;
20558                7;
20559            }
20560        }
20561        "
20562    };
20563    let base_text = indoc! {
20564        "
20565        impl A {
20566            fn b() {
20567                0;
20568                1;
20569                2;
20570                3;
20571                4;
20572            }
20573            fn c() {
20574                5;
20575                6;
20576                7;
20577            }
20578        }
20579        "
20580    };
20581
20582    cx.update_editor(|editor, window, cx| {
20583        editor.set_text(text, window, cx);
20584
20585        editor.buffer().update(cx, |multibuffer, cx| {
20586            let buffer = multibuffer.as_singleton().unwrap();
20587            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
20588
20589            multibuffer.set_all_diff_hunks_expanded(cx);
20590            multibuffer.add_diff(diff, cx);
20591
20592            buffer.read(cx).remote_id()
20593        })
20594    });
20595    cx.run_until_parked();
20596
20597    cx.assert_state_with_diff(
20598        indoc! { "
20599          impl A {
20600              fn b() {
20601                  0;
20602        -         1;
20603        -         2;
20604                  3;
20605        -         4;
20606        -     }
20607        -     fn c() {
20608                  5;
20609                  6;
20610                  7;
20611              }
20612          }
20613          ˇ"
20614        }
20615        .to_string(),
20616    );
20617
20618    let mut actual_guides = cx.update_editor(|editor, window, cx| {
20619        editor
20620            .snapshot(window, cx)
20621            .buffer_snapshot
20622            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
20623            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
20624            .collect::<Vec<_>>()
20625    });
20626    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
20627    assert_eq!(
20628        actual_guides,
20629        vec![
20630            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
20631            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
20632            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
20633        ]
20634    );
20635}
20636
20637#[gpui::test]
20638async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20639    init_test(cx, |_| {});
20640    let mut cx = EditorTestContext::new(cx).await;
20641
20642    let diff_base = r#"
20643        a
20644        b
20645        c
20646        "#
20647    .unindent();
20648
20649    cx.set_state(
20650        &r#"
20651        ˇA
20652        b
20653        C
20654        "#
20655        .unindent(),
20656    );
20657    cx.set_head_text(&diff_base);
20658    cx.update_editor(|editor, window, cx| {
20659        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20660    });
20661    executor.run_until_parked();
20662
20663    let both_hunks_expanded = r#"
20664        - a
20665        + ˇA
20666          b
20667        - c
20668        + C
20669        "#
20670    .unindent();
20671
20672    cx.assert_state_with_diff(both_hunks_expanded.clone());
20673
20674    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20675        let snapshot = editor.snapshot(window, cx);
20676        let hunks = editor
20677            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20678            .collect::<Vec<_>>();
20679        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20680        let buffer_id = hunks[0].buffer_id;
20681        hunks
20682            .into_iter()
20683            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20684            .collect::<Vec<_>>()
20685    });
20686    assert_eq!(hunk_ranges.len(), 2);
20687
20688    cx.update_editor(|editor, _, cx| {
20689        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20690    });
20691    executor.run_until_parked();
20692
20693    let second_hunk_expanded = r#"
20694          ˇA
20695          b
20696        - c
20697        + C
20698        "#
20699    .unindent();
20700
20701    cx.assert_state_with_diff(second_hunk_expanded);
20702
20703    cx.update_editor(|editor, _, cx| {
20704        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20705    });
20706    executor.run_until_parked();
20707
20708    cx.assert_state_with_diff(both_hunks_expanded.clone());
20709
20710    cx.update_editor(|editor, _, cx| {
20711        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20712    });
20713    executor.run_until_parked();
20714
20715    let first_hunk_expanded = r#"
20716        - a
20717        + ˇA
20718          b
20719          C
20720        "#
20721    .unindent();
20722
20723    cx.assert_state_with_diff(first_hunk_expanded);
20724
20725    cx.update_editor(|editor, _, cx| {
20726        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20727    });
20728    executor.run_until_parked();
20729
20730    cx.assert_state_with_diff(both_hunks_expanded);
20731
20732    cx.set_state(
20733        &r#"
20734        ˇA
20735        b
20736        "#
20737        .unindent(),
20738    );
20739    cx.run_until_parked();
20740
20741    // TODO this cursor position seems bad
20742    cx.assert_state_with_diff(
20743        r#"
20744        - ˇa
20745        + A
20746          b
20747        "#
20748        .unindent(),
20749    );
20750
20751    cx.update_editor(|editor, window, cx| {
20752        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20753    });
20754
20755    cx.assert_state_with_diff(
20756        r#"
20757            - ˇa
20758            + A
20759              b
20760            - c
20761            "#
20762        .unindent(),
20763    );
20764
20765    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20766        let snapshot = editor.snapshot(window, cx);
20767        let hunks = editor
20768            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20769            .collect::<Vec<_>>();
20770        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20771        let buffer_id = hunks[0].buffer_id;
20772        hunks
20773            .into_iter()
20774            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20775            .collect::<Vec<_>>()
20776    });
20777    assert_eq!(hunk_ranges.len(), 2);
20778
20779    cx.update_editor(|editor, _, cx| {
20780        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
20781    });
20782    executor.run_until_parked();
20783
20784    cx.assert_state_with_diff(
20785        r#"
20786        - ˇa
20787        + A
20788          b
20789        "#
20790        .unindent(),
20791    );
20792}
20793
20794#[gpui::test]
20795async fn test_toggle_deletion_hunk_at_start_of_file(
20796    executor: BackgroundExecutor,
20797    cx: &mut TestAppContext,
20798) {
20799    init_test(cx, |_| {});
20800    let mut cx = EditorTestContext::new(cx).await;
20801
20802    let diff_base = r#"
20803        a
20804        b
20805        c
20806        "#
20807    .unindent();
20808
20809    cx.set_state(
20810        &r#"
20811        ˇb
20812        c
20813        "#
20814        .unindent(),
20815    );
20816    cx.set_head_text(&diff_base);
20817    cx.update_editor(|editor, window, cx| {
20818        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20819    });
20820    executor.run_until_parked();
20821
20822    let hunk_expanded = r#"
20823        - a
20824          ˇb
20825          c
20826        "#
20827    .unindent();
20828
20829    cx.assert_state_with_diff(hunk_expanded.clone());
20830
20831    let hunk_ranges = cx.update_editor(|editor, window, cx| {
20832        let snapshot = editor.snapshot(window, cx);
20833        let hunks = editor
20834            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
20835            .collect::<Vec<_>>();
20836        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
20837        let buffer_id = hunks[0].buffer_id;
20838        hunks
20839            .into_iter()
20840            .map(|hunk| Anchor::range_in_buffer(excerpt_id, buffer_id, hunk.buffer_range))
20841            .collect::<Vec<_>>()
20842    });
20843    assert_eq!(hunk_ranges.len(), 1);
20844
20845    cx.update_editor(|editor, _, cx| {
20846        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20847    });
20848    executor.run_until_parked();
20849
20850    let hunk_collapsed = r#"
20851          ˇb
20852          c
20853        "#
20854    .unindent();
20855
20856    cx.assert_state_with_diff(hunk_collapsed);
20857
20858    cx.update_editor(|editor, _, cx| {
20859        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
20860    });
20861    executor.run_until_parked();
20862
20863    cx.assert_state_with_diff(hunk_expanded);
20864}
20865
20866#[gpui::test]
20867async fn test_display_diff_hunks(cx: &mut TestAppContext) {
20868    init_test(cx, |_| {});
20869
20870    let fs = FakeFs::new(cx.executor());
20871    fs.insert_tree(
20872        path!("/test"),
20873        json!({
20874            ".git": {},
20875            "file-1": "ONE\n",
20876            "file-2": "TWO\n",
20877            "file-3": "THREE\n",
20878        }),
20879    )
20880    .await;
20881
20882    fs.set_head_for_repo(
20883        path!("/test/.git").as_ref(),
20884        &[
20885            ("file-1".into(), "one\n".into()),
20886            ("file-2".into(), "two\n".into()),
20887            ("file-3".into(), "three\n".into()),
20888        ],
20889        "deadbeef",
20890    );
20891
20892    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
20893    let mut buffers = vec![];
20894    for i in 1..=3 {
20895        let buffer = project
20896            .update(cx, |project, cx| {
20897                let path = format!(path!("/test/file-{}"), i);
20898                project.open_local_buffer(path, cx)
20899            })
20900            .await
20901            .unwrap();
20902        buffers.push(buffer);
20903    }
20904
20905    let multibuffer = cx.new(|cx| {
20906        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
20907        multibuffer.set_all_diff_hunks_expanded(cx);
20908        for buffer in &buffers {
20909            let snapshot = buffer.read(cx).snapshot();
20910            multibuffer.set_excerpts_for_path(
20911                PathKey::namespaced(0, buffer.read(cx).file().unwrap().path().clone()),
20912                buffer.clone(),
20913                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
20914                2,
20915                cx,
20916            );
20917        }
20918        multibuffer
20919    });
20920
20921    let editor = cx.add_window(|window, cx| {
20922        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
20923    });
20924    cx.run_until_parked();
20925
20926    let snapshot = editor
20927        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
20928        .unwrap();
20929    let hunks = snapshot
20930        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
20931        .map(|hunk| match hunk {
20932            DisplayDiffHunk::Unfolded {
20933                display_row_range, ..
20934            } => display_row_range,
20935            DisplayDiffHunk::Folded { .. } => unreachable!(),
20936        })
20937        .collect::<Vec<_>>();
20938    assert_eq!(
20939        hunks,
20940        [
20941            DisplayRow(2)..DisplayRow(4),
20942            DisplayRow(7)..DisplayRow(9),
20943            DisplayRow(12)..DisplayRow(14),
20944        ]
20945    );
20946}
20947
20948#[gpui::test]
20949async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
20950    init_test(cx, |_| {});
20951
20952    let mut cx = EditorTestContext::new(cx).await;
20953    cx.set_head_text(indoc! { "
20954        one
20955        two
20956        three
20957        four
20958        five
20959        "
20960    });
20961    cx.set_index_text(indoc! { "
20962        one
20963        two
20964        three
20965        four
20966        five
20967        "
20968    });
20969    cx.set_state(indoc! {"
20970        one
20971        TWO
20972        ˇTHREE
20973        FOUR
20974        five
20975    "});
20976    cx.run_until_parked();
20977    cx.update_editor(|editor, window, cx| {
20978        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20979    });
20980    cx.run_until_parked();
20981    cx.assert_index_text(Some(indoc! {"
20982        one
20983        TWO
20984        THREE
20985        FOUR
20986        five
20987    "}));
20988    cx.set_state(indoc! { "
20989        one
20990        TWO
20991        ˇTHREE-HUNDRED
20992        FOUR
20993        five
20994    "});
20995    cx.run_until_parked();
20996    cx.update_editor(|editor, window, cx| {
20997        let snapshot = editor.snapshot(window, cx);
20998        let hunks = editor
20999            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot)
21000            .collect::<Vec<_>>();
21001        assert_eq!(hunks.len(), 1);
21002        assert_eq!(
21003            hunks[0].status(),
21004            DiffHunkStatus {
21005                kind: DiffHunkStatusKind::Modified,
21006                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21007            }
21008        );
21009
21010        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21011    });
21012    cx.run_until_parked();
21013    cx.assert_index_text(Some(indoc! {"
21014        one
21015        TWO
21016        THREE-HUNDRED
21017        FOUR
21018        five
21019    "}));
21020}
21021
21022#[gpui::test]
21023fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21024    init_test(cx, |_| {});
21025
21026    let editor = cx.add_window(|window, cx| {
21027        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21028        build_editor(buffer, window, cx)
21029    });
21030
21031    let render_args = Arc::new(Mutex::new(None));
21032    let snapshot = editor
21033        .update(cx, |editor, window, cx| {
21034            let snapshot = editor.buffer().read(cx).snapshot(cx);
21035            let range =
21036                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21037
21038            struct RenderArgs {
21039                row: MultiBufferRow,
21040                folded: bool,
21041                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21042            }
21043
21044            let crease = Crease::inline(
21045                range,
21046                FoldPlaceholder::test(),
21047                {
21048                    let toggle_callback = render_args.clone();
21049                    move |row, folded, callback, _window, _cx| {
21050                        *toggle_callback.lock() = Some(RenderArgs {
21051                            row,
21052                            folded,
21053                            callback,
21054                        });
21055                        div()
21056                    }
21057                },
21058                |_row, _folded, _window, _cx| div(),
21059            );
21060
21061            editor.insert_creases(Some(crease), cx);
21062            let snapshot = editor.snapshot(window, cx);
21063            let _div =
21064                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21065            snapshot
21066        })
21067        .unwrap();
21068
21069    let render_args = render_args.lock().take().unwrap();
21070    assert_eq!(render_args.row, MultiBufferRow(1));
21071    assert!(!render_args.folded);
21072    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21073
21074    cx.update_window(*editor, |_, window, cx| {
21075        (render_args.callback)(true, 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    cx.update_window(*editor, |_, window, cx| {
21084        (render_args.callback)(false, window, cx)
21085    })
21086    .unwrap();
21087    let snapshot = editor
21088        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21089        .unwrap();
21090    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21091}
21092
21093#[gpui::test]
21094async fn test_input_text(cx: &mut TestAppContext) {
21095    init_test(cx, |_| {});
21096    let mut cx = EditorTestContext::new(cx).await;
21097
21098    cx.set_state(
21099        &r#"ˇone
21100        two
21101
21102        three
21103        fourˇ
21104        five
21105
21106        siˇx"#
21107            .unindent(),
21108    );
21109
21110    cx.dispatch_action(HandleInput(String::new()));
21111    cx.assert_editor_state(
21112        &r#"ˇone
21113        two
21114
21115        three
21116        fourˇ
21117        five
21118
21119        siˇx"#
21120            .unindent(),
21121    );
21122
21123    cx.dispatch_action(HandleInput("AAAA".to_string()));
21124    cx.assert_editor_state(
21125        &r#"AAAAˇone
21126        two
21127
21128        three
21129        fourAAAAˇ
21130        five
21131
21132        siAAAAˇx"#
21133            .unindent(),
21134    );
21135}
21136
21137#[gpui::test]
21138async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
21139    init_test(cx, |_| {});
21140
21141    let mut cx = EditorTestContext::new(cx).await;
21142    cx.set_state(
21143        r#"let foo = 1;
21144let foo = 2;
21145let foo = 3;
21146let fooˇ = 4;
21147let foo = 5;
21148let foo = 6;
21149let foo = 7;
21150let foo = 8;
21151let foo = 9;
21152let foo = 10;
21153let foo = 11;
21154let foo = 12;
21155let foo = 13;
21156let foo = 14;
21157let foo = 15;"#,
21158    );
21159
21160    cx.update_editor(|e, window, cx| {
21161        assert_eq!(
21162            e.next_scroll_position,
21163            NextScrollCursorCenterTopBottom::Center,
21164            "Default next scroll direction is center",
21165        );
21166
21167        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21168        assert_eq!(
21169            e.next_scroll_position,
21170            NextScrollCursorCenterTopBottom::Top,
21171            "After center, next scroll direction should be top",
21172        );
21173
21174        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21175        assert_eq!(
21176            e.next_scroll_position,
21177            NextScrollCursorCenterTopBottom::Bottom,
21178            "After top, next scroll direction should be bottom",
21179        );
21180
21181        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21182        assert_eq!(
21183            e.next_scroll_position,
21184            NextScrollCursorCenterTopBottom::Center,
21185            "After bottom, scrolling should start over",
21186        );
21187
21188        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
21189        assert_eq!(
21190            e.next_scroll_position,
21191            NextScrollCursorCenterTopBottom::Top,
21192            "Scrolling continues if retriggered fast enough"
21193        );
21194    });
21195
21196    cx.executor()
21197        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
21198    cx.executor().run_until_parked();
21199    cx.update_editor(|e, _, _| {
21200        assert_eq!(
21201            e.next_scroll_position,
21202            NextScrollCursorCenterTopBottom::Center,
21203            "If scrolling is not triggered fast enough, it should reset"
21204        );
21205    });
21206}
21207
21208#[gpui::test]
21209async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
21210    init_test(cx, |_| {});
21211    let mut cx = EditorLspTestContext::new_rust(
21212        lsp::ServerCapabilities {
21213            definition_provider: Some(lsp::OneOf::Left(true)),
21214            references_provider: Some(lsp::OneOf::Left(true)),
21215            ..lsp::ServerCapabilities::default()
21216        },
21217        cx,
21218    )
21219    .await;
21220
21221    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
21222        let go_to_definition = cx
21223            .lsp
21224            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21225                move |params, _| async move {
21226                    if empty_go_to_definition {
21227                        Ok(None)
21228                    } else {
21229                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
21230                            uri: params.text_document_position_params.text_document.uri,
21231                            range: lsp::Range::new(
21232                                lsp::Position::new(4, 3),
21233                                lsp::Position::new(4, 6),
21234                            ),
21235                        })))
21236                    }
21237                },
21238            );
21239        let references = cx
21240            .lsp
21241            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21242                Ok(Some(vec![lsp::Location {
21243                    uri: params.text_document_position.text_document.uri,
21244                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
21245                }]))
21246            });
21247        (go_to_definition, references)
21248    };
21249
21250    cx.set_state(
21251        &r#"fn one() {
21252            let mut a = ˇtwo();
21253        }
21254
21255        fn two() {}"#
21256            .unindent(),
21257    );
21258    set_up_lsp_handlers(false, &mut cx);
21259    let navigated = cx
21260        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21261        .await
21262        .expect("Failed to navigate to definition");
21263    assert_eq!(
21264        navigated,
21265        Navigated::Yes,
21266        "Should have navigated to definition from the GetDefinition response"
21267    );
21268    cx.assert_editor_state(
21269        &r#"fn one() {
21270            let mut a = two();
21271        }
21272
21273        fn «twoˇ»() {}"#
21274            .unindent(),
21275    );
21276
21277    let editors = cx.update_workspace(|workspace, _, cx| {
21278        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21279    });
21280    cx.update_editor(|_, _, test_editor_cx| {
21281        assert_eq!(
21282            editors.len(),
21283            1,
21284            "Initially, only one, test, editor should be open in the workspace"
21285        );
21286        assert_eq!(
21287            test_editor_cx.entity(),
21288            editors.last().expect("Asserted len is 1").clone()
21289        );
21290    });
21291
21292    set_up_lsp_handlers(true, &mut cx);
21293    let navigated = cx
21294        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21295        .await
21296        .expect("Failed to navigate to lookup references");
21297    assert_eq!(
21298        navigated,
21299        Navigated::Yes,
21300        "Should have navigated to references as a fallback after empty GoToDefinition response"
21301    );
21302    // We should not change the selections in the existing file,
21303    // if opening another milti buffer with the references
21304    cx.assert_editor_state(
21305        &r#"fn one() {
21306            let mut a = two();
21307        }
21308
21309        fn «twoˇ»() {}"#
21310            .unindent(),
21311    );
21312    let editors = cx.update_workspace(|workspace, _, cx| {
21313        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21314    });
21315    cx.update_editor(|_, _, test_editor_cx| {
21316        assert_eq!(
21317            editors.len(),
21318            2,
21319            "After falling back to references search, we open a new editor with the results"
21320        );
21321        let references_fallback_text = editors
21322            .into_iter()
21323            .find(|new_editor| *new_editor != test_editor_cx.entity())
21324            .expect("Should have one non-test editor now")
21325            .read(test_editor_cx)
21326            .text(test_editor_cx);
21327        assert_eq!(
21328            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
21329            "Should use the range from the references response and not the GoToDefinition one"
21330        );
21331    });
21332}
21333
21334#[gpui::test]
21335async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
21336    init_test(cx, |_| {});
21337    cx.update(|cx| {
21338        let mut editor_settings = EditorSettings::get_global(cx).clone();
21339        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
21340        EditorSettings::override_global(editor_settings, cx);
21341    });
21342    let mut cx = EditorLspTestContext::new_rust(
21343        lsp::ServerCapabilities {
21344            definition_provider: Some(lsp::OneOf::Left(true)),
21345            references_provider: Some(lsp::OneOf::Left(true)),
21346            ..lsp::ServerCapabilities::default()
21347        },
21348        cx,
21349    )
21350    .await;
21351    let original_state = r#"fn one() {
21352        let mut a = ˇtwo();
21353    }
21354
21355    fn two() {}"#
21356        .unindent();
21357    cx.set_state(&original_state);
21358
21359    let mut go_to_definition = cx
21360        .lsp
21361        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
21362            move |_, _| async move { Ok(None) },
21363        );
21364    let _references = cx
21365        .lsp
21366        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
21367            panic!("Should not call for references with no go to definition fallback")
21368        });
21369
21370    let navigated = cx
21371        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
21372        .await
21373        .expect("Failed to navigate to lookup references");
21374    go_to_definition
21375        .next()
21376        .await
21377        .expect("Should have called the go_to_definition handler");
21378
21379    assert_eq!(
21380        navigated,
21381        Navigated::No,
21382        "Should have navigated to references as a fallback after empty GoToDefinition response"
21383    );
21384    cx.assert_editor_state(&original_state);
21385    let editors = cx.update_workspace(|workspace, _, cx| {
21386        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21387    });
21388    cx.update_editor(|_, _, _| {
21389        assert_eq!(
21390            editors.len(),
21391            1,
21392            "After unsuccessful fallback, no other editor should have been opened"
21393        );
21394    });
21395}
21396
21397#[gpui::test]
21398async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
21399    init_test(cx, |_| {});
21400    let mut cx = EditorLspTestContext::new_rust(
21401        lsp::ServerCapabilities {
21402            references_provider: Some(lsp::OneOf::Left(true)),
21403            ..lsp::ServerCapabilities::default()
21404        },
21405        cx,
21406    )
21407    .await;
21408
21409    cx.set_state(
21410        &r#"
21411        fn one() {
21412            let mut a = two();
21413        }
21414
21415        fn ˇtwo() {}"#
21416            .unindent(),
21417    );
21418    cx.lsp
21419        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21420            Ok(Some(vec![
21421                lsp::Location {
21422                    uri: params.text_document_position.text_document.uri.clone(),
21423                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21424                },
21425                lsp::Location {
21426                    uri: params.text_document_position.text_document.uri,
21427                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
21428                },
21429            ]))
21430        });
21431    let navigated = cx
21432        .update_editor(|editor, window, cx| {
21433            editor.find_all_references(&FindAllReferences, window, cx)
21434        })
21435        .unwrap()
21436        .await
21437        .expect("Failed to navigate to references");
21438    assert_eq!(
21439        navigated,
21440        Navigated::Yes,
21441        "Should have navigated to references from the FindAllReferences response"
21442    );
21443    cx.assert_editor_state(
21444        &r#"fn one() {
21445            let mut a = two();
21446        }
21447
21448        fn ˇtwo() {}"#
21449            .unindent(),
21450    );
21451
21452    let editors = cx.update_workspace(|workspace, _, cx| {
21453        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21454    });
21455    cx.update_editor(|_, _, _| {
21456        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
21457    });
21458
21459    cx.set_state(
21460        &r#"fn one() {
21461            let mut a = ˇtwo();
21462        }
21463
21464        fn two() {}"#
21465            .unindent(),
21466    );
21467    let navigated = cx
21468        .update_editor(|editor, window, cx| {
21469            editor.find_all_references(&FindAllReferences, window, cx)
21470        })
21471        .unwrap()
21472        .await
21473        .expect("Failed to navigate to references");
21474    assert_eq!(
21475        navigated,
21476        Navigated::Yes,
21477        "Should have navigated to references from the FindAllReferences response"
21478    );
21479    cx.assert_editor_state(
21480        &r#"fn one() {
21481            let mut a = ˇtwo();
21482        }
21483
21484        fn two() {}"#
21485            .unindent(),
21486    );
21487    let editors = cx.update_workspace(|workspace, _, cx| {
21488        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21489    });
21490    cx.update_editor(|_, _, _| {
21491        assert_eq!(
21492            editors.len(),
21493            2,
21494            "should have re-used the previous multibuffer"
21495        );
21496    });
21497
21498    cx.set_state(
21499        &r#"fn one() {
21500            let mut a = ˇtwo();
21501        }
21502        fn three() {}
21503        fn two() {}"#
21504            .unindent(),
21505    );
21506    cx.lsp
21507        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
21508            Ok(Some(vec![
21509                lsp::Location {
21510                    uri: params.text_document_position.text_document.uri.clone(),
21511                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
21512                },
21513                lsp::Location {
21514                    uri: params.text_document_position.text_document.uri,
21515                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
21516                },
21517            ]))
21518        });
21519    let navigated = cx
21520        .update_editor(|editor, window, cx| {
21521            editor.find_all_references(&FindAllReferences, window, cx)
21522        })
21523        .unwrap()
21524        .await
21525        .expect("Failed to navigate to references");
21526    assert_eq!(
21527        navigated,
21528        Navigated::Yes,
21529        "Should have navigated to references from the FindAllReferences response"
21530    );
21531    cx.assert_editor_state(
21532        &r#"fn one() {
21533                let mut a = ˇtwo();
21534            }
21535            fn three() {}
21536            fn two() {}"#
21537            .unindent(),
21538    );
21539    let editors = cx.update_workspace(|workspace, _, cx| {
21540        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
21541    });
21542    cx.update_editor(|_, _, _| {
21543        assert_eq!(
21544            editors.len(),
21545            3,
21546            "should have used a new multibuffer as offsets changed"
21547        );
21548    });
21549}
21550#[gpui::test]
21551async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
21552    init_test(cx, |_| {});
21553
21554    let language = Arc::new(Language::new(
21555        LanguageConfig::default(),
21556        Some(tree_sitter_rust::LANGUAGE.into()),
21557    ));
21558
21559    let text = r#"
21560        #[cfg(test)]
21561        mod tests() {
21562            #[test]
21563            fn runnable_1() {
21564                let a = 1;
21565            }
21566
21567            #[test]
21568            fn runnable_2() {
21569                let a = 1;
21570                let b = 2;
21571            }
21572        }
21573    "#
21574    .unindent();
21575
21576    let fs = FakeFs::new(cx.executor());
21577    fs.insert_file("/file.rs", Default::default()).await;
21578
21579    let project = Project::test(fs, ["/a".as_ref()], cx).await;
21580    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21581    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21582    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
21583    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
21584
21585    let editor = cx.new_window_entity(|window, cx| {
21586        Editor::new(
21587            EditorMode::full(),
21588            multi_buffer,
21589            Some(project.clone()),
21590            window,
21591            cx,
21592        )
21593    });
21594
21595    editor.update_in(cx, |editor, window, cx| {
21596        let snapshot = editor.buffer().read(cx).snapshot(cx);
21597        editor.tasks.insert(
21598            (buffer.read(cx).remote_id(), 3),
21599            RunnableTasks {
21600                templates: vec![],
21601                offset: snapshot.anchor_before(43),
21602                column: 0,
21603                extra_variables: HashMap::default(),
21604                context_range: BufferOffset(43)..BufferOffset(85),
21605            },
21606        );
21607        editor.tasks.insert(
21608            (buffer.read(cx).remote_id(), 8),
21609            RunnableTasks {
21610                templates: vec![],
21611                offset: snapshot.anchor_before(86),
21612                column: 0,
21613                extra_variables: HashMap::default(),
21614                context_range: BufferOffset(86)..BufferOffset(191),
21615            },
21616        );
21617
21618        // Test finding task when cursor is inside function body
21619        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21620            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
21621        });
21622        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21623        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
21624
21625        // Test finding task when cursor is on function name
21626        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21627            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
21628        });
21629        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
21630        assert_eq!(row, 8, "Should find task when cursor is on function name");
21631    });
21632}
21633
21634#[gpui::test]
21635async fn test_folding_buffers(cx: &mut TestAppContext) {
21636    init_test(cx, |_| {});
21637
21638    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21639    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
21640    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
21641
21642    let fs = FakeFs::new(cx.executor());
21643    fs.insert_tree(
21644        path!("/a"),
21645        json!({
21646            "first.rs": sample_text_1,
21647            "second.rs": sample_text_2,
21648            "third.rs": sample_text_3,
21649        }),
21650    )
21651    .await;
21652    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21653    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21654    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21655    let worktree = project.update(cx, |project, cx| {
21656        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21657        assert_eq!(worktrees.len(), 1);
21658        worktrees.pop().unwrap()
21659    });
21660    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21661
21662    let buffer_1 = project
21663        .update(cx, |project, cx| {
21664            project.open_buffer((worktree_id, "first.rs"), cx)
21665        })
21666        .await
21667        .unwrap();
21668    let buffer_2 = project
21669        .update(cx, |project, cx| {
21670            project.open_buffer((worktree_id, "second.rs"), cx)
21671        })
21672        .await
21673        .unwrap();
21674    let buffer_3 = project
21675        .update(cx, |project, cx| {
21676            project.open_buffer((worktree_id, "third.rs"), cx)
21677        })
21678        .await
21679        .unwrap();
21680
21681    let multi_buffer = cx.new(|cx| {
21682        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21683        multi_buffer.push_excerpts(
21684            buffer_1.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_2.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.push_excerpts(
21702            buffer_3.clone(),
21703            [
21704                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
21705                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
21706                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
21707            ],
21708            cx,
21709        );
21710        multi_buffer
21711    });
21712    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21713        Editor::new(
21714            EditorMode::full(),
21715            multi_buffer.clone(),
21716            Some(project.clone()),
21717            window,
21718            cx,
21719        )
21720    });
21721
21722    assert_eq!(
21723        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21724        "\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",
21725    );
21726
21727    multi_buffer_editor.update(cx, |editor, cx| {
21728        editor.fold_buffer(buffer_1.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\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21733        "After folding the first buffer, its text should not be displayed"
21734    );
21735
21736    multi_buffer_editor.update(cx, |editor, cx| {
21737        editor.fold_buffer(buffer_2.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\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
21742        "After folding the second buffer, its text should not be displayed"
21743    );
21744
21745    multi_buffer_editor.update(cx, |editor, cx| {
21746        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
21747    });
21748    assert_eq!(
21749        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21750        "\n\n\n\n\n",
21751        "After folding the third buffer, its text should not be displayed"
21752    );
21753
21754    // Emulate selection inside the fold logic, that should work
21755    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21756        editor
21757            .snapshot(window, cx)
21758            .next_line_boundary(Point::new(0, 4));
21759    });
21760
21761    multi_buffer_editor.update(cx, |editor, cx| {
21762        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21763    });
21764    assert_eq!(
21765        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21766        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
21767        "After unfolding the second buffer, its text should be displayed"
21768    );
21769
21770    // Typing inside of buffer 1 causes that buffer to be unfolded.
21771    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21772        assert_eq!(
21773            multi_buffer
21774                .read(cx)
21775                .snapshot(cx)
21776                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
21777                .collect::<String>(),
21778            "bbbb"
21779        );
21780        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
21781            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
21782        });
21783        editor.handle_input("B", window, cx);
21784    });
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",
21789        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
21790    );
21791
21792    multi_buffer_editor.update(cx, |editor, cx| {
21793        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21794    });
21795    assert_eq!(
21796        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21797        "\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",
21798        "After unfolding the all buffers, all original text should be displayed"
21799    );
21800}
21801
21802#[gpui::test]
21803async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
21804    init_test(cx, |_| {});
21805
21806    let sample_text_1 = "1111\n2222\n3333".to_string();
21807    let sample_text_2 = "4444\n5555\n6666".to_string();
21808    let sample_text_3 = "7777\n8888\n9999".to_string();
21809
21810    let fs = FakeFs::new(cx.executor());
21811    fs.insert_tree(
21812        path!("/a"),
21813        json!({
21814            "first.rs": sample_text_1,
21815            "second.rs": sample_text_2,
21816            "third.rs": sample_text_3,
21817        }),
21818    )
21819    .await;
21820    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21821    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21822    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21823    let worktree = project.update(cx, |project, cx| {
21824        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21825        assert_eq!(worktrees.len(), 1);
21826        worktrees.pop().unwrap()
21827    });
21828    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21829
21830    let buffer_1 = project
21831        .update(cx, |project, cx| {
21832            project.open_buffer((worktree_id, "first.rs"), cx)
21833        })
21834        .await
21835        .unwrap();
21836    let buffer_2 = project
21837        .update(cx, |project, cx| {
21838            project.open_buffer((worktree_id, "second.rs"), cx)
21839        })
21840        .await
21841        .unwrap();
21842    let buffer_3 = project
21843        .update(cx, |project, cx| {
21844            project.open_buffer((worktree_id, "third.rs"), cx)
21845        })
21846        .await
21847        .unwrap();
21848
21849    let multi_buffer = cx.new(|cx| {
21850        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21851        multi_buffer.push_excerpts(
21852            buffer_1.clone(),
21853            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21854            cx,
21855        );
21856        multi_buffer.push_excerpts(
21857            buffer_2.clone(),
21858            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21859            cx,
21860        );
21861        multi_buffer.push_excerpts(
21862            buffer_3.clone(),
21863            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
21864            cx,
21865        );
21866        multi_buffer
21867    });
21868
21869    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21870        Editor::new(
21871            EditorMode::full(),
21872            multi_buffer,
21873            Some(project.clone()),
21874            window,
21875            cx,
21876        )
21877    });
21878
21879    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
21880    assert_eq!(
21881        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21882        full_text,
21883    );
21884
21885    multi_buffer_editor.update(cx, |editor, cx| {
21886        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
21887    });
21888    assert_eq!(
21889        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21890        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
21891        "After folding the first buffer, its text should not be displayed"
21892    );
21893
21894    multi_buffer_editor.update(cx, |editor, cx| {
21895        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
21896    });
21897
21898    assert_eq!(
21899        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21900        "\n\n\n\n\n\n7777\n8888\n9999",
21901        "After folding the second buffer, its text should not be displayed"
21902    );
21903
21904    multi_buffer_editor.update(cx, |editor, cx| {
21905        editor.fold_buffer(buffer_3.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\n\n",
21910        "After folding the third buffer, its text should not be displayed"
21911    );
21912
21913    multi_buffer_editor.update(cx, |editor, cx| {
21914        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
21915    });
21916    assert_eq!(
21917        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21918        "\n\n\n\n4444\n5555\n6666\n\n",
21919        "After unfolding the second buffer, its text should be displayed"
21920    );
21921
21922    multi_buffer_editor.update(cx, |editor, cx| {
21923        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
21924    });
21925    assert_eq!(
21926        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21927        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
21928        "After unfolding the first buffer, its text should be displayed"
21929    );
21930
21931    multi_buffer_editor.update(cx, |editor, cx| {
21932        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
21933    });
21934    assert_eq!(
21935        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
21936        full_text,
21937        "After unfolding all buffers, all original text should be displayed"
21938    );
21939}
21940
21941#[gpui::test]
21942async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
21943    init_test(cx, |_| {});
21944
21945    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
21946
21947    let fs = FakeFs::new(cx.executor());
21948    fs.insert_tree(
21949        path!("/a"),
21950        json!({
21951            "main.rs": sample_text,
21952        }),
21953    )
21954    .await;
21955    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
21956    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
21957    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
21958    let worktree = project.update(cx, |project, cx| {
21959        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
21960        assert_eq!(worktrees.len(), 1);
21961        worktrees.pop().unwrap()
21962    });
21963    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
21964
21965    let buffer_1 = project
21966        .update(cx, |project, cx| {
21967            project.open_buffer((worktree_id, "main.rs"), cx)
21968        })
21969        .await
21970        .unwrap();
21971
21972    let multi_buffer = cx.new(|cx| {
21973        let mut multi_buffer = MultiBuffer::new(ReadWrite);
21974        multi_buffer.push_excerpts(
21975            buffer_1.clone(),
21976            [ExcerptRange::new(
21977                Point::new(0, 0)
21978                    ..Point::new(
21979                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
21980                        0,
21981                    ),
21982            )],
21983            cx,
21984        );
21985        multi_buffer
21986    });
21987    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
21988        Editor::new(
21989            EditorMode::full(),
21990            multi_buffer,
21991            Some(project.clone()),
21992            window,
21993            cx,
21994        )
21995    });
21996
21997    let selection_range = Point::new(1, 0)..Point::new(2, 0);
21998    multi_buffer_editor.update_in(cx, |editor, window, cx| {
21999        enum TestHighlight {}
22000        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22001        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22002        editor.highlight_text::<TestHighlight>(
22003            vec![highlight_range.clone()],
22004            HighlightStyle::color(Hsla::green()),
22005            cx,
22006        );
22007        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22008            s.select_ranges(Some(highlight_range))
22009        });
22010    });
22011
22012    let full_text = format!("\n\n{sample_text}");
22013    assert_eq!(
22014        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22015        full_text,
22016    );
22017}
22018
22019#[gpui::test]
22020async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22021    init_test(cx, |_| {});
22022    cx.update(|cx| {
22023        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22024            "keymaps/default-linux.json",
22025            cx,
22026        )
22027        .unwrap();
22028        cx.bind_keys(default_key_bindings);
22029    });
22030
22031    let (editor, cx) = cx.add_window_view(|window, cx| {
22032        let multi_buffer = MultiBuffer::build_multi(
22033            [
22034                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22035                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22036                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22037                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22038            ],
22039            cx,
22040        );
22041        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22042
22043        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22044        // fold all but the second buffer, so that we test navigating between two
22045        // adjacent folded buffers, as well as folded buffers at the start and
22046        // end the multibuffer
22047        editor.fold_buffer(buffer_ids[0], cx);
22048        editor.fold_buffer(buffer_ids[2], cx);
22049        editor.fold_buffer(buffer_ids[3], cx);
22050
22051        editor
22052    });
22053    cx.simulate_resize(size(px(1000.), px(1000.)));
22054
22055    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22056    cx.assert_excerpts_with_selections(indoc! {"
22057        [EXCERPT]
22058        ˇ[FOLDED]
22059        [EXCERPT]
22060        a1
22061        b1
22062        [EXCERPT]
22063        [FOLDED]
22064        [EXCERPT]
22065        [FOLDED]
22066        "
22067    });
22068    cx.simulate_keystroke("down");
22069    cx.assert_excerpts_with_selections(indoc! {"
22070        [EXCERPT]
22071        [FOLDED]
22072        [EXCERPT]
22073        ˇa1
22074        b1
22075        [EXCERPT]
22076        [FOLDED]
22077        [EXCERPT]
22078        [FOLDED]
22079        "
22080    });
22081    cx.simulate_keystroke("down");
22082    cx.assert_excerpts_with_selections(indoc! {"
22083        [EXCERPT]
22084        [FOLDED]
22085        [EXCERPT]
22086        a1
22087        ˇb1
22088        [EXCERPT]
22089        [FOLDED]
22090        [EXCERPT]
22091        [FOLDED]
22092        "
22093    });
22094    cx.simulate_keystroke("down");
22095    cx.assert_excerpts_with_selections(indoc! {"
22096        [EXCERPT]
22097        [FOLDED]
22098        [EXCERPT]
22099        a1
22100        b1
22101        ˇ[EXCERPT]
22102        [FOLDED]
22103        [EXCERPT]
22104        [FOLDED]
22105        "
22106    });
22107    cx.simulate_keystroke("down");
22108    cx.assert_excerpts_with_selections(indoc! {"
22109        [EXCERPT]
22110        [FOLDED]
22111        [EXCERPT]
22112        a1
22113        b1
22114        [EXCERPT]
22115        ˇ[FOLDED]
22116        [EXCERPT]
22117        [FOLDED]
22118        "
22119    });
22120    for _ in 0..5 {
22121        cx.simulate_keystroke("down");
22122        cx.assert_excerpts_with_selections(indoc! {"
22123            [EXCERPT]
22124            [FOLDED]
22125            [EXCERPT]
22126            a1
22127            b1
22128            [EXCERPT]
22129            [FOLDED]
22130            [EXCERPT]
22131            ˇ[FOLDED]
22132            "
22133        });
22134    }
22135
22136    cx.simulate_keystroke("up");
22137    cx.assert_excerpts_with_selections(indoc! {"
22138        [EXCERPT]
22139        [FOLDED]
22140        [EXCERPT]
22141        a1
22142        b1
22143        [EXCERPT]
22144        ˇ[FOLDED]
22145        [EXCERPT]
22146        [FOLDED]
22147        "
22148    });
22149    cx.simulate_keystroke("up");
22150    cx.assert_excerpts_with_selections(indoc! {"
22151        [EXCERPT]
22152        [FOLDED]
22153        [EXCERPT]
22154        a1
22155        b1
22156        ˇ[EXCERPT]
22157        [FOLDED]
22158        [EXCERPT]
22159        [FOLDED]
22160        "
22161    });
22162    cx.simulate_keystroke("up");
22163    cx.assert_excerpts_with_selections(indoc! {"
22164        [EXCERPT]
22165        [FOLDED]
22166        [EXCERPT]
22167        a1
22168        ˇb1
22169        [EXCERPT]
22170        [FOLDED]
22171        [EXCERPT]
22172        [FOLDED]
22173        "
22174    });
22175    cx.simulate_keystroke("up");
22176    cx.assert_excerpts_with_selections(indoc! {"
22177        [EXCERPT]
22178        [FOLDED]
22179        [EXCERPT]
22180        ˇa1
22181        b1
22182        [EXCERPT]
22183        [FOLDED]
22184        [EXCERPT]
22185        [FOLDED]
22186        "
22187    });
22188    for _ in 0..5 {
22189        cx.simulate_keystroke("up");
22190        cx.assert_excerpts_with_selections(indoc! {"
22191            [EXCERPT]
22192            ˇ[FOLDED]
22193            [EXCERPT]
22194            a1
22195            b1
22196            [EXCERPT]
22197            [FOLDED]
22198            [EXCERPT]
22199            [FOLDED]
22200            "
22201        });
22202    }
22203}
22204
22205#[gpui::test]
22206async fn test_edit_prediction_text(cx: &mut TestAppContext) {
22207    init_test(cx, |_| {});
22208
22209    // Simple insertion
22210    assert_highlighted_edits(
22211        "Hello, world!",
22212        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
22213        true,
22214        cx,
22215        |highlighted_edits, cx| {
22216            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
22217            assert_eq!(highlighted_edits.highlights.len(), 1);
22218            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
22219            assert_eq!(
22220                highlighted_edits.highlights[0].1.background_color,
22221                Some(cx.theme().status().created_background)
22222            );
22223        },
22224    )
22225    .await;
22226
22227    // Replacement
22228    assert_highlighted_edits(
22229        "This is a test.",
22230        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
22231        false,
22232        cx,
22233        |highlighted_edits, cx| {
22234            assert_eq!(highlighted_edits.text, "That is a test.");
22235            assert_eq!(highlighted_edits.highlights.len(), 1);
22236            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
22237            assert_eq!(
22238                highlighted_edits.highlights[0].1.background_color,
22239                Some(cx.theme().status().created_background)
22240            );
22241        },
22242    )
22243    .await;
22244
22245    // Multiple edits
22246    assert_highlighted_edits(
22247        "Hello, world!",
22248        vec![
22249            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
22250            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
22251        ],
22252        false,
22253        cx,
22254        |highlighted_edits, cx| {
22255            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
22256            assert_eq!(highlighted_edits.highlights.len(), 2);
22257            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
22258            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
22259            assert_eq!(
22260                highlighted_edits.highlights[0].1.background_color,
22261                Some(cx.theme().status().created_background)
22262            );
22263            assert_eq!(
22264                highlighted_edits.highlights[1].1.background_color,
22265                Some(cx.theme().status().created_background)
22266            );
22267        },
22268    )
22269    .await;
22270
22271    // Multiple lines with edits
22272    assert_highlighted_edits(
22273        "First line\nSecond line\nThird line\nFourth line",
22274        vec![
22275            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
22276            (
22277                Point::new(2, 0)..Point::new(2, 10),
22278                "New third line".to_string(),
22279            ),
22280            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
22281        ],
22282        false,
22283        cx,
22284        |highlighted_edits, cx| {
22285            assert_eq!(
22286                highlighted_edits.text,
22287                "Second modified\nNew third line\nFourth updated line"
22288            );
22289            assert_eq!(highlighted_edits.highlights.len(), 3);
22290            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
22291            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
22292            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
22293            for highlight in &highlighted_edits.highlights {
22294                assert_eq!(
22295                    highlight.1.background_color,
22296                    Some(cx.theme().status().created_background)
22297                );
22298            }
22299        },
22300    )
22301    .await;
22302}
22303
22304#[gpui::test]
22305async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
22306    init_test(cx, |_| {});
22307
22308    // Deletion
22309    assert_highlighted_edits(
22310        "Hello, world!",
22311        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
22312        true,
22313        cx,
22314        |highlighted_edits, cx| {
22315            assert_eq!(highlighted_edits.text, "Hello, world!");
22316            assert_eq!(highlighted_edits.highlights.len(), 1);
22317            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
22318            assert_eq!(
22319                highlighted_edits.highlights[0].1.background_color,
22320                Some(cx.theme().status().deleted_background)
22321            );
22322        },
22323    )
22324    .await;
22325
22326    // Insertion
22327    assert_highlighted_edits(
22328        "Hello, world!",
22329        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
22330        true,
22331        cx,
22332        |highlighted_edits, cx| {
22333            assert_eq!(highlighted_edits.highlights.len(), 1);
22334            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
22335            assert_eq!(
22336                highlighted_edits.highlights[0].1.background_color,
22337                Some(cx.theme().status().created_background)
22338            );
22339        },
22340    )
22341    .await;
22342}
22343
22344async fn assert_highlighted_edits(
22345    text: &str,
22346    edits: Vec<(Range<Point>, String)>,
22347    include_deletions: bool,
22348    cx: &mut TestAppContext,
22349    assertion_fn: impl Fn(HighlightedText, &App),
22350) {
22351    let window = cx.add_window(|window, cx| {
22352        let buffer = MultiBuffer::build_simple(text, cx);
22353        Editor::new(EditorMode::full(), buffer, None, window, cx)
22354    });
22355    let cx = &mut VisualTestContext::from_window(*window, cx);
22356
22357    let (buffer, snapshot) = window
22358        .update(cx, |editor, _window, cx| {
22359            (
22360                editor.buffer().clone(),
22361                editor.buffer().read(cx).snapshot(cx),
22362            )
22363        })
22364        .unwrap();
22365
22366    let edits = edits
22367        .into_iter()
22368        .map(|(range, edit)| {
22369            (
22370                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
22371                edit,
22372            )
22373        })
22374        .collect::<Vec<_>>();
22375
22376    let text_anchor_edits = edits
22377        .clone()
22378        .into_iter()
22379        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit))
22380        .collect::<Vec<_>>();
22381
22382    let edit_preview = window
22383        .update(cx, |_, _window, cx| {
22384            buffer
22385                .read(cx)
22386                .as_singleton()
22387                .unwrap()
22388                .read(cx)
22389                .preview_edits(text_anchor_edits.into(), cx)
22390        })
22391        .unwrap()
22392        .await;
22393
22394    cx.update(|_window, cx| {
22395        let highlighted_edits = edit_prediction_edit_text(
22396            snapshot.as_singleton().unwrap().2,
22397            &edits,
22398            &edit_preview,
22399            include_deletions,
22400            cx,
22401        );
22402        assertion_fn(highlighted_edits, cx)
22403    });
22404}
22405
22406#[track_caller]
22407fn assert_breakpoint(
22408    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
22409    path: &Arc<Path>,
22410    expected: Vec<(u32, Breakpoint)>,
22411) {
22412    if expected.is_empty() {
22413        assert!(!breakpoints.contains_key(path), "{}", path.display());
22414    } else {
22415        let mut breakpoint = breakpoints
22416            .get(path)
22417            .unwrap()
22418            .iter()
22419            .map(|breakpoint| {
22420                (
22421                    breakpoint.row,
22422                    Breakpoint {
22423                        message: breakpoint.message.clone(),
22424                        state: breakpoint.state,
22425                        condition: breakpoint.condition.clone(),
22426                        hit_condition: breakpoint.hit_condition.clone(),
22427                    },
22428                )
22429            })
22430            .collect::<Vec<_>>();
22431
22432        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
22433
22434        assert_eq!(expected, breakpoint);
22435    }
22436}
22437
22438fn add_log_breakpoint_at_cursor(
22439    editor: &mut Editor,
22440    log_message: &str,
22441    window: &mut Window,
22442    cx: &mut Context<Editor>,
22443) {
22444    let (anchor, bp) = editor
22445        .breakpoints_at_cursors(window, cx)
22446        .first()
22447        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
22448        .unwrap_or_else(|| {
22449            let cursor_position: Point = editor.selections.newest(cx).head();
22450
22451            let breakpoint_position = editor
22452                .snapshot(window, cx)
22453                .display_snapshot
22454                .buffer_snapshot
22455                .anchor_before(Point::new(cursor_position.row, 0));
22456
22457            (breakpoint_position, Breakpoint::new_log(log_message))
22458        });
22459
22460    editor.edit_breakpoint_at_anchor(
22461        anchor,
22462        bp,
22463        BreakpointEditAction::EditLogMessage(log_message.into()),
22464        cx,
22465    );
22466}
22467
22468#[gpui::test]
22469async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
22470    init_test(cx, |_| {});
22471
22472    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22473    let fs = FakeFs::new(cx.executor());
22474    fs.insert_tree(
22475        path!("/a"),
22476        json!({
22477            "main.rs": sample_text,
22478        }),
22479    )
22480    .await;
22481    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22482    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22483    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22484
22485    let fs = FakeFs::new(cx.executor());
22486    fs.insert_tree(
22487        path!("/a"),
22488        json!({
22489            "main.rs": sample_text,
22490        }),
22491    )
22492    .await;
22493    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22494    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22495    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22496    let worktree_id = workspace
22497        .update(cx, |workspace, _window, cx| {
22498            workspace.project().update(cx, |project, cx| {
22499                project.worktrees(cx).next().unwrap().read(cx).id()
22500            })
22501        })
22502        .unwrap();
22503
22504    let buffer = project
22505        .update(cx, |project, cx| {
22506            project.open_buffer((worktree_id, "main.rs"), cx)
22507        })
22508        .await
22509        .unwrap();
22510
22511    let (editor, cx) = cx.add_window_view(|window, cx| {
22512        Editor::new(
22513            EditorMode::full(),
22514            MultiBuffer::build_from_buffer(buffer, cx),
22515            Some(project.clone()),
22516            window,
22517            cx,
22518        )
22519    });
22520
22521    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22522    let abs_path = project.read_with(cx, |project, cx| {
22523        project
22524            .absolute_path(&project_path, cx)
22525            .map(Arc::from)
22526            .unwrap()
22527    });
22528
22529    // assert we can add breakpoint on the first line
22530    editor.update_in(cx, |editor, window, cx| {
22531        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22532        editor.move_to_end(&MoveToEnd, window, cx);
22533        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22534    });
22535
22536    let breakpoints = editor.update(cx, |editor, cx| {
22537        editor
22538            .breakpoint_store()
22539            .as_ref()
22540            .unwrap()
22541            .read(cx)
22542            .all_source_breakpoints(cx)
22543    });
22544
22545    assert_eq!(1, breakpoints.len());
22546    assert_breakpoint(
22547        &breakpoints,
22548        &abs_path,
22549        vec![
22550            (0, Breakpoint::new_standard()),
22551            (3, Breakpoint::new_standard()),
22552        ],
22553    );
22554
22555    editor.update_in(cx, |editor, window, cx| {
22556        editor.move_to_beginning(&MoveToBeginning, window, cx);
22557        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22558    });
22559
22560    let breakpoints = editor.update(cx, |editor, cx| {
22561        editor
22562            .breakpoint_store()
22563            .as_ref()
22564            .unwrap()
22565            .read(cx)
22566            .all_source_breakpoints(cx)
22567    });
22568
22569    assert_eq!(1, breakpoints.len());
22570    assert_breakpoint(
22571        &breakpoints,
22572        &abs_path,
22573        vec![(3, Breakpoint::new_standard())],
22574    );
22575
22576    editor.update_in(cx, |editor, window, cx| {
22577        editor.move_to_end(&MoveToEnd, window, cx);
22578        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22579    });
22580
22581    let breakpoints = editor.update(cx, |editor, cx| {
22582        editor
22583            .breakpoint_store()
22584            .as_ref()
22585            .unwrap()
22586            .read(cx)
22587            .all_source_breakpoints(cx)
22588    });
22589
22590    assert_eq!(0, breakpoints.len());
22591    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22592}
22593
22594#[gpui::test]
22595async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
22596    init_test(cx, |_| {});
22597
22598    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22599
22600    let fs = FakeFs::new(cx.executor());
22601    fs.insert_tree(
22602        path!("/a"),
22603        json!({
22604            "main.rs": sample_text,
22605        }),
22606    )
22607    .await;
22608    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22609    let (workspace, cx) =
22610        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
22611
22612    let worktree_id = workspace.update(cx, |workspace, cx| {
22613        workspace.project().update(cx, |project, cx| {
22614            project.worktrees(cx).next().unwrap().read(cx).id()
22615        })
22616    });
22617
22618    let buffer = project
22619        .update(cx, |project, cx| {
22620            project.open_buffer((worktree_id, "main.rs"), cx)
22621        })
22622        .await
22623        .unwrap();
22624
22625    let (editor, cx) = cx.add_window_view(|window, cx| {
22626        Editor::new(
22627            EditorMode::full(),
22628            MultiBuffer::build_from_buffer(buffer, cx),
22629            Some(project.clone()),
22630            window,
22631            cx,
22632        )
22633    });
22634
22635    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22636    let abs_path = project.read_with(cx, |project, cx| {
22637        project
22638            .absolute_path(&project_path, cx)
22639            .map(Arc::from)
22640            .unwrap()
22641    });
22642
22643    editor.update_in(cx, |editor, window, cx| {
22644        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22645    });
22646
22647    let breakpoints = editor.update(cx, |editor, cx| {
22648        editor
22649            .breakpoint_store()
22650            .as_ref()
22651            .unwrap()
22652            .read(cx)
22653            .all_source_breakpoints(cx)
22654    });
22655
22656    assert_breakpoint(
22657        &breakpoints,
22658        &abs_path,
22659        vec![(0, Breakpoint::new_log("hello world"))],
22660    );
22661
22662    // Removing a log message from a log breakpoint should remove it
22663    editor.update_in(cx, |editor, window, cx| {
22664        add_log_breakpoint_at_cursor(editor, "", window, cx);
22665    });
22666
22667    let breakpoints = editor.update(cx, |editor, cx| {
22668        editor
22669            .breakpoint_store()
22670            .as_ref()
22671            .unwrap()
22672            .read(cx)
22673            .all_source_breakpoints(cx)
22674    });
22675
22676    assert_breakpoint(&breakpoints, &abs_path, vec![]);
22677
22678    editor.update_in(cx, |editor, window, cx| {
22679        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22680        editor.move_to_end(&MoveToEnd, window, cx);
22681        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22682        // Not adding a log message to a standard breakpoint shouldn't remove it
22683        add_log_breakpoint_at_cursor(editor, "", window, cx);
22684    });
22685
22686    let breakpoints = editor.update(cx, |editor, cx| {
22687        editor
22688            .breakpoint_store()
22689            .as_ref()
22690            .unwrap()
22691            .read(cx)
22692            .all_source_breakpoints(cx)
22693    });
22694
22695    assert_breakpoint(
22696        &breakpoints,
22697        &abs_path,
22698        vec![
22699            (0, Breakpoint::new_standard()),
22700            (3, Breakpoint::new_standard()),
22701        ],
22702    );
22703
22704    editor.update_in(cx, |editor, window, cx| {
22705        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
22706    });
22707
22708    let breakpoints = editor.update(cx, |editor, cx| {
22709        editor
22710            .breakpoint_store()
22711            .as_ref()
22712            .unwrap()
22713            .read(cx)
22714            .all_source_breakpoints(cx)
22715    });
22716
22717    assert_breakpoint(
22718        &breakpoints,
22719        &abs_path,
22720        vec![
22721            (0, Breakpoint::new_standard()),
22722            (3, Breakpoint::new_log("hello world")),
22723        ],
22724    );
22725
22726    editor.update_in(cx, |editor, window, cx| {
22727        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
22728    });
22729
22730    let breakpoints = editor.update(cx, |editor, cx| {
22731        editor
22732            .breakpoint_store()
22733            .as_ref()
22734            .unwrap()
22735            .read(cx)
22736            .all_source_breakpoints(cx)
22737    });
22738
22739    assert_breakpoint(
22740        &breakpoints,
22741        &abs_path,
22742        vec![
22743            (0, Breakpoint::new_standard()),
22744            (3, Breakpoint::new_log("hello Earth!!")),
22745        ],
22746    );
22747}
22748
22749/// This also tests that Editor::breakpoint_at_cursor_head is working properly
22750/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
22751/// or when breakpoints were placed out of order. This tests for a regression too
22752#[gpui::test]
22753async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
22754    init_test(cx, |_| {});
22755
22756    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
22757    let fs = FakeFs::new(cx.executor());
22758    fs.insert_tree(
22759        path!("/a"),
22760        json!({
22761            "main.rs": sample_text,
22762        }),
22763    )
22764    .await;
22765    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22766    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22767    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22768
22769    let fs = FakeFs::new(cx.executor());
22770    fs.insert_tree(
22771        path!("/a"),
22772        json!({
22773            "main.rs": sample_text,
22774        }),
22775    )
22776    .await;
22777    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22778    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22779    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22780    let worktree_id = workspace
22781        .update(cx, |workspace, _window, cx| {
22782            workspace.project().update(cx, |project, cx| {
22783                project.worktrees(cx).next().unwrap().read(cx).id()
22784            })
22785        })
22786        .unwrap();
22787
22788    let buffer = project
22789        .update(cx, |project, cx| {
22790            project.open_buffer((worktree_id, "main.rs"), cx)
22791        })
22792        .await
22793        .unwrap();
22794
22795    let (editor, cx) = cx.add_window_view(|window, cx| {
22796        Editor::new(
22797            EditorMode::full(),
22798            MultiBuffer::build_from_buffer(buffer, cx),
22799            Some(project.clone()),
22800            window,
22801            cx,
22802        )
22803    });
22804
22805    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
22806    let abs_path = project.read_with(cx, |project, cx| {
22807        project
22808            .absolute_path(&project_path, cx)
22809            .map(Arc::from)
22810            .unwrap()
22811    });
22812
22813    // assert we can add breakpoint on the first line
22814    editor.update_in(cx, |editor, window, cx| {
22815        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22816        editor.move_to_end(&MoveToEnd, window, cx);
22817        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22818        editor.move_up(&MoveUp, window, cx);
22819        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
22820    });
22821
22822    let breakpoints = editor.update(cx, |editor, cx| {
22823        editor
22824            .breakpoint_store()
22825            .as_ref()
22826            .unwrap()
22827            .read(cx)
22828            .all_source_breakpoints(cx)
22829    });
22830
22831    assert_eq!(1, breakpoints.len());
22832    assert_breakpoint(
22833        &breakpoints,
22834        &abs_path,
22835        vec![
22836            (0, Breakpoint::new_standard()),
22837            (2, Breakpoint::new_standard()),
22838            (3, Breakpoint::new_standard()),
22839        ],
22840    );
22841
22842    editor.update_in(cx, |editor, window, cx| {
22843        editor.move_to_beginning(&MoveToBeginning, window, cx);
22844        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22845        editor.move_to_end(&MoveToEnd, window, cx);
22846        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22847        // Disabling a breakpoint that doesn't exist should do nothing
22848        editor.move_up(&MoveUp, window, cx);
22849        editor.move_up(&MoveUp, window, cx);
22850        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22851    });
22852
22853    let breakpoints = editor.update(cx, |editor, cx| {
22854        editor
22855            .breakpoint_store()
22856            .as_ref()
22857            .unwrap()
22858            .read(cx)
22859            .all_source_breakpoints(cx)
22860    });
22861
22862    let disable_breakpoint = {
22863        let mut bp = Breakpoint::new_standard();
22864        bp.state = BreakpointState::Disabled;
22865        bp
22866    };
22867
22868    assert_eq!(1, breakpoints.len());
22869    assert_breakpoint(
22870        &breakpoints,
22871        &abs_path,
22872        vec![
22873            (0, disable_breakpoint.clone()),
22874            (2, Breakpoint::new_standard()),
22875            (3, disable_breakpoint.clone()),
22876        ],
22877    );
22878
22879    editor.update_in(cx, |editor, window, cx| {
22880        editor.move_to_beginning(&MoveToBeginning, window, cx);
22881        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22882        editor.move_to_end(&MoveToEnd, window, cx);
22883        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
22884        editor.move_up(&MoveUp, window, cx);
22885        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
22886    });
22887
22888    let breakpoints = editor.update(cx, |editor, cx| {
22889        editor
22890            .breakpoint_store()
22891            .as_ref()
22892            .unwrap()
22893            .read(cx)
22894            .all_source_breakpoints(cx)
22895    });
22896
22897    assert_eq!(1, breakpoints.len());
22898    assert_breakpoint(
22899        &breakpoints,
22900        &abs_path,
22901        vec![
22902            (0, Breakpoint::new_standard()),
22903            (2, disable_breakpoint),
22904            (3, Breakpoint::new_standard()),
22905        ],
22906    );
22907}
22908
22909#[gpui::test]
22910async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
22911    init_test(cx, |_| {});
22912    let capabilities = lsp::ServerCapabilities {
22913        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
22914            prepare_provider: Some(true),
22915            work_done_progress_options: Default::default(),
22916        })),
22917        ..Default::default()
22918    };
22919    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22920
22921    cx.set_state(indoc! {"
22922        struct Fˇoo {}
22923    "});
22924
22925    cx.update_editor(|editor, _, cx| {
22926        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
22927        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
22928        editor.highlight_background::<DocumentHighlightRead>(
22929            &[highlight_range],
22930            |theme| theme.colors().editor_document_highlight_read_background,
22931            cx,
22932        );
22933    });
22934
22935    let mut prepare_rename_handler = cx
22936        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
22937            move |_, _, _| async move {
22938                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
22939                    start: lsp::Position {
22940                        line: 0,
22941                        character: 7,
22942                    },
22943                    end: lsp::Position {
22944                        line: 0,
22945                        character: 10,
22946                    },
22947                })))
22948            },
22949        );
22950    let prepare_rename_task = cx
22951        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
22952        .expect("Prepare rename was not started");
22953    prepare_rename_handler.next().await.unwrap();
22954    prepare_rename_task.await.expect("Prepare rename failed");
22955
22956    let mut rename_handler =
22957        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
22958            let edit = lsp::TextEdit {
22959                range: lsp::Range {
22960                    start: lsp::Position {
22961                        line: 0,
22962                        character: 7,
22963                    },
22964                    end: lsp::Position {
22965                        line: 0,
22966                        character: 10,
22967                    },
22968                },
22969                new_text: "FooRenamed".to_string(),
22970            };
22971            Ok(Some(lsp::WorkspaceEdit::new(
22972                // Specify the same edit twice
22973                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
22974            )))
22975        });
22976    let rename_task = cx
22977        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
22978        .expect("Confirm rename was not started");
22979    rename_handler.next().await.unwrap();
22980    rename_task.await.expect("Confirm rename failed");
22981    cx.run_until_parked();
22982
22983    // Despite two edits, only one is actually applied as those are identical
22984    cx.assert_editor_state(indoc! {"
22985        struct FooRenamedˇ {}
22986    "});
22987}
22988
22989#[gpui::test]
22990async fn test_rename_without_prepare(cx: &mut TestAppContext) {
22991    init_test(cx, |_| {});
22992    // These capabilities indicate that the server does not support prepare rename.
22993    let capabilities = lsp::ServerCapabilities {
22994        rename_provider: Some(lsp::OneOf::Left(true)),
22995        ..Default::default()
22996    };
22997    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
22998
22999    cx.set_state(indoc! {"
23000        struct Fˇoo {}
23001    "});
23002
23003    cx.update_editor(|editor, _window, cx| {
23004        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23005        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23006        editor.highlight_background::<DocumentHighlightRead>(
23007            &[highlight_range],
23008            |theme| theme.colors().editor_document_highlight_read_background,
23009            cx,
23010        );
23011    });
23012
23013    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23014        .expect("Prepare rename was not started")
23015        .await
23016        .expect("Prepare rename failed");
23017
23018    let mut rename_handler =
23019        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23020            let edit = lsp::TextEdit {
23021                range: lsp::Range {
23022                    start: lsp::Position {
23023                        line: 0,
23024                        character: 7,
23025                    },
23026                    end: lsp::Position {
23027                        line: 0,
23028                        character: 10,
23029                    },
23030                },
23031                new_text: "FooRenamed".to_string(),
23032            };
23033            Ok(Some(lsp::WorkspaceEdit::new(
23034                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23035            )))
23036        });
23037    let rename_task = cx
23038        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23039        .expect("Confirm rename was not started");
23040    rename_handler.next().await.unwrap();
23041    rename_task.await.expect("Confirm rename failed");
23042    cx.run_until_parked();
23043
23044    // Correct range is renamed, as `surrounding_word` is used to find it.
23045    cx.assert_editor_state(indoc! {"
23046        struct FooRenamedˇ {}
23047    "});
23048}
23049
23050#[gpui::test]
23051async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23052    init_test(cx, |_| {});
23053    let mut cx = EditorTestContext::new(cx).await;
23054
23055    let language = Arc::new(
23056        Language::new(
23057            LanguageConfig::default(),
23058            Some(tree_sitter_html::LANGUAGE.into()),
23059        )
23060        .with_brackets_query(
23061            r#"
23062            ("<" @open "/>" @close)
23063            ("</" @open ">" @close)
23064            ("<" @open ">" @close)
23065            ("\"" @open "\"" @close)
23066            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23067        "#,
23068        )
23069        .unwrap(),
23070    );
23071    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23072
23073    cx.set_state(indoc! {"
23074        <span>ˇ</span>
23075    "});
23076    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23077    cx.assert_editor_state(indoc! {"
23078        <span>
23079        ˇ
23080        </span>
23081    "});
23082
23083    cx.set_state(indoc! {"
23084        <span><span></span>ˇ</span>
23085    "});
23086    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23087    cx.assert_editor_state(indoc! {"
23088        <span><span></span>
23089        ˇ</span>
23090    "});
23091
23092    cx.set_state(indoc! {"
23093        <span>ˇ
23094        </span>
23095    "});
23096    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23097    cx.assert_editor_state(indoc! {"
23098        <span>
23099        ˇ
23100        </span>
23101    "});
23102}
23103
23104#[gpui::test(iterations = 10)]
23105async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23106    init_test(cx, |_| {});
23107
23108    let fs = FakeFs::new(cx.executor());
23109    fs.insert_tree(
23110        path!("/dir"),
23111        json!({
23112            "a.ts": "a",
23113        }),
23114    )
23115    .await;
23116
23117    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23118    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23119    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23120
23121    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23122    language_registry.add(Arc::new(Language::new(
23123        LanguageConfig {
23124            name: "TypeScript".into(),
23125            matcher: LanguageMatcher {
23126                path_suffixes: vec!["ts".to_string()],
23127                ..Default::default()
23128            },
23129            ..Default::default()
23130        },
23131        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
23132    )));
23133    let mut fake_language_servers = language_registry.register_fake_lsp(
23134        "TypeScript",
23135        FakeLspAdapter {
23136            capabilities: lsp::ServerCapabilities {
23137                code_lens_provider: Some(lsp::CodeLensOptions {
23138                    resolve_provider: Some(true),
23139                }),
23140                execute_command_provider: Some(lsp::ExecuteCommandOptions {
23141                    commands: vec!["_the/command".to_string()],
23142                    ..lsp::ExecuteCommandOptions::default()
23143                }),
23144                ..lsp::ServerCapabilities::default()
23145            },
23146            ..FakeLspAdapter::default()
23147        },
23148    );
23149
23150    let editor = workspace
23151        .update(cx, |workspace, window, cx| {
23152            workspace.open_abs_path(
23153                PathBuf::from(path!("/dir/a.ts")),
23154                OpenOptions::default(),
23155                window,
23156                cx,
23157            )
23158        })
23159        .unwrap()
23160        .await
23161        .unwrap()
23162        .downcast::<Editor>()
23163        .unwrap();
23164    cx.executor().run_until_parked();
23165
23166    let fake_server = fake_language_servers.next().await.unwrap();
23167
23168    let buffer = editor.update(cx, |editor, cx| {
23169        editor
23170            .buffer()
23171            .read(cx)
23172            .as_singleton()
23173            .expect("have opened a single file by path")
23174    });
23175
23176    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
23177    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
23178    drop(buffer_snapshot);
23179    let actions = cx
23180        .update_window(*workspace, |_, window, cx| {
23181            project.code_actions(&buffer, anchor..anchor, window, cx)
23182        })
23183        .unwrap();
23184
23185    fake_server
23186        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23187            Ok(Some(vec![
23188                lsp::CodeLens {
23189                    range: lsp::Range::default(),
23190                    command: Some(lsp::Command {
23191                        title: "Code lens command".to_owned(),
23192                        command: "_the/command".to_owned(),
23193                        arguments: None,
23194                    }),
23195                    data: None,
23196                },
23197                lsp::CodeLens {
23198                    range: lsp::Range::default(),
23199                    command: Some(lsp::Command {
23200                        title: "Command not in capabilities".to_owned(),
23201                        command: "not in capabilities".to_owned(),
23202                        arguments: None,
23203                    }),
23204                    data: None,
23205                },
23206                lsp::CodeLens {
23207                    range: lsp::Range {
23208                        start: lsp::Position {
23209                            line: 1,
23210                            character: 1,
23211                        },
23212                        end: lsp::Position {
23213                            line: 1,
23214                            character: 1,
23215                        },
23216                    },
23217                    command: Some(lsp::Command {
23218                        title: "Command not in range".to_owned(),
23219                        command: "_the/command".to_owned(),
23220                        arguments: None,
23221                    }),
23222                    data: None,
23223                },
23224            ]))
23225        })
23226        .next()
23227        .await;
23228
23229    let actions = actions.await.unwrap();
23230    assert_eq!(
23231        actions.len(),
23232        1,
23233        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
23234    );
23235    let action = actions[0].clone();
23236    let apply = project.update(cx, |project, cx| {
23237        project.apply_code_action(buffer.clone(), action, true, cx)
23238    });
23239
23240    // Resolving the code action does not populate its edits. In absence of
23241    // edits, we must execute the given command.
23242    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
23243        |mut lens, _| async move {
23244            let lens_command = lens.command.as_mut().expect("should have a command");
23245            assert_eq!(lens_command.title, "Code lens command");
23246            lens_command.arguments = Some(vec![json!("the-argument")]);
23247            Ok(lens)
23248        },
23249    );
23250
23251    // While executing the command, the language server sends the editor
23252    // a `workspaceEdit` request.
23253    fake_server
23254        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
23255            let fake = fake_server.clone();
23256            move |params, _| {
23257                assert_eq!(params.command, "_the/command");
23258                let fake = fake.clone();
23259                async move {
23260                    fake.server
23261                        .request::<lsp::request::ApplyWorkspaceEdit>(
23262                            lsp::ApplyWorkspaceEditParams {
23263                                label: None,
23264                                edit: lsp::WorkspaceEdit {
23265                                    changes: Some(
23266                                        [(
23267                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
23268                                            vec![lsp::TextEdit {
23269                                                range: lsp::Range::new(
23270                                                    lsp::Position::new(0, 0),
23271                                                    lsp::Position::new(0, 0),
23272                                                ),
23273                                                new_text: "X".into(),
23274                                            }],
23275                                        )]
23276                                        .into_iter()
23277                                        .collect(),
23278                                    ),
23279                                    ..lsp::WorkspaceEdit::default()
23280                                },
23281                            },
23282                        )
23283                        .await
23284                        .into_response()
23285                        .unwrap();
23286                    Ok(Some(json!(null)))
23287                }
23288            }
23289        })
23290        .next()
23291        .await;
23292
23293    // Applying the code lens command returns a project transaction containing the edits
23294    // sent by the language server in its `workspaceEdit` request.
23295    let transaction = apply.await.unwrap();
23296    assert!(transaction.0.contains_key(&buffer));
23297    buffer.update(cx, |buffer, cx| {
23298        assert_eq!(buffer.text(), "Xa");
23299        buffer.undo(cx);
23300        assert_eq!(buffer.text(), "a");
23301    });
23302
23303    let actions_after_edits = cx
23304        .update_window(*workspace, |_, window, cx| {
23305            project.code_actions(&buffer, anchor..anchor, window, cx)
23306        })
23307        .unwrap()
23308        .await
23309        .unwrap();
23310    assert_eq!(
23311        actions, actions_after_edits,
23312        "For the same selection, same code lens actions should be returned"
23313    );
23314
23315    let _responses =
23316        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
23317            panic!("No more code lens requests are expected");
23318        });
23319    editor.update_in(cx, |editor, window, cx| {
23320        editor.select_all(&SelectAll, window, cx);
23321    });
23322    cx.executor().run_until_parked();
23323    let new_actions = cx
23324        .update_window(*workspace, |_, window, cx| {
23325            project.code_actions(&buffer, anchor..anchor, window, cx)
23326        })
23327        .unwrap()
23328        .await
23329        .unwrap();
23330    assert_eq!(
23331        actions, new_actions,
23332        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
23333    );
23334}
23335
23336#[gpui::test]
23337async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
23338    init_test(cx, |_| {});
23339
23340    let fs = FakeFs::new(cx.executor());
23341    let main_text = r#"fn main() {
23342println!("1");
23343println!("2");
23344println!("3");
23345println!("4");
23346println!("5");
23347}"#;
23348    let lib_text = "mod foo {}";
23349    fs.insert_tree(
23350        path!("/a"),
23351        json!({
23352            "lib.rs": lib_text,
23353            "main.rs": main_text,
23354        }),
23355    )
23356    .await;
23357
23358    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23359    let (workspace, cx) =
23360        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23361    let worktree_id = workspace.update(cx, |workspace, cx| {
23362        workspace.project().update(cx, |project, cx| {
23363            project.worktrees(cx).next().unwrap().read(cx).id()
23364        })
23365    });
23366
23367    let expected_ranges = vec![
23368        Point::new(0, 0)..Point::new(0, 0),
23369        Point::new(1, 0)..Point::new(1, 1),
23370        Point::new(2, 0)..Point::new(2, 2),
23371        Point::new(3, 0)..Point::new(3, 3),
23372    ];
23373
23374    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23375    let editor_1 = workspace
23376        .update_in(cx, |workspace, window, cx| {
23377            workspace.open_path(
23378                (worktree_id, "main.rs"),
23379                Some(pane_1.downgrade()),
23380                true,
23381                window,
23382                cx,
23383            )
23384        })
23385        .unwrap()
23386        .await
23387        .downcast::<Editor>()
23388        .unwrap();
23389    pane_1.update(cx, |pane, cx| {
23390        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23391        open_editor.update(cx, |editor, cx| {
23392            assert_eq!(
23393                editor.display_text(cx),
23394                main_text,
23395                "Original main.rs text on initial open",
23396            );
23397            assert_eq!(
23398                editor
23399                    .selections
23400                    .all::<Point>(cx)
23401                    .into_iter()
23402                    .map(|s| s.range())
23403                    .collect::<Vec<_>>(),
23404                vec![Point::zero()..Point::zero()],
23405                "Default selections on initial open",
23406            );
23407        })
23408    });
23409    editor_1.update_in(cx, |editor, window, cx| {
23410        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23411            s.select_ranges(expected_ranges.clone());
23412        });
23413    });
23414
23415    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
23416        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
23417    });
23418    let editor_2 = workspace
23419        .update_in(cx, |workspace, window, cx| {
23420            workspace.open_path(
23421                (worktree_id, "main.rs"),
23422                Some(pane_2.downgrade()),
23423                true,
23424                window,
23425                cx,
23426            )
23427        })
23428        .unwrap()
23429        .await
23430        .downcast::<Editor>()
23431        .unwrap();
23432    pane_2.update(cx, |pane, cx| {
23433        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23434        open_editor.update(cx, |editor, cx| {
23435            assert_eq!(
23436                editor.display_text(cx),
23437                main_text,
23438                "Original main.rs text on initial open in another panel",
23439            );
23440            assert_eq!(
23441                editor
23442                    .selections
23443                    .all::<Point>(cx)
23444                    .into_iter()
23445                    .map(|s| s.range())
23446                    .collect::<Vec<_>>(),
23447                vec![Point::zero()..Point::zero()],
23448                "Default selections on initial open in another panel",
23449            );
23450        })
23451    });
23452
23453    editor_2.update_in(cx, |editor, window, cx| {
23454        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
23455    });
23456
23457    let _other_editor_1 = workspace
23458        .update_in(cx, |workspace, window, cx| {
23459            workspace.open_path(
23460                (worktree_id, "lib.rs"),
23461                Some(pane_1.downgrade()),
23462                true,
23463                window,
23464                cx,
23465            )
23466        })
23467        .unwrap()
23468        .await
23469        .downcast::<Editor>()
23470        .unwrap();
23471    pane_1
23472        .update_in(cx, |pane, window, cx| {
23473            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23474        })
23475        .await
23476        .unwrap();
23477    drop(editor_1);
23478    pane_1.update(cx, |pane, cx| {
23479        pane.active_item()
23480            .unwrap()
23481            .downcast::<Editor>()
23482            .unwrap()
23483            .update(cx, |editor, cx| {
23484                assert_eq!(
23485                    editor.display_text(cx),
23486                    lib_text,
23487                    "Other file should be open and active",
23488                );
23489            });
23490        assert_eq!(pane.items().count(), 1, "No other editors should be open");
23491    });
23492
23493    let _other_editor_2 = workspace
23494        .update_in(cx, |workspace, window, cx| {
23495            workspace.open_path(
23496                (worktree_id, "lib.rs"),
23497                Some(pane_2.downgrade()),
23498                true,
23499                window,
23500                cx,
23501            )
23502        })
23503        .unwrap()
23504        .await
23505        .downcast::<Editor>()
23506        .unwrap();
23507    pane_2
23508        .update_in(cx, |pane, window, cx| {
23509            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
23510        })
23511        .await
23512        .unwrap();
23513    drop(editor_2);
23514    pane_2.update(cx, |pane, cx| {
23515        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23516        open_editor.update(cx, |editor, cx| {
23517            assert_eq!(
23518                editor.display_text(cx),
23519                lib_text,
23520                "Other file should be open and active in another panel too",
23521            );
23522        });
23523        assert_eq!(
23524            pane.items().count(),
23525            1,
23526            "No other editors should be open in another pane",
23527        );
23528    });
23529
23530    let _editor_1_reopened = workspace
23531        .update_in(cx, |workspace, window, cx| {
23532            workspace.open_path(
23533                (worktree_id, "main.rs"),
23534                Some(pane_1.downgrade()),
23535                true,
23536                window,
23537                cx,
23538            )
23539        })
23540        .unwrap()
23541        .await
23542        .downcast::<Editor>()
23543        .unwrap();
23544    let _editor_2_reopened = workspace
23545        .update_in(cx, |workspace, window, cx| {
23546            workspace.open_path(
23547                (worktree_id, "main.rs"),
23548                Some(pane_2.downgrade()),
23549                true,
23550                window,
23551                cx,
23552            )
23553        })
23554        .unwrap()
23555        .await
23556        .downcast::<Editor>()
23557        .unwrap();
23558    pane_1.update(cx, |pane, cx| {
23559        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23560        open_editor.update(cx, |editor, cx| {
23561            assert_eq!(
23562                editor.display_text(cx),
23563                main_text,
23564                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
23565            );
23566            assert_eq!(
23567                editor
23568                    .selections
23569                    .all::<Point>(cx)
23570                    .into_iter()
23571                    .map(|s| s.range())
23572                    .collect::<Vec<_>>(),
23573                expected_ranges,
23574                "Previous editor in the 1st panel had selections and should get them restored on reopen",
23575            );
23576        })
23577    });
23578    pane_2.update(cx, |pane, cx| {
23579        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23580        open_editor.update(cx, |editor, cx| {
23581            assert_eq!(
23582                editor.display_text(cx),
23583                r#"fn main() {
23584⋯rintln!("1");
23585⋯intln!("2");
23586⋯ntln!("3");
23587println!("4");
23588println!("5");
23589}"#,
23590                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
23591            );
23592            assert_eq!(
23593                editor
23594                    .selections
23595                    .all::<Point>(cx)
23596                    .into_iter()
23597                    .map(|s| s.range())
23598                    .collect::<Vec<_>>(),
23599                vec![Point::zero()..Point::zero()],
23600                "Previous editor in the 2nd pane had no selections changed hence should restore none",
23601            );
23602        })
23603    });
23604}
23605
23606#[gpui::test]
23607async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
23608    init_test(cx, |_| {});
23609
23610    let fs = FakeFs::new(cx.executor());
23611    let main_text = r#"fn main() {
23612println!("1");
23613println!("2");
23614println!("3");
23615println!("4");
23616println!("5");
23617}"#;
23618    let lib_text = "mod foo {}";
23619    fs.insert_tree(
23620        path!("/a"),
23621        json!({
23622            "lib.rs": lib_text,
23623            "main.rs": main_text,
23624        }),
23625    )
23626    .await;
23627
23628    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23629    let (workspace, cx) =
23630        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23631    let worktree_id = workspace.update(cx, |workspace, cx| {
23632        workspace.project().update(cx, |project, cx| {
23633            project.worktrees(cx).next().unwrap().read(cx).id()
23634        })
23635    });
23636
23637    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23638    let editor = workspace
23639        .update_in(cx, |workspace, window, cx| {
23640            workspace.open_path(
23641                (worktree_id, "main.rs"),
23642                Some(pane.downgrade()),
23643                true,
23644                window,
23645                cx,
23646            )
23647        })
23648        .unwrap()
23649        .await
23650        .downcast::<Editor>()
23651        .unwrap();
23652    pane.update(cx, |pane, cx| {
23653        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23654        open_editor.update(cx, |editor, cx| {
23655            assert_eq!(
23656                editor.display_text(cx),
23657                main_text,
23658                "Original main.rs text on initial open",
23659            );
23660        })
23661    });
23662    editor.update_in(cx, |editor, window, cx| {
23663        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
23664    });
23665
23666    cx.update_global(|store: &mut SettingsStore, cx| {
23667        store.update_user_settings(cx, |s| {
23668            s.workspace.restore_on_file_reopen = Some(false);
23669        });
23670    });
23671    editor.update_in(cx, |editor, window, cx| {
23672        editor.fold_ranges(
23673            vec![
23674                Point::new(1, 0)..Point::new(1, 1),
23675                Point::new(2, 0)..Point::new(2, 2),
23676                Point::new(3, 0)..Point::new(3, 3),
23677            ],
23678            false,
23679            window,
23680            cx,
23681        );
23682    });
23683    pane.update_in(cx, |pane, window, cx| {
23684        pane.close_all_items(&CloseAllItems::default(), window, cx)
23685    })
23686    .await
23687    .unwrap();
23688    pane.update(cx, |pane, _| {
23689        assert!(pane.active_item().is_none());
23690    });
23691    cx.update_global(|store: &mut SettingsStore, cx| {
23692        store.update_user_settings(cx, |s| {
23693            s.workspace.restore_on_file_reopen = Some(true);
23694        });
23695    });
23696
23697    let _editor_reopened = workspace
23698        .update_in(cx, |workspace, window, cx| {
23699            workspace.open_path(
23700                (worktree_id, "main.rs"),
23701                Some(pane.downgrade()),
23702                true,
23703                window,
23704                cx,
23705            )
23706        })
23707        .unwrap()
23708        .await
23709        .downcast::<Editor>()
23710        .unwrap();
23711    pane.update(cx, |pane, cx| {
23712        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23713        open_editor.update(cx, |editor, cx| {
23714            assert_eq!(
23715                editor.display_text(cx),
23716                main_text,
23717                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
23718            );
23719        })
23720    });
23721}
23722
23723#[gpui::test]
23724async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
23725    struct EmptyModalView {
23726        focus_handle: gpui::FocusHandle,
23727    }
23728    impl EventEmitter<DismissEvent> for EmptyModalView {}
23729    impl Render for EmptyModalView {
23730        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
23731            div()
23732        }
23733    }
23734    impl Focusable for EmptyModalView {
23735        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
23736            self.focus_handle.clone()
23737        }
23738    }
23739    impl workspace::ModalView for EmptyModalView {}
23740    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
23741        EmptyModalView {
23742            focus_handle: cx.focus_handle(),
23743        }
23744    }
23745
23746    init_test(cx, |_| {});
23747
23748    let fs = FakeFs::new(cx.executor());
23749    let project = Project::test(fs, [], cx).await;
23750    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23751    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
23752    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23753    let editor = cx.new_window_entity(|window, cx| {
23754        Editor::new(
23755            EditorMode::full(),
23756            buffer,
23757            Some(project.clone()),
23758            window,
23759            cx,
23760        )
23761    });
23762    workspace
23763        .update(cx, |workspace, window, cx| {
23764            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
23765        })
23766        .unwrap();
23767    editor.update_in(cx, |editor, window, cx| {
23768        editor.open_context_menu(&OpenContextMenu, window, cx);
23769        assert!(editor.mouse_context_menu.is_some());
23770    });
23771    workspace
23772        .update(cx, |workspace, window, cx| {
23773            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
23774        })
23775        .unwrap();
23776    cx.read(|cx| {
23777        assert!(editor.read(cx).mouse_context_menu.is_none());
23778    });
23779}
23780
23781#[gpui::test]
23782async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
23783    init_test(cx, |_| {});
23784
23785    let fs = FakeFs::new(cx.executor());
23786    fs.insert_file(path!("/file.html"), Default::default())
23787        .await;
23788
23789    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
23790
23791    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23792    let html_language = Arc::new(Language::new(
23793        LanguageConfig {
23794            name: "HTML".into(),
23795            matcher: LanguageMatcher {
23796                path_suffixes: vec!["html".to_string()],
23797                ..LanguageMatcher::default()
23798            },
23799            brackets: BracketPairConfig {
23800                pairs: vec![BracketPair {
23801                    start: "<".into(),
23802                    end: ">".into(),
23803                    close: true,
23804                    ..Default::default()
23805                }],
23806                ..Default::default()
23807            },
23808            ..Default::default()
23809        },
23810        Some(tree_sitter_html::LANGUAGE.into()),
23811    ));
23812    language_registry.add(html_language);
23813    let mut fake_servers = language_registry.register_fake_lsp(
23814        "HTML",
23815        FakeLspAdapter {
23816            capabilities: lsp::ServerCapabilities {
23817                completion_provider: Some(lsp::CompletionOptions {
23818                    resolve_provider: Some(true),
23819                    ..Default::default()
23820                }),
23821                ..Default::default()
23822            },
23823            ..Default::default()
23824        },
23825    );
23826
23827    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23828    let cx = &mut VisualTestContext::from_window(*workspace, cx);
23829
23830    let worktree_id = workspace
23831        .update(cx, |workspace, _window, cx| {
23832            workspace.project().update(cx, |project, cx| {
23833                project.worktrees(cx).next().unwrap().read(cx).id()
23834            })
23835        })
23836        .unwrap();
23837    project
23838        .update(cx, |project, cx| {
23839            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
23840        })
23841        .await
23842        .unwrap();
23843    let editor = workspace
23844        .update(cx, |workspace, window, cx| {
23845            workspace.open_path((worktree_id, "file.html"), None, true, window, cx)
23846        })
23847        .unwrap()
23848        .await
23849        .unwrap()
23850        .downcast::<Editor>()
23851        .unwrap();
23852
23853    let fake_server = fake_servers.next().await.unwrap();
23854    editor.update_in(cx, |editor, window, cx| {
23855        editor.set_text("<ad></ad>", window, cx);
23856        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23857            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
23858        });
23859        let Some((buffer, _)) = editor
23860            .buffer
23861            .read(cx)
23862            .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
23863        else {
23864            panic!("Failed to get buffer for selection position");
23865        };
23866        let buffer = buffer.read(cx);
23867        let buffer_id = buffer.remote_id();
23868        let opening_range =
23869            buffer.anchor_before(Point::new(0, 1))..buffer.anchor_after(Point::new(0, 3));
23870        let closing_range =
23871            buffer.anchor_before(Point::new(0, 6))..buffer.anchor_after(Point::new(0, 8));
23872        let mut linked_ranges = HashMap::default();
23873        linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
23874        editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
23875    });
23876    let mut completion_handle =
23877        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
23878            Ok(Some(lsp::CompletionResponse::Array(vec![
23879                lsp::CompletionItem {
23880                    label: "head".to_string(),
23881                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23882                        lsp::InsertReplaceEdit {
23883                            new_text: "head".to_string(),
23884                            insert: lsp::Range::new(
23885                                lsp::Position::new(0, 1),
23886                                lsp::Position::new(0, 3),
23887                            ),
23888                            replace: lsp::Range::new(
23889                                lsp::Position::new(0, 1),
23890                                lsp::Position::new(0, 3),
23891                            ),
23892                        },
23893                    )),
23894                    ..Default::default()
23895                },
23896            ])))
23897        });
23898    editor.update_in(cx, |editor, window, cx| {
23899        editor.show_completions(&ShowCompletions { trigger: None }, window, cx);
23900    });
23901    cx.run_until_parked();
23902    completion_handle.next().await.unwrap();
23903    editor.update(cx, |editor, _| {
23904        assert!(
23905            editor.context_menu_visible(),
23906            "Completion menu should be visible"
23907        );
23908    });
23909    editor.update_in(cx, |editor, window, cx| {
23910        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
23911    });
23912    cx.executor().run_until_parked();
23913    editor.update(cx, |editor, cx| {
23914        assert_eq!(editor.text(cx), "<head></head>");
23915    });
23916}
23917
23918#[gpui::test]
23919async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
23920    init_test(cx, |_| {});
23921
23922    let fs = FakeFs::new(cx.executor());
23923    fs.insert_tree(
23924        path!("/root"),
23925        json!({
23926            "a": {
23927                "main.rs": "fn main() {}",
23928            },
23929            "foo": {
23930                "bar": {
23931                    "external_file.rs": "pub mod external {}",
23932                }
23933            }
23934        }),
23935    )
23936    .await;
23937
23938    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
23939    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23940    language_registry.add(rust_lang());
23941    let _fake_servers = language_registry.register_fake_lsp(
23942        "Rust",
23943        FakeLspAdapter {
23944            ..FakeLspAdapter::default()
23945        },
23946    );
23947    let (workspace, cx) =
23948        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23949    let worktree_id = workspace.update(cx, |workspace, cx| {
23950        workspace.project().update(cx, |project, cx| {
23951            project.worktrees(cx).next().unwrap().read(cx).id()
23952        })
23953    });
23954
23955    let assert_language_servers_count =
23956        |expected: usize, context: &str, cx: &mut VisualTestContext| {
23957            project.update(cx, |project, cx| {
23958                let current = project
23959                    .lsp_store()
23960                    .read(cx)
23961                    .as_local()
23962                    .unwrap()
23963                    .language_servers
23964                    .len();
23965                assert_eq!(expected, current, "{context}");
23966            });
23967        };
23968
23969    assert_language_servers_count(
23970        0,
23971        "No servers should be running before any file is open",
23972        cx,
23973    );
23974    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
23975    let main_editor = workspace
23976        .update_in(cx, |workspace, window, cx| {
23977            workspace.open_path(
23978                (worktree_id, "main.rs"),
23979                Some(pane.downgrade()),
23980                true,
23981                window,
23982                cx,
23983            )
23984        })
23985        .unwrap()
23986        .await
23987        .downcast::<Editor>()
23988        .unwrap();
23989    pane.update(cx, |pane, cx| {
23990        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
23991        open_editor.update(cx, |editor, cx| {
23992            assert_eq!(
23993                editor.display_text(cx),
23994                "fn main() {}",
23995                "Original main.rs text on initial open",
23996            );
23997        });
23998        assert_eq!(open_editor, main_editor);
23999    });
24000    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24001
24002    let external_editor = workspace
24003        .update_in(cx, |workspace, window, cx| {
24004            workspace.open_abs_path(
24005                PathBuf::from("/root/foo/bar/external_file.rs"),
24006                OpenOptions::default(),
24007                window,
24008                cx,
24009            )
24010        })
24011        .await
24012        .expect("opening external file")
24013        .downcast::<Editor>()
24014        .expect("downcasted external file's open element to editor");
24015    pane.update(cx, |pane, cx| {
24016        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24017        open_editor.update(cx, |editor, cx| {
24018            assert_eq!(
24019                editor.display_text(cx),
24020                "pub mod external {}",
24021                "External file is open now",
24022            );
24023        });
24024        assert_eq!(open_editor, external_editor);
24025    });
24026    assert_language_servers_count(
24027        1,
24028        "Second, external, *.rs file should join the existing server",
24029        cx,
24030    );
24031
24032    pane.update_in(cx, |pane, window, cx| {
24033        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24034    })
24035    .await
24036    .unwrap();
24037    pane.update_in(cx, |pane, window, cx| {
24038        pane.navigate_backward(&Default::default(), window, cx);
24039    });
24040    cx.run_until_parked();
24041    pane.update(cx, |pane, cx| {
24042        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24043        open_editor.update(cx, |editor, cx| {
24044            assert_eq!(
24045                editor.display_text(cx),
24046                "pub mod external {}",
24047                "External file is open now",
24048            );
24049        });
24050    });
24051    assert_language_servers_count(
24052        1,
24053        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
24054        cx,
24055    );
24056
24057    cx.update(|_, cx| {
24058        workspace::reload(cx);
24059    });
24060    assert_language_servers_count(
24061        1,
24062        "After reloading the worktree with local and external files opened, only one project should be started",
24063        cx,
24064    );
24065}
24066
24067#[gpui::test]
24068async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
24069    init_test(cx, |_| {});
24070
24071    let mut cx = EditorTestContext::new(cx).await;
24072    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24073    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24074
24075    // test cursor move to start of each line on tab
24076    // for `if`, `elif`, `else`, `while`, `with` and `for`
24077    cx.set_state(indoc! {"
24078        def main():
24079        ˇ    for item in items:
24080        ˇ        while item.active:
24081        ˇ            if item.value > 10:
24082        ˇ                continue
24083        ˇ            elif item.value < 0:
24084        ˇ                break
24085        ˇ            else:
24086        ˇ                with item.context() as ctx:
24087        ˇ                    yield count
24088        ˇ        else:
24089        ˇ            log('while else')
24090        ˇ    else:
24091        ˇ        log('for else')
24092    "});
24093    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24094    cx.assert_editor_state(indoc! {"
24095        def main():
24096            ˇfor item in items:
24097                ˇwhile item.active:
24098                    ˇif item.value > 10:
24099                        ˇcontinue
24100                    ˇelif item.value < 0:
24101                        ˇbreak
24102                    ˇelse:
24103                        ˇwith item.context() as ctx:
24104                            ˇyield count
24105                ˇelse:
24106                    ˇlog('while else')
24107            ˇelse:
24108                ˇlog('for else')
24109    "});
24110    // test relative indent is preserved when tab
24111    // for `if`, `elif`, `else`, `while`, `with` and `for`
24112    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24113    cx.assert_editor_state(indoc! {"
24114        def main():
24115                ˇfor item in items:
24116                    ˇwhile item.active:
24117                        ˇif item.value > 10:
24118                            ˇcontinue
24119                        ˇelif item.value < 0:
24120                            ˇbreak
24121                        ˇelse:
24122                            ˇwith item.context() as ctx:
24123                                ˇyield count
24124                    ˇelse:
24125                        ˇlog('while else')
24126                ˇelse:
24127                    ˇlog('for else')
24128    "});
24129
24130    // test cursor move to start of each line on tab
24131    // for `try`, `except`, `else`, `finally`, `match` and `def`
24132    cx.set_state(indoc! {"
24133        def main():
24134        ˇ    try:
24135        ˇ        fetch()
24136        ˇ    except ValueError:
24137        ˇ        handle_error()
24138        ˇ    else:
24139        ˇ        match value:
24140        ˇ            case _:
24141        ˇ    finally:
24142        ˇ        def status():
24143        ˇ            return 0
24144    "});
24145    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24146    cx.assert_editor_state(indoc! {"
24147        def main():
24148            ˇtry:
24149                ˇfetch()
24150            ˇexcept ValueError:
24151                ˇhandle_error()
24152            ˇelse:
24153                ˇmatch value:
24154                    ˇcase _:
24155            ˇfinally:
24156                ˇdef status():
24157                    ˇreturn 0
24158    "});
24159    // test relative indent is preserved when tab
24160    // for `try`, `except`, `else`, `finally`, `match` and `def`
24161    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24162    cx.assert_editor_state(indoc! {"
24163        def main():
24164                ˇtry:
24165                    ˇfetch()
24166                ˇexcept ValueError:
24167                    ˇhandle_error()
24168                ˇelse:
24169                    ˇmatch value:
24170                        ˇcase _:
24171                ˇfinally:
24172                    ˇdef status():
24173                        ˇreturn 0
24174    "});
24175}
24176
24177#[gpui::test]
24178async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
24179    init_test(cx, |_| {});
24180
24181    let mut cx = EditorTestContext::new(cx).await;
24182    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24183    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24184
24185    // test `else` auto outdents when typed inside `if` block
24186    cx.set_state(indoc! {"
24187        def main():
24188            if i == 2:
24189                return
24190                ˇ
24191    "});
24192    cx.update_editor(|editor, window, cx| {
24193        editor.handle_input("else:", window, cx);
24194    });
24195    cx.assert_editor_state(indoc! {"
24196        def main():
24197            if i == 2:
24198                return
24199            else:ˇ
24200    "});
24201
24202    // test `except` auto outdents when typed inside `try` block
24203    cx.set_state(indoc! {"
24204        def main():
24205            try:
24206                i = 2
24207                ˇ
24208    "});
24209    cx.update_editor(|editor, window, cx| {
24210        editor.handle_input("except:", window, cx);
24211    });
24212    cx.assert_editor_state(indoc! {"
24213        def main():
24214            try:
24215                i = 2
24216            except:ˇ
24217    "});
24218
24219    // test `else` auto outdents when typed inside `except` block
24220    cx.set_state(indoc! {"
24221        def main():
24222            try:
24223                i = 2
24224            except:
24225                j = 2
24226                ˇ
24227    "});
24228    cx.update_editor(|editor, window, cx| {
24229        editor.handle_input("else:", window, cx);
24230    });
24231    cx.assert_editor_state(indoc! {"
24232        def main():
24233            try:
24234                i = 2
24235            except:
24236                j = 2
24237            else:ˇ
24238    "});
24239
24240    // test `finally` auto outdents when typed inside `else` block
24241    cx.set_state(indoc! {"
24242        def main():
24243            try:
24244                i = 2
24245            except:
24246                j = 2
24247            else:
24248                k = 2
24249                ˇ
24250    "});
24251    cx.update_editor(|editor, window, cx| {
24252        editor.handle_input("finally:", window, cx);
24253    });
24254    cx.assert_editor_state(indoc! {"
24255        def main():
24256            try:
24257                i = 2
24258            except:
24259                j = 2
24260            else:
24261                k = 2
24262            finally:ˇ
24263    "});
24264
24265    // test `else` does not outdents when typed inside `except` block right after for block
24266    cx.set_state(indoc! {"
24267        def main():
24268            try:
24269                i = 2
24270            except:
24271                for i in range(n):
24272                    pass
24273                ˇ
24274    "});
24275    cx.update_editor(|editor, window, cx| {
24276        editor.handle_input("else:", window, cx);
24277    });
24278    cx.assert_editor_state(indoc! {"
24279        def main():
24280            try:
24281                i = 2
24282            except:
24283                for i in range(n):
24284                    pass
24285                else:ˇ
24286    "});
24287
24288    // test `finally` auto outdents when typed inside `else` block right after for block
24289    cx.set_state(indoc! {"
24290        def main():
24291            try:
24292                i = 2
24293            except:
24294                j = 2
24295            else:
24296                for i in range(n):
24297                    pass
24298                ˇ
24299    "});
24300    cx.update_editor(|editor, window, cx| {
24301        editor.handle_input("finally:", window, cx);
24302    });
24303    cx.assert_editor_state(indoc! {"
24304        def main():
24305            try:
24306                i = 2
24307            except:
24308                j = 2
24309            else:
24310                for i in range(n):
24311                    pass
24312            finally:ˇ
24313    "});
24314
24315    // test `except` outdents to inner "try" block
24316    cx.set_state(indoc! {"
24317        def main():
24318            try:
24319                i = 2
24320                if i == 2:
24321                    try:
24322                        i = 3
24323                        ˇ
24324    "});
24325    cx.update_editor(|editor, window, cx| {
24326        editor.handle_input("except:", window, cx);
24327    });
24328    cx.assert_editor_state(indoc! {"
24329        def main():
24330            try:
24331                i = 2
24332                if i == 2:
24333                    try:
24334                        i = 3
24335                    except:ˇ
24336    "});
24337
24338    // test `except` outdents to outer "try" block
24339    cx.set_state(indoc! {"
24340        def main():
24341            try:
24342                i = 2
24343                if i == 2:
24344                    try:
24345                        i = 3
24346                ˇ
24347    "});
24348    cx.update_editor(|editor, window, cx| {
24349        editor.handle_input("except:", window, cx);
24350    });
24351    cx.assert_editor_state(indoc! {"
24352        def main():
24353            try:
24354                i = 2
24355                if i == 2:
24356                    try:
24357                        i = 3
24358            except:ˇ
24359    "});
24360
24361    // test `else` stays at correct indent when typed after `for` block
24362    cx.set_state(indoc! {"
24363        def main():
24364            for i in range(10):
24365                if i == 3:
24366                    break
24367            ˇ
24368    "});
24369    cx.update_editor(|editor, window, cx| {
24370        editor.handle_input("else:", window, cx);
24371    });
24372    cx.assert_editor_state(indoc! {"
24373        def main():
24374            for i in range(10):
24375                if i == 3:
24376                    break
24377            else:ˇ
24378    "});
24379
24380    // test does not outdent on typing after line with square brackets
24381    cx.set_state(indoc! {"
24382        def f() -> list[str]:
24383            ˇ
24384    "});
24385    cx.update_editor(|editor, window, cx| {
24386        editor.handle_input("a", window, cx);
24387    });
24388    cx.assert_editor_state(indoc! {"
24389        def f() -> list[str]:
2439024391    "});
24392
24393    // test does not outdent on typing : after case keyword
24394    cx.set_state(indoc! {"
24395        match 1:
24396            caseˇ
24397    "});
24398    cx.update_editor(|editor, window, cx| {
24399        editor.handle_input(":", window, cx);
24400    });
24401    cx.assert_editor_state(indoc! {"
24402        match 1:
24403            case:ˇ
24404    "});
24405}
24406
24407#[gpui::test]
24408async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
24409    init_test(cx, |_| {});
24410    update_test_language_settings(cx, |settings| {
24411        settings.defaults.extend_comment_on_newline = Some(false);
24412    });
24413    let mut cx = EditorTestContext::new(cx).await;
24414    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
24415    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24416
24417    // test correct indent after newline on comment
24418    cx.set_state(indoc! {"
24419        # COMMENT:ˇ
24420    "});
24421    cx.update_editor(|editor, window, cx| {
24422        editor.newline(&Newline, window, cx);
24423    });
24424    cx.assert_editor_state(indoc! {"
24425        # COMMENT:
24426        ˇ
24427    "});
24428
24429    // test correct indent after newline in brackets
24430    cx.set_state(indoc! {"
24431        {ˇ}
24432    "});
24433    cx.update_editor(|editor, window, cx| {
24434        editor.newline(&Newline, window, cx);
24435    });
24436    cx.run_until_parked();
24437    cx.assert_editor_state(indoc! {"
24438        {
24439            ˇ
24440        }
24441    "});
24442
24443    cx.set_state(indoc! {"
24444        (ˇ)
24445    "});
24446    cx.update_editor(|editor, window, cx| {
24447        editor.newline(&Newline, window, cx);
24448    });
24449    cx.run_until_parked();
24450    cx.assert_editor_state(indoc! {"
24451        (
24452            ˇ
24453        )
24454    "});
24455
24456    // do not indent after empty lists or dictionaries
24457    cx.set_state(indoc! {"
24458        a = []ˇ
24459    "});
24460    cx.update_editor(|editor, window, cx| {
24461        editor.newline(&Newline, window, cx);
24462    });
24463    cx.run_until_parked();
24464    cx.assert_editor_state(indoc! {"
24465        a = []
24466        ˇ
24467    "});
24468}
24469
24470#[gpui::test]
24471async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
24472    init_test(cx, |_| {});
24473
24474    let mut cx = EditorTestContext::new(cx).await;
24475    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24476    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24477
24478    // test cursor move to start of each line on tab
24479    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
24480    cx.set_state(indoc! {"
24481        function main() {
24482        ˇ    for item in $items; do
24483        ˇ        while [ -n \"$item\" ]; do
24484        ˇ            if [ \"$value\" -gt 10 ]; then
24485        ˇ                continue
24486        ˇ            elif [ \"$value\" -lt 0 ]; then
24487        ˇ                break
24488        ˇ            else
24489        ˇ                echo \"$item\"
24490        ˇ            fi
24491        ˇ        done
24492        ˇ    done
24493        ˇ}
24494    "});
24495    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24496    cx.assert_editor_state(indoc! {"
24497        function main() {
24498            ˇfor item in $items; do
24499                ˇwhile [ -n \"$item\" ]; do
24500                    ˇif [ \"$value\" -gt 10 ]; then
24501                        ˇcontinue
24502                    ˇelif [ \"$value\" -lt 0 ]; then
24503                        ˇbreak
24504                    ˇelse
24505                        ˇecho \"$item\"
24506                    ˇfi
24507                ˇdone
24508            ˇdone
24509        ˇ}
24510    "});
24511    // test relative indent is preserved when tab
24512    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24513    cx.assert_editor_state(indoc! {"
24514        function main() {
24515                ˇfor item in $items; do
24516                    ˇwhile [ -n \"$item\" ]; do
24517                        ˇif [ \"$value\" -gt 10 ]; then
24518                            ˇcontinue
24519                        ˇelif [ \"$value\" -lt 0 ]; then
24520                            ˇbreak
24521                        ˇelse
24522                            ˇecho \"$item\"
24523                        ˇfi
24524                    ˇdone
24525                ˇdone
24526            ˇ}
24527    "});
24528
24529    // test cursor move to start of each line on tab
24530    // for `case` statement with patterns
24531    cx.set_state(indoc! {"
24532        function handle() {
24533        ˇ    case \"$1\" in
24534        ˇ        start)
24535        ˇ            echo \"a\"
24536        ˇ            ;;
24537        ˇ        stop)
24538        ˇ            echo \"b\"
24539        ˇ            ;;
24540        ˇ        *)
24541        ˇ            echo \"c\"
24542        ˇ            ;;
24543        ˇ    esac
24544        ˇ}
24545    "});
24546    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
24547    cx.assert_editor_state(indoc! {"
24548        function handle() {
24549            ˇcase \"$1\" in
24550                ˇstart)
24551                    ˇecho \"a\"
24552                    ˇ;;
24553                ˇstop)
24554                    ˇecho \"b\"
24555                    ˇ;;
24556                ˇ*)
24557                    ˇecho \"c\"
24558                    ˇ;;
24559            ˇesac
24560        ˇ}
24561    "});
24562}
24563
24564#[gpui::test]
24565async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
24566    init_test(cx, |_| {});
24567
24568    let mut cx = EditorTestContext::new(cx).await;
24569    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24570    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24571
24572    // test indents on comment insert
24573    cx.set_state(indoc! {"
24574        function main() {
24575        ˇ    for item in $items; do
24576        ˇ        while [ -n \"$item\" ]; do
24577        ˇ            if [ \"$value\" -gt 10 ]; then
24578        ˇ                continue
24579        ˇ            elif [ \"$value\" -lt 0 ]; then
24580        ˇ                break
24581        ˇ            else
24582        ˇ                echo \"$item\"
24583        ˇ            fi
24584        ˇ        done
24585        ˇ    done
24586        ˇ}
24587    "});
24588    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
24589    cx.assert_editor_state(indoc! {"
24590        function main() {
24591        #ˇ    for item in $items; do
24592        #ˇ        while [ -n \"$item\" ]; do
24593        #ˇ            if [ \"$value\" -gt 10 ]; then
24594        #ˇ                continue
24595        #ˇ            elif [ \"$value\" -lt 0 ]; then
24596        #ˇ                break
24597        #ˇ            else
24598        #ˇ                echo \"$item\"
24599        #ˇ            fi
24600        #ˇ        done
24601        #ˇ    done
24602        #ˇ}
24603    "});
24604}
24605
24606#[gpui::test]
24607async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
24608    init_test(cx, |_| {});
24609
24610    let mut cx = EditorTestContext::new(cx).await;
24611    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24612    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24613
24614    // test `else` auto outdents when typed inside `if` block
24615    cx.set_state(indoc! {"
24616        if [ \"$1\" = \"test\" ]; then
24617            echo \"foo bar\"
24618            ˇ
24619    "});
24620    cx.update_editor(|editor, window, cx| {
24621        editor.handle_input("else", window, cx);
24622    });
24623    cx.assert_editor_state(indoc! {"
24624        if [ \"$1\" = \"test\" ]; then
24625            echo \"foo bar\"
24626        elseˇ
24627    "});
24628
24629    // test `elif` auto outdents when typed inside `if` block
24630    cx.set_state(indoc! {"
24631        if [ \"$1\" = \"test\" ]; then
24632            echo \"foo bar\"
24633            ˇ
24634    "});
24635    cx.update_editor(|editor, window, cx| {
24636        editor.handle_input("elif", window, cx);
24637    });
24638    cx.assert_editor_state(indoc! {"
24639        if [ \"$1\" = \"test\" ]; then
24640            echo \"foo bar\"
24641        elifˇ
24642    "});
24643
24644    // test `fi` auto outdents when typed inside `else` block
24645    cx.set_state(indoc! {"
24646        if [ \"$1\" = \"test\" ]; then
24647            echo \"foo bar\"
24648        else
24649            echo \"bar baz\"
24650            ˇ
24651    "});
24652    cx.update_editor(|editor, window, cx| {
24653        editor.handle_input("fi", window, cx);
24654    });
24655    cx.assert_editor_state(indoc! {"
24656        if [ \"$1\" = \"test\" ]; then
24657            echo \"foo bar\"
24658        else
24659            echo \"bar baz\"
24660        fiˇ
24661    "});
24662
24663    // test `done` auto outdents when typed inside `while` block
24664    cx.set_state(indoc! {"
24665        while read line; do
24666            echo \"$line\"
24667            ˇ
24668    "});
24669    cx.update_editor(|editor, window, cx| {
24670        editor.handle_input("done", window, cx);
24671    });
24672    cx.assert_editor_state(indoc! {"
24673        while read line; do
24674            echo \"$line\"
24675        doneˇ
24676    "});
24677
24678    // test `done` auto outdents when typed inside `for` block
24679    cx.set_state(indoc! {"
24680        for file in *.txt; do
24681            cat \"$file\"
24682            ˇ
24683    "});
24684    cx.update_editor(|editor, window, cx| {
24685        editor.handle_input("done", window, cx);
24686    });
24687    cx.assert_editor_state(indoc! {"
24688        for file in *.txt; do
24689            cat \"$file\"
24690        doneˇ
24691    "});
24692
24693    // test `esac` auto outdents when typed inside `case` block
24694    cx.set_state(indoc! {"
24695        case \"$1\" in
24696            start)
24697                echo \"foo bar\"
24698                ;;
24699            stop)
24700                echo \"bar baz\"
24701                ;;
24702            ˇ
24703    "});
24704    cx.update_editor(|editor, window, cx| {
24705        editor.handle_input("esac", window, cx);
24706    });
24707    cx.assert_editor_state(indoc! {"
24708        case \"$1\" in
24709            start)
24710                echo \"foo bar\"
24711                ;;
24712            stop)
24713                echo \"bar baz\"
24714                ;;
24715        esacˇ
24716    "});
24717
24718    // test `*)` auto outdents when typed inside `case` block
24719    cx.set_state(indoc! {"
24720        case \"$1\" in
24721            start)
24722                echo \"foo bar\"
24723                ;;
24724                ˇ
24725    "});
24726    cx.update_editor(|editor, window, cx| {
24727        editor.handle_input("*)", window, cx);
24728    });
24729    cx.assert_editor_state(indoc! {"
24730        case \"$1\" in
24731            start)
24732                echo \"foo bar\"
24733                ;;
24734            *)ˇ
24735    "});
24736
24737    // test `fi` outdents to correct level with nested if blocks
24738    cx.set_state(indoc! {"
24739        if [ \"$1\" = \"test\" ]; then
24740            echo \"outer if\"
24741            if [ \"$2\" = \"debug\" ]; then
24742                echo \"inner if\"
24743                ˇ
24744    "});
24745    cx.update_editor(|editor, window, cx| {
24746        editor.handle_input("fi", window, cx);
24747    });
24748    cx.assert_editor_state(indoc! {"
24749        if [ \"$1\" = \"test\" ]; then
24750            echo \"outer if\"
24751            if [ \"$2\" = \"debug\" ]; then
24752                echo \"inner if\"
24753            fiˇ
24754    "});
24755}
24756
24757#[gpui::test]
24758async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
24759    init_test(cx, |_| {});
24760    update_test_language_settings(cx, |settings| {
24761        settings.defaults.extend_comment_on_newline = Some(false);
24762    });
24763    let mut cx = EditorTestContext::new(cx).await;
24764    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
24765    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24766
24767    // test correct indent after newline on comment
24768    cx.set_state(indoc! {"
24769        # COMMENT:ˇ
24770    "});
24771    cx.update_editor(|editor, window, cx| {
24772        editor.newline(&Newline, window, cx);
24773    });
24774    cx.assert_editor_state(indoc! {"
24775        # COMMENT:
24776        ˇ
24777    "});
24778
24779    // test correct indent after newline after `then`
24780    cx.set_state(indoc! {"
24781
24782        if [ \"$1\" = \"test\" ]; thenˇ
24783    "});
24784    cx.update_editor(|editor, window, cx| {
24785        editor.newline(&Newline, window, cx);
24786    });
24787    cx.run_until_parked();
24788    cx.assert_editor_state(indoc! {"
24789
24790        if [ \"$1\" = \"test\" ]; then
24791            ˇ
24792    "});
24793
24794    // test correct indent after newline after `else`
24795    cx.set_state(indoc! {"
24796        if [ \"$1\" = \"test\" ]; then
24797        elseˇ
24798    "});
24799    cx.update_editor(|editor, window, cx| {
24800        editor.newline(&Newline, window, cx);
24801    });
24802    cx.run_until_parked();
24803    cx.assert_editor_state(indoc! {"
24804        if [ \"$1\" = \"test\" ]; then
24805        else
24806            ˇ
24807    "});
24808
24809    // test correct indent after newline after `elif`
24810    cx.set_state(indoc! {"
24811        if [ \"$1\" = \"test\" ]; then
24812        elifˇ
24813    "});
24814    cx.update_editor(|editor, window, cx| {
24815        editor.newline(&Newline, window, cx);
24816    });
24817    cx.run_until_parked();
24818    cx.assert_editor_state(indoc! {"
24819        if [ \"$1\" = \"test\" ]; then
24820        elif
24821            ˇ
24822    "});
24823
24824    // test correct indent after newline after `do`
24825    cx.set_state(indoc! {"
24826        for file in *.txt; doˇ
24827    "});
24828    cx.update_editor(|editor, window, cx| {
24829        editor.newline(&Newline, window, cx);
24830    });
24831    cx.run_until_parked();
24832    cx.assert_editor_state(indoc! {"
24833        for file in *.txt; do
24834            ˇ
24835    "});
24836
24837    // test correct indent after newline after case pattern
24838    cx.set_state(indoc! {"
24839        case \"$1\" in
24840            start)ˇ
24841    "});
24842    cx.update_editor(|editor, window, cx| {
24843        editor.newline(&Newline, window, cx);
24844    });
24845    cx.run_until_parked();
24846    cx.assert_editor_state(indoc! {"
24847        case \"$1\" in
24848            start)
24849                ˇ
24850    "});
24851
24852    // test correct indent after newline after case pattern
24853    cx.set_state(indoc! {"
24854        case \"$1\" in
24855            start)
24856                ;;
24857            *)ˇ
24858    "});
24859    cx.update_editor(|editor, window, cx| {
24860        editor.newline(&Newline, window, cx);
24861    });
24862    cx.run_until_parked();
24863    cx.assert_editor_state(indoc! {"
24864        case \"$1\" in
24865            start)
24866                ;;
24867            *)
24868                ˇ
24869    "});
24870
24871    // test correct indent after newline after function opening brace
24872    cx.set_state(indoc! {"
24873        function test() {ˇ}
24874    "});
24875    cx.update_editor(|editor, window, cx| {
24876        editor.newline(&Newline, window, cx);
24877    });
24878    cx.run_until_parked();
24879    cx.assert_editor_state(indoc! {"
24880        function test() {
24881            ˇ
24882        }
24883    "});
24884
24885    // test no extra indent after semicolon on same line
24886    cx.set_state(indoc! {"
24887        echo \"test\"24888    "});
24889    cx.update_editor(|editor, window, cx| {
24890        editor.newline(&Newline, window, cx);
24891    });
24892    cx.run_until_parked();
24893    cx.assert_editor_state(indoc! {"
24894        echo \"test\";
24895        ˇ
24896    "});
24897}
24898
24899fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
24900    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
24901    point..point
24902}
24903
24904#[track_caller]
24905fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
24906    let (text, ranges) = marked_text_ranges(marked_text, true);
24907    assert_eq!(editor.text(cx), text);
24908    assert_eq!(
24909        editor.selections.ranges(cx),
24910        ranges,
24911        "Assert selections are {}",
24912        marked_text
24913    );
24914}
24915
24916pub fn handle_signature_help_request(
24917    cx: &mut EditorLspTestContext,
24918    mocked_response: lsp::SignatureHelp,
24919) -> impl Future<Output = ()> + use<> {
24920    let mut request =
24921        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
24922            let mocked_response = mocked_response.clone();
24923            async move { Ok(Some(mocked_response)) }
24924        });
24925
24926    async move {
24927        request.next().await;
24928    }
24929}
24930
24931#[track_caller]
24932pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
24933    cx.update_editor(|editor, _, _| {
24934        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
24935            let entries = menu.entries.borrow();
24936            let entries = entries
24937                .iter()
24938                .map(|entry| entry.string.as_str())
24939                .collect::<Vec<_>>();
24940            assert_eq!(entries, expected);
24941        } else {
24942            panic!("Expected completions menu");
24943        }
24944    });
24945}
24946
24947/// Handle completion request passing a marked string specifying where the completion
24948/// should be triggered from using '|' character, what range should be replaced, and what completions
24949/// should be returned using '<' and '>' to delimit the range.
24950///
24951/// Also see `handle_completion_request_with_insert_and_replace`.
24952#[track_caller]
24953pub fn handle_completion_request(
24954    marked_string: &str,
24955    completions: Vec<&'static str>,
24956    is_incomplete: bool,
24957    counter: Arc<AtomicUsize>,
24958    cx: &mut EditorLspTestContext,
24959) -> impl Future<Output = ()> {
24960    let complete_from_marker: TextRangeMarker = '|'.into();
24961    let replace_range_marker: TextRangeMarker = ('<', '>').into();
24962    let (_, mut marked_ranges) = marked_text_ranges_by(
24963        marked_string,
24964        vec![complete_from_marker.clone(), replace_range_marker.clone()],
24965    );
24966
24967    let complete_from_position =
24968        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
24969    let replace_range =
24970        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
24971
24972    let mut request =
24973        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
24974            let completions = completions.clone();
24975            counter.fetch_add(1, atomic::Ordering::Release);
24976            async move {
24977                assert_eq!(params.text_document_position.text_document.uri, url.clone());
24978                assert_eq!(
24979                    params.text_document_position.position,
24980                    complete_from_position
24981                );
24982                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
24983                    is_incomplete,
24984                    item_defaults: None,
24985                    items: completions
24986                        .iter()
24987                        .map(|completion_text| lsp::CompletionItem {
24988                            label: completion_text.to_string(),
24989                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
24990                                range: replace_range,
24991                                new_text: completion_text.to_string(),
24992                            })),
24993                            ..Default::default()
24994                        })
24995                        .collect(),
24996                })))
24997            }
24998        });
24999
25000    async move {
25001        request.next().await;
25002    }
25003}
25004
25005/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
25006/// given instead, which also contains an `insert` range.
25007///
25008/// This function uses markers to define ranges:
25009/// - `|` marks the cursor position
25010/// - `<>` marks the replace range
25011/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
25012pub fn handle_completion_request_with_insert_and_replace(
25013    cx: &mut EditorLspTestContext,
25014    marked_string: &str,
25015    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
25016    counter: Arc<AtomicUsize>,
25017) -> impl Future<Output = ()> {
25018    let complete_from_marker: TextRangeMarker = '|'.into();
25019    let replace_range_marker: TextRangeMarker = ('<', '>').into();
25020    let insert_range_marker: TextRangeMarker = ('{', '}').into();
25021
25022    let (_, mut marked_ranges) = marked_text_ranges_by(
25023        marked_string,
25024        vec![
25025            complete_from_marker.clone(),
25026            replace_range_marker.clone(),
25027            insert_range_marker.clone(),
25028        ],
25029    );
25030
25031    let complete_from_position =
25032        cx.to_lsp(marked_ranges.remove(&complete_from_marker).unwrap()[0].start);
25033    let replace_range =
25034        cx.to_lsp_range(marked_ranges.remove(&replace_range_marker).unwrap()[0].clone());
25035
25036    let insert_range = match marked_ranges.remove(&insert_range_marker) {
25037        Some(ranges) if !ranges.is_empty() => cx.to_lsp_range(ranges[0].clone()),
25038        _ => lsp::Range {
25039            start: replace_range.start,
25040            end: complete_from_position,
25041        },
25042    };
25043
25044    let mut request =
25045        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
25046            let completions = completions.clone();
25047            counter.fetch_add(1, atomic::Ordering::Release);
25048            async move {
25049                assert_eq!(params.text_document_position.text_document.uri, url.clone());
25050                assert_eq!(
25051                    params.text_document_position.position, complete_from_position,
25052                    "marker `|` position doesn't match",
25053                );
25054                Ok(Some(lsp::CompletionResponse::Array(
25055                    completions
25056                        .iter()
25057                        .map(|(label, new_text)| lsp::CompletionItem {
25058                            label: label.to_string(),
25059                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25060                                lsp::InsertReplaceEdit {
25061                                    insert: insert_range,
25062                                    replace: replace_range,
25063                                    new_text: new_text.to_string(),
25064                                },
25065                            )),
25066                            ..Default::default()
25067                        })
25068                        .collect(),
25069                )))
25070            }
25071        });
25072
25073    async move {
25074        request.next().await;
25075    }
25076}
25077
25078fn handle_resolve_completion_request(
25079    cx: &mut EditorLspTestContext,
25080    edits: Option<Vec<(&'static str, &'static str)>>,
25081) -> impl Future<Output = ()> {
25082    let edits = edits.map(|edits| {
25083        edits
25084            .iter()
25085            .map(|(marked_string, new_text)| {
25086                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
25087                let replace_range = cx.to_lsp_range(marked_ranges[0].clone());
25088                lsp::TextEdit::new(replace_range, new_text.to_string())
25089            })
25090            .collect::<Vec<_>>()
25091    });
25092
25093    let mut request =
25094        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
25095            let edits = edits.clone();
25096            async move {
25097                Ok(lsp::CompletionItem {
25098                    additional_text_edits: edits,
25099                    ..Default::default()
25100                })
25101            }
25102        });
25103
25104    async move {
25105        request.next().await;
25106    }
25107}
25108
25109pub(crate) fn update_test_language_settings(
25110    cx: &mut TestAppContext,
25111    f: impl Fn(&mut AllLanguageSettingsContent),
25112) {
25113    cx.update(|cx| {
25114        SettingsStore::update_global(cx, |store, cx| {
25115            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
25116        });
25117    });
25118}
25119
25120pub(crate) fn update_test_project_settings(
25121    cx: &mut TestAppContext,
25122    f: impl Fn(&mut ProjectSettingsContent),
25123) {
25124    cx.update(|cx| {
25125        SettingsStore::update_global(cx, |store, cx| {
25126            store.update_user_settings(cx, |settings| f(&mut settings.project));
25127        });
25128    });
25129}
25130
25131pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
25132    cx.update(|cx| {
25133        assets::Assets.load_test_fonts(cx);
25134        let store = SettingsStore::test(cx);
25135        cx.set_global(store);
25136        theme::init(theme::LoadThemes::JustBase, cx);
25137        release_channel::init(SemanticVersion::default(), cx);
25138        client::init_settings(cx);
25139        language::init(cx);
25140        Project::init_settings(cx);
25141        workspace::init_settings(cx);
25142        crate::init(cx);
25143    });
25144    zlog::init_test();
25145    update_test_language_settings(cx, f);
25146}
25147
25148#[track_caller]
25149fn assert_hunk_revert(
25150    not_reverted_text_with_selections: &str,
25151    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
25152    expected_reverted_text_with_selections: &str,
25153    base_text: &str,
25154    cx: &mut EditorLspTestContext,
25155) {
25156    cx.set_state(not_reverted_text_with_selections);
25157    cx.set_head_text(base_text);
25158    cx.executor().run_until_parked();
25159
25160    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
25161        let snapshot = editor.snapshot(window, cx);
25162        let reverted_hunk_statuses = snapshot
25163            .buffer_snapshot
25164            .diff_hunks_in_range(0..snapshot.buffer_snapshot.len())
25165            .map(|hunk| hunk.status().kind)
25166            .collect::<Vec<_>>();
25167
25168        editor.git_restore(&Default::default(), window, cx);
25169        reverted_hunk_statuses
25170    });
25171    cx.executor().run_until_parked();
25172    cx.assert_editor_state(expected_reverted_text_with_selections);
25173    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
25174}
25175
25176#[gpui::test(iterations = 10)]
25177async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
25178    init_test(cx, |_| {});
25179
25180    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
25181    let counter = diagnostic_requests.clone();
25182
25183    let fs = FakeFs::new(cx.executor());
25184    fs.insert_tree(
25185        path!("/a"),
25186        json!({
25187            "first.rs": "fn main() { let a = 5; }",
25188            "second.rs": "// Test file",
25189        }),
25190    )
25191    .await;
25192
25193    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25194    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25195    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25196
25197    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25198    language_registry.add(rust_lang());
25199    let mut fake_servers = language_registry.register_fake_lsp(
25200        "Rust",
25201        FakeLspAdapter {
25202            capabilities: lsp::ServerCapabilities {
25203                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
25204                    lsp::DiagnosticOptions {
25205                        identifier: None,
25206                        inter_file_dependencies: true,
25207                        workspace_diagnostics: true,
25208                        work_done_progress_options: Default::default(),
25209                    },
25210                )),
25211                ..Default::default()
25212            },
25213            ..Default::default()
25214        },
25215    );
25216
25217    let editor = workspace
25218        .update(cx, |workspace, window, cx| {
25219            workspace.open_abs_path(
25220                PathBuf::from(path!("/a/first.rs")),
25221                OpenOptions::default(),
25222                window,
25223                cx,
25224            )
25225        })
25226        .unwrap()
25227        .await
25228        .unwrap()
25229        .downcast::<Editor>()
25230        .unwrap();
25231    let fake_server = fake_servers.next().await.unwrap();
25232    let server_id = fake_server.server.server_id();
25233    let mut first_request = fake_server
25234        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
25235            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
25236            let result_id = Some(new_result_id.to_string());
25237            assert_eq!(
25238                params.text_document.uri,
25239                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25240            );
25241            async move {
25242                Ok(lsp::DocumentDiagnosticReportResult::Report(
25243                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
25244                        related_documents: None,
25245                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
25246                            items: Vec::new(),
25247                            result_id,
25248                        },
25249                    }),
25250                ))
25251            }
25252        });
25253
25254    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
25255        project.update(cx, |project, cx| {
25256            let buffer_id = editor
25257                .read(cx)
25258                .buffer()
25259                .read(cx)
25260                .as_singleton()
25261                .expect("created a singleton buffer")
25262                .read(cx)
25263                .remote_id();
25264            let buffer_result_id = project
25265                .lsp_store()
25266                .read(cx)
25267                .result_id(server_id, buffer_id, cx);
25268            assert_eq!(expected, buffer_result_id);
25269        });
25270    };
25271
25272    ensure_result_id(None, cx);
25273    cx.executor().advance_clock(Duration::from_millis(60));
25274    cx.executor().run_until_parked();
25275    assert_eq!(
25276        diagnostic_requests.load(atomic::Ordering::Acquire),
25277        1,
25278        "Opening file should trigger diagnostic request"
25279    );
25280    first_request
25281        .next()
25282        .await
25283        .expect("should have sent the first diagnostics pull request");
25284    ensure_result_id(Some("1".to_string()), cx);
25285
25286    // Editing should trigger diagnostics
25287    editor.update_in(cx, |editor, window, cx| {
25288        editor.handle_input("2", window, cx)
25289    });
25290    cx.executor().advance_clock(Duration::from_millis(60));
25291    cx.executor().run_until_parked();
25292    assert_eq!(
25293        diagnostic_requests.load(atomic::Ordering::Acquire),
25294        2,
25295        "Editing should trigger diagnostic request"
25296    );
25297    ensure_result_id(Some("2".to_string()), cx);
25298
25299    // Moving cursor should not trigger diagnostic request
25300    editor.update_in(cx, |editor, window, cx| {
25301        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25302            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
25303        });
25304    });
25305    cx.executor().advance_clock(Duration::from_millis(60));
25306    cx.executor().run_until_parked();
25307    assert_eq!(
25308        diagnostic_requests.load(atomic::Ordering::Acquire),
25309        2,
25310        "Cursor movement should not trigger diagnostic request"
25311    );
25312    ensure_result_id(Some("2".to_string()), cx);
25313    // Multiple rapid edits should be debounced
25314    for _ in 0..5 {
25315        editor.update_in(cx, |editor, window, cx| {
25316            editor.handle_input("x", window, cx)
25317        });
25318    }
25319    cx.executor().advance_clock(Duration::from_millis(60));
25320    cx.executor().run_until_parked();
25321
25322    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
25323    assert!(
25324        final_requests <= 4,
25325        "Multiple rapid edits should be debounced (got {final_requests} requests)",
25326    );
25327    ensure_result_id(Some(final_requests.to_string()), cx);
25328}
25329
25330#[gpui::test]
25331async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
25332    // Regression test for issue #11671
25333    // Previously, adding a cursor after moving multiple cursors would reset
25334    // the cursor count instead of adding to the existing cursors.
25335    init_test(cx, |_| {});
25336    let mut cx = EditorTestContext::new(cx).await;
25337
25338    // Create a simple buffer with cursor at start
25339    cx.set_state(indoc! {"
25340        ˇaaaa
25341        bbbb
25342        cccc
25343        dddd
25344        eeee
25345        ffff
25346        gggg
25347        hhhh"});
25348
25349    // Add 2 cursors below (so we have 3 total)
25350    cx.update_editor(|editor, window, cx| {
25351        editor.add_selection_below(&Default::default(), window, cx);
25352        editor.add_selection_below(&Default::default(), window, cx);
25353    });
25354
25355    // Verify we have 3 cursors
25356    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
25357    assert_eq!(
25358        initial_count, 3,
25359        "Should have 3 cursors after adding 2 below"
25360    );
25361
25362    // Move down one line
25363    cx.update_editor(|editor, window, cx| {
25364        editor.move_down(&MoveDown, window, cx);
25365    });
25366
25367    // Add another cursor below
25368    cx.update_editor(|editor, window, cx| {
25369        editor.add_selection_below(&Default::default(), window, cx);
25370    });
25371
25372    // Should now have 4 cursors (3 original + 1 new)
25373    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
25374    assert_eq!(
25375        final_count, 4,
25376        "Should have 4 cursors after moving and adding another"
25377    );
25378}
25379
25380#[gpui::test(iterations = 10)]
25381async fn test_document_colors(cx: &mut TestAppContext) {
25382    let expected_color = Rgba {
25383        r: 0.33,
25384        g: 0.33,
25385        b: 0.33,
25386        a: 0.33,
25387    };
25388
25389    init_test(cx, |_| {});
25390
25391    let fs = FakeFs::new(cx.executor());
25392    fs.insert_tree(
25393        path!("/a"),
25394        json!({
25395            "first.rs": "fn main() { let a = 5; }",
25396        }),
25397    )
25398    .await;
25399
25400    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25401    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25402    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25403
25404    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25405    language_registry.add(rust_lang());
25406    let mut fake_servers = language_registry.register_fake_lsp(
25407        "Rust",
25408        FakeLspAdapter {
25409            capabilities: lsp::ServerCapabilities {
25410                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
25411                ..lsp::ServerCapabilities::default()
25412            },
25413            name: "rust-analyzer",
25414            ..FakeLspAdapter::default()
25415        },
25416    );
25417    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
25418        "Rust",
25419        FakeLspAdapter {
25420            capabilities: lsp::ServerCapabilities {
25421                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
25422                ..lsp::ServerCapabilities::default()
25423            },
25424            name: "not-rust-analyzer",
25425            ..FakeLspAdapter::default()
25426        },
25427    );
25428
25429    let editor = workspace
25430        .update(cx, |workspace, window, cx| {
25431            workspace.open_abs_path(
25432                PathBuf::from(path!("/a/first.rs")),
25433                OpenOptions::default(),
25434                window,
25435                cx,
25436            )
25437        })
25438        .unwrap()
25439        .await
25440        .unwrap()
25441        .downcast::<Editor>()
25442        .unwrap();
25443    let fake_language_server = fake_servers.next().await.unwrap();
25444    let fake_language_server_without_capabilities =
25445        fake_servers_without_capabilities.next().await.unwrap();
25446    let requests_made = Arc::new(AtomicUsize::new(0));
25447    let closure_requests_made = Arc::clone(&requests_made);
25448    let mut color_request_handle = fake_language_server
25449        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
25450            let requests_made = Arc::clone(&closure_requests_made);
25451            async move {
25452                assert_eq!(
25453                    params.text_document.uri,
25454                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
25455                );
25456                requests_made.fetch_add(1, atomic::Ordering::Release);
25457                Ok(vec![
25458                    lsp::ColorInformation {
25459                        range: lsp::Range {
25460                            start: lsp::Position {
25461                                line: 0,
25462                                character: 0,
25463                            },
25464                            end: lsp::Position {
25465                                line: 0,
25466                                character: 1,
25467                            },
25468                        },
25469                        color: lsp::Color {
25470                            red: 0.33,
25471                            green: 0.33,
25472                            blue: 0.33,
25473                            alpha: 0.33,
25474                        },
25475                    },
25476                    lsp::ColorInformation {
25477                        range: lsp::Range {
25478                            start: lsp::Position {
25479                                line: 0,
25480                                character: 0,
25481                            },
25482                            end: lsp::Position {
25483                                line: 0,
25484                                character: 1,
25485                            },
25486                        },
25487                        color: lsp::Color {
25488                            red: 0.33,
25489                            green: 0.33,
25490                            blue: 0.33,
25491                            alpha: 0.33,
25492                        },
25493                    },
25494                ])
25495            }
25496        });
25497
25498    let _handle = fake_language_server_without_capabilities
25499        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
25500            panic!("Should not be called");
25501        });
25502    cx.executor().advance_clock(Duration::from_millis(100));
25503    color_request_handle.next().await.unwrap();
25504    cx.run_until_parked();
25505    assert_eq!(
25506        1,
25507        requests_made.load(atomic::Ordering::Acquire),
25508        "Should query for colors once per editor open"
25509    );
25510    editor.update_in(cx, |editor, _, cx| {
25511        assert_eq!(
25512            vec![expected_color],
25513            extract_color_inlays(editor, cx),
25514            "Should have an initial inlay"
25515        );
25516    });
25517
25518    // opening another file in a split should not influence the LSP query counter
25519    workspace
25520        .update(cx, |workspace, window, cx| {
25521            assert_eq!(
25522                workspace.panes().len(),
25523                1,
25524                "Should have one pane with one editor"
25525            );
25526            workspace.move_item_to_pane_in_direction(
25527                &MoveItemToPaneInDirection {
25528                    direction: SplitDirection::Right,
25529                    focus: false,
25530                    clone: true,
25531                },
25532                window,
25533                cx,
25534            );
25535        })
25536        .unwrap();
25537    cx.run_until_parked();
25538    workspace
25539        .update(cx, |workspace, _, cx| {
25540            let panes = workspace.panes();
25541            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
25542            for pane in panes {
25543                let editor = pane
25544                    .read(cx)
25545                    .active_item()
25546                    .and_then(|item| item.downcast::<Editor>())
25547                    .expect("Should have opened an editor in each split");
25548                let editor_file = editor
25549                    .read(cx)
25550                    .buffer()
25551                    .read(cx)
25552                    .as_singleton()
25553                    .expect("test deals with singleton buffers")
25554                    .read(cx)
25555                    .file()
25556                    .expect("test buffese should have a file")
25557                    .path();
25558                assert_eq!(
25559                    editor_file.as_ref(),
25560                    Path::new("first.rs"),
25561                    "Both editors should be opened for the same file"
25562                )
25563            }
25564        })
25565        .unwrap();
25566
25567    cx.executor().advance_clock(Duration::from_millis(500));
25568    let save = editor.update_in(cx, |editor, window, cx| {
25569        editor.move_to_end(&MoveToEnd, window, cx);
25570        editor.handle_input("dirty", window, cx);
25571        editor.save(
25572            SaveOptions {
25573                format: true,
25574                autosave: true,
25575            },
25576            project.clone(),
25577            window,
25578            cx,
25579        )
25580    });
25581    save.await.unwrap();
25582
25583    color_request_handle.next().await.unwrap();
25584    cx.run_until_parked();
25585    assert_eq!(
25586        3,
25587        requests_made.load(atomic::Ordering::Acquire),
25588        "Should query for colors once per save and once per formatting after save"
25589    );
25590
25591    drop(editor);
25592    let close = workspace
25593        .update(cx, |workspace, window, cx| {
25594            workspace.active_pane().update(cx, |pane, cx| {
25595                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25596            })
25597        })
25598        .unwrap();
25599    close.await.unwrap();
25600    let close = workspace
25601        .update(cx, |workspace, window, cx| {
25602            workspace.active_pane().update(cx, |pane, cx| {
25603                pane.close_active_item(&CloseActiveItem::default(), window, cx)
25604            })
25605        })
25606        .unwrap();
25607    close.await.unwrap();
25608    assert_eq!(
25609        3,
25610        requests_made.load(atomic::Ordering::Acquire),
25611        "After saving and closing all editors, no extra requests should be made"
25612    );
25613    workspace
25614        .update(cx, |workspace, _, cx| {
25615            assert!(
25616                workspace.active_item(cx).is_none(),
25617                "Should close all editors"
25618            )
25619        })
25620        .unwrap();
25621
25622    workspace
25623        .update(cx, |workspace, window, cx| {
25624            workspace.active_pane().update(cx, |pane, cx| {
25625                pane.navigate_backward(&Default::default(), window, cx);
25626            })
25627        })
25628        .unwrap();
25629    cx.executor().advance_clock(Duration::from_millis(100));
25630    cx.run_until_parked();
25631    let editor = workspace
25632        .update(cx, |workspace, _, cx| {
25633            workspace
25634                .active_item(cx)
25635                .expect("Should have reopened the editor again after navigating back")
25636                .downcast::<Editor>()
25637                .expect("Should be an editor")
25638        })
25639        .unwrap();
25640    color_request_handle.next().await.unwrap();
25641    assert_eq!(
25642        3,
25643        requests_made.load(atomic::Ordering::Acquire),
25644        "Cache should be reused on buffer close and reopen"
25645    );
25646    editor.update(cx, |editor, cx| {
25647        assert_eq!(
25648            vec![expected_color],
25649            extract_color_inlays(editor, cx),
25650            "Should have an initial inlay"
25651        );
25652    });
25653}
25654
25655#[gpui::test]
25656async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
25657    init_test(cx, |_| {});
25658    let (editor, cx) = cx.add_window_view(Editor::single_line);
25659    editor.update_in(cx, |editor, window, cx| {
25660        editor.set_text("oops\n\nwow\n", window, cx)
25661    });
25662    cx.run_until_parked();
25663    editor.update(cx, |editor, cx| {
25664        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
25665    });
25666    editor.update(cx, |editor, cx| editor.edit([(3..5, "")], cx));
25667    cx.run_until_parked();
25668    editor.update(cx, |editor, cx| {
25669        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
25670    });
25671}
25672
25673#[gpui::test]
25674async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
25675    init_test(cx, |_| {});
25676
25677    cx.update(|cx| {
25678        register_project_item::<Editor>(cx);
25679    });
25680
25681    let fs = FakeFs::new(cx.executor());
25682    fs.insert_tree("/root1", json!({})).await;
25683    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
25684        .await;
25685
25686    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
25687    let (workspace, cx) =
25688        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25689
25690    let worktree_id = project.update(cx, |project, cx| {
25691        project.worktrees(cx).next().unwrap().read(cx).id()
25692    });
25693
25694    let handle = workspace
25695        .update_in(cx, |workspace, window, cx| {
25696            let project_path = (worktree_id, "one.pdf");
25697            workspace.open_path(project_path, None, true, window, cx)
25698        })
25699        .await
25700        .unwrap();
25701
25702    assert_eq!(
25703        handle.to_any().entity_type(),
25704        TypeId::of::<InvalidBufferView>()
25705    );
25706}
25707
25708#[gpui::test]
25709async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
25710    init_test(cx, |_| {});
25711
25712    let language = Arc::new(Language::new(
25713        LanguageConfig::default(),
25714        Some(tree_sitter_rust::LANGUAGE.into()),
25715    ));
25716
25717    // Test hierarchical sibling navigation
25718    let text = r#"
25719        fn outer() {
25720            if condition {
25721                let a = 1;
25722            }
25723            let b = 2;
25724        }
25725
25726        fn another() {
25727            let c = 3;
25728        }
25729    "#;
25730
25731    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
25732    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25733    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
25734
25735    // Wait for parsing to complete
25736    editor
25737        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
25738        .await;
25739
25740    editor.update_in(cx, |editor, window, cx| {
25741        // Start by selecting "let a = 1;" inside the if block
25742        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25743            s.select_display_ranges([
25744                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
25745            ]);
25746        });
25747
25748        let initial_selection = editor.selections.display_ranges(cx);
25749        assert_eq!(initial_selection.len(), 1, "Should have one selection");
25750
25751        // Test select next sibling - should move up levels to find the next sibling
25752        // Since "let a = 1;" has no siblings in the if block, it should move up
25753        // to find "let b = 2;" which is a sibling of the if block
25754        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25755        let next_selection = editor.selections.display_ranges(cx);
25756
25757        // Should have a selection and it should be different from the initial
25758        assert_eq!(
25759            next_selection.len(),
25760            1,
25761            "Should have one selection after next"
25762        );
25763        assert_ne!(
25764            next_selection[0], initial_selection[0],
25765            "Next sibling selection should be different"
25766        );
25767
25768        // Test hierarchical navigation by going to the end of the current function
25769        // and trying to navigate to the next function
25770        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25771            s.select_display_ranges([
25772                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
25773            ]);
25774        });
25775
25776        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
25777        let function_next_selection = editor.selections.display_ranges(cx);
25778
25779        // Should move to the next function
25780        assert_eq!(
25781            function_next_selection.len(),
25782            1,
25783            "Should have one selection after function next"
25784        );
25785
25786        // Test select previous sibling navigation
25787        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
25788        let prev_selection = editor.selections.display_ranges(cx);
25789
25790        // Should have a selection and it should be different
25791        assert_eq!(
25792            prev_selection.len(),
25793            1,
25794            "Should have one selection after prev"
25795        );
25796        assert_ne!(
25797            prev_selection[0], function_next_selection[0],
25798            "Previous sibling selection should be different from next"
25799        );
25800    });
25801}
25802
25803#[gpui::test]
25804async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
25805    init_test(cx, |_| {});
25806
25807    let mut cx = EditorTestContext::new(cx).await;
25808    cx.set_state(
25809        "let ˇvariable = 42;
25810let another = variable + 1;
25811let result = variable * 2;",
25812    );
25813
25814    // Set up document highlights manually (simulating LSP response)
25815    cx.update_editor(|editor, _window, cx| {
25816        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
25817
25818        // Create highlights for "variable" occurrences
25819        let highlight_ranges = [
25820            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
25821            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
25822            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
25823        ];
25824
25825        let anchor_ranges: Vec<_> = highlight_ranges
25826            .iter()
25827            .map(|range| range.clone().to_anchors(&buffer_snapshot))
25828            .collect();
25829
25830        editor.highlight_background::<DocumentHighlightRead>(
25831            &anchor_ranges,
25832            |theme| theme.colors().editor_document_highlight_read_background,
25833            cx,
25834        );
25835    });
25836
25837    // Go to next highlight - should move to second "variable"
25838    cx.update_editor(|editor, window, cx| {
25839        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25840    });
25841    cx.assert_editor_state(
25842        "let variable = 42;
25843let another = ˇvariable + 1;
25844let result = variable * 2;",
25845    );
25846
25847    // Go to next highlight - should move to third "variable"
25848    cx.update_editor(|editor, window, cx| {
25849        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25850    });
25851    cx.assert_editor_state(
25852        "let variable = 42;
25853let another = variable + 1;
25854let result = ˇvariable * 2;",
25855    );
25856
25857    // Go to next highlight - should stay at third "variable" (no wrap-around)
25858    cx.update_editor(|editor, window, cx| {
25859        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
25860    });
25861    cx.assert_editor_state(
25862        "let variable = 42;
25863let another = variable + 1;
25864let result = ˇvariable * 2;",
25865    );
25866
25867    // Now test going backwards from third position
25868    cx.update_editor(|editor, window, cx| {
25869        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25870    });
25871    cx.assert_editor_state(
25872        "let variable = 42;
25873let another = ˇvariable + 1;
25874let result = variable * 2;",
25875    );
25876
25877    // Go to previous highlight - should move to first "variable"
25878    cx.update_editor(|editor, window, cx| {
25879        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25880    });
25881    cx.assert_editor_state(
25882        "let ˇvariable = 42;
25883let another = variable + 1;
25884let result = variable * 2;",
25885    );
25886
25887    // Go to previous highlight - should stay on first "variable"
25888    cx.update_editor(|editor, window, cx| {
25889        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
25890    });
25891    cx.assert_editor_state(
25892        "let ˇvariable = 42;
25893let another = variable + 1;
25894let result = variable * 2;",
25895    );
25896}
25897
25898#[track_caller]
25899fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
25900    editor
25901        .all_inlays(cx)
25902        .into_iter()
25903        .filter_map(|inlay| inlay.get_color())
25904        .map(Rgba::from)
25905        .collect()
25906}